diff --git a/Cargo.lock b/Cargo.lock index 19d4e244179..5b0ea821915 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,173 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "alloc-traits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483" + +[[package]] +name = "anchor-attribute-access-control" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb6a4b9c53ca04146d47df41db96e79ca3fd1fe60ba2691b317648a5e314bbd" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.32", + "quote 1.0.9", + "regex", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568fdd7655eca414649cba63c10d34856569aa07acc5996d50eaf74e28495f80" +dependencies = [ + "anchor-syn", + "anyhow", + "bs58 0.4.0", + "proc-macro2 1.0.32", + "quote 1.0.9", + "rustversion", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1653f067f9830a3851d3171c3a5b94b4a25780369e7d57961a3d8eff0dff5272" +dependencies = [ + "anchor-syn", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e5631befc10143e6c64dea1ce4d1106300ab06f8b82e33c33bacb076057402" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-interface" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f56e9d28f58effb298763e6397ebadfb7b84e3a853fd1995d8316d4d76fe5d" +dependencies = [ + "anchor-syn", + "anyhow", + "heck", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1f2ba3fe5da5f5653742781d0fcecbddb7105e4f933ba968802a2e10db294c" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-attribute-state" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3086b3196184a98f8ff1fe4584c4f391c686bf38cfab2cbfac9f224cdcfef5cd" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640ae4b58427c05900d4903bd5a042fc7841272977d643b3e7f3ea73f6704720" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "anchor-lang" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c38773566b5111c76f47cb33c93a82b86131cb35405587a90be639de904cf00" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-interface", + "anchor-attribute-program", + "anchor-attribute-state", + "anchor-derive-accounts", + "base64 0.13.0", + "borsh 0.9.1", + "bytemuck", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-spl" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c995dfac730f6ead86280aa32636e0abd6ec3189dd42e37cc3be4df380cc7008" +dependencies = [ + "anchor-lang", + "lazy_static", + "serum_dex", + "solana-program", + "spl-associated-token-account 1.0.3", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "anchor-syn" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dda645a57fe2222560ebb5fde2e22d6fd1e2b65dd7ba14250c468b285bd615f" +dependencies = [ + "anyhow", + "bs58 0.3.1", + "heck", + "proc-macro2 1.0.32", + "proc-macro2-diagnostics", + "quote 1.0.9", + "serde", + "serde_json", + "sha2", + "syn 1.0.81", + "thiserror", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -57,6 +224,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.40" @@ -78,12 +254,27 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arraystring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185" +dependencies = [ + "typenum", +] + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "ascii" version = "0.9.3" @@ -96,15 +287,45 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + [[package]] name = "async-trait" version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", ] [[package]] @@ -139,6 +360,12 @@ dependencies = [ "serde", ] +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base32" version = "0.4.0" @@ -168,13 +395,19 @@ name = "binary-option" version = "0.1.0" dependencies = [ "arrayref", - "borsh", + "borsh 0.9.1", "solana-program", "spl-token 3.2.0", "thiserror", - "uint", + "uint 0.9.1", ] +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + [[package]] name = "bincode" version = "1.3.3" @@ -212,7 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.5.2", "cc", "cfg-if 0.1.10", "constant_time_eq", @@ -257,27 +490,83 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "borsh" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b13fa9bf62be34702e5ee4526aff22530ae22fe34a0c4290d30d5e4e782e6" +dependencies = [ + "borsh-derive 0.7.2", +] + [[package]] name = "borsh" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18dda7dc709193c0d86a1a51050a926dc3df1cf262ec46a23a25dba421ea1924" dependencies = [ - "borsh-derive", + "borsh-derive 0.9.1", "hashbrown", ] +[[package]] +name = "borsh-derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6aaa45f8eec26e4bf71e7e5492cf53a91591af8f871f422d550e7cc43f6b927" +dependencies = [ + "borsh-derive-internal 0.7.2", + "borsh-schema-derive-internal 0.7.2", + "proc-macro2 1.0.32", + "syn 1.0.81", +] + +[[package]] +name = "borsh-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd" +dependencies = [ + "borsh-derive-internal 0.8.2", + "borsh-schema-derive-internal 0.8.2", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.32", + "syn 1.0.81", +] + [[package]] name = "borsh-derive" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "684155372435f578c0fa1acd13ebbb182cc19d6b38b64ae7901da4393217d264" dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", + "borsh-derive-internal 0.9.1", + "borsh-schema-derive-internal 0.9.1", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.26", - "syn 1.0.69", + "proc-macro2 1.0.32", + "syn 1.0.81", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61621b9d3cca65cc54e2583db84ef912d59ae60d2f04ba61bc0d7fc57556bda2" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", ] [[package]] @@ -286,9 +575,31 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2102f62f8b6d3edeab871830782285b64cc1830168094db05c8e458f209bc5c3" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b38abfda570837b0949c2c7ebd31417e15607861c23eacb2f668c69f6f3bf7" +dependencies = [ + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", ] [[package]] @@ -297,9 +608,9 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196c978c4c9b0b142d446ef3240690bf5a8a33497074a113ff9a337ccb750483" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -351,9 +662,9 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -416,11 +727,11 @@ dependencies = [ "heck", "indexmap", "log", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", "serde", "serde_json", - "syn 1.0.69", + "syn 1.0.81", "tempfile", "toml", ] @@ -463,7 +774,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", + "time 0.1.44", "winapi", ] @@ -482,7 +793,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim", @@ -545,12 +856,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cookie" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" +dependencies = [ + "percent-encoding", + "time 0.2.27", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -691,7 +1019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" dependencies = [ "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -746,9 +1074,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -757,9 +1085,42 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a012b5e473dc912f0db0546a1c9c6a194ce8494feb66fa0237160926f9e0e6" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + +[[package]] +name = "devise" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +dependencies = [ + "devise_core", + "quote 1.0.9", +] + +[[package]] +name = "devise_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +dependencies = [ + "bitflags", + "proc-macro2 1.0.32", + "proc-macro2-diagnostics", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -821,6 +1182,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dtoa" version = "0.4.8" @@ -872,9 +1239,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54ed56329d95e524ef98177ad672881bdfe7f22f254eb6ae80deb6fdd2ab20c4" dependencies = [ "enum-ordinalize", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -906,9 +1273,9 @@ checksum = "2d52ff39419d3e16961ecfb9e32f5042bdaacf9a4cc553d2d688057117bae49b" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -918,9 +1285,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd53b3fde38a39a06b2e66dc282f3e86191e53bd04cc499929c15742beae3df8" dependencies = [ "once_cell", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "enumflags2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", ] [[package]] @@ -952,9 +1339,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "synstructure", ] @@ -970,6 +1357,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version 0.3.3", +] + +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "filetime" version = "0.2.14" @@ -1089,9 +1500,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -1126,6 +1537,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -1186,9 +1610,9 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -1216,9 +1640,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00" +checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" dependencies = [ "bytes 1.0.1", "fnv", @@ -1368,15 +1792,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.6" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc35c995b9d93ec174cf9a27d425c7892722101e14993cd227fdb51d70cf9589" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "httpdate" -version = "0.3.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" [[package]] name = "humantime" @@ -1386,9 +1810,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.5" +version = "0.14.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf09f61b52cfcf4c00de50df88ae423d6c02354e385a86341133b5338630ad1" +checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" dependencies = [ "bytes 1.0.1", "futures-channel", @@ -1400,7 +1824,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project", + "pin-project-lite", "socket2 0.4.0", "tokio", "tower-service", @@ -1423,6 +1847,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.0.1", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.2.2" @@ -1442,6 +1879,7 @@ checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -1485,12 +1923,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "unindent", ] +[[package]] +name = "inlinable_string" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" + [[package]] name = "input_buffer" version = "0.3.1" @@ -1526,9 +1970,9 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c094e94816723ab936484666968f5b58060492e880f3c8d00489a1e244fa51" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -1606,7 +2050,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -1706,6 +2150,30 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "loom" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b9df80a3804094bf49bb29881d18f6f05048db72127e84e09c26fc7c2324f5" +dependencies = [ + "cfg-if 1.0.0", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.8" @@ -1759,7 +2227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abcc939f0afdc6db054b9998a1292d0a016244b382462e61cfc7c570624982cb" dependencies = [ "arrayref", - "borsh", + "borsh 0.9.1", "metaplex-token-vault", "num-derive", "num-traits", @@ -1774,7 +2242,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5211991ba3273df89cd5e0f6f558bc8d7453c87c0546f915b4a319e1541df33" dependencies = [ - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "solana-program", @@ -1820,11 +2288,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "multer" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" +dependencies = [ + "bytes 1.0.1", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "mime", + "spin 0.9.2", + "tokio", + "tokio-util", + "twoway", + "version_check", +] + [[package]] name = "native-tls" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", @@ -1888,9 +2376,9 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -1939,9 +2427,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" dependencies = [ "proc-macro-crate 1.0.0", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -2032,9 +2520,9 @@ checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -2123,6 +2611,29 @@ dependencies = [ "crypto-mac 0.10.0", ] +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2 1.0.32", + "proc-macro2-diagnostics", + "quote 1.0.9", + "syn 1.0.81", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2153,9 +2664,9 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -2214,9 +2725,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "version_check", ] @@ -2226,7 +2737,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", "version_check", ] @@ -2254,13 +2765,26 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.0.0" @@ -2303,9 +2827,9 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10ecd0eb6ed7b3d9965b4f4370b5b9e99e3e5e8742000e1c452c018f8c2a322f" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -2316,16 +2840,54 @@ checksum = "d344fdaa6a834a06dd1720ff104ea12fe101dad2e8db89345af9db74c0bb11a0" dependencies = [ "pyo3-derive-backend", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "quarry-mine" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265f47c9d701f26da6a522780936f23d3e88aac2d748ef19d98b26150d86f7e7" +dependencies = [ + "anchor-lang", + "anchor-spl", + "num-traits", + "quarry-mint-wrapper", + "spl-associated-token-account 1.0.3", + "spl-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vipers", +] + +[[package]] +name = "quarry-mint-wrapper" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b8bd78312b5485238e3f693c6fbf06aa613e20aed3f99f9080640684466e92" +dependencies = [ + "anchor-lang", + "anchor-spl", + "vipers", ] [[package]] -name = "qstring" -version = "0.7.2" +name = "quarry-redeemer" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +checksum = "fdf750ad6919ba674029a8277b16287778545e1cee3fce627f0007221396aae9" dependencies = [ - "percent-encoding", + "anchor-lang", + "anchor-spl", + "spl-associated-token-account 1.0.3", + "vipers", ] [[package]] @@ -2355,7 +2917,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", ] [[package]] @@ -2508,6 +3070,26 @@ dependencies = [ "redox_syscall 0.2.6", ] +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + [[package]] name = "regex" version = "1.4.5" @@ -2519,6 +3101,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.23" @@ -2536,9 +3127,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124" +checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" dependencies = [ "base64 0.13.0", "bytes 1.0.1", @@ -2549,11 +3140,13 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "lazy_static", "log", "mime", + "native-tls", "percent-encoding", "pin-project-lite", "rustls", @@ -2561,6 +3154,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "url", "wasm-bindgen", @@ -2579,12 +3173,95 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", ] +[[package]] +name = "rocket" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "atty", + "binascii", + "bytes 1.0.1", + "either", + "figment", + "futures", + "indexmap", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot 0.11.1", + "pin-project-lite", + "rand 0.8.3", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.2.27", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2 1.0.32", + "quote 1.0.9", + "rocket_http", + "syn 1.0.81", + "unicode-xid 0.2.1", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +dependencies = [ + "cookie", + "either", + "http", + "hyper", + "indexmap", + "log", + "memchr", + "mime", + "parking_lot 0.11.1", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time 0.2.27", + "tokio", + "uncased", +] + [[package]] name = "roots" version = "0.0.7" @@ -2613,6 +3290,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2622,6 +3305,15 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustls" version = "0.19.0" @@ -2659,6 +3351,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "safe-transmute" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a01dab6acf992653be49205bdd549f32f17cb2803e8eacf1560bf97259aae8" + [[package]] name = "same-file" version = "1.0.6" @@ -2678,6 +3376,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2699,9 +3403,9 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -2794,16 +3498,16 @@ version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" dependencies = [ "itoa", "ryu", @@ -2834,6 +3538,30 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serum_dex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02705854bae4622e552346c8edd43ab90c7425da35d63d2c689f39238f8d8b25" +dependencies = [ + "arrayref", + "bincode", + "bytemuck", + "byteorder", + "enumflags2", + "field-offset", + "itertools", + "num-traits", + "num_enum", + "safe-transmute", + "serde", + "solana-program", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "thiserror", + "without-alloc", +] + [[package]] name = "sha-1" version = "0.8.2" @@ -2846,6 +3574,12 @@ dependencies = [ "opaque-debug 0.2.3", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "sha2" version = "0.9.3" @@ -2871,6 +3605,15 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -2956,8 +3699,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eab68373f57d2944165df23d0befd3e94cbc13b5850115a73c80ea1c737efcd" dependencies = [ "bincode", - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "futures", "mio", "solana-banks-interface", @@ -3155,7 +3898,7 @@ dependencies = [ "ring", "serde", "syn 0.15.44", - "syn 1.0.69", + "syn 1.0.81", "winapi", ] @@ -3168,6 +3911,168 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-farm-client" +version = "0.0.1" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "base64 0.13.0", + "bincode", + "bs58 0.4.0", + "chrono", + "clap", + "log", + "quarry-mine", + "serde", + "solana-account-decoder", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-farm-sdk", + "solana-logger", + "solana-sdk", + "solana-version", + "spl-associated-token-account 1.0.3", + "spl-governance 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable-swap-client", + "stable-swap-math", + "thiserror", +] + +[[package]] +name = "solana-farm-ctrl" +version = "0.0.1" +dependencies = [ + "clap", + "log", + "quarry-mine", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-farm-client", + "solana-farm-sdk", + "solana-logger", + "solana-sdk", + "solana-version", + "spl-associated-token-account 1.0.3", + "spl-governance 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url", +] + +[[package]] +name = "solana-farm-router-main" +version = "0.0.1" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "solana-farm-sdk", + "solana-program", + "solana-program-test", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "solana-farm-router-orca" +version = "0.0.1" +dependencies = [ + "arrayref", + "solana-farm-sdk", + "solana-program", + "solana-program-test", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token-swap 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "solana-farm-router-raydium" +version = "0.0.1" +dependencies = [ + "arrayref", + "solana-farm-sdk", + "solana-program", + "solana-program-test", +] + +[[package]] +name = "solana-farm-router-saber" +version = "0.0.1" +dependencies = [ + "arrayref", + "quarry-mine", + "quarry-mint-wrapper", + "quarry-redeemer", + "solana-farm-sdk", + "solana-program", + "solana-program-test", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable-swap-client", +] + +[[package]] +name = "solana-farm-rpc" +version = "0.0.1" +dependencies = [ + "bs58 0.4.0", + "clap", + "dirs-next", + "lazy_static", + "log", + "reqwest", + "rocket", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "solana-account-decoder", + "solana-clap-utils", + "solana-client", + "solana-farm-client", + "solana-farm-sdk", + "solana-logger", + "solana-sdk", + "solana-version", + "url", +] + +[[package]] +name = "solana-farm-sdk" +version = "0.0.1" +dependencies = [ + "arrayref", + "arraystring", + "num-traits", + "num_enum", + "quarry-mine", + "quarry-mint-wrapper", + "quarry-redeemer", + "serde", + "serde_derive", + "serde_json", + "solana-program", + "solana-program-test", + "spl-associated-token-account 1.0.3", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable-swap-client", +] + +[[package]] +name = "solana-farm-vaults" +version = "0.0.1" +dependencies = [ + "arrayref", + "solana-farm-sdk", + "solana-program", + "solana-program-test", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "solana-faucet" version = "1.8.1" @@ -3202,7 +4107,7 @@ dependencies = [ "generic-array 0.14.4", "log", "memmap2", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_derive", "sha2", @@ -3217,10 +4122,10 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da3f4c84ab2308a310a6fc0457e7ad234b2f375ee62d9c4ef3d83319df6f3df9" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "rustc_version", - "syn 1.0.69", + "rustc_version 0.2.3", + "syn 1.0.81", ] [[package]] @@ -3290,8 +4195,8 @@ dependencies = [ "base64 0.13.0", "bincode", "blake3", - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "bs58 0.3.1", "bv", "bytemuck", @@ -3304,7 +4209,7 @@ dependencies = [ "num-derive", "num-traits", "rand 0.7.3", - "rustc_version", + "rustc_version 0.2.3", "rustversion", "serde", "serde_bytes", @@ -3406,7 +4311,7 @@ dependencies = [ "rand 0.7.3", "rayon", "regex", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_derive", "solana-compute-budget-program", @@ -3438,8 +4343,8 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "bs58 0.4.0", "bv", "bytemuck", @@ -3464,7 +4369,7 @@ dependencies = [ "rand 0.7.3", "rand_chacha 0.2.2", "rand_core 0.6.2", - "rustc_version", + "rustc_version 0.2.3", "rustversion", "serde", "serde_bytes", @@ -3489,10 +4394,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0743f2383bb6848f239f4e28d6a2a6fedb04c8ca8f4e5be019c46676c3695178" dependencies = [ "bs58 0.3.1", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", "rustversion", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -3514,7 +4419,7 @@ dependencies = [ "log", "num-derive", "num-traits", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_derive", "solana-config-program", @@ -3560,7 +4465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70bda8264abb192f5606f7f18cc8821928eeeb90d259ee37c32e6bb8e05a6132" dependencies = [ "log", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_derive", "solana-frozen-abi", @@ -3579,7 +4484,7 @@ dependencies = [ "log", "num-derive", "num-traits", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_derive", "solana-frozen-abi", @@ -3606,7 +4511,7 @@ dependencies = [ "rustc-demangle", "scroll", "thiserror", - "time", + "time 0.1.44", ] [[package]] @@ -3615,6 +4520,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" + [[package]] name = "spl-associated-token-account" version = "1.0.3" @@ -3629,7 +4540,7 @@ dependencies = [ name = "spl-associated-token-account" version = "1.0.4" dependencies = [ - "borsh", + "borsh 0.9.1", "solana-program", "solana-program-test", "solana-sdk", @@ -3641,7 +4552,7 @@ name = "spl-binary-oracle-pair" version = "0.1.0" dependencies = [ "arbitrary", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "solana-program", @@ -3649,7 +4560,7 @@ dependencies = [ "solana-sdk", "spl-token 3.2.0", "thiserror", - "uint", + "uint 0.9.1", ] [[package]] @@ -3701,8 +4612,8 @@ dependencies = [ name = "spl-feature-proposal" version = "1.0.0" dependencies = [ - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "solana-program", "solana-program-test", "solana-sdk", @@ -3731,7 +4642,7 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "borsh", + "borsh 0.9.1", "lazy_static", "num-derive", "num-traits", @@ -3742,11 +4653,30 @@ dependencies = [ "solana-program-test", "solana-sdk", "spl-governance-test-sdk", - "spl-governance-tools", + "spl-governance-tools 0.1.0", "spl-token 3.2.0", "thiserror", ] +[[package]] +name = "spl-governance" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc31263f3feca631b33aca30658d644c43fb3108fde479e1b429290a707e76c" +dependencies = [ + "arrayref", + "bincode", + "borsh 0.9.1", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-program", + "spl-governance-tools 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + [[package]] name = "spl-governance-chat" version = "0.1.0" @@ -3755,7 +4685,7 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "proptest", @@ -3764,9 +4694,9 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", - "spl-governance", + "spl-governance 2.1.4", "spl-governance-test-sdk", - "spl-governance-tools", + "spl-governance-tools 0.1.0", "spl-token 3.2.0", "thiserror", ] @@ -3777,7 +4707,7 @@ version = "0.1.0" dependencies = [ "arrayref", "bincode", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "serde", @@ -3795,7 +4725,7 @@ version = "0.1.0" dependencies = [ "arrayref", "bincode", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "serde", @@ -3805,6 +4735,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-governance-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7983eeef811e31e39948ceca10fab09a5abad04f179f85f6e6eb552c19d528" +dependencies = [ + "arrayref", + "bincode", + "borsh 0.9.1", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-program", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + [[package]] name = "spl-governance-voter-weight-addin" version = "0.1.0" @@ -3813,7 +4761,7 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "proptest", @@ -3822,9 +4770,9 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", - "spl-governance", + "spl-governance 2.1.4", "spl-governance-test-sdk", - "spl-governance-tools", + "spl-governance-tools 0.1.0", "spl-token 3.2.0", "thiserror", ] @@ -3833,8 +4781,8 @@ dependencies = [ name = "spl-math" version = "0.1.0" dependencies = [ - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "num-derive", "num-traits", "proptest", @@ -3842,7 +4790,22 @@ dependencies = [ "solana-program-test", "solana-sdk", "thiserror", - "uint", + "uint 0.9.1", +] + +[[package]] +name = "spl-math" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecdd22720b9e5ab578a862928f5010ca197419502bdace600ccd5d23dae9352" +dependencies = [ + "borsh 0.7.2", + "borsh-derive 0.8.2", + "num-derive", + "num-traits", + "solana-program", + "thiserror", + "uint 0.8.5", ] [[package]] @@ -3867,7 +4830,7 @@ dependencies = [ name = "spl-name-service" version = "0.2.0" dependencies = [ - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "solana-program", @@ -3880,8 +4843,8 @@ dependencies = [ name = "spl-record" version = "0.1.0" dependencies = [ - "borsh", - "borsh-derive", + "borsh 0.9.1", + "borsh-derive 0.9.1", "num-derive", "num-traits", "solana-program", @@ -3906,7 +4869,7 @@ version = "0.6.3" dependencies = [ "arrayref", "bincode", - "borsh", + "borsh 0.9.1", "num-derive", "num-traits", "num_enum", @@ -3917,7 +4880,7 @@ dependencies = [ "solana-program-test", "solana-sdk", "solana-vote-program", - "spl-math", + "spl-math 0.1.0", "spl-token 3.2.0", "thiserror", ] @@ -3927,7 +4890,7 @@ name = "spl-stake-pool-cli" version = "0.6.3" dependencies = [ "bincode", - "borsh", + "borsh 0.9.1", "bs58 0.4.0", "clap", "serde", @@ -4032,7 +4995,7 @@ dependencies = [ "solana-sdk", "spl-token 3.2.0", "thiserror", - "uint", + "uint 0.9.1", ] [[package]] @@ -4064,11 +5027,27 @@ dependencies = [ "sim", "solana-program", "solana-sdk", - "spl-math", + "spl-math 0.1.0", "spl-token 3.2.0", "thiserror", ] +[[package]] +name = "spl-token-swap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63b79be6174568e8724912b15e62d0c6b0424ac98397e9a5a867ac2881553af" +dependencies = [ + "arrayref", + "enum_dispatch", + "num-derive", + "num-traits", + "solana-program", + "spl-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + [[package]] name = "spl-token-swap-fuzz" version = "0.0.1" @@ -4076,9 +5055,44 @@ dependencies = [ "arbitrary", "honggfuzz", "solana-program", - "spl-math", + "spl-math 0.1.0", "spl-token 3.2.0", - "spl-token-swap", + "spl-token-swap 2.1.0", +] + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "stable-swap-client" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cfffa935d5c3658a3b3271a3932d8ed7e34e3f8308c02449d160c7426f39d5" +dependencies = [ + "arrayref", + "num-derive", + "num-traits", + "solana-program", + "spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + +[[package]] +name = "stable-swap-math" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59754130b247e9e35212040f31f92ab5ba5bdda30be7e6a188d0e90cd57ec368" +dependencies = [ + "borsh 0.9.1", + "num-traits", + "stable-swap-client", + "uint 0.9.1", ] [[package]] @@ -4087,11 +5101,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "state" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +dependencies = [ + "loom", +] + [[package]] name = "stateless-asks" version = "0.1.0" dependencies = [ - "borsh", + "borsh 0.9.1", "metaplex-token-metadata", "solana-program", "solana-program-test", @@ -4107,6 +5139,55 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "serde", + "serde_derive", + "syn 1.0.81", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.32", + "quote 1.0.9", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.81", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" @@ -4138,11 +5219,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.69" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", "unicode-xid 0.2.1", ] @@ -4153,9 +5234,9 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "unicode-xid 0.2.1", ] @@ -4197,9 +5278,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3240378a22b1195734e085ba71d1d4188d50f034aea82635acc430b7005afb5" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", ] [[package]] @@ -4251,7 +5332,7 @@ dependencies = [ "solana-sdk", "spl-memo 3.0.1", "spl-token 3.2.0", - "spl-token-swap", + "spl-token-swap 2.1.0", ] [[package]] @@ -4265,22 +5346,31 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", ] [[package]] @@ -4294,6 +5384,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.32", + "quote 1.0.9", + "standback", + "syn 1.0.81", +] + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -4330,9 +5458,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.5.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" +checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" dependencies = [ "autocfg", "bytes 1.0.1", @@ -4354,9 +5482,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -4388,9 +5526,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", @@ -4428,22 +5566,77 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.25" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.9", + "syn 1.0.81", +] + [[package]] name = "tracing-core" -version = "0.1.17" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ + "ansi_term 0.12.1", + "chrono", "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -4472,18 +5665,49 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twoway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] + [[package]] name = "typenum" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ubyte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +dependencies = [ + "serde", +] + [[package]] name = "ucd-trie" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + [[package]] name = "uint" version = "0.9.1" @@ -4496,6 +5720,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicode-bidi" version = "0.3.5" @@ -4571,9 +5811,9 @@ dependencies = [ [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", @@ -4605,6 +5845,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "vipers" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd0093f01aedc6999cfa99ef5fe45cf28b105f76c19a28d821c6739fc81fa4d" +dependencies = [ + "anchor-lang", + "anchor-spl", + "spl-associated-token-account 1.0.3", +] + [[package]] name = "void" version = "1.0.2" @@ -4660,8 +5911,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ "cfg-if 1.0.0", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -4674,9 +5923,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "wasm-bindgen-shared", ] @@ -4708,9 +5957,9 @@ version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4790,6 +6039,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "without-alloc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e34736feff52a0b3e5680927e947a4d8fac1f0b80dc8120b080dd8de24d75e2" +dependencies = [ + "alloc-traits", +] + [[package]] name = "xattr" version = "0.2.2" @@ -4808,6 +6066,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" + [[package]] name = "zeroize" version = "1.2.0" @@ -4823,9 +6087,9 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.32", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.81", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index 881882d3766..be766e03465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,15 @@ members = [ "examples/rust/logging", "examples/rust/sysvar", "examples/rust/transfer-lamports", + "farms/farm-client", + "farms/farm-ctrl", + "farms/farm-rpc", + "farms/farm-sdk", + "farms/router-main", + "farms/router-raydium", + "farms/router-saber", + "farms/router-orca", + "farms/vaults", "feature-proposal/program", "feature-proposal/cli", "governance/voter-weight-addin/program", diff --git a/farms/README.md b/farms/README.md new file mode 100644 index 00000000000..ae8eb761576 --- /dev/null +++ b/farms/README.md @@ -0,0 +1,250 @@ +# Solana Yield Farming + +## Introduction + +Solana Yield Farming is a set of easy-to-use tools and blockchain contracts for yield optimization strategies. + +It is powered by Solana blockchain to allow for frequent automatic compounding, staking, and rebalancing. + +One of the distinct features of this platform is the On-chain Reference Database. Metadata for all objects: Tokens, Pools, Farms, Vaults, etc., is stored in the blockchain, so clients don't need any state or hard-coded data. + +Solana Yield Farming provides an unified interface to Vaults, regular AMM Pools, Farms, and basic operations on tokens and accounts. Currently, Raydium, Saber, and Orca protocols are supported, but others are under development. + +This source code is an example that third parties can utilize to create and use their own version of a yield farming or aggregation service. + +### Farm Client + +A Rust library that can be used by off-chain programs to interact with Routers, Vaults, perform admin operations, metadata queries, and some common operations with wallets and accounts. + +The Client's methods accept human-readable names (tokens, polls, etc.) and UI (decimal) amounts, so you can simply call: + +```rust +client.swap(&keypair, "RDM", "SOL", "RAY", 0.1); +client.add_liquidity(&keypair, "RDM.RAY-SOL", 0.0, 0.1); +client.stake(&keypair, "RDM.RAY-SOL", 0.0); +client.harvest(&keypair, "RDM.RAY-SOL"); +``` + +to swap 0.1 SOL for RAY, deposit RAY and SOL to a Raydium pool, stake LP tokens, and harvest rewards. `RDM` above is a route or protocol, use `SBR` for Saber pools, and `ORC` for Orca. All metadata required to lookup account addresses, decimals, etc., is stored on-chain. The Client also allows building raw unsigned instructions to be integrated into more complex workflows. See `farms/farm-client/src/client.rs` for examples. + +The Client caches metadata to make subsequent calls faster, most noticeably queries that list a large number of objects, like `get_pools()`. + +In addition to the library, there is also a command-line tool that sets an example for basic usage. See `solana-farm-client --help` for the details. + +### Farm RPC + +A JSON RPC service that can be used to export Farm Client's functionality over HTTP. Most of the Client's functions can then be called from any language that supports HTTP requests, used with the SwaggerHub or tools like curl: + +```sh +curl 'http://127.0.0.1:9090/api/v1/token_account_balance?wallet_address=9wsC5hx5JopG5VwoDiUGrvgM2NaYz6tS3uyhuneRKgcN&token_name=RAY' +``` + +It is also possible to perform POST requests to interact with liquidity pools, farms, and vaults, but you must include a keypair. To avoid that and sign a transaction locally with a wallet app, you can query for a plain instruction in JSON, convert it, sign and send. Here is how it can be done in Javascript with Phantom: + +```js +const json_data = await ( + await fetch( + "http://127.0.0.1:9090/api/v1/new_instruction_add_liquidity_vault?wallet_address=9wsC5hx5JopG5VwoDiUGrvgM2NaYz6tS3uyhuneRKgcN&vault_name=RDM.STC.RAY-SRM&max_token_a_ui_amount=0.1&max_token_b_ui_amount=0.0" + ) +).json(); + +json_data.accounts.forEach(function (item, index) { + let acc = { + isSigner: item.is_signer ? true : false, + isWritable: item.is_writable ? true : false, + pubkey: new PublicKey(item.pubkey), + }; + accounts.push(acc); +}); + +const instruction = new TransactionInstruction({ + programId: new PublicKey(json_data.program_id), + data: json_data.data, + keys: accounts, +}); + +let transaction = new Transaction({ + recentBlockhash: (await this.connection.getRecentBlockhash()).blockhash, + feePayer: this.state.provider.publicKey, +}); +transaction.add(instruction); + +let signed = await this.state.provider.signTransaction(transaction); +let signature = await this.connection.sendRawTransaction(signed.serialize()); +``` + +No additional JS bindings or other dependencies are required for the above to work besides standard @solana/web3.js. + +Note that RPC service should be adequately scaled and put behind a load balancer and HTTPS proxy for production use. + +### Farm SDK + +A Rust library with a common code that is used by all Yield Farming tools and contracts. In addition to account management functionsб it includes definitions for all native and external protocols instructions and metadata objects. + +### Farm Ctrl + +A command-line tool for on-chain data management (init/upload/delete/lookup) and vaults control (init/enable/disable/set parameters etc). It can also generate metadata for Vaults and Vault tokens. Metadata for external protocols, like Raydium, needs to be extracted from relative sources. While such tools are not included, you can find target format examples in the `farm-ctrl/src/metadata` folder. + +### Vaults + +A Vault contract implementation. Individual yield farming strategies are stored under the `strategies` sub-folder. `RDM-STAKE-LP-COMPOUND` strategy works as follows: + +- User deposits tokens into a Vault with add_liquidity transaction. For example, Vault `RDM.STC.RAY-SRM` takes RAY and SRM tokens. To get a list of available Vaults, one can use the `client.get_vaults()` function or `api/v1/vaults` RPC call. Vault tokens are minted back to the user to represent his share in the Vault. +- Vault sends user tokens to the corresponding Raydium Pool, receives LP tokens, and stakes them to the Raydium Farm. +- Vaults should be cranked on a periodic basis. Crank operation is permissionless and can be done by anyone. And it is executed for the entire Vault, not per individual user. Crank consists of three steps: 1. Harvest Farm rewards (in one or both tokens); 2. Rebalance rewards to get proper amounts of each token; 3. Place rewards back into the Pool and stake received LP tokens. A small Vault fee is taken from rewards, and it can be used to incentivize Crank operations. +- Upon liquidity removal, the user gets original tokens back in amounts proportional to Vault tokens he holds. Vault tokens are then burned. + +`SBR-STAKE-LP-COMPOUND` is a similar strategy, but it uses Saber Pools and Farms. + +### Main Router + +An on-chain program that handles the creation, updates, and deletion of all metadata objects: tokens, pools, farms, vaults, program IDs, and generic key-value records, such as user or vault stats. + +### Protocol Routers (Raydium, Saber, and Orca) + +An on-chain programs that demonstrates interaction with Raydium, Saber, and Orca pools and farms. They performs in and out amounts calculations and safety checks for tokens spent and received. They don't hold user funds but validate, wrap, and send instructions to the AMMs and farms. + +## Build + +Before starting the build check `main_router` and `main_router_admin` pubkeys in `farm-sdk/src/id.rs`. They should point to existing main router program and admin account or generate a new set of keys if you plan to maintain your own version of the reference database: + +``` +solana-keygen new -o main_admin.json +solana-keygen new -o main_router.json +``` + +These keys must be used for main router deployment. + +To build the off-chain library or program, run the `cargo build` command from each project directory, for example: + +```sh +cd farms/farm-client +cargo build --release +``` + +To build on-chain programs, use the standard build command for Solana programs: + +```sh +cargo build-bpf +``` + +To build Vaults, specify an additional argument that tells the compiler which strategy needs to be built: + +```sh +cd farms/vaults +cargo build-bpf --no-default-features --features SBR-STAKE-LP-COMPOUND +``` + +## Test + +Tests are executed with the `cargo test` command: + +```sh +cd farms/farm-sdk +cargo test +``` + +Integration tests are located in the `farm-client/tests` directory and can be started as following: + +```sh +cd farms/farm-client +cargo test -- --nocapture --test-threads=1 --ignored +``` + +Bear in mind that integration tests execute transactions, and it will cost you some SOL. + +## Deploy & Run + +To deploy on-chain programs, use the standard `solana program deploy`: + +```sh +solana program deploy --commitment finalized target/deploy/solana_farm_vaults.so +solana program deploy --commitment finalized target/deploy/solana_farm_router_raydium.so +solana program deploy --commitment finalized target/deploy/solana_farm_router_saber.so +solana program deploy --commitment finalized target/deploy/solana_farm_router_orca.so +solana program deploy --commitment finalized --upgrade-authority main_admin.json --program-id main_router.json target/deploy/solana_farm_router_main.so +``` + +To start JSON RPC service: + +```sh +target/release/solana-farm-rpc --farm-client-url https://api.mainnet-beta.solana.com --json-rpc-url http://0.0.0.0:9090 +``` + +Open http://127.0.0.1:9090 in a browser to see available endpoints or check provided swagger schema in farms/farm-rpc/swagger.yaml. + +## On-chain Reference Database + +This project uses on-chain reference database to store required metadata. If you plan to maintain your own copy of the database you need to build and deploy main router and initialize the storage, otherwise skip this step. + +First, generate PDA addresses for the RefDB indexes: + +```sh +solana-farm-ctrl print-pda-all +``` + +Update `farm-ctrl/src/metadata/programs/programs.json` with newly generated addresses. + +Initialize the storage: + +```sh +solana-farm-ctrl --keypair main_admin.json init-all +``` + +And upload metadata: + +```sh +solana-farm-ctrl --keypair main_admin.json load --skip-existing Program src/metadata/programs/programs.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Token src/metadata/tokens/solana_token_list/tokens.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Token src/metadata/tokens/raydium/lp_tokens.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Pool src/metadata/pools/raydium/pools.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Farm src/metadata/farms/raydium/farms.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Token src/metadata/tokens/saber/tokens.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Pool src/metadata/pools/saber/pools_and_farms.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Farm src/metadata/pools/saber/pools_and_farms.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Pool src/metadata/pools/orca/pools.json +solana-farm-ctrl --keypair main_admin.json load --skip-existing Farm src/metadata/pools/orca/farms.json +``` + +To generate metadata for Vaults run: + +```sh +solana-farm-ctrl --keypair main_admin.json generate Vault [VAULT_PROGRAM_ADDRESS] [VAULT_NAME] [VAULT_TOKEN_NAME] +``` + +And then upload it: + +```sh +solana-farm-ctrl --keypair main_admin.json load Token src/metadata/tokens/vault_tokens/vault_tokens.json +solana-farm-ctrl --keypair main_admin.json load Vault src/metadata/vaults/stc_saber/vaults.json +``` + +## Governance + +To initialize the DAO first build and deploy governance program: + +```sh +cd solana-program-library/governance/program +cargo build-bpf +solana program deploy --commitment finalized target/deploy/spl_governance.so +``` + +Then initialize the DAO using main router admin account with: + +```sh +solana-farm-ctrl governance init [DAO_PROGRAM_ADDRESS] [DAO_TOKENS_TO_MINT] +``` + +It will take over on-chain programs upgrade authorities (including the DAO program itself) and DAO mint. Realm authority will also be removed. DAO tokens will be deposited to the admin account for further distribution. + +Farm client can be used to perform all DAO operations: create proposals, deposit tokens, sign-off, add or execute instructions, vote, etc. See help for details: + +```sh +solana-farm-client governance help +``` + +## Disclaimer + +All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the good faith efforts Solana Labs, Inc. and its affiliates ("SL"). It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment. +Any content produced by SL or developer resources that SL provides have not been subject to audit and are for educational and inspiration purposes only. SL does not encourage, induce or sanction the deployment, integration or use of any such applications (including the code comprising the Solana blockchain protocol) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. This includes use of any such applications by the reader (a) in violation of export control or sanctions laws of the United States or any other applicable jurisdiction, (b) if the reader is located in or ordinarily resident in a country or territory subject to comprehensive sanctions administered by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the reader is or is working on behalf of a Specially Designated National (SDN) or a person subject to similar blocking or denied party prohibitions. +The reader should be aware that U.S. export control and sanctions laws prohibit U.S. persons (and other persons that are subject to such laws) from transacting with persons in certain countries and territories or that are on the SDN list. As a project based primarily on open-source software, it is possible that such sanctioned persons may nevertheless bypass prohibitions, obtain the code comprising the Solana blockchain protocol (or other project code or applications) and deploy, integrate, or otherwise use it. Accordingly, there is a risk to individuals that other persons using the Solana blockchain protocol may be sanctioned persons and that transactions with such persons would be a violation of U.S. export controls and sanctions law. This risk applies to individuals, organizations, and other ecosystem participants that deploy, integrate, or use the Solana blockchain protocol code directly (e.g., as a node operator), and individuals that transact on the Solana blockchain through light clients, third party interfaces, and/or wallet software. diff --git a/farms/farm-client/.gitignore b/farms/farm-client/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/farms/farm-client/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/farms/farm-client/Cargo.toml b/farms/farm-client/Cargo.toml new file mode 100644 index 00000000000..9428669d364 --- /dev/null +++ b/farms/farm-client/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "solana-farm-client" +version = "0.0.1" +description = "Solana Farm Client" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +debug = [] + +[dependencies] +log = "0.4.14" +arrayvec = "0.7.2" +arrayref = "0.3.6" +serde = "1.0.130" +clap = "2.33.3" +thiserror = "1.0.30" +bs58 = "0.4.0" +solana-sdk = "1.8.1" +solana-client = "1.8.1" +solana-cli-config = "1.8.1" +solana-account-decoder = "1.8.1" +solana-logger = "1.8.1" +solana-version = "1.8.1" +solana-farm-sdk = { path = "../farm-sdk" } +solana-clap-utils = "1.8.1" +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = "1.0.3", features = ["no-entrypoint"] } +spl-governance = { version = "2.1.4", features = ["no-entrypoint"] } +quarry-mine = { version = "1.10.0", features = ["no-entrypoint"] } +stable-swap-client = "1.5.2" +stable-swap-math = "1.5.3" +chrono = "0.4.19" +base64 = "0.13.0" +bincode = "1.3.3" + + +[lib] +name = "solana_farm_client" +path = "src/lib.rs" + +[[bin]] +name = "solana-farm-client" +path = "src/cli/main.rs" diff --git a/farms/farm-client/src/cache.rs b/farms/farm-client/src/cache.rs new file mode 100644 index 00000000000..420515db55b --- /dev/null +++ b/farms/farm-client/src/cache.rs @@ -0,0 +1,39 @@ +//! Farm Client's cache to reduce the load on RPC endpoint. + +use std::{ + collections::HashMap, + time::{Duration, Instant}, +}; + +pub const RELOAD_INTERVAL: Duration = Duration::from_secs(604800); + +#[derive(Clone)] +pub struct Cache { + pub data: HashMap, + pub last_load: Instant, +} + +impl Default for Cache { + fn default() -> Self { + Self { + data: HashMap::::new(), + last_load: Instant::now() - RELOAD_INTERVAL, + } + } +} + +impl Cache { + pub fn is_stale(&self) -> bool { + self.data.is_empty() || Instant::now() - self.last_load >= RELOAD_INTERVAL + } + + pub fn set(&mut self, data: HashMap) { + self.data = data; + self.last_load = Instant::now(); + } + + pub fn reset(&mut self) { + self.data = HashMap::::new(); + self.last_load = Instant::now() - RELOAD_INTERVAL; + } +} diff --git a/farms/farm-client/src/cli/config.rs b/farms/farm-client/src/cli/config.rs new file mode 100644 index 00000000000..f218a36bced --- /dev/null +++ b/farms/farm-client/src/cli/config.rs @@ -0,0 +1,537 @@ +//! Configuration and command line arguments management. + +use { + clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand}, + solana_clap_utils::{input_validators::is_url, keypair::signer_from_path}, + solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signer}, + std::str::FromStr, +}; + +#[derive(Debug)] +pub struct Config { + pub farm_client_url: String, + pub commitment: CommitmentConfig, + pub keypair: Box, + pub no_pretty_print: bool, +} + +impl Config { + pub fn new(matches: &ArgMatches) -> Self { + let cli_config = if let Some(config_file) = matches.value_of("config_file") { + match solana_cli_config::Config::load(config_file) { + Err(e) => { + panic!( + "Failed to load config file \"{}\":{}", + config_file, + e.to_string() + ); + } + Ok(config) => config, + } + } else { + solana_cli_config::Config::default() + }; + + let farm_client_url = matches + .value_of("farm_client_url") + .unwrap_or(&cli_config.json_rpc_url); + let keypair_path = matches + .value_of("keypair") + .unwrap_or(&cli_config.keypair_path); + let commitment = matches + .value_of("commitment") + .unwrap_or(&cli_config.commitment); + + Self { + farm_client_url: farm_client_url.to_string(), + commitment: CommitmentConfig::from_str(commitment).unwrap(), + keypair: signer_from_path(matches, keypair_path, "this transaction", &mut None) + .unwrap(), + no_pretty_print: matches.is_present("no_pretty_print"), + } + } +} + +pub fn get_target(matches: &ArgMatches) -> String { + matches + .value_of("target") + .unwrap() + .parse::() + .unwrap() + .to_lowercase() +} + +pub fn get_str_val<'a>(matches: &ArgMatches<'a>, argname: &str) -> String { + matches + .value_of(argname) + .unwrap() + .parse::() + .unwrap() + .to_uppercase() +} + +pub fn get_str_val_raw<'a>(matches: &ArgMatches<'a>, argname: &str) -> String { + matches + .value_of(argname) + .unwrap() + .parse::() + .unwrap() +} + +pub fn get_vec_str_val<'a>(matches: &ArgMatches<'a>, argname: &str) -> Vec { + matches + .value_of(argname) + .unwrap() + .parse::() + .unwrap() + .to_uppercase() + .split(',') + .collect::>() + .iter() + .map(|s| s.to_string()) + .collect() +} + +pub fn get_vec_str_val_raw<'a>(matches: &ArgMatches<'a>, argname: &str) -> Vec { + matches + .value_of(argname) + .unwrap() + .parse::() + .unwrap() + .split(',') + .collect::>() + .iter() + .map(|s| s.to_string()) + .collect() +} + +pub fn get_amount_val<'a>(matches: &ArgMatches<'a>, argname: &str) -> f64 { + matches.value_of(argname).unwrap().parse::().unwrap() +} + +pub fn get_pubkey_val<'a>(matches: &ArgMatches<'a>, argname: &str) -> Pubkey { + Pubkey::from_str(matches.value_of(argname).unwrap()).unwrap() +} + +pub fn get_integer_val<'a>(matches: &ArgMatches<'a>, argname: &str) -> u64 { + matches.value_of(argname).unwrap().parse::().unwrap() +} + +fn get_arg(name: &str) -> Arg { + Arg::with_name(name).required(true).takes_value(true) +} + +fn get_integer_arg(name: &str) -> Arg { + Arg::with_name(name) + .takes_value(true) + .required(true) + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned decimal")), + Ok(val) => { + if val >= 0.0 { + Ok(()) + } else { + Err(String::from("Must be unsigned decimal")) + } + } + }) +} + +pub fn get_clap_app<'a, 'b>(version: &'b str) -> App<'a, 'b> { + let target = Arg::with_name("target") + .required(true) + .takes_value(true) + .possible_values(&["program", "vault", "farm", "pool", "token"]) + .hide_possible_values(true) + .help("Target object type (program, vault, etc.)"); + + let objectname = Arg::with_name("object_name") + .required(true) + .takes_value(true) + .help("Target object name"); + + let tokenname = Arg::with_name("token_name") + .required(true) + .takes_value(true) + .help("Token name"); + + let tokenname2 = Arg::with_name("token_name2") + .required(true) + .takes_value(true) + .help("Second token name"); + + let amount = Arg::with_name("amount") + .takes_value(true) + .required(true) + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned decimal")), + Ok(val) => { + if val >= 0.0 { + Ok(()) + } else { + Err(String::from("Must be unsigned decimal")) + } + } + }) + .help("Token amount"); + + let amount2 = Arg::with_name("amount2") + .takes_value(true) + .required(false) + .default_value("0") + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned decimal")), + Ok(val) => { + if val >= 0.0 { + Ok(()) + } else { + Err(String::from("Must be unsigned decimal")) + } + } + }) + .help("Second token amount"); + + let wallet = Arg::with_name("wallet") + .takes_value(true) + .required(true) + .validator(|p| match Pubkey::from_str(&p) { + Err(_) => Err(String::from("Must be public key")), + Ok(_) => Ok(()), + }) + .help("Wallet address"); + + App::new(crate_name!()) + .about(crate_description!()) + .version(version) + .arg( + Arg::with_name("log_level") + .short("L") + .long("log-level") + .takes_value(true) + .default_value("info") + .global(true) + .help("Log verbosity level (debug, info, warning, error)") + .validator(|p| { + let allowed = ["debug", "info", "warning", "error"]; + if allowed.contains(&p.as_str()) { + Ok(()) + } else { + Err(String::from("Must be one of: debug, info, warning, error")) + } + }), + ) + .arg({ + let arg = Arg::with_name("config_file") + .short("C") + .long("config") + .value_name("PATH") + .takes_value(true) + .global(true) + .help("Configuration file to use"); + if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { + arg.default_value(config_file) + } else { + arg + } + }) + .arg( + Arg::with_name("farm_client_url") + .short("f") + .long("farm-client-url") + .value_name("STR") + .takes_value(true) + .global(true) + .validator(is_url) + .help("RPC URL to use with Farm Client"), + ) + .arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("KEYPAIR") + .global(true) + .takes_value(true) + .help("Filepath or URL to a keypair"), + ) + .arg( + Arg::with_name("commitment") + .long("commitment") + .short("c") + .takes_value(true) + .possible_values(&[ + "processed", + "confirmed", + "finalized", + ]) + .value_name("COMMITMENT_LEVEL") + .hide_possible_values(true) + .global(true) + .help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"), + ) + .arg( + Arg::with_name("no_pretty_print") + .short("n") + .long("no-pretty-print") + .global(true) + .takes_value(false) + .help("Print every record in one line"), + ) + .subcommand( + SubCommand::with_name("get") + .about("Query specified object in blockchain and print") + .arg(target.clone()) + .arg(objectname.clone()), + ) + .subcommand( + SubCommand::with_name("get-ref") + .about("Query specified object by reference address and print") + .arg(target.clone()) + .arg(objectname.clone()), + ) + .subcommand( + SubCommand::with_name("get-all") + .about("Query all objects of the given type and print") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("list-all") + .about("Query all object names of the given type and print") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("pool-price") + .about("Print pool price") + .arg(get_arg("pool_name")), + ) + .subcommand( + SubCommand::with_name("transfer") + .about("Transfer SOL to another wallet") + .arg(wallet.clone()) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("token-transfer") + .about("Transfer tokens to another wallet") + .arg(tokenname.clone()) + .arg(wallet.clone()) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("token-address") + .about("Print associated token account address") + .arg(tokenname.clone()), + ) + .subcommand(SubCommand::with_name("balance").about("Print SOL balance")) + .subcommand( + SubCommand::with_name("token-balance") + .about("Print token balance") + .arg(tokenname.clone()), + ) + .subcommand( + SubCommand::with_name("stake-balance") + .about("Print user's stake balance in the farm") + .arg(get_arg("farm_name")), + ) + .subcommand( + SubCommand::with_name("wallet-balances") + .about("Print all token balances for the wallet") + ) + .subcommand( + SubCommand::with_name("token-create") + .about("Create associated token account") + .arg(tokenname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-info") + .about("Print vault stats") + .arg(get_arg("vault_name")), + ) + .subcommand( + SubCommand::with_name("vault-user-info") + .about("Print user stats for the vault") + .arg(get_arg("vault_name")), + ) + .subcommand( + SubCommand::with_name("find-pools") + .about("Find all Pools with tokens A and B") + .arg(get_arg("protocol")) + .arg(tokenname.clone()) + .arg(tokenname2.clone()) + ) + .subcommand( + SubCommand::with_name("find-pools-with-lp") + .about("Find all Pools for the given LP token") + .arg(tokenname.clone()) + ) + .subcommand( + SubCommand::with_name("find-farms-with-lp") + .about("Find all Farms for the given LP token") + .arg(tokenname.clone()) + ) + .subcommand( + SubCommand::with_name("find-vaults") + .about("Find all Vaults with tokens A and B") + .arg(tokenname.clone()) + .arg(tokenname2.clone()) + ) + .subcommand( + SubCommand::with_name("swap") + .about("Swap tokens in the pool") + .arg(get_arg("protocol")) + .arg(tokenname.clone()) + .arg(tokenname2.clone()) + .arg(amount.clone()) + .arg(amount2.clone()), + ) + .subcommand( + SubCommand::with_name("deposit-pool") + .about("Add liquidity to the pool") + .arg(get_arg("pool_name")) + .arg(amount.clone()) + .arg(amount2.clone()), + ) + .subcommand( + SubCommand::with_name("withdraw-pool") + .about("Remove liquidity from the pool") + .arg(get_arg("pool_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("stake") + .about("Stake LP tokens to the farm") + .arg(get_arg("farm_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("harvest") + .about("Harvest farm rewards") + .arg(get_arg("farm_name")), + ) + .subcommand( + SubCommand::with_name("unstake") + .about("Unstake LP tokens from the farm") + .arg(get_arg("farm_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("deposit-vault") + .about("Add liquidity to the vault") + .arg(get_arg("vault_name")) + .arg(amount.clone()) + .arg(amount2.clone()), + ) + .subcommand( + SubCommand::with_name("deposit-vault-locked") + .about("Add locked liquidity to the vault") + .arg(get_arg("vault_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("withdraw-vault") + .about("Remove liquidity from the vault") + .arg(get_arg("vault_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("withdraw-vault-unlocked") + .about("Remove unlocked liquidity from the vault") + .arg(get_arg("vault_name")) + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("governance") + .about("Governance commands. See `solana-farm-client governance help`") + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand( + SubCommand::with_name("tokens-deposit") + .about("Deposit governing tokens") + .arg(amount.clone()), + ) + .subcommand( + SubCommand::with_name("tokens-withdraw") + .about("Withdraw governing tokens") + ) + .subcommand( + SubCommand::with_name("proposal-new") + .about("Create a new proposal") + .arg(get_arg("governed_account_name")) + .arg(get_arg("proposal_name")) + .arg(get_arg("proposal_link")) + .arg(get_integer_arg("proposal_index")) + ) + .subcommand( + SubCommand::with_name("proposal-cancel") + .about("Cancel the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + ) + .subcommand( + SubCommand::with_name("signatory-add") + .about("Add a signatory to the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_arg("signatory")) + ) + .subcommand( + SubCommand::with_name("signatory-remove") + .about("Remove the signatory from the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_arg("signatory")) + ) + .subcommand( + SubCommand::with_name("sign-off") + .about("Sign off the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + ) + .subcommand( + SubCommand::with_name("vote-cast") + .about("Cast a vote on the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_integer_arg("vote")) + ) + .subcommand( + SubCommand::with_name("vote-relinquish") + .about("Remove the vote from the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + ) + .subcommand( + SubCommand::with_name("vote-finalize") + .about("Finalize the vote on the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + ) + .subcommand( + SubCommand::with_name("instruction-insert") + .about("Add a new instruction to the proposal. Must be serialized with base64::encode(bincode::serialize(&inst).unwrap().as_slice())") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_integer_arg("instruction_index")) + .arg(get_arg("base64_instruction")) + ) + .subcommand( + SubCommand::with_name("instruction-remove") + .about("Remove the instruction from the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_integer_arg("instruction_index")) + ) + .subcommand( + SubCommand::with_name("instruction-execute") + .about("Execute the instruction in the proposal") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_integer_arg("instruction_index")) + ) + .subcommand( + SubCommand::with_name("instruction-flag-error") + .about("Mark the instruction as failed") + .arg(get_arg("governed_account_name")) + .arg(get_integer_arg("proposal_index")) + .arg(get_integer_arg("instruction_index")) + ) + ) +} diff --git a/farms/farm-client/src/cli/main.rs b/farms/farm-client/src/cli/main.rs new file mode 100644 index 00000000000..51c317f12b8 --- /dev/null +++ b/farms/farm-client/src/cli/main.rs @@ -0,0 +1,555 @@ +//! Command-line interface for the Farm Client + +mod config; +mod printer; + +use { + log::error, + solana_farm_client::client::FarmClient, + solana_sdk::{instruction::Instruction, pubkey::Pubkey}, + std::str::FromStr, +}; + +fn main() { + let matches = config::get_clap_app(solana_version::version!()).get_matches(); + + // set log verbosity level + let log_level = "solana=".to_string() + matches.value_of("log_level").unwrap(); + solana_logger::setup_with_default(log_level.as_str()); + + // load config params + let config = config::Config::new(&matches); + let client = FarmClient::new_with_commitment(&config.farm_client_url, config.commitment); + let wallet = config.keypair.pubkey(); + + // parse commands + match matches.subcommand() { + ("get", Some(subcommand_matches)) => { + let target = config::get_target(subcommand_matches); + let objects = config::get_vec_str_val(subcommand_matches, "object_name"); + for object in objects { + printer::print(&client, &config, &target, &object.to_string()); + } + } + ("get-ref", Some(subcommand_matches)) => { + let target = config::get_target(subcommand_matches); + let objects = config::get_vec_str_val_raw(subcommand_matches, "object_name"); + for object in objects { + printer::print_with_ref(&client, &config, &target, &object.to_string()); + } + } + ("get-all", Some(subcommand_matches)) => { + let target = config::get_target(subcommand_matches); + printer::print_all(&client, &config, &target); + } + ("list-all", Some(subcommand_matches)) => { + let target = config::get_target(subcommand_matches); + printer::list_all(&client, &config, &target); + } + ("pool-price", Some(subcommand_matches)) => { + let pools = config::get_vec_str_val(subcommand_matches, "pool_name"); + for pool in pools { + println!("{} price: {}", pool, client.get_pool_price(&pool).unwrap()); + } + } + ("transfer", Some(subcommand_matches)) => { + let destination = config::get_pubkey_val(subcommand_matches, "wallet"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + client + .transfer(config.keypair.as_ref(), &destination, amount) + .unwrap(); + } + ("token-transfer", Some(subcommand_matches)) => { + let token_name = config::get_str_val(subcommand_matches, "token_name"); + let destination = config::get_pubkey_val(subcommand_matches, "wallet"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + client + .token_transfer(config.keypair.as_ref(), &token_name, &destination, amount) + .unwrap(); + } + ("token-address", Some(subcommand_matches)) => { + let tokens = config::get_vec_str_val(subcommand_matches, "token_name"); + for token in tokens { + println!( + "{} address: {}", + token, + client + .get_associated_token_address(&wallet, &token) + .unwrap() + ); + } + } + ("balance", Some(_)) => { + println!( + "SOL balance: {}", + client.get_account_balance(&wallet).unwrap() + ); + } + ("token-balance", Some(subcommand_matches)) => { + let tokens = config::get_vec_str_val(subcommand_matches, "token_name"); + for token in tokens { + if let Ok(balance) = client.get_token_account_balance(&wallet, &token) { + println!("{} balance: {}", token, balance); + } else { + println!("{} balance: no account", token); + } + } + } + ("stake-balance", Some(subcommand_matches)) => { + let farms = config::get_vec_str_val(subcommand_matches, "farm_name"); + for farm in farms { + if let Ok(balance) = client.get_user_stake_balance(&wallet, &farm) { + println!("{} balance: {}", farm, balance); + } else { + println!("{} balance: no account", farm); + } + } + } + ("wallet-balances", Some(_subcommand_matches)) => { + println!( + "SOL balance: {}", + client.get_account_balance(&wallet).unwrap() + ); + let tokens = client.get_wallet_tokens(&wallet).unwrap(); + for token in tokens { + if let Ok(balance) = client.get_token_account_balance(&wallet, &token) { + println!("{} balance: {}", token, balance); + } else { + println!("{} balance: no account", token); + } + } + } + ("token-create", Some(subcommand_matches)) => { + let tokens = config::get_vec_str_val(subcommand_matches, "token_name"); + for token in tokens { + println!( + "{} address: {}", + token, + client + .get_or_create_token_account(config.keypair.as_ref(), &token) + .unwrap() + ); + } + } + ("vault-info", Some(subcommand_matches)) => { + let object = config::get_str_val(subcommand_matches, "vault_name"); + let vault = client.get_vault(&object).unwrap(); + let vault_info = client.get_vault_info(&object).unwrap(); + printer::print_object(&config, &vault.info_account, &vault_info); + } + ("vault-user-info", Some(subcommand_matches)) => { + let object = config::get_str_val(subcommand_matches, "vault_name"); + let account = client + .get_vault_user_info_account(&wallet, &object) + .unwrap(); + let user_info = client.get_vault_user_info(&wallet, &object).unwrap(); + printer::print_object(&config, &account, &user_info); + } + ("find-pools", Some(subcommand_matches)) => { + let protocol = config::get_str_val(subcommand_matches, "protocol"); + let token1 = config::get_str_val(subcommand_matches, "token_name"); + let token2 = config::get_str_val(subcommand_matches, "token_name2"); + match client.find_pools(&protocol, &token1, &token2) { + Ok(pools) => { + for pool in pools { + println!("{}", pool.name); + } + } + Err(e) => { + println!("{}", e); + } + } + } + ("find-pools-with-lp", Some(subcommand_matches)) => { + let lp_token = config::get_str_val(subcommand_matches, "token_name"); + match client.find_pools_with_lp(&lp_token) { + Ok(pools) => { + for pool in pools { + println!("{}", pool.name); + } + } + Err(e) => { + println!("{}", e); + } + } + } + ("find-farms-with-lp", Some(subcommand_matches)) => { + let lp_token = config::get_str_val(subcommand_matches, "token_name"); + match client.find_farms_with_lp(&lp_token) { + Ok(farms) => { + for farm in farms { + println!("{}", farm.name); + } + } + Err(e) => { + println!("{}", e); + } + } + } + ("find-vaults", Some(subcommand_matches)) => { + let token1 = config::get_str_val(subcommand_matches, "token_name"); + let token2 = config::get_str_val(subcommand_matches, "token_name2"); + match client.find_vaults(&token1, &token2) { + Ok(vaults) => { + for vault in vaults { + println!("{}", vault.name); + } + } + Err(e) => { + println!("{}", e); + } + } + } + ("swap", Some(subcommand_matches)) => { + let protocol = config::get_str_val(subcommand_matches, "protocol"); + let token_from = config::get_str_val(subcommand_matches, "token_name"); + let token_to = config::get_str_val(subcommand_matches, "token_name2"); + let amount_in = config::get_amount_val(subcommand_matches, "amount"); + let min_amount_out = config::get_amount_val(subcommand_matches, "amount2"); + println!( + "Done: {}", + client + .swap( + config.keypair.as_ref(), + &protocol, + &token_from, + &token_to, + amount_in, + min_amount_out + ) + .unwrap() + ); + } + ("deposit-pool", Some(subcommand_matches)) => { + let pool_name = config::get_str_val(subcommand_matches, "pool_name"); + let token_a_amount = config::get_amount_val(subcommand_matches, "amount"); + let token_b_amount = config::get_amount_val(subcommand_matches, "amount2"); + println!( + "Done: {}", + client + .add_liquidity_pool( + config.keypair.as_ref(), + &pool_name, + token_a_amount, + token_b_amount + ) + .unwrap() + ); + } + ("withdraw-pool", Some(subcommand_matches)) => { + let pool_name = config::get_str_val(subcommand_matches, "pool_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .remove_liquidity_pool(config.keypair.as_ref(), &pool_name, amount) + .unwrap() + ); + } + ("stake", Some(subcommand_matches)) => { + let farm_name = config::get_str_val(subcommand_matches, "farm_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .stake(config.keypair.as_ref(), &farm_name, amount) + .unwrap() + ); + } + ("harvest", Some(subcommand_matches)) => { + let farm_name = config::get_str_val(subcommand_matches, "farm_name"); + println!( + "Done: {}", + client.harvest(config.keypair.as_ref(), &farm_name).unwrap() + ); + } + ("unstake", Some(subcommand_matches)) => { + let farm_name = config::get_str_val(subcommand_matches, "farm_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .unstake(config.keypair.as_ref(), &farm_name, amount) + .unwrap() + ); + } + ("deposit-vault", Some(subcommand_matches)) => { + let vault_name = config::get_str_val(subcommand_matches, "vault_name"); + let token_a_amount = config::get_amount_val(subcommand_matches, "amount"); + let token_b_amount = config::get_amount_val(subcommand_matches, "amount2"); + println!( + "Done: {}", + client + .add_liquidity_vault( + config.keypair.as_ref(), + &vault_name, + token_a_amount, + token_b_amount + ) + .unwrap() + ); + } + ("deposit-vault-locked", Some(subcommand_matches)) => { + let vault_name = config::get_str_val(subcommand_matches, "vault_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .add_locked_liquidity_vault(config.keypair.as_ref(), &vault_name, amount) + .unwrap() + ); + } + ("withdraw-vault", Some(subcommand_matches)) => { + let vault_name = config::get_str_val(subcommand_matches, "vault_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .remove_liquidity_vault(config.keypair.as_ref(), &vault_name, amount) + .unwrap() + ); + } + ("withdraw-vault-unlocked", Some(subcommand_matches)) => { + let vault_name = config::get_str_val(subcommand_matches, "vault_name"); + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .remove_unlocked_liquidity_vault(config.keypair.as_ref(), &vault_name, amount) + .unwrap() + ); + } + ("governance", Some(subcommand_matches)) => match subcommand_matches.subcommand() { + ("tokens-deposit", Some(subcommand_matches)) => { + let amount = config::get_amount_val(subcommand_matches, "amount"); + println!( + "Done: {}", + client + .governance_tokens_deposit(config.keypair.as_ref(), amount) + .unwrap() + ); + } + ("tokens-withdraw", Some(_subcommand_matches)) => { + println!( + "Done: {}", + client + .governance_tokens_withdraw(config.keypair.as_ref()) + .unwrap() + ); + } + ("proposal-new", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_name = config::get_str_val_raw(subcommand_matches, "proposal_name"); + let proposal_link = config::get_str_val_raw(subcommand_matches, "proposal_link"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + println!( + "Done: {}", + client + .governance_proposal_new( + config.keypair.as_ref(), + &governed_account_name, + &proposal_name, + &proposal_link, + proposal_index as u32 + ) + .unwrap() + ); + } + ("proposal-cancel", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + println!( + "Done: {}", + client + .governance_proposal_cancel( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32 + ) + .unwrap() + ); + } + ("signatory-add", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let signatory = + Pubkey::from_str(&config::get_str_val_raw(subcommand_matches, "signatory")) + .unwrap(); + println!( + "Done: {}", + client + .governance_signatory_add( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + &signatory + ) + .unwrap() + ); + } + ("signatory-remove", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let signatory = + Pubkey::from_str(&config::get_str_val_raw(subcommand_matches, "signatory")) + .unwrap(); + println!( + "Done: {}", + client + .governance_signatory_remove( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + &signatory + ) + .unwrap() + ); + } + ("sign-off", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + println!( + "Done: {}", + client + .governance_sign_off( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32 + ) + .unwrap() + ); + } + ("vote-cast", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let vote = config::get_integer_val(subcommand_matches, "vote"); + println!( + "Done: {}", + client + .governance_vote_cast( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + vote as u8 + ) + .unwrap() + ); + } + ("vote-relinquish", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + println!( + "Done: {}", + client + .governance_vote_relinquish( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32 + ) + .unwrap() + ); + } + ("vote-finalize", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + println!( + "Done: {}", + client + .governance_vote_finalize( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32 + ) + .unwrap() + ); + } + ("instruction-insert", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let instruction_index = + config::get_integer_val(subcommand_matches, "instruction_index"); + let instruction_str = + config::get_str_val_raw(subcommand_matches, "base64_instruction"); + let data = base64::decode(&instruction_str).unwrap(); + let instruction: Instruction = bincode::deserialize(data.as_slice()).unwrap(); + println!( + "Done: {}", + client + .governance_instruction_insert( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + instruction_index as u16, + &instruction + ) + .unwrap() + ); + } + ("instruction-remove", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let instruction_index = + config::get_integer_val(subcommand_matches, "instruction_index"); + println!( + "Done: {}", + client + .governance_instruction_remove( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + instruction_index as u16, + ) + .unwrap() + ); + } + ("instruction-execute", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let instruction_index = + config::get_integer_val(subcommand_matches, "instruction_index"); + println!( + "Done: {}", + client + .governance_instruction_execute( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + instruction_index as u16, + ) + .unwrap() + ); + } + ("instruction-flag-error", Some(subcommand_matches)) => { + let governed_account_name = + config::get_str_val_raw(subcommand_matches, "governed_account_name"); + let proposal_index = config::get_integer_val(subcommand_matches, "proposal_index"); + let instruction_index = + config::get_integer_val(subcommand_matches, "instruction_index"); + println!( + "Done: {}", + client + .governance_instruction_flag_error( + config.keypair.as_ref(), + &governed_account_name, + proposal_index as u32, + instruction_index as u16, + ) + .unwrap() + ); + } + _ => unreachable!(), + }, + _ => error!("Unrecognized command. Use --help to list known commands."), + }; +} diff --git a/farms/farm-client/src/cli/printer.rs b/farms/farm-client/src/cli/printer.rs new file mode 100644 index 00000000000..07930e0c0f4 --- /dev/null +++ b/farms/farm-client/src/cli/printer.rs @@ -0,0 +1,177 @@ +//! Handlers for get and get_all command + +use { + crate::config::Config, + log::{error, info}, + serde::Serialize, + solana_farm_client::client::FarmClient, + solana_farm_sdk::string::to_pretty_json, + solana_sdk::pubkey::Pubkey, + std::str::FromStr, +}; + +pub fn print(client: &FarmClient, config: &Config, target: &str, object: &str) { + match target { + "program" => { + println!("{}: {}", object, client.get_program_id(object).unwrap()); + } + "vault" => { + print_object( + config, + &client.get_vault_ref(object).unwrap(), + &client.get_vault(object).unwrap(), + ); + } + "farm" => { + print_object( + config, + &client.get_farm_ref(object).unwrap(), + &client.get_farm(object).unwrap(), + ); + } + "pool" => { + print_object( + config, + &client.get_pool_ref(object).unwrap(), + &client.get_pool(object).unwrap(), + ); + } + "token" => { + print_object( + config, + &client.get_token_ref(object).unwrap(), + &client.get_token(object).unwrap(), + ); + } + _ => { + error!("Unrecognized target. Must be one of: token, pool, farm, vault, or program."); + } + } +} + +pub fn print_with_ref(client: &FarmClient, config: &Config, target: &str, object: &str) { + let ref_key = Pubkey::from_str(object).unwrap(); + match target { + "program" => { + println!("{}: {}", client.get_program_name(&ref_key).unwrap(), object); + } + "vault" => { + print_object( + config, + &ref_key, + &client.get_vault_by_ref(&ref_key).unwrap(), + ); + } + "farm" => { + print_object(config, &ref_key, &client.get_farm_by_ref(&ref_key).unwrap()); + } + "pool" => { + print_object(config, &ref_key, &client.get_pool_by_ref(&ref_key).unwrap()); + } + "token" => { + print_object( + config, + &ref_key, + &client.get_token_by_ref(&ref_key).unwrap(), + ); + } + _ => { + error!("Unrecognized target. Must be one of: token, pool, farm, vault, or program."); + } + } +} + +pub fn print_all(client: &FarmClient, config: &Config, target: &str) { + info!("Loading {} objects...", target); + + match target { + "program" => { + let storage = client.get_program_ids().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + "vault" => { + let storage = client.get_vaults().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_vault_ref(name).unwrap(), key); + } + } + "farm" => { + let storage = client.get_farms().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_farm_ref(name).unwrap(), key); + } + } + "pool" => { + let storage = client.get_pools().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_pool_ref(name).unwrap(), key); + } + } + "token" => { + let storage = client.get_tokens().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_token_ref(name).unwrap(), key); + } + } + _ => { + error!("Unrecognized target. Must be one of: token, pool, farm, vault, or program."); + } + } + + info!("Done.") +} + +pub fn list_all(client: &FarmClient, _config: &Config, target: &str) { + info!("Loading {} objects...", target); + + match target { + "program" => { + let storage = client.get_program_ids().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + "vault" => { + let storage = client.get_vault_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + "farm" => { + let storage = client.get_farm_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + "pool" => { + let storage = client.get_pool_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + "token" => { + let storage = client.get_token_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + _ => { + error!("Unrecognized target. Must be one of: token, pool, farm, vault, or program."); + } + } + + info!("Done.") +} + +pub fn print_object(config: &Config, key: &Pubkey, object: &T) +where + T: ?Sized + Serialize + std::fmt::Display, +{ + if config.no_pretty_print { + println!("{}: {}", key, object); + } else { + println!("{}: {}", key, to_pretty_json(object).unwrap()); + } +} diff --git a/farms/farm-client/src/client.rs b/farms/farm-client/src/client.rs new file mode 100644 index 00000000000..ec6ef49b7d7 --- /dev/null +++ b/farms/farm-client/src/client.rs @@ -0,0 +1,3875 @@ +//! Solana Farm Client +//! +//! Solana Farm Client provides an easy way to interact with pools, farms, and vaults, +//! query on-chain objects metadata, and perform common operations with accounts. +//! +//! Client's methods accept human readable names (tokens, polls, etc.) and UI (decimal) +//! amounts, so you can simply call client.swap(&keypair, "RDM", "SOL", "RAY", 0.1, 0.0) +//! to swap 0.1 SOL for RAY in a Raydium pool. All metadata required to lookup account +//! addresses, decimals, etc. is stored on-chain. +//! +//! Under the hood it leverages the official Solana RPC Client which can be accessed with +//! client.rpc_client, for example: client.rpc_client.get_recent_blockhash(). +//! +//! Naming convention for Pools and Farms is [PROTOCOL].[TOKEN_A]-[TOKEN_B]-[VERSION] +//! Naming convention for Vaults is [PROTOCOL].[STRATEGY].[TOKEN_A]-[TOKEN_B]-[VERSION] +//! There are single token pools where TOKEN_B is not present. +//! If VERSION is omitted then Pool, Farm, or Vault with the latest version will be used. +//! +//! A few examples: +//! # use { +//! # solana_farm_client::client::FarmClient, +//! # solana_sdk::{pubkey::Pubkey, signer::Signer}, +//! # }; +//! # +//! # let client = FarmClient::new("https://api.mainnet-beta.solana.com"); +//! # let keypair = FarmClient::read_keypair_from_file( +//! # &(std::env::var("HOME").unwrap().to_string() + "/.config/solana/id.json"), +//! # ) +//! # .unwrap(); +//! # +//! # // get SOL account balance +//! # client.get_account_balance(&keypair.pubkey()).unwrap(); +//! # +//! # // get SPL token account balance +//! # client +//! # .get_token_account_balance(&keypair.pubkey(), "SRM") +//! # .unwrap(); +//! # +//! # // get token metadata +//! # client.get_token("SRM").unwrap(); +//! # +//! # // find Raydium pools with RAY and SRM tokens +//! # client.find_pools("RDM", "RAY", "SRM").unwrap(); +//! # +//! # // find Saber pools with USDC and USDT tokens +//! # client.find_pools("SBR", "USDC", "USDT").unwrap(); +//! # +//! # // get pool metadata +//! # client.get_pool("RDM.RAY-SRM").unwrap(); +//! # +//! # // get farm metadata +//! # client.get_farm("RDM.RAY-SRM").unwrap(); +//! # +//! # // find all vaults with RAY and SRM tokens +//! # client.find_vaults("RAY", "SRM").unwrap(); +//! # +//! # // get vault metadata +//! # client.get_vault("RDM.STC.RAY-SRM").unwrap(); +//! # +//! # // get the list of all pools +//! # client.get_pools().unwrap(); +//! # +//! # // find farms for specific LP token +//! # client.find_farms_with_lp("LP.RDM.RAY-SRM-V4").unwrap(); +//! # +//! # // get Raydium pool price +//! # client.get_pool_price("RDM.RAY-SRM").unwrap(); +//! # // or specify version for specific pool +//! # client.get_pool_price("RDM.RAY-SRM-V4").unwrap(); +//! # +//! # // list official program IDs +//! # client.get_program_ids().unwrap(); +//! # +//! # // swap in the Raydium pool +//! # client.swap(&keypair, "RDM", "SOL", "RAY", 0.01, 0.0).unwrap(); +//! # +//! # // swap in the Saber pool +//! # client.swap(&keypair, "SBR", "USDC", "USDT", 0.01, 0.0).unwrap(); +//! # +//! # // deposit liquidity to the Raydium pool (zero second token amount means calculate it automatically) +//! # client +//! # .add_liquidity_pool(&keypair, "RDM.GRAPE-USDC", 0.1, 0.0) +//! # .unwrap(); +//! # +//! # // withdraw your liquidity from the Raydium pool (zero amount means remove all tokens) +//! # client +//! # .remove_liquidity_pool(&keypair, "RDM.GRAPE-USDC", 0.0) +//! # .unwrap(); +//! # +//! # // stake LP tokens to the Raydium farm (zero amount means stake all) +//! # client.stake(&keypair, "RDM.GRAPE-USDC", 0.0).unwrap(); +//! # +//! # // harvest rewards +//! # client.harvest(&keypair, "RDM.GRAPE-USDC").unwrap(); +//! # +//! # // unstake LP tokens from the farm (zero amount means unstake all) +//! # client.unstake(&keypair, "RDM.GRAPE-USDC", 0.0).unwrap(); +//! # +//! # // deposit liquidity to the vault (zero second token amount means calculate it automatically) +//! # client +//! # .add_liquidity_vault(&keypair, "RDM.STC.RAY-SRM", 0.01, 0.0) +//! # .unwrap(); +//! # +//! # // withdraw liquidity from the vault (zero amount means remove all tokens) +//! # client +//! # .remove_liquidity_vault(&keypair, "RDM.STC.RAY-SRM", 0.0) +//! # .unwrap(); +//! # +//! # // transfer SOL to another wallet +//! # client +//! # .transfer(&keypair, &Pubkey::new_unique(), 0.001) +//! # .unwrap(); +//! # +//! # // transfer SPL tokens to another wallet +//! # client +//! # .token_transfer(&keypair, "SRM", &Pubkey::new_unique(), 0.001) +//! # .unwrap(); +//! # +//! # // create associated token account for the wallet +//! # client.get_or_create_token_account(&keypair, "SRM").unwrap(); +//! # +//! # // get vault stats +//! # client.get_vault_info("RDM.STC.RAY-SRM").unwrap(); +//! # +//! # // get user stats for particular vault +//! # client +//! # .get_vault_user_info(&keypair.pubkey(), "RDM.STC.RAY-SRM") +//! # .unwrap(); +//! # +//! # // create a new instruction for depositing liquidity to the vault, neither sign nor send it +//! # client +//! # .new_instruction_add_liquidity_vault(&keypair.pubkey(), "RDM.STC.RAY-SRM", 0.1, 0.0) +//! # .unwrap(); +//! # + +use { + crate::{cache::Cache, error::FarmClientError}, + arrayref::array_ref, + solana_account_decoder::parse_token::{ + parse_token, TokenAccountType, UiAccountState, UiTokenAccount, + }, + solana_client::{ + client_error::ClientErrorKind, + rpc_client::RpcClient, + rpc_config::RpcProgramAccountsConfig, + rpc_custom_error, rpc_filter, + rpc_request::{RpcError, TokenAccountsFilter}, + }, + solana_farm_sdk::{ + farm::{Farm, FarmRoute}, + id::zero, + id::{main_router, main_router_admin, ProgramIDType}, + instruction::orca::OrcaUserInit, + pool::{Pool, PoolRoute}, + program::pda::find_refdb_pda, + program::protocol::{ + raydium::{RaydiumUserStakeInfo, RaydiumUserStakeInfoV4}, + saber::Miner, + }, + refdb, + refdb::RefDB, + token::{Token, TokenType}, + vault::{UserInfo, Vault, VaultInfo, VaultStrategy}, + }, + solana_sdk::{ + clock::UnixTimestamp, + commitment_config::CommitmentConfig, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + signature::{read_keypair, read_keypair_file, Keypair, Signature, Signer}, + signers::Signers, + system_program, + transaction::Transaction, + }, + spl_associated_token_account::{create_associated_token_account, get_associated_token_address}, + spl_token::state::Mint, + stable_swap_client::state::SwapInfo, + stable_swap_math::price::SaberSwap, + std::{ + cell::RefCell, collections::HashMap, str::FromStr, thread, time, time::Duration, vec::Vec, + }, +}; + +pub type VaultMap = HashMap; +pub type PoolMap = HashMap; +pub type FarmMap = HashMap; +pub type TokenMap = HashMap; +pub type PubkeyMap = HashMap; +pub type StakeAccMap = HashMap; +pub type U64Map = HashMap; + +/// Farm Client +pub struct FarmClient { + pub rpc_client: RpcClient, + tokens: RefCell>, + pools: RefCell>, + farms: RefCell>, + vaults: RefCell>, + token_refs: RefCell>, + pool_refs: RefCell>, + farm_refs: RefCell>, + vault_refs: RefCell>, + official_ids: RefCell>, + stake_accounts: RefCell>>, + latest_pools: RefCell>, + latest_farms: RefCell>, + latest_vaults: RefCell>, +} + +impl Default for FarmClient { + fn default() -> Self { + Self { + rpc_client: RpcClient::new("".to_string()), + tokens: RefCell::new(Cache::::default()), + pools: RefCell::new(Cache::::default()), + farms: RefCell::new(Cache::::default()), + vaults: RefCell::new(Cache::::default()), + token_refs: RefCell::new(Cache::::default()), + pool_refs: RefCell::new(Cache::::default()), + farm_refs: RefCell::new(Cache::::default()), + vault_refs: RefCell::new(Cache::::default()), + official_ids: RefCell::new(Cache::::default()), + stake_accounts: RefCell::new(vec![HashMap::::new(); 3]), + latest_pools: RefCell::new(HashMap::::new()), + latest_farms: RefCell::new(HashMap::::new()), + latest_vaults: RefCell::new(HashMap::::new()), + } + } +} + +impl FarmClient { + /// Creates a new FarmClient object + /// RPC URLs: + /// Devnet: https://api.devnet.solana.com + /// Testnet: https://api.testnet.solana.com + /// Mainnet-beta: https://api.mainnet-beta.solana.com + /// local node: http://localhost:8899 + pub fn new(url: &str) -> Self { + Self { + rpc_client: RpcClient::new(url.to_string()), + ..FarmClient::default() + } + } + + /// Creates a new FarmClient object with commitment config + pub fn new_with_commitment(url: &str, commitment_config: CommitmentConfig) -> Self { + Self { + rpc_client: RpcClient::new_with_commitment(url.to_string(), commitment_config), + ..FarmClient::default() + } + } + + /// Creates a new FarmClient object with timeout and config + pub fn new_with_timeout_and_commitment( + url: &str, + timeout: Duration, + commitment_config: CommitmentConfig, + ) -> Self { + Self { + rpc_client: RpcClient::new_with_timeout_and_commitment( + url.to_string(), + timeout, + commitment_config, + ), + ..FarmClient::default() + } + } + + pub fn new_mock(url: &str) -> Self { + Self { + rpc_client: RpcClient::new_mock(url.to_string()), + ..FarmClient::default() + } + } + + /// Returns the Vault struct for the given name + pub fn get_vault(&self, name: &str) -> Result { + let mut vault_name = if let Some(val) = self.latest_vaults.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + // reload Vaults if stale + if self.vaults.borrow().is_stale() { + self.vaults.borrow_mut().reset(); + } else { + // if Vault is in cache return it + if let Some(vault) = self.vaults.borrow().data.get(&vault_name) { + return Ok(*vault); + } + } + // reload Vault refs if stale + if self.reload_vault_refs_if_stale()? { + vault_name = if let Some(val) = self.latest_vaults.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + } + // load Vault data from blockchain + if let Some(key) = self.vault_refs.borrow().data.get(&vault_name) { + let vault = self.load_vault_by_ref(key)?; + self.vaults.borrow_mut().data.insert(vault_name, vault); + return Ok(vault); + } + Err(FarmClientError::RecordNotFound(format!("Vault {}", name))) + } + + /// Returns all Vaults available + pub fn get_vaults(&self) -> Result { + if !self.vaults.borrow().is_stale() { + return Ok(self.vaults.borrow().data.clone()); + } + self.reload_vault_refs_if_stale()?; + self.reload_vaults_if_stale()?; + Ok(self.vaults.borrow().data.clone()) + } + + /// Returns the Vault metadata address for the given name + pub fn get_vault_ref(&self, name: &str) -> Result { + // reload Vault refs if stale + self.reload_vault_refs_if_stale()?; + // return the address from cache + let vault_name = if let Some(val) = self.latest_vaults.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + if let Some(key) = self.vault_refs.borrow().data.get(&vault_name) { + return Ok(*key); + } + Err(FarmClientError::RecordNotFound(format!("Vault {}", name))) + } + + /// Returns Vault refs: a map of Vault name to account address with metadata + pub fn get_vault_refs(&self) -> Result { + self.reload_vault_refs_if_stale()?; + Ok(self.vault_refs.borrow().data.clone()) + } + + /// Returns the Vault metadata at the specified address + pub fn get_vault_by_ref(&self, vault_ref: &Pubkey) -> Result { + let name = &self.get_vault_name(vault_ref)?; + self.get_vault(name) + } + + /// Returns the Vault name for the given metadata address + pub fn get_vault_name(&self, vault_ref: &Pubkey) -> Result { + // reload Vault refs if stale + self.reload_vault_refs_if_stale()?; + // return the name from cache + for (name, key) in self.vault_refs.borrow().data.iter() { + if key == vault_ref { + return Ok(name.to_string()); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Vault reference {}", + vault_ref + ))) + } + + /// Returns all Vaults with tokens A and B sorted by version + pub fn find_vaults(&self, token_a: &str, token_b: &str) -> Result, FarmClientError> { + self.reload_vault_refs_if_stale()?; + let pattern1 = format!(".{}-{}-", token_a, token_b); + let pattern2 = format!(".{}-{}-", token_b, token_a); + let mut res = vec![]; + for (name, _) in self.vault_refs.borrow().data.iter() { + if name.contains(&pattern1) || name.contains(&pattern2) { + res.push(self.get_vault(name)?); + } + } + if res.is_empty() { + Err(FarmClientError::RecordNotFound(format!( + "Vault with tokens {} and {}", + token_a, token_b + ))) + } else { + res.sort_by(|a, b| b.version.cmp(&a.version)); + Ok(res) + } + } + + /// Returns the Pool struct for the given name + pub fn get_pool(&self, name: &str) -> Result { + let mut pool_name = if let Some(val) = self.latest_pools.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + // reload Pools if stale + if self.pools.borrow().is_stale() { + self.pools.borrow_mut().reset(); + } else { + // if Pool is in cache return it + if let Some(pool) = self.pools.borrow().data.get(&pool_name) { + return Ok(*pool); + } + } + // reload Pool refs if stale + if self.reload_pool_refs_if_stale()? { + pool_name = if let Some(val) = self.latest_pools.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + } + // load Pool data from blockchain + if let Some(key) = self.pool_refs.borrow().data.get(&pool_name) { + let pool = self.load_pool_by_ref(key)?; + self.pools.borrow_mut().data.insert(pool_name, pool); + return Ok(pool); + } + Err(FarmClientError::RecordNotFound(format!("Pool {}", name))) + } + + /// Returns all Pools available + pub fn get_pools(&self) -> Result { + if !self.pools.borrow().is_stale() { + return Ok(self.pools.borrow().data.clone()); + } + self.reload_pool_refs_if_stale()?; + self.reload_pools_if_stale()?; + Ok(self.pools.borrow().data.clone()) + } + + /// Returns the Pool metadata address for the given name + pub fn get_pool_ref(&self, name: &str) -> Result { + // reload Pool refs if stale + self.reload_pool_refs_if_stale()?; + // return the address from cache + let pool_name = if let Some(val) = self.latest_pools.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + if let Some(key) = self.pool_refs.borrow().data.get(&pool_name) { + return Ok(*key); + } + Err(FarmClientError::RecordNotFound(format!("Pool {}", name))) + } + + /// Returns Pool refs: a map of Pool name to account address with metadata + pub fn get_pool_refs(&self) -> Result { + self.reload_pool_refs_if_stale()?; + Ok(self.pool_refs.borrow().data.clone()) + } + + /// Returns the Pool metadata at the specified address + pub fn get_pool_by_ref(&self, pool_ref: &Pubkey) -> Result { + let name = &self.get_pool_name(pool_ref)?; + self.get_pool(name) + } + + /// Returns the Pool name for the given metadata address + pub fn get_pool_name(&self, pool_ref: &Pubkey) -> Result { + // reload Pool refs if stale + self.reload_pool_refs_if_stale()?; + // return the name from cache + for (name, key) in self.pool_refs.borrow().data.iter() { + if key == pool_ref { + return Ok(name.to_string()); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Pool reference {}", + pool_ref + ))) + } + + /// Returns all Pools with tokens A and B sorted by version for the given protocol + pub fn find_pools( + &self, + protocol: &str, + token_a: &str, + token_b: &str, + ) -> Result, FarmClientError> { + self.reload_pool_refs_if_stale()?; + let pattern1 = format!("{}.{}-{}-", protocol, token_a, token_b); + let pattern2 = format!("{}.{}-{}-", protocol, token_b, token_a); + let mut res = vec![]; + for (name, _) in self.pool_refs.borrow().data.iter() { + if name.starts_with(&pattern1) || name.starts_with(&pattern2) { + res.push(self.get_pool(name)?); + } + } + if res.is_empty() { + Err(FarmClientError::RecordNotFound(format!( + "{} Pool with tokens {} and {}", + protocol, token_a, token_b + ))) + } else { + res.sort_by(|a, b| b.version.cmp(&a.version)); + Ok(res) + } + } + + /// Returns all Pools sorted by version for the given LP token + pub fn find_pools_with_lp(&self, lp_token_name: &str) -> Result, FarmClientError> { + let (protocol, token_a, token_b) = FarmClient::extract_token_names(lp_token_name)?; + let pools = self.find_pools(&protocol, &token_a, &token_b)?; + let mut res = vec![]; + for pool in pools { + if let Some(lp_token) = self.get_token_by_ref_from_cache(&pool.lp_token_ref)? { + if lp_token.name.as_str() == lp_token_name { + res.push(pool); + } + } + } + + if res.is_empty() { + Err(FarmClientError::RecordNotFound(format!( + "{} Pool with LP token {}", + protocol, lp_token_name + ))) + } else { + res.sort_by(|a, b| b.version.cmp(&a.version)); + Ok(res) + } + } + + /// Returns pair's price based on the ratio of tokens in the pool + pub fn get_pool_price(&self, pool_name: &str) -> Result { + let pool = self.get_pool(pool_name)?; + if pool.token_a_ref.is_none() || pool.token_b_ref.is_none() { + return Ok(0.0); + } + let token_a = self.get_token_by_ref(&pool.token_a_ref.unwrap())?; + let token_b = self.get_token_by_ref(&pool.token_b_ref.unwrap())?; + let token_a_balance = self + .rpc_client + .get_token_account_balance( + &pool + .token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + )? + .amount + .parse::() + .unwrap(); + let token_b_balance = self + .rpc_client + .get_token_account_balance( + &pool + .token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + )? + .amount + .parse::() + .unwrap(); + + match pool.route { + PoolRoute::Raydium { + amm_id, + amm_open_orders, + .. + } => self.get_pool_price_raydium( + token_a_balance, + token_b_balance, + token_a.decimals, + token_b.decimals, + &amm_id, + &amm_open_orders, + ), + PoolRoute::Saber { swap_account, .. } => { + let lp_token = self.get_token_by_ref(&pool.lp_token_ref.unwrap())?; + self.get_pool_price_saber( + &swap_account, + token_a_balance, + token_b_balance, + &lp_token, + ) + } + PoolRoute::Orca { .. } => self.get_pool_price_orca( + token_a_balance, + token_b_balance, + token_a.decimals, + token_b.decimals, + ), + } + } + + /// Returns the Farm struct for the given name + pub fn get_farm(&self, name: &str) -> Result { + let mut farm_name = if let Some(val) = self.latest_farms.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + // reload Farms if stale + if self.farms.borrow().is_stale() { + self.farms.borrow_mut().reset(); + } else { + // if Farm is in cache return it + if let Some(farm) = self.farms.borrow().data.get(&farm_name) { + return Ok(*farm); + } + } + // reload Farm refs if stale + if self.reload_farm_refs_if_stale()? { + farm_name = if let Some(val) = self.latest_farms.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + } + // load Farm data from blockchain + if let Some(key) = self.farm_refs.borrow().data.get(&farm_name) { + let farm = self.load_farm_by_ref(key)?; + self.farms.borrow_mut().data.insert(farm_name, farm); + return Ok(farm); + } + Err(FarmClientError::RecordNotFound(format!("Farm {}", name))) + } + + /// Returns all Farms available + pub fn get_farms(&self) -> Result { + if !self.farms.borrow().is_stale() { + return Ok(self.farms.borrow().data.clone()); + } + self.reload_farm_refs_if_stale()?; + self.reload_farms_if_stale()?; + Ok(self.farms.borrow().data.clone()) + } + + /// Returns the Farm metadata address for the given name + pub fn get_farm_ref(&self, name: &str) -> Result { + // reload Farm refs if stale + self.reload_farm_refs_if_stale()?; + // return the address from cache + let farm_name = if let Some(val) = self.latest_farms.borrow().get(name) { + val.clone() + } else { + name.to_string() + }; + if let Some(key) = self.farm_refs.borrow().data.get(&farm_name) { + return Ok(*key); + } + Err(FarmClientError::RecordNotFound(format!("Farm {}", name))) + } + + /// Returns Farm refs: a map of Farm name to account address with metadata + pub fn get_farm_refs(&self) -> Result { + self.reload_farm_refs_if_stale()?; + Ok(self.farm_refs.borrow().data.clone()) + } + + /// Returns the Farm metadata at the specified address + pub fn get_farm_by_ref(&self, farm_ref: &Pubkey) -> Result { + let name = &self.get_farm_name(farm_ref)?; + self.get_farm(name) + } + + /// Returns the Farm name for the given metadata address + pub fn get_farm_name(&self, farm_ref: &Pubkey) -> Result { + // reload Farm refs if stale + self.reload_farm_refs_if_stale()?; + // return the name from cache + for (name, key) in self.farm_refs.borrow().data.iter() { + if key == farm_ref { + return Ok(name.to_string()); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Farm reference {}", + farm_ref + ))) + } + + /// Returns all Farms for the given LP token + pub fn find_farms_with_lp(&self, lp_token_name: &str) -> Result, FarmClientError> { + self.reload_farm_refs_if_stale()?; + let (protocol, token_a, token_b) = FarmClient::extract_token_names(lp_token_name)?; + let pattern1 = format!("{}.{}-{}-", protocol, token_a, token_b); + let pattern2 = format!("{}.{}-{}-", protocol, token_b, token_a); + let mut res = vec![]; + for (name, _) in self.farm_refs.borrow().data.iter() { + if name.contains(&pattern1) || name.contains(&pattern2) { + let farm = self.get_farm(name)?; + if let Some(lp_token) = self.get_token_by_ref_from_cache(&farm.lp_token_ref)? { + if lp_token.name.as_str() == lp_token_name { + res.push(farm); + } + } + } + } + + if res.is_empty() { + Err(FarmClientError::RecordNotFound(format!( + "{} Farm with LP token {}", + protocol, lp_token_name + ))) + } else { + res.sort_by(|a, b| b.version.cmp(&a.version)); + Ok(res) + } + } + + /// Returns the Token struct for the given name + pub fn get_token(&self, name: &str) -> Result { + // reload Tokens if stale + if self.tokens.borrow().is_stale() { + self.tokens.borrow_mut().reset(); + } else { + // if Token is in cache return it + if let Some(token) = self.tokens.borrow().data.get(name) { + return Ok(*token); + } + } + // reload Token refs if stale + self.reload_token_refs_if_stale()?; + // load Token data from blockchain + if let Some(key) = self.token_refs.borrow().data.get(name) { + let token = self.load_token_by_ref(key)?; + self.tokens + .borrow_mut() + .data + .insert(name.to_string(), token); + return Ok(token); + } + Err(FarmClientError::RecordNotFound(format!("Token {}", name))) + } + + /// Returns all Tokens available + pub fn get_tokens(&self) -> Result { + if !self.tokens.borrow().is_stale() { + return Ok(self.tokens.borrow().data.clone()); + } + self.reload_token_refs_if_stale()?; + self.reload_tokens_if_stale()?; + Ok(self.tokens.borrow().data.clone()) + } + + /// Returns the Token metadata address for the given name + pub fn get_token_ref(&self, name: &str) -> Result { + // reload Token refs if stale + self.reload_token_refs_if_stale()?; + // return the address from cache + if let Some(key) = self.token_refs.borrow().data.get(name) { + return Ok(*key); + } + Err(FarmClientError::RecordNotFound(format!("Token {}", name))) + } + + /// Returns Token refs: a map of Token name to account address with metadata + pub fn get_token_refs(&self) -> Result { + self.reload_token_refs_if_stale()?; + self.get_refdb_pubkey_map(&refdb::StorageType::Token.to_string()) + } + + /// Returns the Token metadata at the specified address + pub fn get_token_by_ref(&self, token_ref: &Pubkey) -> Result { + let name = &self.get_token_name(token_ref)?; + self.get_token(name) + } + + /// Returns the Token name for the given metadata address + pub fn get_token_name(&self, token_ref: &Pubkey) -> Result { + // reload Token refs if stale + self.reload_token_refs_if_stale()?; + // return the name from cache + for (name, key) in self.token_refs.borrow().data.iter() { + if key == token_ref { + return Ok(name.to_string()); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Token reference {}", + token_ref + ))) + } + + /// Returns the Token metadata for the specified mint + /// This function loads all tokens to the cache, slow on the first call. + pub fn get_token_with_mint(&self, token_mint: &Pubkey) -> Result { + let tokens = self.get_tokens()?; + for (_name, token) in tokens.iter() { + if token_mint == &token.mint { + return Ok(*token); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Token with mint {}", + token_mint + ))) + } + + /// Returns the official Program ID for the given name + pub fn get_program_id(&self, name: &str) -> Result { + // reload program ids if stale + self.reload_program_ids_if_stale()?; + // if program id is in cache return it + if let Some(pubkey) = self.official_ids.borrow().data.get(name) { + return Ok(*pubkey); + } + Err(FarmClientError::RecordNotFound(format!("Program {}", name))) + } + + /// Returns all official Program IDs available + pub fn get_program_ids(&self) -> Result { + self.reload_program_ids_if_stale()?; + self.get_refdb_pubkey_map(&refdb::StorageType::Program.to_string()) + } + + /// Returns the official program name for the given Program ID + pub fn get_program_name(&self, prog_id: &Pubkey) -> Result { + // reload program ids if stale + self.reload_program_ids_if_stale()?; + for (name, key) in self.official_ids.borrow().data.iter() { + if key == prog_id { + return Ok(name.to_string()); + } + } + Err(FarmClientError::RecordNotFound(format!( + "Program ID {}", + prog_id + ))) + } + + /// Checks if the given address is the official Program ID + pub fn is_official_id(&self, prog_id: &Pubkey) -> Result { + Ok(*prog_id == main_router::id() || self.get_program_name(prog_id).is_ok()) + } + + /// Reads the Keypair from stdin + pub fn read_keypair_from_stdin() -> Result { + let mut stdin = std::io::stdin(); + read_keypair(&mut stdin).map_err(|e| FarmClientError::IOError(e.to_string())) + } + + /// Reads the Keypair from the file + pub fn read_keypair_from_file(path: &str) -> Result { + read_keypair_file(path).map_err(|e| FarmClientError::IOError(e.to_string())) + } + + /// Signs and sends instructions + pub fn sign_and_send_instructions( + &self, + signers: &S, + instructions: &[Instruction], + ) -> Result { + let mut transaction = + Transaction::new_with_payer(instructions, Some(&signers.pubkeys()[0])); + + for i in 0..20 { + let (recent_blockhash, _) = self.rpc_client.get_recent_blockhash()?; + transaction.sign(signers, recent_blockhash); + + let result = self + .rpc_client + .send_and_confirm_transaction_with_spinner(&transaction); + if let Ok(signature) = result { + return Ok(signature); + } else if i != 19 { + if let Err(ref error) = result { + if let ClientErrorKind::RpcError(ref rpc_error) = error.kind { + if let RpcError::RpcResponseError { code, message, .. } = rpc_error { + if *code == rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY + || *code + == rpc_custom_error::JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE + || (*code == rpc_custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE + && message.ends_with("Blockhash not found")) + { + println!("Node is unhealthy, re-trying in 5 secs..."); + thread::sleep(time::Duration::from_secs(5)); + continue; + } + } else if let RpcError::ForUser(msg) = rpc_error { + if msg.starts_with("unable to confirm transaction") { + println!("Unable to confirm transaction, re-trying in 5 secs..."); + thread::sleep(time::Duration::from_secs(5)); + continue; + } + } + } + } + return Err(FarmClientError::RpcClientError(result.unwrap_err())); + } else { + return Err(FarmClientError::RpcClientError(result.unwrap_err())); + } + } + unreachable!(); + } + + /// Wait for the transaction to become finalized + pub fn confirm_async_transaction(&self, signature: &Signature) -> Result<(), FarmClientError> { + let recent_blockhash = self.rpc_client.get_recent_blockhash()?.0; + self.rpc_client + .confirm_transaction_with_spinner( + signature, + &recent_blockhash, + CommitmentConfig::finalized(), + ) + .map_err(Into::into) + } + + /// Creates a new system account + pub fn create_system_account( + &self, + signer: &dyn Signer, + new_account_signer: &dyn Signer, + lamports: u64, + space: usize, + owner: &Pubkey, + ) -> Result { + let inst = self.new_instruction_create_system_account( + &signer.pubkey(), + &new_account_signer.pubkey(), + lamports, + space, + owner, + )?; + self.sign_and_send_instructions(&[signer, new_account_signer], &[inst]) + } + + /// Closes the system account + pub fn close_system_account( + &self, + signer: &dyn Signer, + target_account_signer: &dyn Signer, + ) -> Result { + let inst = self.new_instruction_close_system_account( + &signer.pubkey(), + &target_account_signer.pubkey(), + )?; + self.sign_and_send_instructions(&[signer, target_account_signer], &[inst]) + } + + /// Creates a new system account + pub fn create_system_account_with_seed( + &self, + signer: &dyn Signer, + base_address: &Pubkey, + seed: &str, + lamports: u64, + space: usize, + owner: &Pubkey, + ) -> Result { + let inst = self.new_instruction_create_system_account_with_seed( + &signer.pubkey(), + base_address, + seed, + lamports, + space, + owner, + )?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Assigns system account to a program + pub fn assign_system_account( + &self, + signer: &dyn Signer, + program_address: &Pubkey, + ) -> Result { + let inst = self.new_instruction_assign_system_account(&signer.pubkey(), program_address)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Transfers native SOL from the wallet to the destination + pub fn transfer( + &self, + signer: &dyn Signer, + destination_wallet: &Pubkey, + sol_ui_amount: f64, + ) -> Result { + let inst = + self.new_instruction_transfer(&signer.pubkey(), destination_wallet, sol_ui_amount)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Transfers native SOL from the wallet to the associated Wrapped SOL account. + pub fn transfer_sol_to_wsol( + &self, + signer: &dyn Signer, + sol_ui_amount: f64, + ) -> Result { + let target_account = self.get_associated_token_address(&signer.pubkey(), "SOL")?; + let mut inst = Vec::::new(); + if !self.has_active_token_account(&signer.pubkey(), "SOL") { + inst.push(self.new_instruction_create_token_account(&signer.pubkey(), "SOL")?); + } + inst.push(self.new_instruction_transfer( + &signer.pubkey(), + &target_account, + sol_ui_amount, + )?); + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Transfers tokens from the wallet to the destination + pub fn token_transfer( + &self, + signer: &dyn Signer, + token_name: &str, + destination_wallet: &Pubkey, + ui_amount: f64, + ) -> Result { + let mut inst = vec![]; + if !self.has_active_token_account(&signer.pubkey(), token_name) { + return Err(FarmClientError::RecordNotFound(format!( + "Source account with token {}", + token_name + ))); + } + if !self.has_active_token_account(destination_wallet, token_name) { + let token = self.get_token(token_name)?; + inst.push(create_associated_token_account( + &signer.pubkey(), + destination_wallet, + &token.mint, + )); + } + inst.push(self.new_instruction_token_transfer( + &signer.pubkey(), + token_name, + destination_wallet, + ui_amount, + )?); + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Updates token balance of the account, usefull after transfer SOL to WSOL account + pub fn sync_token_balance( + &self, + signer: &dyn Signer, + token_name: &str, + ) -> Result { + let inst = self.new_instruction_sync_token_balance(&signer.pubkey(), token_name)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Returns the associated token account for the given user's main account or creates one + /// if it doesn't exist + pub fn get_or_create_token_account( + &self, + signer: &dyn Signer, + token_name: &str, + ) -> Result { + let wallet_address = signer.pubkey(); + let token_addr = self.get_associated_token_address(&wallet_address, token_name)?; + if !self.has_active_token_account(&wallet_address, token_name) { + let inst = self.new_instruction_create_token_account(&wallet_address, token_name)?; + self.sign_and_send_instructions(&[signer], &[inst])?; + } + Ok(token_addr) + } + + /// Closes the associated token account for the given user's main account + pub fn close_token_account( + &self, + signer: &dyn Signer, + token_name: &str, + ) -> Result { + let inst = self.new_instruction_close_token_account(&signer.pubkey(), token_name)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Returns the associated token account address for the given token name + pub fn get_associated_token_address( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token = self.get_token(token_name)?; + Ok(get_associated_token_address(wallet_address, &token.mint)) + } + + /// Returns all tokens with active account in the wallet. + /// This function loads all tokens to the cache, slow on the first call. + pub fn get_wallet_tokens( + &self, + wallet_address: &Pubkey, + ) -> Result, FarmClientError> { + let accounts = self.rpc_client.get_token_accounts_by_owner( + wallet_address, + TokenAccountsFilter::ProgramId(spl_token::id()), + )?; + let mut res = Vec::::new(); + for acc in accounts.iter() { + let token_address = Pubkey::from_str(&acc.pubkey).map_err(|_| { + FarmClientError::ValueError(format!( + "Failed to convert the String to a Pubkey {}", + acc.pubkey + )) + })?; + + let data = self.rpc_client.get_account_data(&token_address)?; + let token_info = parse_token(data.as_slice(), Some(0))?; + if let TokenAccountType::Account(ui_account) = token_info { + let token_mint = Pubkey::from_str(&ui_account.mint).map_err(|_| { + FarmClientError::ValueError(format!( + "Failed to convert the String to a Pubkey {}", + ui_account.mint + )) + })?; + if let Ok(token) = self.get_token_with_mint(&token_mint) { + res.push(token.name.as_str().to_string()); + } else { + res.push(acc.pubkey.clone()); + } + } + } + Ok(res) + } + + /// Returns UiTokenAccount struct data for the associated token account address + pub fn get_token_account_data( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token_address = self.get_associated_token_address(wallet_address, token_name)?; + let data = self.rpc_client.get_account_data(&token_address)?; + let token = self.get_token(token_name)?; + let res = parse_token(data.as_slice(), Some(token.decimals))?; + if let TokenAccountType::Account(ui_account) = res { + Ok(ui_account) + } else { + Err(FarmClientError::ValueError(format!( + "No account data found for token {}", + token_name + ))) + } + } + + /// Returns native SOL balance + pub fn get_account_balance(&self, wallet_address: &Pubkey) -> Result { + Ok(self.tokens_to_ui_amount_with_decimals( + self.rpc_client.get_balance(wallet_address)?, + spl_token::native_mint::DECIMALS, + )) + } + + /// Returns token balance for the associated token account address + pub fn get_token_account_balance( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token_name = if token_name == "WSOL" { + "SOL" + } else { + token_name + }; + let token_address = self.get_associated_token_address(wallet_address, token_name)?; + let balance = self.rpc_client.get_token_account_balance(&token_address)?; + if let Some(ui_amount) = balance.ui_amount { + Ok(ui_amount) + } else { + Err(FarmClientError::ParseError(format!( + "Failed to parse balance for token {}", + token_name + ))) + } + } + + /// Returns true if the associated token account exists and is initialized + pub fn has_active_token_account(&self, wallet_address: &Pubkey, token_name: &str) -> bool { + if let Ok(account) = self.get_token_account_data(wallet_address, token_name) { + account.state == UiAccountState::Initialized + } else { + false + } + } + + /// Returns the account address where Vault stats are stored for the user + pub fn get_vault_user_info_account( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ) -> Result { + let vault = self.get_vault(vault_name)?; + Ok(Pubkey::find_program_address( + &[ + b"user_info_account", + &wallet_address.to_bytes()[..], + vault.name.as_bytes(), + ], + &vault.vault_program_id, + ) + .0) + } + + /// Returns number of decimal digits of the Vault token + pub fn get_vault_token_decimals(&self, vault_name: &str) -> Result { + let vault = self.get_vault(vault_name)?; + if let Some(vault_token) = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))? { + Ok(vault_token.decimals) + } else { + Err(FarmClientError::RecordNotFound(format!( + "Vault token for {}", + vault_name + ))) + } + } + + /// Returns number of decimal digits for the Pool tokens + pub fn get_pool_tokens_decimals(&self, pool_name: &str) -> Result, FarmClientError> { + let pool = self.get_pool(pool_name)?; + let mut res = vec![]; + if let Some(token) = self.get_token_by_ref_from_cache(&pool.lp_token_ref)? { + res.push(token.decimals); + } + if let Some(token) = self.get_token_by_ref_from_cache(&pool.token_a_ref)? { + res.push(token.decimals); + } + if let Some(token) = self.get_token_by_ref_from_cache(&pool.token_b_ref)? { + res.push(token.decimals); + } + Ok(res) + } + + /// Returns user stats for specific Vault + pub fn get_vault_user_info( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ) -> Result { + let user_info_account = self.get_vault_user_info_account(wallet_address, vault_name)?; + let data = self.rpc_client.get_account_data(&user_info_account)?; + if !RefDB::is_initialized(data.as_slice()) { + return Err(ProgramError::UninitializedAccount.into()); + } + let mut user_info = UserInfo::default(); + let rec_vec = RefDB::read_all(data.as_slice())?; + for rec in rec_vec.iter() { + if let refdb::Reference::U64 { data } = rec.reference { + match rec.name.as_str() { + "LastDeposit" => user_info.last_deposit_time = data as UnixTimestamp, + "LastWithdrawal" => user_info.last_withdrawal_time = data as UnixTimestamp, + "TokenAAdded" => user_info.tokens_a_added = data, + "TokenBAdded" => user_info.tokens_b_added = data, + "TokenARemoved" => user_info.tokens_a_removed = data, + "TokenBRemoved" => user_info.tokens_b_removed = data, + "LpTokensDebt" => user_info.lp_tokens_debt = data, + _ => {} + } + } + } + + Ok(user_info) + } + + /// Returns Vault stats + pub fn get_vault_info(&self, vault_name: &str) -> Result { + let vault = self.get_vault(vault_name)?; + let data = self.rpc_client.get_account_data(&vault.info_account)?; + if !RefDB::is_initialized(data.as_slice()) { + return Err(ProgramError::UninitializedAccount.into()); + } + let mut vault_info = VaultInfo::default(); + let rec_vec = RefDB::read_all(data.as_slice())?; + for rec in rec_vec.iter() { + if let refdb::Reference::U64 { data } = rec.reference { + match rec.name.as_str() { + "CrankTime" => vault_info.crank_time = data as UnixTimestamp, + "CrankStep" => vault_info.crank_step = data, + "TokenAAdded" => vault_info.tokens_a_added = data, + "TokenBAdded" => vault_info.tokens_b_added = data, + "TokenARemoved" => vault_info.tokens_a_removed = data, + "TokenBRemoved" => vault_info.tokens_b_removed = data, + "TokenARewards" => vault_info.tokens_a_rewards = data, + "TokenBRewards" => vault_info.tokens_b_rewards = data, + "DepositAllowed" => vault_info.deposit_allowed = data > 0, + "WithdrawalAllowed" => vault_info.withdrawal_allowed = data > 0, + "MinCrankInterval" => vault_info.min_crank_interval = data, + "Fee" => vault_info.fee = f64::from_bits(data), + "ExternalFee" => vault_info.external_fee = f64::from_bits(data), + _ => {} + } + } + } + vault_info.stake_balance = self.get_vault_stake_balance(vault_name)?; + + Ok(vault_info) + } + + /// Returns User's stacked balance + pub fn get_user_stake_balance( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result { + let farm = self.get_farm(farm_name)?; + match farm.route { + FarmRoute::Raydium { .. } => { + if let Some(stake_account) = self.get_stake_account(wallet_address, farm_name)? { + let stake_data = self.rpc_client.get_account_data(&stake_account)?; + if !stake_data.is_empty() { + let deposit_balance = if farm.version >= 4 { + RaydiumUserStakeInfoV4::unpack(stake_data.as_slice())?.deposit_balance + } else { + RaydiumUserStakeInfo::unpack(stake_data.as_slice())?.deposit_balance + }; + let farm_token = self.get_token_by_ref(&farm.lp_token_ref.unwrap())?; + Ok(self.tokens_to_ui_amount_with_decimals( + deposit_balance, + farm_token.decimals, + )) + } else { + Ok(0.0) + } + } else { + Ok(0.0) + } + } + FarmRoute::Saber { .. } => { + if let Some(stake_account) = self.get_stake_account(wallet_address, farm_name)? { + let stake_data = self.rpc_client.get_account_data(&stake_account)?; + if !stake_data.is_empty() { + let deposit_balance = Miner::unpack(stake_data.as_slice())?.balance; + let farm_token = self.get_token_by_ref(&farm.lp_token_ref.unwrap())?; + return Ok(self.tokens_to_ui_amount_with_decimals( + deposit_balance, + farm_token.decimals, + )); + } + } + Ok(0.0) + } + FarmRoute::Orca { farm_token_ref, .. } => { + let farm_token = self.get_token_by_ref(&farm_token_ref)?; + self.get_token_account_balance(wallet_address, &farm_token.name) + } + } + } + + /// Returns Vault's stacked balance + pub fn get_vault_stake_balance(&self, vault_name: &str) -> Result { + let vault = self.get_vault(vault_name)?; + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + farm_id_ref, + vault_stake_info, + .. + } => { + let farm = self.get_farm_by_ref(&farm_id_ref)?; + let farm_token = self.get_token_by_ref(&farm.lp_token_ref.unwrap())?; + + let balance = + if let Ok(stake_data) = self.rpc_client.get_account_data(&vault_stake_info) { + if !stake_data.is_empty() { + match farm.route { + FarmRoute::Raydium { .. } => { + if farm.version >= 4 { + RaydiumUserStakeInfoV4::unpack(stake_data.as_slice())? + .deposit_balance + } else { + RaydiumUserStakeInfo::unpack(stake_data.as_slice())? + .deposit_balance + } + } + FarmRoute::Saber { .. } => { + Miner::unpack(stake_data.as_slice())?.balance + } + FarmRoute::Orca { .. } => 0, + } + } else { + 0 + } + } else { + 0 + }; + Ok(self.tokens_to_ui_amount_with_decimals(balance, farm_token.decimals)) + } + _ => Ok(0.0), + } + } + + /// Initializes a new User for the Vault + pub fn user_init_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + ) -> Result { + // create and send the instruction + let inst = self.new_instruction_user_init_vault(&signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Adds liquidity to the Vault + pub fn add_liquidity_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result { + if max_token_a_ui_amount < 0.0 + || max_token_b_ui_amount < 0.0 + || (max_token_a_ui_amount == 0.0 && max_token_b_ui_amount == 0.0) + { + return Err(FarmClientError::ValueError(format!( + "Invalid add liquidity amounts {} and {} specified for Vault {}: Must be greater or equal to zero and at least one non-zero.", + max_token_a_ui_amount, max_token_b_ui_amount, vault_name + ))); + } + // if one of the tokens is SOL and amount is zero, we need to estimate that + // amount to get it transfered to WSOL + let is_saber_vault = vault_name.starts_with("SBR."); + let (is_token_a_sol, is_token_b_sol) = self.vault_has_sol_tokens(vault_name)?; + let token_a_ui_amount = if max_token_a_ui_amount == 0.0 && is_token_a_sol && !is_saber_vault + { + let pool_price = self.get_vault_price(vault_name)?; + if pool_price > 0.0 { + max_token_b_ui_amount * 1.03 / pool_price + } else { + 0.0 + } + } else { + max_token_a_ui_amount + }; + let token_b_ui_amount = if max_token_b_ui_amount == 0.0 && is_token_b_sol && !is_saber_vault + { + max_token_a_ui_amount * self.get_vault_price(vault_name)? * 1.03 + } else { + max_token_b_ui_amount + }; + + // check user accounts + let mut inst = Vec::::new(); + self.check_vault_accounts( + signer, + vault_name, + token_a_ui_amount, + token_b_ui_amount, + 0.0, + true, + true, + &mut inst, + )?; + + if !inst.is_empty() { + self.sign_and_send_instructions(&[signer], inst.as_slice())?; + inst.clear(); + } + + // check if tokens must be wrapped to Saber decimal token + if is_saber_vault { + let pool_name = self.get_underlying_pool(vault_name)?.name.to_string(); + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(&pool_name)?; + if is_token_a_wrapped && max_token_a_ui_amount > 0.0 { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + &pool_name, + true, + max_token_a_ui_amount, + )?); + } + if is_token_b_wrapped && max_token_b_ui_amount > 0.0 { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + &pool_name, + false, + max_token_b_ui_amount, + )?); + } + } + + // insert add liquidity instruction + inst.push(self.new_instruction_add_liquidity_vault( + &signer.pubkey(), + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + )?); + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + + // lock liquidity if required by the vault + let vault = self.get_vault(vault_name)?; + if vault.lock_required { + let lp_debt_initial = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + let _ = self.sign_and_send_instructions(&[signer], inst.as_slice())?; + + let lp_debt = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + if lp_debt > lp_debt_initial { + let pool_token_decimals = self.get_vault_lp_token_decimals(vault_name)?; + let locked_amount = self.tokens_to_ui_amount_with_decimals( + lp_debt - lp_debt_initial, + pool_token_decimals, + ); + + let lock_inst = self.new_instruction_lock_liquidity_vault( + &signer.pubkey(), + vault_name, + locked_amount, + )?; + self.sign_and_send_instructions(&[signer], &[lock_inst]) + } else { + Err(FarmClientError::InsufficientBalance( + "No tokens were locked".to_string(), + )) + } + } else { + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + } + + /// Adds locked liquidity to the Vault. + /// Useful if add liquidity operation partially failed. + pub fn add_locked_liquidity_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // check user accounts + let mut inst = Vec::::new(); + self.check_vault_accounts(signer, vault_name, 0.0, 0.0, 0.0, true, false, &mut inst)?; + if !inst.is_empty() { + self.sign_and_send_instructions(&[signer], inst.as_slice())?; + inst.clear(); + } + + // check if the user has locked balance + if ui_amount > 0.0 { + let lp_debt = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + let pool_token_decimals = self.get_vault_lp_token_decimals(vault_name)?; + if self.tokens_to_ui_amount_with_decimals(lp_debt, pool_token_decimals) < ui_amount { + return Err(FarmClientError::InsufficientBalance( + "Not enough locked tokens to deposit".to_string(), + )); + } + } + + inst.push(self.new_instruction_lock_liquidity_vault( + &signer.pubkey(), + vault_name, + ui_amount, + )?); + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Removes liquidity from the Vault + pub fn remove_liquidity_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // check user accounts + let vault = self.get_vault(vault_name)?; + let mut inst = Vec::::new(); + self.check_vault_accounts( + signer, vault_name, 0.0, 0.0, ui_amount, true, false, &mut inst, + )?; + if !inst.is_empty() { + self.sign_and_send_instructions(&[signer], inst.as_slice())?; + inst.clear(); + } + + // unlock liquidity first if required by the vault + let mut unlocked_amount = ui_amount; + if vault.unlock_required { + let lp_debt_initial = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + let unlock_inst = self.new_instruction_unlock_liquidity_vault( + &signer.pubkey(), + vault_name, + ui_amount, + )?; + self.sign_and_send_instructions(&[signer], &[unlock_inst])?; + let lp_debt = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + if lp_debt > lp_debt_initial { + let pool_token_decimals = self.get_vault_lp_token_decimals(vault_name)?; + unlocked_amount = self.tokens_to_ui_amount_with_decimals( + lp_debt - lp_debt_initial, + pool_token_decimals, + ); + } else { + return Err(FarmClientError::InsufficientBalance( + "No tokens were unlocked".to_string(), + )); + } + } + + // remove liquidity + inst.push(self.new_instruction_remove_liquidity_vault( + &signer.pubkey(), + vault_name, + unlocked_amount, + )?); + + // check if tokens need to be unwrapped + let (is_token_a_sol, is_token_b_sol) = self.vault_has_sol_tokens(vault_name)?; + let pool_name = self.get_underlying_pool(vault_name)?.name.to_string(); + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(&pool_name)?; + + if is_token_a_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool_name, + true, + 0.0, + )?); + } + if is_token_b_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool_name, + false, + 0.0, + )?); + } + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Removes unlocked liquidity from the Vault. + /// Useful if remove liquidity operation failed after unlock step. + pub fn remove_unlocked_liquidity_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // check user accounts + let mut inst = Vec::::new(); + self.check_vault_accounts(signer, vault_name, 0.0, 0.0, 0.0, false, false, &mut inst)?; + if !inst.is_empty() { + self.sign_and_send_instructions(&[signer], inst.as_slice())?; + inst.clear(); + } + + // check if the user has unlocked balance + if ui_amount > 0.0 { + let lp_debt = self + .get_vault_user_info(&signer.pubkey(), vault_name)? + .lp_tokens_debt; + let pool_token_decimals = self.get_vault_lp_token_decimals(vault_name)?; + if self.tokens_to_ui_amount_with_decimals(lp_debt, pool_token_decimals) < ui_amount { + return Err(FarmClientError::InsufficientBalance( + "Not enough unlocked tokens to remove".to_string(), + )); + } + } + + inst.push(self.new_instruction_remove_liquidity_vault( + &signer.pubkey(), + vault_name, + ui_amount, + )?); + + // check if tokens need to be unwrapped + let (is_token_a_sol, is_token_b_sol) = self.vault_has_sol_tokens(vault_name)?; + let pool_name = self.get_underlying_pool(vault_name)?.name.to_string(); + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(&pool_name)?; + + if is_token_a_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool_name, + true, + 0.0, + )?); + } + if is_token_b_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool_name, + false, + 0.0, + )?); + } + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Adds liquidity to the Pool. + /// If one of token amounts is set to zero it will be determined based on the pool + /// price and the specified amount of another token. + pub fn add_liquidity_pool( + &self, + signer: &dyn Signer, + pool_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result { + if max_token_a_ui_amount < 0.0 + || max_token_b_ui_amount < 0.0 + || (max_token_a_ui_amount == 0.0 && max_token_b_ui_amount == 0.0) + { + return Err(FarmClientError::ValueError(format!( + "Invalid add liquidity amounts {} and {} specified for Pool {}: Must be greater or equal to zero and at least one non-zero.", + max_token_a_ui_amount, max_token_b_ui_amount, pool_name + ))); + } + // if one of the tokens is SOL and amount is zero, we need to estimate that + // amount to get it transfered to WSOL + let is_saber_pool = pool_name.starts_with("SBR."); + let (is_token_a_sol, is_token_b_sol) = self.pool_has_sol_tokens(pool_name)?; + let token_a_ui_amount = if max_token_a_ui_amount == 0.0 && is_token_a_sol && !is_saber_pool + { + let pool_price = self.get_pool_price(pool_name)?; + if pool_price > 0.0 { + max_token_b_ui_amount * 1.03 / pool_price + } else { + 0.0 + } + } else { + max_token_a_ui_amount + }; + let token_b_ui_amount = if max_token_b_ui_amount == 0.0 && is_token_b_sol && !is_saber_pool + { + max_token_a_ui_amount * self.get_pool_price(pool_name)? * 1.03 + } else { + max_token_b_ui_amount + }; + + let mut inst = Vec::::new(); + let _ = self.check_pool_accounts( + signer, + pool_name, + token_a_ui_amount, + token_b_ui_amount, + 0.0, + true, + &mut inst, + )?; + + // check if tokens need to be wrapped to a Saber decimal token + if is_saber_pool { + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(pool_name)?; + if is_token_a_wrapped && max_token_a_ui_amount > 0.0 { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + pool_name, + true, + max_token_a_ui_amount, + )?); + } + if is_token_b_wrapped && max_token_b_ui_amount > 0.0 { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + pool_name, + false, + max_token_b_ui_amount, + )?); + } + } + + // create and send instruction + inst.push(self.new_instruction_add_liquidity_pool( + &signer.pubkey(), + pool_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + )?); + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Removes liquidity from the Pool. + /// If the amount is set to zero entire balance will be removed from the pool. + pub fn remove_liquidity_pool( + &self, + signer: &dyn Signer, + pool_name: &str, + ui_amount: f64, + ) -> Result { + let mut inst = Vec::::new(); + let _ = + self.check_pool_accounts(signer, pool_name, 0.0, 0.0, ui_amount, true, &mut inst)?; + + inst.push(self.new_instruction_remove_liquidity_pool( + &signer.pubkey(), + pool_name, + ui_amount, + )?); + + // check if tokens need to be unwrapped + let (is_token_a_sol, is_token_b_sol) = self.pool_has_sol_tokens(pool_name)?; + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(pool_name)?; + + if is_token_a_wrapped { + inst.push(self.new_instruction_unwrap_token(&signer.pubkey(), pool_name, true, 0.0)?); + } + if is_token_b_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + pool_name, + false, + 0.0, + )?); + } + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Swaps tokens + pub fn swap( + &self, + signer: &dyn Signer, + protocol: &str, + from_token: &str, + to_token: &str, + ui_amount_in: f64, + min_ui_amount_out: f64, + ) -> Result { + // find pool to swap in + let pool = self.find_pools(protocol, from_token, to_token)?[0]; + + // check amount + if ui_amount_in < 0.0 { + return Err(FarmClientError::ValueError(format!( + "Invalid token amount {} specified for pool {}: Must be zero or greater.", + ui_amount_in, + pool.name.as_str() + ))); + } + + // if amount is zero use entire balance + let ui_amount_in = if ui_amount_in == 0.0 { + if from_token == "SOL" { + return Err(FarmClientError::ValueError(format!( + "Invalid SOL amount {} specified for pool {}: Must be greater than zero.", + ui_amount_in, + pool.name.as_str() + ))); + } + let balance = self.get_token_account_balance(&signer.pubkey(), from_token)?; + if balance == 0.0 { + return Err(FarmClientError::InsufficientBalance(from_token.to_string())); + } + balance + } else { + ui_amount_in + }; + + // check token accounts + let mut inst = Vec::::new(); + let reverse = FarmClient::pool_has_reverse_tokens(&pool.name, from_token)?; + if reverse { + let _ = self.check_pool_accounts( + signer, + &pool.name.to_string(), + 0.0, + ui_amount_in, + 0.0, + false, + &mut inst, + )?; + } else { + let _ = self.check_pool_accounts( + signer, + &pool.name.to_string(), + ui_amount_in, + 0.0, + 0.0, + false, + &mut inst, + )?; + } + + // check if tokens must be wrapped to Saber decimal token + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(&pool.name)?; + if is_token_a_wrapped && !reverse { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + &pool.name, + true, + ui_amount_in, + )?); + } + if is_token_b_wrapped && reverse { + inst.push(self.new_instruction_wrap_token( + &signer.pubkey(), + &pool.name, + false, + ui_amount_in, + )?); + } + + // create and send instruction + inst.push(self.new_instruction_swap( + &signer.pubkey(), + protocol, + from_token, + to_token, + ui_amount_in, + min_ui_amount_out, + )?); + if is_token_b_wrapped && !reverse { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool.name, + false, + 0.0, + )?); + } + if is_token_a_wrapped && reverse { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + &pool.name, + true, + 0.0, + )?); + } + if to_token == "SOL" { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + /// Stakes tokens to the Farm. + /// If the amount is set to zero entire LP tokens balance will be staked. + pub fn stake( + &self, + signer: &dyn Signer, + farm_name: &str, + ui_amount: f64, + ) -> Result { + let mut inst = Vec::::new(); + let mut signers = Vec::>::new(); + let _ = self.check_farm_accounts(signer, farm_name, ui_amount, &mut inst, &mut signers)?; + inst.push(self.new_instruction_stake(&signer.pubkey(), farm_name, ui_amount)?); + + let mut unboxed_signers: Vec<&dyn Signer> = vec![signer]; + unboxed_signers.append(&mut signers.iter().map(|x| x.as_ref()).collect()); + self.sign_and_send_instructions(&unboxed_signers, inst.as_slice()) + } + + /// Unstakes tokens from the Farm. + /// If the amount is set to zero entire balance will be unstaked. + pub fn unstake( + &self, + signer: &dyn Signer, + farm_name: &str, + ui_amount: f64, + ) -> Result { + let mut inst = Vec::::new(); + let mut signers = Vec::>::new(); + let _ = self.check_farm_accounts(signer, farm_name, 0.0, &mut inst, &mut signers)?; + inst.push(self.new_instruction_unstake(&signer.pubkey(), farm_name, ui_amount)?); + + let mut unboxed_signers: Vec<&dyn Signer> = vec![signer]; + unboxed_signers.append(&mut signers.iter().map(|x| x.as_ref()).collect()); + self.sign_and_send_instructions(&unboxed_signers, inst.as_slice()) + } + + /// Harvests rewards from the Pool + pub fn harvest( + &self, + signer: &dyn Signer, + farm_name: &str, + ) -> Result { + let mut inst = Vec::::new(); + let mut signers = Vec::>::new(); + let _ = self.check_farm_accounts(signer, farm_name, 0.0, &mut inst, &mut signers)?; + inst.push(self.new_instruction_harvest(&signer.pubkey(), farm_name)?); + + let mut unboxed_signers: Vec<&dyn Signer> = vec![signer]; + unboxed_signers.append(&mut signers.iter().map(|x| x.as_ref()).collect()); + self.sign_and_send_instructions(&unboxed_signers, inst.as_slice()) + } + + /// Clears cache records to force re-pull from blockchain + pub fn reset_cache(&self) { + self.tokens.borrow_mut().reset(); + self.pools.borrow_mut().reset(); + self.vaults.borrow_mut().reset(); + self.token_refs.borrow_mut().reset(); + self.pool_refs.borrow_mut().reset(); + self.vault_refs.borrow_mut().reset(); + self.official_ids.borrow_mut().reset(); + self.latest_pools.borrow_mut().clear(); + self.latest_farms.borrow_mut().clear(); + self.latest_vaults.borrow_mut().clear(); + } + + /// Reads records from the RefDB PDA into a Pubkey map + pub fn get_refdb_pubkey_map(&self, refdb_name: &str) -> Result { + let refdb_address = find_refdb_pda(refdb_name).0; + let data = self.rpc_client.get_account_data(&refdb_address)?; + if !RefDB::is_initialized(data.as_slice()) { + return Err(ProgramError::UninitializedAccount.into()); + } + let mut map = PubkeyMap::default(); + let rec_vec = RefDB::read_all(data.as_slice())?; + for rec in rec_vec.iter() { + if let refdb::Reference::Pubkey { data } = rec.reference { + map.insert(rec.name.to_string(), data); + } + } + Ok(map) + } + + /// Returns raw RefDB data, can be further used with refdb::RefDB + pub fn get_refdb_data(&self, refdb_name: &str) -> Result, FarmClientError> { + let refdb_address = find_refdb_pda(refdb_name).0; + self.rpc_client + .get_account_data(&refdb_address) + .map_err(Into::into) + } + + /// Returns the index of the first empty record at the back of the RefDB storage, + /// i.e. there will be no active records after the index + pub fn get_refdb_last_index(&self, refdb_name: &str) -> Result { + let refdb_address = find_refdb_pda(refdb_name).0; + RefDB::find_last_index(self.rpc_client.get_account_data(&refdb_address)?.as_slice()) + .map_err(Into::into) + } + + /// Returns the index of the next available record to write to in the RefDB storage + pub fn get_refdb_next_index(&self, refdb_name: &str) -> Result { + let refdb_address = find_refdb_pda(refdb_name).0; + RefDB::find_next_index(self.rpc_client.get_account_data(&refdb_address)?.as_slice()) + .map_err(Into::into) + } + + /// Checks if RefDB is initialized + pub fn is_refdb_initialized(&self, refdb_name: &str) -> Result { + let refdb_address = find_refdb_pda(refdb_name).0; + if let Ok(data) = self.rpc_client.get_account_data(&refdb_address) { + Ok(RefDB::is_initialized(data.as_slice())) + } else { + Ok(false) + } + } + + /// Initializes a new RefDB storage + pub fn initialize_refdb( + &self, + admin_signer: &dyn Signer, + refdb_name: &str, + reference_type: refdb::ReferenceType, + max_records: usize, + init_account: bool, + ) -> Result { + if init_account && !refdb::REFDB_ONCHAIN_INIT { + if admin_signer.pubkey() != main_router_admin::id() { + return Err(FarmClientError::ValueError( + "Admin keypair must match main_router_admin::id()".to_string(), + )); + } + self.create_system_account_with_seed( + admin_signer, + &admin_signer.pubkey(), + refdb_name, + 0, + refdb::StorageType::get_storage_size_for_records(reference_type, max_records), + &main_router::id(), + )?; + } + + let inst = self.new_instruction_refdb_init( + &admin_signer.pubkey(), + refdb_name, + reference_type, + max_records as u32, + init_account, + )?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the RefDB storage + pub fn drop_refdb( + &self, + admin_signer: &dyn Signer, + refdb_name: &str, + close_account: bool, + ) -> Result { + let inst = + self.new_instruction_refdb_drop(&admin_signer.pubkey(), refdb_name, close_account)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Records the Program ID metadata on-chain + pub fn add_program_id( + &self, + admin_signer: &dyn Signer, + name: &str, + program_id: &Pubkey, + program_id_type: ProgramIDType, + refdb_index: Option, + ) -> Result { + let inst = self.new_instruction_add_program_id( + &admin_signer.pubkey(), + name, + program_id, + program_id_type, + refdb_index, + )?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the Program ID metadata from chain + pub fn remove_program_id( + &self, + admin_signer: &dyn Signer, + name: &str, + ) -> Result { + let inst = self.new_instruction_remove_program_id(&admin_signer.pubkey(), name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Records the Vault metadata on-chain + pub fn add_vault( + &self, + admin_signer: &dyn Signer, + vault: Vault, + ) -> Result { + self.vaults + .borrow_mut() + .data + .insert(vault.name.to_string(), vault); + self.vault_refs.borrow_mut().reset(); + let inst = self.new_instruction_add_vault(&admin_signer.pubkey(), vault)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the Vault's on-chain metadata + pub fn remove_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = self.new_instruction_remove_vault(&admin_signer.pubkey(), vault_name)?; + self.vaults.borrow_mut().data.remove(vault_name); + self.vault_refs.borrow_mut().data.remove(vault_name); + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Records the Pool metadata on-chain + pub fn add_pool( + &self, + admin_signer: &dyn Signer, + pool: Pool, + ) -> Result { + self.pools + .borrow_mut() + .data + .insert(pool.name.to_string(), pool); + self.pool_refs.borrow_mut().reset(); + let inst = self.new_instruction_add_pool(&admin_signer.pubkey(), pool)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the Pool's on-chain metadata + pub fn remove_pool( + &self, + admin_signer: &dyn Signer, + pool_name: &str, + ) -> Result { + let inst = self.new_instruction_remove_pool(&admin_signer.pubkey(), pool_name)?; + self.pools.borrow_mut().data.remove(pool_name); + self.pool_refs.borrow_mut().data.remove(pool_name); + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Records the Farm metadata on-chain + pub fn add_farm( + &self, + admin_signer: &dyn Signer, + farm: Farm, + ) -> Result { + self.farms + .borrow_mut() + .data + .insert(farm.name.to_string(), farm); + self.farm_refs.borrow_mut().reset(); + let inst = self.new_instruction_add_farm(&admin_signer.pubkey(), farm)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the Farm's on-chain metadata + pub fn remove_farm( + &self, + admin_signer: &dyn Signer, + farm_name: &str, + ) -> Result { + let inst = self.new_instruction_remove_farm(&admin_signer.pubkey(), farm_name)?; + self.farms.borrow_mut().data.remove(farm_name); + self.farm_refs.borrow_mut().data.remove(farm_name); + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Records the Token metadata on-chain + pub fn add_token( + &self, + admin_signer: &dyn Signer, + token: Token, + ) -> Result { + self.tokens + .borrow_mut() + .data + .insert(token.name.to_string(), token); + self.token_refs.borrow_mut().reset(); + let inst = self.new_instruction_add_token(&admin_signer.pubkey(), token)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Removes the Token's on-chain metadata + pub fn remove_token( + &self, + admin_signer: &dyn Signer, + token_name: &str, + ) -> Result { + let inst = self.new_instruction_remove_token(&admin_signer.pubkey(), token_name)?; + self.tokens.borrow_mut().data.remove(token_name); + self.token_refs.borrow_mut().data.remove(token_name); + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Initializes a Vault + pub fn init_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + step: u64, + ) -> Result { + let inst = self.new_instruction_init_vault(&admin_signer.pubkey(), vault_name, step)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Shutdowns a Vault + pub fn shutdown_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = self.new_instruction_shutdown_vault(&admin_signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Cranks single Vault + pub fn crank_vault( + &self, + signer: &dyn Signer, + vault_name: &str, + step: u64, + ) -> Result { + let inst = self.new_instruction_crank_vault(&signer.pubkey(), vault_name, step)?; + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Cranks all Vaults + pub fn crank_vaults(&self, signer: &dyn Signer, step: u64) -> Result { + let vaults = self.get_vaults()?; + for (vault_name, _) in vaults.iter() { + let _ = self.crank_vault(signer, vault_name, step)?; + } + Ok(vaults.len()) + } + + /// Sets the Vault's min crank interval + pub fn set_min_crank_interval_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + min_crank_interval: u32, + ) -> Result { + let inst = self.new_instruction_set_min_crank_interval_vault( + &admin_signer.pubkey(), + vault_name, + min_crank_interval, + )?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Sets the Vault's fee + pub fn set_fee_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + fee_percent: f32, + ) -> Result { + let inst = + self.new_instruction_set_fee_vault(&admin_signer.pubkey(), vault_name, fee_percent)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Sets the Vault's external fee + pub fn set_external_fee_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + external_fee_percent: f32, + ) -> Result { + let inst = self.new_instruction_set_external_fee_vault( + &admin_signer.pubkey(), + vault_name, + external_fee_percent, + )?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Disables deposits to the Vault + pub fn disable_deposit_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = + self.new_instruction_disable_deposit_vault(&admin_signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Enables deposits to the Vault + pub fn enable_deposit_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = self.new_instruction_enable_deposit_vault(&admin_signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Disables withdrawal from the Vault + pub fn disable_withdrawal_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = + self.new_instruction_disable_withdrawal_vault(&admin_signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Enables withdrawals from the Vault + pub fn enable_withdrawal_vault( + &self, + admin_signer: &dyn Signer, + vault_name: &str, + ) -> Result { + let inst = + self.new_instruction_enable_withdrawal_vault(&admin_signer.pubkey(), vault_name)?; + self.sign_and_send_instructions(&[admin_signer], &[inst]) + } + + /// Deposits governing tokens to the farms realm + pub fn governance_tokens_deposit( + &self, + signer: &dyn Signer, + ui_amount: f64, + ) -> Result { + let inst = self.new_instruction_governance_tokens_deposit(&signer.pubkey(), ui_amount)?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Withdraws governing tokens from the farms realm + pub fn governance_tokens_withdraw( + &self, + signer: &dyn Signer, + ) -> Result { + let inst = self.new_instruction_governance_tokens_withdraw(&signer.pubkey())?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Creates a new governance proposal + pub fn governance_proposal_new( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_name: &str, + proposal_link: &str, + proposal_index: u32, + ) -> Result { + let inst = self.new_instruction_governance_proposal_new( + &signer.pubkey(), + governed_account_name, + proposal_name, + proposal_link, + proposal_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Cancels governance proposal + pub fn governance_proposal_cancel( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let inst = self.new_instruction_governance_proposal_cancel( + &signer.pubkey(), + governed_account_name, + proposal_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Adds a signatory to governance proposal + pub fn governance_signatory_add( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + signatory: &Pubkey, + ) -> Result { + let inst = self.new_instruction_governance_signatory_add( + &signer.pubkey(), + governed_account_name, + proposal_index, + signatory, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Removes the signatory from governance proposal + pub fn governance_signatory_remove( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + signatory: &Pubkey, + ) -> Result { + let inst = self.new_instruction_governance_signatory_remove( + &signer.pubkey(), + governed_account_name, + proposal_index, + signatory, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Signs off governance proposal + pub fn governance_sign_off( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let inst = self.new_instruction_governance_sign_off( + &signer.pubkey(), + governed_account_name, + proposal_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Casts a vote on governance proposal + pub fn governance_vote_cast( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + vote: u8, + ) -> Result { + let inst = self.new_instruction_governance_vote_cast( + &signer.pubkey(), + governed_account_name, + proposal_index, + vote, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Removes the vote from governance proposal + pub fn governance_vote_relinquish( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let inst = self.new_instruction_governance_vote_relinquish( + &signer.pubkey(), + governed_account_name, + proposal_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Finalizes the vote on governance proposal + pub fn governance_vote_finalize( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let inst = self.new_instruction_governance_vote_finalize( + &signer.pubkey(), + governed_account_name, + proposal_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Adds a new instruction to governance proposal + pub fn governance_instruction_insert( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + instruction: &Instruction, + ) -> Result { + let inst = self.new_instruction_governance_instruction_insert( + &signer.pubkey(), + governed_account_name, + proposal_index, + instruction_index, + instruction, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Removes the instruction from governance proposal + pub fn governance_instruction_remove( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let inst = self.new_instruction_governance_instruction_remove( + &signer.pubkey(), + governed_account_name, + proposal_index, + instruction_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Executes the instruction in governance proposal + pub fn governance_instruction_execute( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let inst = self.new_instruction_governance_instruction_execute( + &signer.pubkey(), + governed_account_name, + proposal_index, + instruction_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /// Marks the instruction in governance proposal as failed + pub fn governance_instruction_flag_error( + &self, + signer: &dyn Signer, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let inst = self.new_instruction_governance_instruction_flag_error( + &signer.pubkey(), + governed_account_name, + proposal_index, + instruction_index, + )?; + + self.sign_and_send_instructions(&[signer], &[inst]) + } + + /////////////// helpers + pub fn ui_amount_to_tokens( + &self, + ui_amount: f64, + token_name: &str, + ) -> Result { + if ui_amount == 0.0 { + return Ok(0); + } + let multiplier = 10usize.pow(self.get_token(token_name)?.decimals as u32); + Ok((ui_amount * multiplier as f64).round() as u64) + } + + pub fn tokens_to_ui_amount( + &self, + amount: u64, + token_name: &str, + ) -> Result { + if amount == 0 { + return Ok(0.0); + } + let divisor = 10usize.pow(self.get_token(token_name)?.decimals as u32); + Ok(amount as f64 / divisor as f64) + } + + pub fn ui_amount_to_tokens_with_decimals(&self, ui_amount: f64, decimals: u8) -> u64 { + if ui_amount == 0.0 { + return 0; + } + let multiplier = 10usize.pow(decimals as u32); + (ui_amount * multiplier as f64).round() as u64 + } + + pub fn tokens_to_ui_amount_with_decimals(&self, amount: u64, decimals: u8) -> f64 { + if amount == 0 { + return 0.0; + } + let divisor = 10usize.pow(decimals as u32); + amount as f64 / divisor as f64 + } + + pub fn pool_has_sol_tokens(&self, pool_name: &str) -> Result<(bool, bool), FarmClientError> { + let pool = self.get_pool(pool_name)?; + let mut is_token_a_sol = false; + let mut is_token_b_sol = false; + if let Some(token_a_ref) = pool.token_a_ref { + let token_a = self.get_token_by_ref(&token_a_ref)?; + if token_a.token_type == TokenType::WrappedSol { + is_token_a_sol = true; + } + } + if let Some(token_b_ref) = pool.token_b_ref { + let token_b = self.get_token_by_ref(&token_b_ref)?; + if token_b.token_type == TokenType::WrappedSol { + is_token_b_sol = true; + } + } + Ok((is_token_a_sol, is_token_b_sol)) + } + + pub fn pool_has_saber_wrapped_tokens( + &self, + pool_name: &str, + ) -> Result<(bool, bool), FarmClientError> { + let pool = self.get_pool(pool_name)?; + + match pool.route { + PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } => Ok((wrapped_token_a_ref.is_some(), wrapped_token_b_ref.is_some())), + _ => Ok((false, false)), + } + } + + pub fn vault_has_sol_tokens(&self, vault_name: &str) -> Result<(bool, bool), FarmClientError> { + let pool_name = self.get_underlying_pool(vault_name)?.name.to_string(); + self.pool_has_sol_tokens(&pool_name) + } + + pub fn get_pool_token_names( + &self, + pool_name: &str, + ) -> Result<(String, String, String), FarmClientError> { + let pool = self.get_pool(pool_name)?; + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + Ok(( + if let Some(token) = token_a { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = token_b { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = lp_token { + token.name.to_string() + } else { + String::default() + }, + )) + } + + pub fn get_farm_token_names( + &self, + farm_name: &str, + ) -> Result<(String, String, String), FarmClientError> { + let farm = self.get_farm(farm_name)?; + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + Ok(( + if let Some(token) = token_a { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = token_b { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = lp_token { + token.name.to_string() + } else { + String::default() + }, + )) + } + + pub fn get_vault_token_names( + &self, + vault_name: &str, + ) -> Result<(String, String, String), FarmClientError> { + let vault = self.get_vault(vault_name)?; + let vt_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + + Ok(( + if let Some(token) = token_a { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = token_b { + token.name.to_string() + } else { + String::default() + }, + if let Some(token) = vt_token { + token.name.to_string() + } else { + String::default() + }, + )) + } + _ => { + unreachable!(); + } + } + } + + pub fn unwrap_pool_tokens( + &self, + signer: &dyn Signer, + pool_name: &str, + ) -> Result { + let mut inst = Vec::::new(); + + let (is_token_a_sol, is_token_b_sol) = self.pool_has_sol_tokens(pool_name)?; + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(pool_name)?; + + if is_token_a_wrapped { + inst.push(self.new_instruction_unwrap_token(&signer.pubkey(), pool_name, true, 0.0)?); + } + if is_token_b_wrapped { + inst.push(self.new_instruction_unwrap_token( + &signer.pubkey(), + pool_name, + false, + 0.0, + )?); + } + if is_token_a_sol || is_token_b_sol { + inst.push(self.new_instruction_close_token_account(&signer.pubkey(), "SOL")?); + } + + self.sign_and_send_instructions(&[signer], inst.as_slice()) + } + + ////////////// private helpers + fn to_token_amount(&self, ui_amount: f64, token: &Token) -> u64 { + self.ui_amount_to_tokens_with_decimals(ui_amount, token.decimals) + } + + fn to_token_amount_option( + &self, + ui_amount: f64, + token: &Option, + ) -> Result { + if let Some(tkn) = token { + Ok(self.to_token_amount(ui_amount, tkn)) + } else { + Err(ProgramError::UninitializedAccount.into()) + } + } + + fn load_token_by_ref(&self, token_ref: &Pubkey) -> Result { + let data = self.rpc_client.get_account_data(token_ref)?; + Ok(Token::unpack(data.as_slice())?) + } + + fn load_pool_by_ref(&self, pool_ref: &Pubkey) -> Result { + let data = self.rpc_client.get_account_data(pool_ref)?; + Ok(Pool::unpack(data.as_slice())?) + } + + fn load_vault_by_ref(&self, vault_ref: &Pubkey) -> Result { + let data = self.rpc_client.get_account_data(vault_ref)?; + Ok(Vault::unpack(data.as_slice())?) + } + + fn load_farm_by_ref(&self, farm_ref: &Pubkey) -> Result { + let data = self.rpc_client.get_account_data(farm_ref)?; + Ok(Farm::unpack(data.as_slice())?) + } + + fn extract_version(name: &str) -> Result { + if &name[..1].to_uppercase() == "V" { + if let Ok(ver) = name[1..].parse::() { + return Ok(ver); + } + } + Err(FarmClientError::ProgramError(ProgramError::InvalidArgument)) + } + + fn extract_name_and_version(name: &str) -> Result<(String, u16), FarmClientError> { + let dot_split = name.split('.').collect::>(); + if dot_split.len() < 2 || dot_split[0].is_empty() { + return Err(FarmClientError::ProgramError(ProgramError::InvalidArgument)); + } + let dash_split = dot_split.last().unwrap().split('-').collect::>(); + if dash_split.len() < 2 { + return Err(FarmClientError::ProgramError(ProgramError::InvalidArgument)); + } + let ver_string = dash_split.last().unwrap(); + let ver = FarmClient::extract_version(ver_string)?; + Ok((name[..name.len() - ver_string.len() - 1].to_string(), ver)) + } + + // insert version-stripped names that point to the latest version + fn reinsert_latest_versions( + source: &HashMap, + dest: &mut HashMap, + ) { + let mut latest = HashMap::::default(); + for (full_name, _) in source.iter() { + if let Ok((name_no_ver, ver)) = FarmClient::extract_name_and_version(full_name) { + if let Some((_, cur_ver)) = latest.get(&name_no_ver) { + if *cur_ver < ver { + latest.insert(name_no_ver, (full_name.clone(), ver)); + } + } else { + latest.insert(name_no_ver, (full_name.clone(), ver)); + } + } + } + for (name, (full_name, _)) in latest { + dest.insert(name, full_name); + } + } + + fn reload_vault_refs_if_stale(&self) -> Result { + if self.vault_refs.borrow().is_stale() { + let vault_refs = self.get_refdb_pubkey_map(&refdb::StorageType::Vault.to_string())?; + FarmClient::reinsert_latest_versions(&vault_refs, &mut self.latest_vaults.borrow_mut()); + self.vault_refs.borrow_mut().set(vault_refs); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_vaults_if_stale(&self) -> Result { + if self.vaults.borrow().is_stale() { + let refs_map = &self.vault_refs.borrow().data; + let refs: Vec = refs_map.values().copied().collect(); + let mut vault_map = VaultMap::new(); + + let mut idx = 0; + while idx < refs.len() - 1 { + let accounts = self.rpc_client.get_multiple_accounts( + &refs.as_slice()[idx..std::cmp::min(idx + 100, refs.len())], + )?; + + for (account_option, account_ref) in accounts.iter().zip(refs.iter()) { + if let Some(account) = account_option { + let vault = Vault::unpack(account.data.as_slice())?; + vault_map.insert(vault.name.as_str().to_string(), vault); + } else { + return Err(FarmClientError::RecordNotFound(format!( + "Vault with ref {}", + account_ref + ))); + } + } + idx += 100; + } + + self.vaults.borrow_mut().set(vault_map); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_pool_refs_if_stale(&self) -> Result { + if self.pool_refs.borrow().is_stale() { + let pool_refs = self.get_refdb_pubkey_map(&refdb::StorageType::Pool.to_string())?; + FarmClient::reinsert_latest_versions(&pool_refs, &mut self.latest_pools.borrow_mut()); + self.pool_refs.borrow_mut().set(pool_refs); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_pools_if_stale(&self) -> Result { + if self.pools.borrow().is_stale() { + let refs_map = &self.pool_refs.borrow().data; + let refs: Vec = refs_map.values().copied().collect(); + let mut pool_map = PoolMap::new(); + + let mut idx = 0; + while idx < refs.len() - 1 { + let accounts = self.rpc_client.get_multiple_accounts( + &refs.as_slice()[idx..std::cmp::min(idx + 100, refs.len())], + )?; + + for (account_option, account_ref) in accounts.iter().zip(refs.iter()) { + if let Some(account) = account_option { + let pool = Pool::unpack(account.data.as_slice())?; + pool_map.insert(pool.name.as_str().to_string(), pool); + } else { + return Err(FarmClientError::RecordNotFound(format!( + "Pool with ref {}", + account_ref + ))); + } + } + idx += 100; + } + + self.pools.borrow_mut().set(pool_map); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_farm_refs_if_stale(&self) -> Result { + if self.farm_refs.borrow().is_stale() { + let farm_refs = self.get_refdb_pubkey_map(&refdb::StorageType::Farm.to_string())?; + FarmClient::reinsert_latest_versions(&farm_refs, &mut self.latest_farms.borrow_mut()); + self.farm_refs.borrow_mut().set(farm_refs); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_farms_if_stale(&self) -> Result { + if self.farms.borrow().is_stale() { + let refs_map = &self.farm_refs.borrow().data; + let refs: Vec = refs_map.values().copied().collect(); + let mut farm_map = FarmMap::new(); + + let mut idx = 0; + while idx < refs.len() - 1 { + let accounts = self.rpc_client.get_multiple_accounts( + &refs.as_slice()[idx..std::cmp::min(idx + 100, refs.len())], + )?; + + for (account_option, account_ref) in accounts.iter().zip(refs.iter()) { + if let Some(account) = account_option { + let farm = Farm::unpack(account.data.as_slice())?; + farm_map.insert(farm.name.as_str().to_string(), farm); + } else { + return Err(FarmClientError::RecordNotFound(format!( + "Farm with ref {}", + account_ref + ))); + } + } + idx += 100; + } + + self.farms.borrow_mut().set(farm_map); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_token_refs_if_stale(&self) -> Result { + if self.token_refs.borrow().is_stale() { + let token_refs = self.get_refdb_pubkey_map(&refdb::StorageType::Token.to_string())?; + self.token_refs.borrow_mut().set(token_refs); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_tokens_if_stale(&self) -> Result { + if self.tokens.borrow().is_stale() { + let refs_map = &self.token_refs.borrow().data; + let refs: Vec = refs_map.values().copied().collect(); + let mut token_map = TokenMap::new(); + + let mut idx = 0; + while idx < refs.len() - 1 { + let accounts = self.rpc_client.get_multiple_accounts( + &refs.as_slice()[idx..std::cmp::min(idx + 100, refs.len())], + )?; + + for (account_option, account_ref) in accounts.iter().zip(refs.iter()) { + if let Some(account) = account_option { + let token = Token::unpack(account.data.as_slice())?; + token_map.insert(token.name.as_str().to_string(), token); + } else { + return Err(FarmClientError::RecordNotFound(format!( + "Token with ref {}", + account_ref + ))); + } + } + idx += 100; + } + + self.tokens.borrow_mut().set(token_map); + Ok(true) + } else { + Ok(false) + } + } + + fn reload_program_ids_if_stale(&self) -> Result { + if self.official_ids.borrow().is_stale() { + let official_ids = + self.get_refdb_pubkey_map(&refdb::StorageType::Program.to_string())?; + self.official_ids.borrow_mut().set(official_ids); + Ok(true) + } else { + Ok(false) + } + } + + fn get_token_by_ref_from_cache( + &self, + token_ref: &Option, + ) -> Result, FarmClientError> { + if let Some(pubkey) = token_ref { + let name = self.get_token_name(pubkey)?; + Ok(Some(self.get_token(&name)?)) + } else { + Ok(None) + } + } + + fn get_token_account(&self, wallet_address: &Pubkey, token: &Option) -> Option { + token.map(|token_info| get_associated_token_address(wallet_address, &token_info.mint)) + } + + fn extract_token_names(name: &str) -> Result<(String, String, String), FarmClientError> { + let dot_split = if name.starts_with("LP.") || name.starts_with("VT.") { + name[3..].split('.').collect::>() + } else { + name.split('.').collect::>() + }; + if dot_split.len() < 2 || dot_split[0].is_empty() { + return Err(FarmClientError::ValueError(format!( + "Can't extract token names from {}", + name + ))); + } + let dash_split = dot_split.last().unwrap().split('-').collect::>(); + if dash_split.is_empty() + || dash_split[0].is_empty() + || (dash_split.len() > 1 && dash_split[1].is_empty()) + { + return Err(FarmClientError::ValueError(format!( + "Can't extract token names from {}", + name + ))); + } + Ok(( + dot_split[0].to_string(), + dash_split[0].to_string(), + if dash_split.len() > 1 && FarmClient::extract_version(dash_split[1]).is_err() { + dash_split[1].to_string() + } else { + String::default() + }, + )) + } + + fn pool_has_reverse_tokens(pool_name: &str, token_a: &str) -> Result { + let (_, pool_token_a, _) = FarmClient::extract_token_names(pool_name)?; + Ok(pool_token_a != token_a) + } + + fn get_raydium_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + ) -> Result, FarmClientError> { + let farm_id = match farm.route { + FarmRoute::Raydium { farm_id, .. } => farm_id, + _ => unreachable!(), + }; + // look-up in cache + if let Some(addr_map) = self.stake_accounts.borrow()[0].get(&wallet_address.to_string()) { + if let Some(stake_acc) = addr_map.get(&farm_id.to_string()) { + return Ok(Some(*stake_acc)); + } + } + // search on-chain + let filters = Some(vec![rpc_filter::RpcFilterType::Memcmp( + rpc_filter::Memcmp { + offset: 40, + bytes: rpc_filter::MemcmpEncodedBytes::Base58( + bs58::encode(wallet_address).into_string(), + ), + encoding: Some(rpc_filter::MemcmpEncoding::Binary), + }, + )]); + let acc_vec = self.rpc_client.get_program_accounts_with_config( + &farm.farm_program_id, + RpcProgramAccountsConfig { + filters, + ..RpcProgramAccountsConfig::default() + }, + )?; + let user_acc_str = wallet_address.to_string(); + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[0]; + let mut user_acc_map = stake_accounts_map.get_mut(&user_acc_str); + if user_acc_map.is_none() { + stake_accounts_map.insert(user_acc_str.clone(), StakeAccMap::new()); + user_acc_map = stake_accounts_map.get_mut(&user_acc_str); + } + let user_acc_map = user_acc_map.unwrap(); + let target_farm_id_str = farm_id.to_string(); + let mut stake_acc = None; + for (stake_acc_key, account) in acc_vec.iter() { + let farm_id_str = if farm.version >= 4 { + RaydiumUserStakeInfoV4::unpack(account.data.as_slice())? + .farm_id + .to_string() + } else { + RaydiumUserStakeInfo::unpack(account.data.as_slice())? + .farm_id + .to_string() + }; + user_acc_map.insert(farm_id_str.clone(), *stake_acc_key); + if farm_id_str == target_farm_id_str { + stake_acc = Some(*stake_acc_key); + } + } + Ok(stake_acc) + } + + fn get_saber_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + ) -> Result, FarmClientError> { + let quarry = match farm.route { + FarmRoute::Saber { quarry, .. } => quarry, + _ => unreachable!(), + }; + // look-up in cache + if let Some(addr_map) = self.stake_accounts.borrow()[1].get(&wallet_address.to_string()) { + if let Some(stake_acc) = addr_map.get(&quarry.to_string()) { + return Ok(Some(*stake_acc)); + } + } + + // update cache + let user_acc_str = wallet_address.to_string(); + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[1]; + if stake_accounts_map.get(&user_acc_str).is_none() { + stake_accounts_map.insert(user_acc_str, StakeAccMap::new()); + } + + // check if account exists on-chain + let (miner, _) = Pubkey::find_program_address( + &[b"Miner", &quarry.to_bytes(), &wallet_address.to_bytes()], + &quarry_mine::id(), + ); + if let Ok(data) = self.rpc_client.get_account_data(&miner) { + if !data.is_empty() { + return Ok(Some(miner)); + } + } + Ok(None) + } + + fn get_orca_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + ) -> Result, FarmClientError> { + let farm_id = match farm.route { + FarmRoute::Orca { farm_id, .. } => farm_id, + _ => unreachable!(), + }; + // look-up in cache + if let Some(addr_map) = self.stake_accounts.borrow()[2].get(&wallet_address.to_string()) { + if let Some(stake_acc) = addr_map.get(&farm_id.to_string()) { + return Ok(Some(*stake_acc)); + } + } + + // update cache + let user_acc_str = wallet_address.to_string(); + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[2]; + if stake_accounts_map.get(&user_acc_str).is_none() { + stake_accounts_map.insert(user_acc_str, StakeAccMap::new()); + } + + // check if account exists on-chain + let farmer = Pubkey::find_program_address( + &[ + &farm_id.to_bytes(), + &wallet_address.to_bytes(), + &spl_token::id().to_bytes(), + ], + &farm.farm_program_id, + ) + .0; + if let Ok(data) = self.rpc_client.get_account_data(&farmer) { + if !data.is_empty() { + return Ok(Some(farmer)); + } + } + Ok(None) + } + + fn get_stake_account( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + let farm = self.get_farm(farm_name)?; + match farm.route { + FarmRoute::Raydium { .. } => self.get_raydium_stake_account(wallet_address, &farm), + FarmRoute::Saber { .. } => self.get_saber_stake_account(wallet_address, &farm), + FarmRoute::Orca { .. } => self.get_orca_stake_account(wallet_address, &farm), + } + } + + fn create_raydium_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + instruction_vec: &mut Vec, + signers: &mut Vec>, + ) -> Result<(), FarmClientError> { + let farm_id = match farm.route { + FarmRoute::Raydium { farm_id, .. } => farm_id, + _ => unreachable!(), + }; + let new_keypair = Keypair::new(); + let new_pubkey = new_keypair.pubkey(); + let target_farm_id_str = farm_id.to_string(); + let user_acc_str = wallet_address.to_string(); + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[0]; + let user_acc_map = stake_accounts_map.get_mut(&user_acc_str).unwrap(); + user_acc_map.insert(target_farm_id_str, new_pubkey); + signers.push(Box::new(new_keypair)); + instruction_vec.push(self.new_instruction_create_system_account( + wallet_address, + &new_pubkey, + 0, + if farm.version >= 4 { + RaydiumUserStakeInfoV4::LEN + } else { + RaydiumUserStakeInfo::LEN + }, + &farm.farm_program_id, + )?); + Ok(()) + } + + fn create_saber_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + instruction_vec: &mut Vec, + _signers: &mut Vec>, + ) -> Result<(), FarmClientError> { + let (quarry, rewarder) = match farm.route { + FarmRoute::Saber { + quarry, rewarder, .. + } => (quarry, rewarder), + _ => unreachable!(), + }; + + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + let lp_mint = lp_token.ok_or(ProgramError::UninitializedAccount)?.mint; + + let (miner, bump) = Pubkey::find_program_address( + &[b"Miner", &quarry.to_bytes(), &wallet_address.to_bytes()], + &quarry_mine::id(), + ); + + let miner_vault = + spl_associated_token_account::get_associated_token_address(&miner, &lp_mint); + + let mut hasher = Hasher::default(); + hasher.hash(b"global:create_miner"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.push(bump); + + let accounts = vec![ + AccountMeta::new_readonly(*wallet_address, true), + AccountMeta::new(miner, false), + AccountMeta::new(quarry, false), + AccountMeta::new(rewarder, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(*wallet_address, true), + AccountMeta::new(lp_mint, false), + AccountMeta::new(miner_vault, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + instruction_vec.push(Instruction { + program_id: quarry_mine::id(), + accounts, + data, + }); + + // update cache + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[1]; + let user_acc_map = stake_accounts_map + .get_mut(&wallet_address.to_string()) + .unwrap(); + user_acc_map.insert(quarry.to_string(), miner); + + Ok(()) + } + + fn create_orca_stake_account( + &self, + wallet_address: &Pubkey, + farm: &Farm, + instruction_vec: &mut Vec, + _signers: &mut Vec>, + ) -> Result<(), FarmClientError> { + let farm_id = match farm.route { + FarmRoute::Orca { farm_id, .. } => farm_id, + _ => unreachable!(), + }; + + let farmer = Pubkey::find_program_address( + &[ + &farm_id.to_bytes(), + &wallet_address.to_bytes(), + &spl_token::id().to_bytes(), + ], + &farm.farm_program_id, + ) + .0; + + let orca_accounts = vec![ + AccountMeta::new_readonly(farm_id, false), + AccountMeta::new(farmer, false), + AccountMeta::new_readonly(*wallet_address, true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + instruction_vec.push(Instruction { + program_id: farm.farm_program_id, + accounts: orca_accounts, + data: OrcaUserInit {}.to_vec()?, + }); + + // update cache + let stake_accounts_map = &mut self.stake_accounts.borrow_mut()[2]; + let user_acc_map = stake_accounts_map + .get_mut(&wallet_address.to_string()) + .unwrap(); + user_acc_map.insert(farm_id.to_string(), farmer); + + Ok(()) + } + + fn check_user_stake_account( + &self, + wallet_address: &Pubkey, + farm_name: &str, + instruction_vec: &mut Vec, + signers: &mut Vec>, + ) -> Result<(), FarmClientError> { + // lookup in cache + let farm = self.get_farm(farm_name)?; + if self.get_stake_account(wallet_address, farm_name)?.is_some() { + return Ok(()); + } + // create new + match farm.route { + FarmRoute::Raydium { .. } => { + self.create_raydium_stake_account(wallet_address, &farm, instruction_vec, signers) + } + FarmRoute::Saber { .. } => { + self.create_saber_stake_account(wallet_address, &farm, instruction_vec, signers) + } + FarmRoute::Orca { .. } => { + self.create_orca_stake_account(wallet_address, &farm, instruction_vec, signers) + } + } + } + + fn get_pool_price_raydium( + &self, + token_a_balance: u64, + token_b_balance: u64, + token_a_decimals: u8, + token_b_decimals: u8, + amm_id: &Pubkey, + amm_open_orders: &Pubkey, + ) -> Result { + // adjust with open orders + let mut token_a_balance = token_a_balance; + let mut token_b_balance = token_b_balance; + let open_orders_data = self.rpc_client.get_account_data(amm_open_orders)?; + if open_orders_data.len() == 3228 { + let base_token_total = array_ref![open_orders_data, 85, 8]; + let quote_token_total = array_ref![open_orders_data, 101, 8]; + + token_a_balance += u64::from_le_bytes(*base_token_total); + token_b_balance += u64::from_le_bytes(*quote_token_total); + } + + // adjust with amm take pnl + let amm_id_data = self.rpc_client.get_account_data(amm_id)?; + let (pnl_coin_offset, pnl_pc_offset) = if amm_id_data.len() == 624 { + (136, 144) + } else if amm_id_data.len() == 680 { + (144, 152) + } else if amm_id_data.len() == 752 { + (192, 200) + } else { + (0, 0) + }; + if pnl_coin_offset > 0 { + let need_take_pnl_coin = + u64::from_le_bytes(*array_ref![amm_id_data, pnl_coin_offset, 8]); + let need_take_pnl_pc = u64::from_le_bytes(*array_ref![amm_id_data, pnl_pc_offset, 8]); + + token_a_balance -= if need_take_pnl_coin < token_a_balance { + need_take_pnl_coin + } else { + token_a_balance + }; + token_b_balance -= if need_take_pnl_pc < token_b_balance { + need_take_pnl_pc + } else { + token_b_balance + }; + } + + if token_a_balance == 0 || token_b_balance == 0 { + Ok(0.0) + } else { + Ok( + self.tokens_to_ui_amount_with_decimals(token_b_balance, token_b_decimals) + / self.tokens_to_ui_amount_with_decimals(token_a_balance, token_a_decimals), + ) + } + } + + fn get_pool_price_saber( + &self, + swap_account: &Pubkey, + token_a_balance: u64, + token_b_balance: u64, + lp_token: &Token, + ) -> Result { + let swap_data = self.rpc_client.get_account_data(swap_account)?; + let swap_info = SwapInfo::unpack(swap_data.as_slice())?; + + let mint_data = self.rpc_client.get_account_data(&lp_token.mint)?; + let lp_mint = Mint::unpack(mint_data.as_slice())?; + + let swap = SaberSwap { + initial_amp_factor: swap_info.initial_amp_factor, + target_amp_factor: swap_info.target_amp_factor, + current_ts: chrono::Utc::now().timestamp(), + start_ramp_ts: swap_info.start_ramp_ts, + stop_ramp_ts: swap_info.stop_ramp_ts, + lp_mint_supply: lp_mint.supply, + token_a_reserve: token_a_balance, + token_b_reserve: token_b_balance, + }; + + if let Some(price) = swap.calculate_virtual_price_of_pool_tokens(1000000) { + Ok(price as f64 / 1000000.0) + } else { + Ok(0.0) + } + } + + fn get_pool_price_orca( + &self, + token_a_balance: u64, + token_b_balance: u64, + token_a_decimals: u8, + token_b_decimals: u8, + ) -> Result { + if token_a_balance == 0 || token_b_balance == 0 { + Ok(0.0) + } else { + Ok( + self.tokens_to_ui_amount_with_decimals(token_b_balance, token_b_decimals) + / self.tokens_to_ui_amount_with_decimals(token_a_balance, token_a_decimals), + ) + } + } + + fn send_sol_to_wsol( + &self, + wallet_address: &Pubkey, + ui_amount: f64, + instruction_vec: &mut Vec, + ) -> Result<(), FarmClientError> { + let token_addr = self.get_associated_token_address(wallet_address, "SOL")?; + instruction_vec.push(self.new_instruction_transfer( + wallet_address, + &token_addr, + ui_amount, + )?); + instruction_vec.push(self.new_instruction_sync_token_balance(wallet_address, "SOL")?); + Ok(()) + } + + fn check_token_account( + &self, + wallet_address: &Pubkey, + token: &Option, + ui_amount: f64, + instruction_vec: &mut Vec, + ) -> Result<(), FarmClientError> { + if let Some(tkn) = token { + if !self.has_active_token_account(wallet_address, &tkn.name) { + instruction_vec + .push(self.new_instruction_create_token_account(wallet_address, &tkn.name)?); + if ui_amount > 0.0 { + if tkn.token_type == TokenType::WrappedSol { + let _ = + self.send_sol_to_wsol(wallet_address, ui_amount, instruction_vec)?; + } else { + return Err(FarmClientError::InsufficientBalance(tkn.name.to_string())); + } + } + } else if ui_amount > 0.0 { + let balance = self.get_token_account_balance(wallet_address, &tkn.name)?; + if balance < ui_amount { + if tkn.token_type == TokenType::WrappedSol { + let _ = self.send_sol_to_wsol( + wallet_address, + ui_amount - balance, + instruction_vec, + )?; + } else { + return Err(FarmClientError::InsufficientBalance(tkn.name.to_string())); + } + } + } + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn check_pool_accounts( + &self, + signer: &dyn Signer, + pool_name: &str, + ui_amount_token_a: f64, + ui_amount_token_b: f64, + ui_amount_lp_token: f64, + check_lp_token: bool, + instruction_vec: &mut Vec, + ) -> Result<(), FarmClientError> { + let pool = self.get_pool(pool_name)?; + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + if check_lp_token { + let _ = self.check_token_account( + &signer.pubkey(), + &lp_token, + ui_amount_lp_token, + instruction_vec, + )?; + } + let _ = self.check_token_account( + &signer.pubkey(), + &token_a, + ui_amount_token_a, + instruction_vec, + )?; + let _ = self.check_token_account( + &signer.pubkey(), + &token_b, + ui_amount_token_b, + instruction_vec, + )?; + + if let PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + if let Some(token) = self.get_token_by_ref_from_cache(&wrapped_token_a_ref)? { + let _ = self.check_token_account_with_mint( + &signer.pubkey(), + &token.mint, + instruction_vec, + )?; + } + if let Some(token) = self.get_token_by_ref_from_cache(&wrapped_token_b_ref)? { + let _ = self.check_token_account_with_mint( + &signer.pubkey(), + &token.mint, + instruction_vec, + )?; + } + } + + Ok(()) + } + + fn check_token_account_with_mint( + &self, + wallet_address: &Pubkey, + mint: &Pubkey, + instruction_vec: &mut Vec, + ) -> Result<(), FarmClientError> { + let token_address = get_associated_token_address(wallet_address, mint); + if let Ok(data) = self.rpc_client.get_account_data(&token_address) { + if let Ok(TokenAccountType::Account(ui_account)) = parse_token(data.as_slice(), Some(0)) + { + if ui_account.state == UiAccountState::Initialized { + return Ok(()); + } + } + } + + instruction_vec.push(create_associated_token_account( + wallet_address, + wallet_address, + mint, + )); + Ok(()) + } + + fn check_farm_accounts( + &self, + signer: &dyn Signer, + farm_name: &str, + ui_amount: f64, + instruction_vec: &mut Vec, + signers: &mut Vec>, + ) -> Result<(), FarmClientError> { + let farm = self.get_farm(farm_name)?; + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + let _ = self.check_token_account(&signer.pubkey(), &token_a, 0.0, instruction_vec)?; + let _ = self.check_token_account(&signer.pubkey(), &token_b, 0.0, instruction_vec)?; + let _ = + self.check_token_account(&signer.pubkey(), &lp_token, ui_amount, instruction_vec)?; + + let _ = + self.check_user_stake_account(&signer.pubkey(), farm_name, instruction_vec, signers)?; + + match farm.route { + FarmRoute::Saber { .. } => { + let user_info_account = self + .get_stake_account(&signer.pubkey(), farm_name)? + .unwrap(); + + let user_vault_account = self + .get_token_account(&user_info_account, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + let data = self.rpc_client.get_account_data(&user_vault_account); + if data.is_err() || data.unwrap().is_empty() { + instruction_vec.insert( + 0, + create_associated_token_account( + &signer.pubkey(), + &user_info_account, + &lp_token.unwrap().mint, + ), + ); + } + } + FarmRoute::Orca { farm_token_ref, .. } => { + let farm_lp_token = self.get_token_by_ref(&farm_token_ref)?; + let user_farm_lp_token_account = + get_associated_token_address(&signer.pubkey(), &farm_lp_token.mint); + let data = self + .rpc_client + .get_account_data(&user_farm_lp_token_account); + if data.is_err() || data.unwrap().is_empty() { + instruction_vec.insert( + 0, + create_associated_token_account( + &signer.pubkey(), + &signer.pubkey(), + &farm_lp_token.mint, + ), + ); + } + } + _ => {} + } + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn check_vault_accounts( + &self, + signer: &dyn Signer, + vault_name: &str, + ui_amount_token_a: f64, + ui_amount_token_b: f64, + ui_amount_vt_token: f64, + check_vt_token: bool, + check_lp_token: bool, + instruction_vec: &mut Vec, + ) -> Result<(), FarmClientError> { + let vault = self.get_vault(vault_name)?; + let vault_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + let pool = self.get_underlying_pool(vault_name)?; + let farm = self.get_underlying_farm(vault_name)?; + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let token_a_reward = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let token_b_reward = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + + if check_vt_token { + let _ = self.check_token_account( + &signer.pubkey(), + &vault_token, + ui_amount_vt_token, + instruction_vec, + )?; + } + if check_lp_token { + let _ = self.check_token_account(&signer.pubkey(), &lp_token, 0.0, instruction_vec)?; + } + let _ = + self.check_token_account(&signer.pubkey(), &token_a_reward, 0.0, instruction_vec)?; + let _ = + self.check_token_account(&signer.pubkey(), &token_b_reward, 0.0, instruction_vec)?; + let _ = self.check_token_account( + &signer.pubkey(), + &token_a, + ui_amount_token_a, + instruction_vec, + )?; + let _ = self.check_token_account( + &signer.pubkey(), + &token_b, + ui_amount_token_b, + instruction_vec, + )?; + + let user_info_account = self.get_vault_user_info_account(&signer.pubkey(), vault_name)?; + let data = self.rpc_client.get_account_data(&user_info_account); + if data.is_err() || !RefDB::is_initialized(data.unwrap().as_slice()) { + instruction_vec + .push(self.new_instruction_user_init_vault(&signer.pubkey(), vault_name)?); + } + + Ok(()) + } + + fn get_vault_lp_token_decimals(&self, vault_name: &str) -> Result { + let pool = self.get_underlying_pool(vault_name)?; + if let Some(pool_token) = self.get_token_by_ref_from_cache(&pool.lp_token_ref)? { + Ok(pool_token.decimals) + } else { + Err(FarmClientError::RecordNotFound(format!( + "LP token for {}", + vault_name + ))) + } + } + + // note: there could be multiple underlying pools in the future + fn get_underlying_pool(&self, vault_name: &str) -> Result { + let vault = self.get_vault(vault_name)?; + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + self.get_pool_by_ref(&pool_id_ref) + } + VaultStrategy::DynamicHedge { .. } => self.get_pool_by_ref(&zero::id()), + } + } + + // note: there could be multiple underlying farms in the future + fn get_underlying_farm(&self, vault_name: &str) -> Result { + let vault = self.get_vault(vault_name)?; + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { farm_id_ref, .. } => { + self.get_farm_by_ref(&farm_id_ref) + } + VaultStrategy::DynamicHedge { .. } => self.get_farm_by_ref(&zero::id()), + } + } + + fn get_vault_price(&self, vault_name: &str) -> Result { + let pool_name = self.get_underlying_pool(vault_name)?.name.to_string(); + self.get_pool_price(&pool_name) + } +} + +mod farm_accounts_orca; +mod farm_accounts_raydium; +mod farm_accounts_saber; +mod farm_instructions; +mod governance_instructions; +mod pool_accounts_orca; +mod pool_accounts_raydium; +mod pool_accounts_saber; +mod pool_instructions; +mod refdb_instructions; +mod system_instructions; +mod vault_instructions; +mod vault_stc_accounts_raydium; +mod vault_stc_accounts_saber; + +#[cfg(test)] +mod test { + use solana_farm_sdk::string::{str_to_as64, ArrayString64}; + + #[test] + fn test_to_array_string() { + let arrstr: ArrayString64 = ArrayString64::try_from_str("test").unwrap(); + assert_eq!(arrstr, str_to_as64("test").unwrap()); + assert_eq!(arrstr.as_str(), "test"); + assert!(matches!( + ArrayString64::try_from_str( + "very long string, longer than 64 characters, conversion must fail" + ), + Err(_) + )); + } +} diff --git a/farms/farm-client/src/client/farm_accounts_orca.rs b/farms/farm-client/src/client/farm_accounts_orca.rs new file mode 100644 index 00000000000..4cf10865328 --- /dev/null +++ b/farms/farm-client/src/client/farm_accounts_orca.rs @@ -0,0 +1,189 @@ +//! Solana Farm Client Orca Farms accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::farm::FarmRoute, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for tokens staking in an Orca farm + pub fn get_stake_accounts_orca( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_reward_token_account = self.get_token_account(wallet_address, &token_a); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Orca { + farm_id, + farm_authority, + farm_token_ref, + base_token_vault, + reward_token_vault, + } = farm.route + { + let user_info_account = self.get_stake_account(wallet_address, farm_name)?; + let farm_lp_token = self.get_token_by_ref_from_cache(&Some(farm_token_ref))?; + let user_farm_lp_token_account = self.get_token_account(wallet_address, &farm_lp_token); + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_info_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_farm_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + farm_lp_token + .ok_or(ProgramError::UninitializedAccount)? + .mint, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(base_token_vault, false)); + accounts.push(AccountMeta::new(reward_token_vault, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for unstaking tokens from an Orca farm + pub fn get_unstake_accounts_orca( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_reward_token_account = self.get_token_account(wallet_address, &token_a); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Orca { + farm_id, + farm_authority, + farm_token_ref, + base_token_vault, + reward_token_vault, + } = farm.route + { + let user_info_account = self.get_stake_account(wallet_address, farm_name)?; + let farm_lp_token = self.get_token_by_ref_from_cache(&Some(farm_token_ref))?; + let user_farm_lp_token_account = self.get_token_account(wallet_address, &farm_lp_token); + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_info_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_farm_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + farm_lp_token + .ok_or(ProgramError::UninitializedAccount)? + .mint, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(base_token_vault, false)); + accounts.push(AccountMeta::new(reward_token_vault, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for rewards harvesting in an Orca farm + pub fn get_harvest_accounts_orca( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + + // get user accounts info + let user_reward_token_account = self.get_token_account(wallet_address, &token_a); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Orca { + farm_id, + farm_authority, + farm_token_ref: _, + base_token_vault, + reward_token_vault, + } = farm.route + { + let user_info_account = self.get_stake_account(wallet_address, farm_name)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_info_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(base_token_vault, false)); + accounts.push(AccountMeta::new(reward_token_vault, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + } + + Ok(accounts) + } +} diff --git a/farms/farm-client/src/client/farm_accounts_raydium.rs b/farms/farm-client/src/client/farm_accounts_raydium.rs new file mode 100644 index 00000000000..3e75ea53f28 --- /dev/null +++ b/farms/farm-client/src/client/farm_accounts_raydium.rs @@ -0,0 +1,157 @@ +//! Solana Farm Client Raydium Farms accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::{farm::FarmRoute, id::zero}, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey, sysvar}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for tokens staking in a Raydium farm + pub fn get_stake_accounts_raydium( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_reward_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_reward_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } = farm.route + { + let user_info_account = self.get_stake_account(wallet_address, farm_name)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_info_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(farm_lp_token_account, false)); + accounts.push(AccountMeta::new(farm_reward_token_a_account, false)); + accounts.push(AccountMeta::new( + farm_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for unstaking tokens from a Raydium farm + pub fn get_unstake_accounts_raydium( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_reward_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_reward_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } = farm.route + { + let user_info_account = self.get_stake_account(wallet_address, farm_name)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_info_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(farm_lp_token_account, false)); + accounts.push(AccountMeta::new(farm_reward_token_a_account, false)); + accounts.push(AccountMeta::new( + farm_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for rewards harvesting in a Raydium farm + pub fn get_harvest_accounts_raydium( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + self.get_stake_accounts_raydium(wallet_address, farm_name) + } +} diff --git a/farms/farm-client/src/client/farm_accounts_saber.rs b/farms/farm-client/src/client/farm_accounts_saber.rs new file mode 100644 index 00000000000..4ee1cce7a9a --- /dev/null +++ b/farms/farm-client/src/client/farm_accounts_saber.rs @@ -0,0 +1,179 @@ +//! Solana Farm Client Saber Farms accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::{farm::FarmRoute, id::zero}, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for tokens staking in a Saber farm + pub fn get_stake_accounts_saber( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Saber { + quarry, rewarder, .. + } = farm.route + { + let user_info_account = self + .get_stake_account(wallet_address, farm_name)? + .ok_or(ProgramError::UninitializedAccount)?; + let user_vault_account = self + .get_token_account(&user_info_account, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(user_info_account, false)); + accounts.push(AccountMeta::new(user_vault_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for unstaking tokens from a Saber farm + pub fn get_unstake_accounts_saber( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // get user accounts info + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Saber { + quarry, rewarder, .. + } = farm.route + { + let user_info_account = self + .get_stake_account(wallet_address, farm_name)? + .ok_or(ProgramError::UninitializedAccount)?; + let user_vault_account = self + .get_token_account(&user_info_account, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(user_info_account, false)); + accounts.push(AccountMeta::new(user_vault_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for rewards harvesting in a Saber farm + pub fn get_harvest_accounts_saber( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result, FarmClientError> { + // get farm info + let farm = self.get_farm(farm_name)?; + + // get tokens info + let sbr_token = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let iou_token = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + + // get user accounts info + let user_sbr_token_account = self.get_token_account(wallet_address, &sbr_token); + let user_iou_token_account = self.get_token_account(wallet_address, &iou_token); + + // fill in accounts + let mut accounts = vec![]; + if let FarmRoute::Saber { + quarry, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + iou_fees_account, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info, + } = farm.route + { + let user_info_account = self + .get_stake_account(wallet_address, farm_name)? + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_iou_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_sbr_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(zero::id(), false)); + accounts.push(AccountMeta::new(user_info_account, false)); + + accounts.push(AccountMeta::new_readonly(rewarder, false)); + accounts.push(AccountMeta::new_readonly(redeemer, false)); + accounts.push(AccountMeta::new_readonly(redeemer_program, false)); + accounts.push(AccountMeta::new(minter, false)); + accounts.push(AccountMeta::new(mint_wrapper, false)); + accounts.push(AccountMeta::new_readonly(mint_wrapper_program, false)); + accounts.push(AccountMeta::new( + sbr_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new( + iou_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new(iou_fees_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new(sbr_vault, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_program, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_authority, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_state, false)); + accounts.push(AccountMeta::new(minter_info, false)); + } + + Ok(accounts) + } +} diff --git a/farms/farm-client/src/client/farm_instructions.rs b/farms/farm-client/src/client/farm_instructions.rs new file mode 100644 index 00000000000..6bd4f6ca844 --- /dev/null +++ b/farms/farm-client/src/client/farm_instructions.rs @@ -0,0 +1,106 @@ +//! Solana Farm Client Farm Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{farm::FarmRoute, instruction::amm::AmmInstruction}, + solana_sdk::{instruction::Instruction, pubkey::Pubkey}, +}; + +use super::FarmClient; + +impl FarmClient { + /// Creates a new Instruction for tokens staking + pub fn new_instruction_stake( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ui_amount: f64, + ) -> Result { + // get farm info + let farm = self.get_farm(farm_name)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // fill in accounts and instruction data + let data = AmmInstruction::Stake { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + + let accounts = match farm.route { + FarmRoute::Raydium { .. } => { + self.get_stake_accounts_raydium(wallet_address, farm_name)? + } + FarmRoute::Saber { .. } => self.get_stake_accounts_saber(wallet_address, farm_name)?, + FarmRoute::Orca { .. } => self.get_stake_accounts_orca(wallet_address, farm_name)?, + }; + + Ok(Instruction { + program_id: farm.router_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for tokens unstaking + pub fn new_instruction_unstake( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ui_amount: f64, + ) -> Result { + // get farm info + let farm = self.get_farm(farm_name)?; + let lp_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + + // fill in accounts and instruction data + let data = AmmInstruction::Unstake { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + + let accounts = match farm.route { + FarmRoute::Raydium { .. } => { + self.get_unstake_accounts_raydium(wallet_address, farm_name)? + } + FarmRoute::Saber { .. } => { + self.get_unstake_accounts_saber(wallet_address, farm_name)? + } + FarmRoute::Orca { .. } => self.get_unstake_accounts_orca(wallet_address, farm_name)?, + }; + + Ok(Instruction { + program_id: farm.router_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for rewards harvesting + pub fn new_instruction_harvest( + &self, + wallet_address: &Pubkey, + farm_name: &str, + ) -> Result { + // get farm info + let farm = self.get_farm(farm_name)?; + + // fill in accounts and instruction data + let data = AmmInstruction::Harvest.to_vec()?; + + let accounts = match farm.route { + FarmRoute::Raydium { .. } => { + self.get_harvest_accounts_raydium(wallet_address, farm_name)? + } + FarmRoute::Saber { .. } => { + self.get_harvest_accounts_saber(wallet_address, farm_name)? + } + FarmRoute::Orca { .. } => self.get_harvest_accounts_orca(wallet_address, farm_name)?, + }; + + Ok(Instruction { + program_id: farm.router_program_id, + data, + accounts, + }) + } +} diff --git a/farms/farm-client/src/client/governance_instructions.rs b/farms/farm-client/src/client/governance_instructions.rs new file mode 100644 index 00000000000..70ce64a14bd --- /dev/null +++ b/farms/farm-client/src/client/governance_instructions.rs @@ -0,0 +1,436 @@ +//! Solana Farm Client Governance Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{ + id::{DAO_MINT_NAME, DAO_PROGRAM_NAME, DAO_TOKEN_NAME}, + token::Token, + }, + solana_sdk::{borsh::try_from_slice_unchecked, instruction::Instruction, pubkey::Pubkey}, + spl_governance::instruction as dao_instruction, + spl_governance::state::{ + governance::{get_mint_governance_address, get_program_governance_address}, + proposal::{get_proposal_address, VoteType}, + proposal_instruction::{ + get_proposal_instruction_address, InstructionData, ProposalInstructionV2, + }, + realm::get_realm_address, + token_owner_record::get_token_owner_record_address, + vote_record::{Vote, VoteChoice}, + }, +}; + +use super::FarmClient; + +impl FarmClient { + /// Creates a new instruction for tokens deposit to the farms realm + pub fn new_instruction_governance_tokens_deposit( + &self, + wallet_address: &Pubkey, + ui_amount: f64, + ) -> Result { + let dao_program = self.get_program_id(DAO_PROGRAM_NAME)?; + let realm_address = get_realm_address(&dao_program, DAO_PROGRAM_NAME); + let dao_token = self.get_token(DAO_TOKEN_NAME)?; + let token_addr = self.get_associated_token_address(wallet_address, DAO_TOKEN_NAME)?; + + let inst = dao_instruction::deposit_governing_tokens( + &dao_program, + &realm_address, + &token_addr, + wallet_address, + wallet_address, + wallet_address, + self.ui_amount_to_tokens_with_decimals(ui_amount, dao_token.decimals), + &dao_token.mint, + ); + + Ok(inst) + } + + /// Creates a new instruction for tokens withdrawal from the farms realm + pub fn new_instruction_governance_tokens_withdraw( + &self, + wallet_address: &Pubkey, + ) -> Result { + let dao_program = self.get_program_id(DAO_PROGRAM_NAME)?; + let realm_address = get_realm_address(&dao_program, DAO_PROGRAM_NAME); + let dao_token = self.get_token(DAO_TOKEN_NAME)?; + let token_addr = self.get_associated_token_address(wallet_address, DAO_TOKEN_NAME)?; + + let inst = dao_instruction::withdraw_governing_tokens( + &dao_program, + &realm_address, + &token_addr, + wallet_address, + &dao_token.mint, + ); + + Ok(inst) + } + + /// Creates a new instruction for initializing a new governance proposal + pub fn new_instruction_governance_proposal_new( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_name: &str, + proposal_link: &str, + proposal_index: u32, + ) -> Result { + let (dao_program, realm_address, dao_token, governance, token_owner, _proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::create_proposal( + &dao_program, + &governance, + &token_owner, + wallet_address, + wallet_address, + None, + &realm_address, + proposal_name.to_string(), + proposal_link.to_string(), + &dao_token.mint, + VoteType::SingleChoice, + vec![proposal_name.to_string()], + true, + proposal_index, + ); + + Ok(inst) + } + + /// Creates a new instruction for canceling governance proposal + pub fn new_instruction_governance_proposal_cancel( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let (dao_program, _realm_address, _dao_token, governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::cancel_proposal( + &dao_program, + &proposal_address, + &token_owner, + wallet_address, + &governance, + ); + + Ok(inst) + } + + /// Creates a new instruction for adding a signatory to governance proposal + pub fn new_instruction_governance_signatory_add( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + signatory: &Pubkey, + ) -> Result { + let (dao_program, _realm_address, _dao_token, _governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::add_signatory( + &dao_program, + &proposal_address, + &token_owner, + wallet_address, + wallet_address, + signatory, + ); + + Ok(inst) + } + + /// Creates a new instruction for removing the signatory from governance proposal + pub fn new_instruction_governance_signatory_remove( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + signatory: &Pubkey, + ) -> Result { + let (dao_program, _realm_address, _dao_token, _governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::remove_signatory( + &dao_program, + &proposal_address, + &token_owner, + wallet_address, + signatory, + wallet_address, + ); + + Ok(inst) + } + + /// Creates a new instruction for signing off governance proposal + pub fn new_instruction_governance_sign_off( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let (dao_program, _realm_address, _dao_token, _governance, _token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = + dao_instruction::sign_off_proposal(&dao_program, &proposal_address, wallet_address); + + Ok(inst) + } + + /// Creates a new instruction for casting a vote on governance proposal + pub fn new_instruction_governance_vote_cast( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + vote: u8, + ) -> Result { + let (dao_program, realm_address, dao_token, governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let voter_token_owner = get_token_owner_record_address( + &dao_program, + &realm_address, + &dao_token.mint, + wallet_address, + ); + + let inst = dao_instruction::cast_vote( + &dao_program, + &realm_address, + &governance, + &proposal_address, + &token_owner, + &voter_token_owner, + wallet_address, + &dao_token.mint, + wallet_address, + None, + if vote > 0 { + Vote::Approve(vec![VoteChoice { + rank: 0, + weight_percentage: 100, + }]) + } else { + Vote::Deny + }, + ); + + Ok(inst) + } + + /// Creates a new instruction for removing the vote from governance proposal + pub fn new_instruction_governance_vote_relinquish( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let (dao_program, _realm_address, dao_token, governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::relinquish_vote( + &dao_program, + &governance, + &proposal_address, + &token_owner, + &dao_token.mint, + Some(*wallet_address), + Some(*wallet_address), + ); + + Ok(inst) + } + + /// Creates a new instruction for finalizing the vote on governance proposal + pub fn new_instruction_governance_vote_finalize( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + ) -> Result { + let (dao_program, realm_address, dao_token, governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let inst = dao_instruction::finalize_vote( + &dao_program, + &realm_address, + &governance, + &proposal_address, + &token_owner, + &dao_token.mint, + ); + + Ok(inst) + } + + /// Creates a new instruction for adding a new instruction to governance proposal + pub fn new_instruction_governance_instruction_insert( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + instruction: &Instruction, + ) -> Result { + let (dao_program, _realm_address, _dao_token, governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let instruction_data: InstructionData = instruction.clone().into(); + + let inst = dao_instruction::insert_instruction( + &dao_program, + &governance, + &proposal_address, + &token_owner, + wallet_address, + wallet_address, + 0, + instruction_index, + 0, + instruction_data, + ); + + Ok(inst) + } + + /// Creates a new instruction for removing the instruction from governance proposal + pub fn new_instruction_governance_instruction_remove( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let (dao_program, _realm_address, _dao_token, _governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let instruction_address = get_proposal_instruction_address( + &dao_program, + &proposal_address, + &0u16.to_le_bytes(), + &instruction_index.to_le_bytes(), + ); + + let inst = dao_instruction::remove_instruction( + &dao_program, + &proposal_address, + &token_owner, + wallet_address, + &instruction_address, + wallet_address, + ); + + Ok(inst) + } + + /// Creates a new instruction for executing the instruction in governance proposal + pub fn new_instruction_governance_instruction_execute( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let (dao_program, _realm_address, _dao_token, governance, _token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let instruction_address = get_proposal_instruction_address( + &dao_program, + &proposal_address, + &0u16.to_le_bytes(), + &instruction_index.to_le_bytes(), + ); + + let data = self.rpc_client.get_account_data(&instruction_address)?; + let ins_data: InstructionData = + try_from_slice_unchecked::(data.as_slice()) + .unwrap() + .instruction; + let instruction: Instruction = (&ins_data).into(); + + let inst = dao_instruction::execute_instruction( + &dao_program, + &governance, + &proposal_address, + &instruction_address, + &instruction.program_id, + instruction.accounts.as_slice(), + ); + + Ok(inst) + } + + /// Creates a new instruction for marking the instruction in governance proposal as failed + pub fn new_instruction_governance_instruction_flag_error( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + instruction_index: u16, + ) -> Result { + let (dao_program, _realm_address, _dao_token, _governance, token_owner, proposal_address) = + self.get_dao_accounts(wallet_address, governed_account_name, proposal_index)?; + + let instruction_address = get_proposal_instruction_address( + &dao_program, + &proposal_address, + &0u16.to_le_bytes(), + &instruction_index.to_le_bytes(), + ); + + let inst = dao_instruction::flag_instruction_error( + &dao_program, + &proposal_address, + &token_owner, + wallet_address, + &instruction_address, + ); + + Ok(inst) + } + + fn get_dao_accounts( + &self, + wallet_address: &Pubkey, + governed_account_name: &str, + proposal_index: u32, + ) -> Result<(Pubkey, Pubkey, Token, Pubkey, Pubkey, Pubkey), FarmClientError> { + let dao_program = self.get_program_id(DAO_PROGRAM_NAME)?; + let realm_address = get_realm_address(&dao_program, DAO_PROGRAM_NAME); + let dao_token = self.get_token(DAO_TOKEN_NAME)?; + let governance = if governed_account_name == DAO_MINT_NAME { + get_mint_governance_address(&dao_program, &realm_address, &dao_token.mint) + } else { + let governed_program = self.get_program_id(governed_account_name)?; + get_program_governance_address(&dao_program, &realm_address, &governed_program) + }; + let token_owner = get_token_owner_record_address( + &dao_program, + &realm_address, + &dao_token.mint, + wallet_address, + ); + let proposal_address = get_proposal_address( + &dao_program, + &governance, + &dao_token.mint, + &proposal_index.to_le_bytes(), + ); + Ok(( + dao_program, + realm_address, + dao_token, + governance, + token_owner, + proposal_address, + )) + } +} diff --git a/farms/farm-client/src/client/pool_accounts_orca.rs b/farms/farm-client/src/client/pool_accounts_orca.rs new file mode 100644 index 00000000000..5aa4928d036 --- /dev/null +++ b/farms/farm-client/src/client/pool_accounts_orca.rs @@ -0,0 +1,198 @@ +//! Solana Farm Client Orca Pools accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::pool::PoolRoute, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for adding liquidity to an Orca pool + pub fn get_add_liquidity_accounts_orca( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Orca { + amm_id, + amm_authority, + .. + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for removing liquidity from an Orca pool + pub fn get_remove_liquidity_accounts_orca( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Orca { + amm_id, + amm_authority, + fees_account, + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(fees_account, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for swapping tokens in an Orca pool + pub fn get_swap_accounts_orca( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Orca { + amm_id, + amm_authority, + fees_account, + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(fees_account, false)); + } + + Ok(accounts) + } +} diff --git a/farms/farm-client/src/client/pool_accounts_raydium.rs b/farms/farm-client/src/client/pool_accounts_raydium.rs new file mode 100644 index 00000000000..0bd409f01ca --- /dev/null +++ b/farms/farm-client/src/client/pool_accounts_raydium.rs @@ -0,0 +1,249 @@ +//! Solana Farm Client Raydium Pools accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::pool::PoolRoute, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for adding liquidity to a Raydium pool + pub fn get_add_liquidity_accounts_raydium( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue: _, + pool_temp_lp_token_account: _, + serum_program_id: _, + serum_market, + .. + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new_readonly(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new_readonly(serum_market, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for removing liquidity from a Raydium pool + pub fn get_remove_liquidity_accounts_raydium( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue, + pool_temp_lp_token_account, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + .. + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + + accounts.push(AccountMeta::new(pool_withdraw_queue, false)); + accounts.push(AccountMeta::new(pool_temp_lp_token_account, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new(serum_market, false)); + accounts.push(AccountMeta::new_readonly(serum_program_id, false)); + accounts.push(AccountMeta::new(serum_coin_vault_account, false)); + accounts.push(AccountMeta::new(serum_pc_vault_account, false)); + accounts.push(AccountMeta::new_readonly(serum_vault_signer, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for swapping tokens in a Raydium pool + pub fn get_swap_accounts_raydium( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue: _, + pool_temp_lp_token_account: _, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue, + } = pool.route + { + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new(serum_market, false)); + accounts.push(AccountMeta::new_readonly(serum_program_id, false)); + accounts.push(AccountMeta::new( + serum_bids.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_asks.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_event_queue.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(serum_coin_vault_account, false)); + accounts.push(AccountMeta::new(serum_pc_vault_account, false)); + accounts.push(AccountMeta::new_readonly(serum_vault_signer, false)); + } + + Ok(accounts) + } +} diff --git a/farms/farm-client/src/client/pool_accounts_saber.rs b/farms/farm-client/src/client/pool_accounts_saber.rs new file mode 100644 index 00000000000..511ec3f30f2 --- /dev/null +++ b/farms/farm-client/src/client/pool_accounts_saber.rs @@ -0,0 +1,319 @@ +//! Solana Farm Client Saber Pools accounts builder + +use { + crate::error::FarmClientError, + solana_farm_sdk::pool::PoolRoute, + solana_sdk::{instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey, sysvar}, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns instruction accounts for adding liquidity to a Saber pool + pub fn get_add_liquidity_accounts_saber( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Saber { + swap_account, + swap_authority, + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + let wrapped_token_a = self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let user_token_a_account = if wrapped_token_a.is_some() { + self.get_token_account(wallet_address, &wrapped_token_a) + } else { + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + self.get_token_account(wallet_address, &token_a) + }; + let wrapped_token_b = self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + let user_token_b_account = if wrapped_token_b.is_some() { + self.get_token_account(wallet_address, &wrapped_token_b) + } else { + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + self.get_token_account(wallet_address, &token_b) + }; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for removing liquidity from a Saber pool + pub fn get_remove_liquidity_accounts_saber( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Saber { + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + let wrapped_token_a = self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let user_token_a_account = if wrapped_token_a.is_some() { + self.get_token_account(wallet_address, &wrapped_token_a) + } else { + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + self.get_token_account(wallet_address, &token_a) + }; + let wrapped_token_b = self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + let user_token_b_account = if wrapped_token_b.is_some() { + self.get_token_account(wallet_address, &wrapped_token_b) + } else { + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + self.get_token_account(wallet_address, &token_b) + }; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + accounts.push(AccountMeta::new(fees_account_a, false)); + accounts.push(AccountMeta::new(fees_account_b, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for swapping tokens in a Saber pool + pub fn get_swap_accounts_saber( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Saber { + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + let wrapped_token_a = self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let user_token_a_account = if wrapped_token_a.is_some() { + self.get_token_account(wallet_address, &wrapped_token_a) + } else { + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + self.get_token_account(wallet_address, &token_a) + }; + let wrapped_token_b = self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + let user_token_b_account = if wrapped_token_b.is_some() { + self.get_token_account(wallet_address, &wrapped_token_b) + } else { + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + self.get_token_account(wallet_address, &token_b) + }; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + accounts.push(AccountMeta::new(fees_account_a, false)); + accounts.push(AccountMeta::new(fees_account_b, false)); + } + + Ok(accounts) + } + + /// Returns instruction accounts for wrapping token into a Saber decimal token + pub fn get_wrap_token_accounts_saber( + &self, + wallet_address: &Pubkey, + pool_name: &str, + wrap_token_a: bool, + ) -> Result, FarmClientError> { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get underlying token info + let token = if wrap_token_a { + self.get_token_by_ref_from_cache(&pool.token_a_ref)? + } else { + self.get_token_by_ref_from_cache(&pool.token_b_ref)? + }; + + // get user accounts info + let user_underlying_token_account = self.get_token_account(wallet_address, &token); + + // fill in accounts data + let mut accounts = vec![]; + if let PoolRoute::Saber { + swap_account: _, + swap_authority: _, + fees_account_a: _, + fees_account_b: _, + decimal_wrapper_program, + wrapped_token_a_ref, + wrapped_token_a_vault, + decimal_wrapper_token_a, + wrapped_token_b_ref, + wrapped_token_b_vault, + decimal_wrapper_token_b, + } = pool.route + { + let (user_wrapped_token_account, wrapped_token, wrapped_token_vault, decimal_wrapper) = + if wrap_token_a { + let wrapped_token_a = self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + ( + self.get_token_account(wallet_address, &wrapped_token_a), + wrapped_token_a, + wrapped_token_a_vault, + decimal_wrapper_token_a, + ) + } else { + let wrapped_token_b = self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + ( + self.get_token_account(wallet_address, &wrapped_token_b), + wrapped_token_b, + wrapped_token_b_vault, + decimal_wrapper_token_b, + ) + }; + + accounts.push(AccountMeta::new_readonly(*wallet_address, true)); + accounts.push(AccountMeta::new( + user_underlying_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly( + token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(decimal_wrapper_program, false)); + accounts.push(AccountMeta::new( + user_wrapped_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + wrapped_token + .ok_or(ProgramError::UninitializedAccount)? + .mint, + false, + )); + accounts.push(AccountMeta::new( + wrapped_token_vault.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly( + decimal_wrapper.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } + + Ok(accounts) + } +} diff --git a/farms/farm-client/src/client/pool_instructions.rs b/farms/farm-client/src/client/pool_instructions.rs new file mode 100644 index 00000000000..e35621bbcc1 --- /dev/null +++ b/farms/farm-client/src/client/pool_instructions.rs @@ -0,0 +1,309 @@ +//! Solana Farm Client Pool Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{instruction::amm::AmmInstruction, pool::PoolRoute, program::account}, + solana_sdk::{instruction::Instruction, program_error::ProgramError, pubkey::Pubkey}, +}; + +use super::FarmClient; + +impl FarmClient { + /// Creates a new Instruction for adding liquidity to the Pool. + /// If one of the token amounts is 0 and pool requires both tokens, + /// amount will be autocalculated based on the current pool price. + pub fn new_instruction_add_liquidity_pool( + &self, + wallet_address: &Pubkey, + pool_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + + // convert amounts if wrapped tokens are used + let mut max_token_a_amount = + self.to_token_amount_option(max_token_a_ui_amount, &token_a)?; + let mut max_token_b_amount = + self.to_token_amount_option(max_token_b_ui_amount, &token_b)?; + if let PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + if let Some(token_ref) = wrapped_token_a_ref { + let underlying_decimals = + token_a.ok_or(ProgramError::UninitializedAccount)?.decimals; + let wrapped_decimals = self.get_token_by_ref(&token_ref)?.decimals; + max_token_a_amount = account::to_amount_with_new_decimals( + max_token_a_amount, + underlying_decimals, + wrapped_decimals, + )?; + } + if let Some(token_ref) = wrapped_token_b_ref { + let underlying_decimals = + token_b.ok_or(ProgramError::UninitializedAccount)?.decimals; + let wrapped_decimals = self.get_token_by_ref(&token_ref)?.decimals; + max_token_b_amount = account::to_amount_with_new_decimals( + max_token_b_amount, + underlying_decimals, + wrapped_decimals, + )?; + } + } + + // fill in instruction data + let data = AmmInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } + .to_vec()?; + + let accounts = match pool.route { + PoolRoute::Raydium { .. } => { + self.get_add_liquidity_accounts_raydium(wallet_address, pool_name)? + } + PoolRoute::Saber { .. } => { + self.get_add_liquidity_accounts_saber(wallet_address, pool_name)? + } + PoolRoute::Orca { .. } => { + self.get_add_liquidity_accounts_orca(wallet_address, pool_name)? + } + }; + + Ok(Instruction { + program_id: pool.router_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for removing liquidity from the Pool + pub fn new_instruction_remove_liquidity_pool( + &self, + wallet_address: &Pubkey, + pool_name: &str, + ui_amount: f64, + ) -> Result { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + + // fill in instruction data + let data = AmmInstruction::RemoveLiquidity { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + + let accounts = match pool.route { + PoolRoute::Raydium { .. } => { + self.get_remove_liquidity_accounts_raydium(wallet_address, pool_name)? + } + PoolRoute::Saber { .. } => { + self.get_remove_liquidity_accounts_saber(wallet_address, pool_name)? + } + PoolRoute::Orca { .. } => { + self.get_remove_liquidity_accounts_orca(wallet_address, pool_name)? + } + }; + + Ok(Instruction { + program_id: pool.router_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for tokens swap + pub fn new_instruction_swap( + &self, + wallet_address: &Pubkey, + pool_code: &str, + from_token: &str, + to_token: &str, + ui_amount_in: f64, + min_ui_amount_out: f64, + ) -> Result { + // get pool to swap in + let pool = self.find_pools(pool_code, from_token, to_token)?[0]; + let reverse = FarmClient::pool_has_reverse_tokens(&pool.name, from_token)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + + // convert amounts if wrapped tokens are used + let mut max_amount_in = if reverse { + self.to_token_amount_option(ui_amount_in, &token_b)? + } else { + self.to_token_amount_option(ui_amount_in, &token_a)? + }; + let mut min_amount_out = if reverse { + self.to_token_amount_option(min_ui_amount_out, &token_a)? + } else { + self.to_token_amount_option(min_ui_amount_out, &token_b)? + }; + if let PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } = pool.route + { + if let Some(token_ref) = wrapped_token_a_ref { + let underlying_decimals = + token_a.ok_or(ProgramError::UninitializedAccount)?.decimals; + let wrapped_decimals = self.get_token_by_ref(&token_ref)?.decimals; + if reverse { + min_amount_out = account::to_amount_with_new_decimals( + min_amount_out, + underlying_decimals, + wrapped_decimals, + )?; + } else { + max_amount_in = account::to_amount_with_new_decimals( + max_amount_in, + underlying_decimals, + wrapped_decimals, + )?; + } + } + if let Some(token_ref) = wrapped_token_b_ref { + let underlying_decimals = + token_b.ok_or(ProgramError::UninitializedAccount)?.decimals; + let wrapped_decimals = self.get_token_by_ref(&token_ref)?.decimals; + if reverse { + max_amount_in = account::to_amount_with_new_decimals( + max_amount_in, + underlying_decimals, + wrapped_decimals, + )?; + } else { + min_amount_out = account::to_amount_with_new_decimals( + min_amount_out, + underlying_decimals, + wrapped_decimals, + )?; + } + } + } + + // fill in accounts and instruction data + let data = if reverse { + AmmInstruction::Swap { + token_a_amount_in: 0, + token_b_amount_in: max_amount_in, + min_token_amount_out: min_amount_out, + } + } else { + AmmInstruction::Swap { + token_a_amount_in: max_amount_in, + token_b_amount_in: 0, + min_token_amount_out: min_amount_out, + } + } + .to_vec()?; + + let accounts = match pool.route { + PoolRoute::Raydium { .. } => { + self.get_swap_accounts_raydium(wallet_address, &pool.name)? + } + PoolRoute::Saber { .. } => self.get_swap_accounts_saber(wallet_address, &pool.name)?, + PoolRoute::Orca { .. } => self.get_swap_accounts_orca(wallet_address, &pool.name)?, + }; + + Ok(Instruction { + program_id: pool.router_program_id, + data, + accounts, + }) + } + + pub fn new_instruction_wrap_token( + &self, + wallet_address: &Pubkey, + pool_name: &str, + wrap_token_a: bool, + ui_amount: f64, + ) -> Result { + // get pool info + let pool = self.get_pool(pool_name)?; + + // get underlying token info + let token = if wrap_token_a { + self.get_token_by_ref_from_cache(&pool.token_a_ref)? + } else { + self.get_token_by_ref_from_cache(&pool.token_b_ref)? + }; + + // fill in instruction data + let data = AmmInstruction::WrapToken { + amount: self.to_token_amount_option(ui_amount, &token)?, + } + .to_vec()?; + + let accounts = match pool.route { + PoolRoute::Saber { .. } => { + self.get_wrap_token_accounts_saber(wallet_address, pool_name, wrap_token_a)? + } + _ => { + panic!("WrapToken instruction is not supported for this route type"); + } + }; + + Ok(Instruction { + program_id: pool.router_program_id, + data, + accounts, + }) + } + + pub fn new_instruction_unwrap_token( + &self, + wallet_address: &Pubkey, + pool_name: &str, + unwrap_token_a: bool, + ui_amount: f64, + ) -> Result { + // get pool info + let pool = self.get_pool(pool_name)?; + + let (accounts, decimals) = match pool.route { + PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } => { + let token = if unwrap_token_a { + self.get_token_by_ref_from_cache(&wrapped_token_a_ref)? + } else { + self.get_token_by_ref_from_cache(&wrapped_token_b_ref)? + }; + ( + self.get_wrap_token_accounts_saber(wallet_address, pool_name, unwrap_token_a)?, + token.ok_or(ProgramError::UninitializedAccount)?.decimals, + ) + } + _ => { + panic!("UnwrapToken instruction is not supported for this route type"); + } + }; + + Ok(Instruction { + program_id: pool.router_program_id, + data: AmmInstruction::UnwrapToken { + amount: self.ui_amount_to_tokens_with_decimals(ui_amount, decimals), + } + .to_vec()?, + accounts, + }) + } +} diff --git a/farms/farm-client/src/client/refdb_instructions.rs b/farms/farm-client/src/client/refdb_instructions.rs new file mode 100644 index 00000000000..2cf8898c865 --- /dev/null +++ b/farms/farm-client/src/client/refdb_instructions.rs @@ -0,0 +1,401 @@ +//! Solana Farm Client RefDB Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{ + farm::Farm, + id::{main_router, ProgramIDType}, + instruction::{main_router::MainInstruction, refdb::RefDbInstruction}, + pool::Pool, + program::pda::{find_refdb_pda, find_target_pda}, + refdb, + string::str_to_as64, + token::Token, + vault::Vault, + }, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, + }, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Creates a new instruction for writing the record into on-chain RefDB + fn new_instruction_refdb_write( + &self, + admin_address: &Pubkey, + refdb_name: &str, + record: refdb::Record, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new(find_refdb_pda(refdb_name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RefDbInstruction { + instruction: RefDbInstruction::Write { record }, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new instruction for deleteing the record from on-chain RefDB + fn new_instruction_refdb_delete( + &self, + admin_address: &Pubkey, + refdb_name: &str, + record: refdb::Record, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new(find_refdb_pda(refdb_name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RefDbInstruction { + instruction: RefDbInstruction::Delete { record }, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new instruction for initializing on-chain RefDB storage + pub fn new_instruction_refdb_init( + &self, + admin_address: &Pubkey, + refdb_name: &str, + reference_type: refdb::ReferenceType, + max_records: u32, + init_account: bool, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new(find_refdb_pda(refdb_name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RefDbInstruction { + instruction: RefDbInstruction::Init { + name: str_to_as64(refdb_name)?, + reference_type, + max_records, + init_account: init_account && refdb::REFDB_ONCHAIN_INIT, + }, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new instruction for removing on-chain RefDB storage + pub fn new_instruction_refdb_drop( + &self, + admin_address: &Pubkey, + refdb_name: &str, + close_account: bool, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new(find_refdb_pda(refdb_name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RefDbInstruction { + instruction: RefDbInstruction::Drop { close_account }, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for recording the Program ID metadata on-chain + pub fn new_instruction_add_program_id( + &self, + admin_address: &Pubkey, + name: &str, + program_id: &Pubkey, + program_id_type: ProgramIDType, + refdb_index: Option, + ) -> Result { + self.new_instruction_refdb_write( + admin_address, + &refdb::StorageType::Program.to_string(), + refdb::Record { + index: refdb_index, + counter: 0, + tag: program_id_type as u16, + name: str_to_as64(name)?, + reference: refdb::Reference::Pubkey { data: *program_id }, + }, + ) + } + + /// Creates a new Instruction for removing the Program ID metadata from chain + pub fn new_instruction_remove_program_id( + &self, + admin_address: &Pubkey, + name: &str, + ) -> Result { + self.new_instruction_refdb_delete( + admin_address, + &refdb::StorageType::Program.to_string(), + refdb::Record { + index: None, + counter: 0, + tag: 0, + name: str_to_as64(name)?, + reference: refdb::Reference::Empty, + }, + ) + } + + /// Creates a new Instruction for recording Vault's metadata on-chain + pub fn new_instruction_add_vault( + &self, + admin_address: &Pubkey, + vault: Vault, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Vault.to_string()).0, + false, + ), + AccountMeta::new( + find_target_pda(refdb::StorageType::Vault, &vault.name).0, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + inst.data = MainInstruction::AddVault { vault }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for removing Vault's on-chain metadata + pub fn new_instruction_remove_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // fill in accounts and instruction data + let name = str_to_as64(vault_name)?; + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Vault.to_string()).0, + false, + ), + AccountMeta::new(find_target_pda(refdb::StorageType::Vault, &name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RemoveVault { name }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for recording Pool's metadata on-chain + pub fn new_instruction_add_pool( + &self, + admin_address: &Pubkey, + pool: Pool, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Pool.to_string()).0, + false, + ), + AccountMeta::new( + find_target_pda(refdb::StorageType::Pool, &pool.name).0, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::AddPool { pool }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for removing Pool's on-chain metadata + pub fn new_instruction_remove_pool( + &self, + admin_address: &Pubkey, + pool_name: &str, + ) -> Result { + // fill in accounts and instruction data + let name = str_to_as64(pool_name)?; + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Pool.to_string()).0, + false, + ), + AccountMeta::new(find_target_pda(refdb::StorageType::Pool, &name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RemovePool { name }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for recording Farm's metadata on-chain + pub fn new_instruction_add_farm( + &self, + admin_address: &Pubkey, + farm: Farm, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Farm.to_string()).0, + false, + ), + AccountMeta::new( + find_target_pda(refdb::StorageType::Farm, &farm.name).0, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::AddFarm { farm }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for removing Farm's on-chain metadata + pub fn new_instruction_remove_farm( + &self, + admin_address: &Pubkey, + farm_name: &str, + ) -> Result { + // fill in accounts and instruction data + let name = str_to_as64(farm_name)?; + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Farm.to_string()).0, + false, + ), + AccountMeta::new(find_target_pda(refdb::StorageType::Farm, &name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RemoveFarm { name }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for recording Token's metadata on-chain + pub fn new_instruction_add_token( + &self, + admin_address: &Pubkey, + token: Token, + ) -> Result { + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Token.to_string()).0, + false, + ), + AccountMeta::new( + find_target_pda(refdb::StorageType::Token, &token.name).0, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::AddToken { token }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for removing Token's on-chain metadata + pub fn new_instruction_remove_token( + &self, + admin_address: &Pubkey, + token_name: &str, + ) -> Result { + // fill in accounts and instruction data + let name = str_to_as64(token_name)?; + let mut inst = Instruction { + program_id: main_router::id(), + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new( + find_refdb_pda(&refdb::StorageType::Token.to_string()).0, + false, + ), + AccountMeta::new(find_target_pda(refdb::StorageType::Token, &name).0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + }; + + inst.data = MainInstruction::RemoveToken { name }.to_vec()?; + + Ok(inst) + } +} diff --git a/farms/farm-client/src/client/system_instructions.rs b/farms/farm-client/src/client/system_instructions.rs new file mode 100644 index 00000000000..a3f2cd9e93e --- /dev/null +++ b/farms/farm-client/src/client/system_instructions.rs @@ -0,0 +1,164 @@ +//! Solana Farm Client System Instructions + +use { + crate::error::FarmClientError, + solana_sdk::{instruction::Instruction, pubkey::Pubkey, system_instruction}, + spl_associated_token_account::create_associated_token_account, + spl_token::instruction as spl_token_instruction, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns a new Instruction for creating system account + pub fn new_instruction_create_system_account( + &self, + wallet_address: &Pubkey, + new_account_address: &Pubkey, + lamports: u64, + space: usize, + owner: &Pubkey, + ) -> Result { + let lamports = if lamports == 0 { + self.rpc_client + .get_minimum_balance_for_rent_exemption(space)? + } else { + lamports + }; + Ok(system_instruction::create_account( + wallet_address, + new_account_address, + lamports, + space as u64, + owner, + )) + } + + /// Returns a new Instruction for closing system account + pub fn new_instruction_close_system_account( + &self, + wallet_address: &Pubkey, + target_account_address: &Pubkey, + ) -> Result { + self.new_instruction_transfer( + target_account_address, + wallet_address, + self.get_account_balance(wallet_address)?, + ) + } + + /// Returns a new Instruction for creating system account with seed + pub fn new_instruction_create_system_account_with_seed( + &self, + wallet_address: &Pubkey, + base_address: &Pubkey, + seed: &str, + lamports: u64, + space: usize, + owner: &Pubkey, + ) -> Result { + let lamports = if lamports == 0 { + self.rpc_client + .get_minimum_balance_for_rent_exemption(space)? + } else { + lamports + }; + let to_pubkey = Pubkey::create_with_seed(base_address, seed, owner)?; + Ok(system_instruction::create_account_with_seed( + wallet_address, + &to_pubkey, + base_address, + seed, + lamports, + space as u64, + owner, + )) + } + + /// Returns a new Instruction for assigning system account to a program + pub fn new_instruction_assign_system_account( + &self, + wallet_address: &Pubkey, + program_address: &Pubkey, + ) -> Result { + Ok(system_instruction::assign(wallet_address, program_address)) + } + + /// Creates the native SOL transfer instruction + pub fn new_instruction_transfer( + &self, + wallet_address: &Pubkey, + destination_wallet: &Pubkey, + sol_ui_amount: f64, + ) -> Result { + Ok(system_instruction::transfer( + wallet_address, + destination_wallet, + self.ui_amount_to_tokens_with_decimals(sol_ui_amount, spl_token::native_mint::DECIMALS), + )) + } + + /// Creates a tokens transfer instruction + pub fn new_instruction_token_transfer( + &self, + wallet_address: &Pubkey, + token_name: &str, + destination_wallet: &Pubkey, + ui_amount: f64, + ) -> Result { + let token_addr = self.get_associated_token_address(wallet_address, token_name)?; + let destination_address = + self.get_associated_token_address(destination_wallet, token_name)?; + Ok(spl_token_instruction::transfer( + &spl_token::id(), + &token_addr, + &destination_address, + wallet_address, + &[], + self.ui_amount_to_tokens(ui_amount, token_name)?, + )?) + } + + /// Creates a new Instruction for syncing token balance for the specified account + pub fn new_instruction_sync_token_balance( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token_addr = self.get_associated_token_address(wallet_address, token_name)?; + Ok(spl_token_instruction::sync_native( + &spl_token::id(), + &token_addr, + )?) + } + + /// Returns a new Instruction for creating associated token account + pub fn new_instruction_create_token_account( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token = self.get_token(token_name)?; + Ok(create_associated_token_account( + wallet_address, + wallet_address, + &token.mint, + )) + } + + /// Returns a new Instruction for closing associated token account + pub fn new_instruction_close_token_account( + &self, + wallet_address: &Pubkey, + token_name: &str, + ) -> Result { + let token_addr = self.get_associated_token_address(wallet_address, token_name)?; + Ok(spl_token_instruction::close_account( + &spl_token::id(), + &token_addr, + wallet_address, + wallet_address, + &[], + )?) + } +} diff --git a/farms/farm-client/src/client/vault_instructions.rs b/farms/farm-client/src/client/vault_instructions.rs new file mode 100644 index 00000000000..0dd74a5b513 --- /dev/null +++ b/farms/farm-client/src/client/vault_instructions.rs @@ -0,0 +1,510 @@ +//! Solana Farm Client Vault Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{ + instruction::vault::VaultInstruction, pool::PoolRoute, vault::VaultStrategy, + }, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + }, +}; + +use super::FarmClient; + +impl FarmClient { + /// Creates a new Instruction for initializing a new User for the Vault + pub fn new_instruction_user_init_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => { + self.get_stc_user_init_accounts_raydium(wallet_address, vault_name) + } + PoolRoute::Saber { .. } => { + self.get_stc_user_init_accounts_saber(wallet_address, vault_name) + } + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for adding liquidity to the Vault + pub fn new_instruction_add_liquidity_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => self.get_stc_add_liquidity_accounts_raydium( + wallet_address, + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ), + PoolRoute::Saber { .. } => self.get_stc_add_liquidity_accounts_saber( + wallet_address, + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ), + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for locking liquidity in the Vault + pub fn new_instruction_lock_liquidity_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => Err(FarmClientError::ValueError(format!( + "LockLiquidity is not supported by Vault {}", + vault_name + ))), + PoolRoute::Saber { .. } => self.get_stc_lock_liquidity_accounts_saber( + wallet_address, + vault_name, + ui_amount, + ), + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for unlocking liquidity in the Vault + pub fn new_instruction_unlock_liquidity_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => self.get_stc_unlock_liquidity_accounts_raydium( + wallet_address, + vault_name, + ui_amount, + ), + PoolRoute::Saber { .. } => Err(FarmClientError::ValueError(format!( + "LockLiquidity is not supported by Vault {}", + vault_name + ))), + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for removing liquidity from the Vault + pub fn new_instruction_remove_liquidity_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => self.get_stc_remove_liquidity_accounts_raydium( + wallet_address, + vault_name, + ui_amount, + ), + PoolRoute::Saber { .. } => self.get_stc_remove_liquidity_accounts_saber( + wallet_address, + vault_name, + ui_amount, + ), + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Vault Init Instruction + pub fn new_instruction_init_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => { + self.get_stc_init_accounts_raydium(admin_address, vault_name, step) + } + PoolRoute::Saber { .. } => { + self.get_stc_init_accounts_saber(admin_address, vault_name, step) + } + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Vault Shutdown Instruction + pub fn new_instruction_shutdown_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => { + self.get_stc_shutdown_accounts_raydium(admin_address, vault_name) + } + PoolRoute::Saber { .. } => { + self.get_stc_shutdown_accounts_saber(admin_address, vault_name) + } + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Vault Crank Instruction + pub fn new_instruction_crank_vault( + &self, + wallet_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + + // fill in accounts and instruction data + let (accounts, data) = match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { pool_id_ref, .. } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + match pool.route { + PoolRoute::Raydium { .. } => { + self.get_stc_crank_accounts_raydium(wallet_address, vault_name, step) + } + PoolRoute::Saber { .. } => { + self.get_stc_crank_accounts_saber(wallet_address, vault_name, step) + } + _ => unreachable!(), + } + } + _ => { + unreachable!() + } + }?; + + Ok(Instruction { + program_id: vault.vault_program_id, + data, + accounts, + }) + } + + /// Creates a new Instruction for updating the Vault's min crank interval + pub fn new_instruction_set_min_crank_interval_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + min_crank_interval: u32, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::SetMinCrankInterval { min_crank_interval }.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for updating the Vault's fee + pub fn new_instruction_set_fee_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + fee_percent: f32, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::SetFee { + fee: fee_percent * 0.01, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for updating the Vault's external fee + pub fn new_instruction_set_external_fee_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + external_fee_percent: f32, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::SetExternalFee { + external_fee: external_fee_percent * 0.01, + } + .to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for disabling deposits to the Vault + pub fn new_instruction_disable_deposit_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::DisableDeposit.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for enabling deposits to the Vault + pub fn new_instruction_enable_deposit_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::EnableDeposit.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for disabling withdrawals from the Vault + pub fn new_instruction_disable_withdrawal_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::DisableWithdrawal.to_vec()?; + + Ok(inst) + } + + /// Creates a new Instruction for enabling withdrawals from the Vault + pub fn new_instruction_enable_withdrawal_vault( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let mut inst = Instruction { + program_id: vault.vault_program_id, + data: Vec::::new(), + accounts: vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ], + }; + + inst.data = VaultInstruction::EnableWithdrawal.to_vec()?; + + Ok(inst) + } +} diff --git a/farms/farm-client/src/client/vault_stc_accounts_raydium.rs b/farms/farm-client/src/client/vault_stc_accounts_raydium.rs new file mode 100644 index 00000000000..daf0647a0ea --- /dev/null +++ b/farms/farm-client/src/client/vault_stc_accounts_raydium.rs @@ -0,0 +1,721 @@ +//! Solana Farm Client Vault Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{ + farm::FarmRoute, id::zero, instruction::vault::VaultInstruction, pool::PoolRoute, + vault::VaultStrategy, + }, + solana_sdk::{ + instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey, system_program, + sysvar, + }, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns accounts and data for initializing a new User for the Vault + pub fn get_stc_user_init_accounts_raydium( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + // fill in accounts and instruction data + let data = VaultInstruction::UserInit.to_vec()?; + let accounts = vec![ + AccountMeta::new_readonly(*wallet_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ]; + Ok((accounts, data)) + } + + /// Returns accounts and data for adding liquidity to the Vault + pub fn get_stc_add_liquidity_accounts_raydium( + &self, + wallet_address: &Pubkey, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + + // fill in accounts and instruction data + let data; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(vault_token.unwrap().mint, false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody: _, + token_b_custody: _, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + let user_vt_token_account = self.get_token_account(wallet_address, &vault_token); + + // fill in pool related accounts + match pool.route { + PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue: _, + pool_temp_lp_token_account: _, + serum_program_id: _, + serum_market, + .. + } => { + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_vt_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new_readonly(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new_readonly(serum_market, false)); + } + _ => { + unreachable!(); + } + } + + // fill in farm related accounts + match farm.route { + FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } => { + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + + accounts.push(AccountMeta::new(farm_lp_token_account, false)); + accounts.push(AccountMeta::new(farm_reward_token_a_account, false)); + accounts.push(AccountMeta::new( + farm_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + } + _ => { + unreachable!(); + } + } + + data = VaultInstruction::AddLiquidity { + max_token_a_amount: self + .to_token_amount_option(max_token_a_ui_amount, &token_a)?, + max_token_b_amount: self + .to_token_amount_option(max_token_b_ui_amount, &token_b)?, + } + .to_vec()?; + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for unlocking liquidity in the Vault + pub fn get_stc_unlock_liquidity_accounts_raydium( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + + // fill in accounts and instruction data + let data = VaultInstruction::UnlockLiquidity { + amount: self.to_token_amount_option(ui_amount, &vault_token)?, + } + .to_vec()?; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(vault_token.unwrap().mint, false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody: _, + token_b_custody: _, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + // get user accounts info + let user_vt_token_account = self.get_token_account(wallet_address, &vault_token); + + accounts.push(AccountMeta::new( + user_vt_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + + // fill in farm related accounts + match farm.route { + FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } => { + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + accounts.push(AccountMeta::new(farm_lp_token_account, false)); + accounts.push(AccountMeta::new(farm_reward_token_a_account, false)); + accounts.push(AccountMeta::new( + farm_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + } + _ => { + unreachable!(); + } + } + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for removing liquidity from the Vault + pub fn get_stc_remove_liquidity_accounts_raydium( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + // fill in accounts and instruction data + let data; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + .. + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + // get user accounts info + let user_token_a_account = self.get_token_account(wallet_address, &token_a); + let user_token_b_account = self.get_token_account(wallet_address, &token_b); + + // fill in pool related accounts + match pool.route { + PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue, + pool_temp_lp_token_account, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + .. + } => { + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(token_a_custody, false)); + accounts.push(AccountMeta::new( + token_b_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new(pool_withdraw_queue, false)); + accounts.push(AccountMeta::new(pool_temp_lp_token_account, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new(serum_market, false)); + accounts.push(AccountMeta::new_readonly(serum_program_id, false)); + accounts.push(AccountMeta::new(serum_coin_vault_account, false)); + accounts.push(AccountMeta::new(serum_pc_vault_account, false)); + accounts.push(AccountMeta::new_readonly(serum_vault_signer, false)); + } + _ => { + unreachable!(); + } + } + + data = VaultInstruction::RemoveLiquidity { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Init Instruction + pub fn get_stc_init_accounts_raydium( + &self, + admin_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self + .get_token_by_ref_from_cache(&Some(vault.vault_token_ref))? + .unwrap(); + + // fill in accounts and instruction data + let data = VaultInstruction::Init { step }.to_vec()?; + let mut accounts = vec![AccountMeta::new_readonly(*admin_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_program_id, false)); + accounts.push(AccountMeta::new_readonly(system_program::id(), false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false)); + + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + // get pools + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + // get tokens info + let token_a = self + .get_token_by_ref_from_cache(&pool.token_a_ref)? + .unwrap(); + let token_b = self + .get_token_by_ref_from_cache(&pool.token_b_ref)? + .unwrap(); + let lp_token = self + .get_token_by_ref_from_cache(&pool.lp_token_ref)? + .unwrap(); + let token_a_reward = self + .get_token_by_ref_from_cache(&farm.reward_token_a_ref)? + .unwrap(); + let token_b_reward = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_token.mint, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_token_ref, false)); + if farm.version >= 4 { + accounts.push(AccountMeta::new(zero::id(), false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + } else { + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(zero::id(), false)); + } + accounts.push(AccountMeta::new( + vault.fees_account_a.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new( + vault.fees_account_b.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(token_a_custody, false)); + accounts.push(AccountMeta::new( + token_b_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new(token_a.mint, false)); + accounts.push(AccountMeta::new(token_b.mint, false)); + accounts.push(AccountMeta::new(lp_token.mint, false)); + + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(token_a_reward.mint, false)); + if let Some(token) = token_b_reward { + accounts.push(AccountMeta::new(token.mint, false)); + } else { + accounts.push(AccountMeta::new(zero::id(), false)); + } + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Shutdown Instruction + pub fn get_stc_shutdown_accounts_raydium( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let data = VaultInstruction::Shutdown.to_vec()?; + let accounts = vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ]; + + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Crank Instruction + pub fn get_stc_crank_accounts_raydium( + &self, + wallet_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let data = VaultInstruction::Crank { step }.to_vec()?; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + if step != 2 { + accounts.push(AccountMeta::new(lp_token_custody, false)); + } + if step == 1 { + accounts.push(AccountMeta::new( + vault + .fees_account_a + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + vault.fees_account_b.or_else(|| Some(zero::id())).unwrap(), + false, + )); + } + + if step == 2 || step == 3 { + match pool.route { + PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue: _, + pool_temp_lp_token_account: _, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue, + } => { + accounts.push(AccountMeta::new(token_a_custody, false)); + accounts.push(AccountMeta::new( + token_b_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + if step == 3 { + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + } + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new(serum_market, false)); + + if step == 2 { + accounts.push(AccountMeta::new_readonly(serum_program_id, false)); + accounts.push(AccountMeta::new(serum_coin_vault_account, false)); + accounts.push(AccountMeta::new(serum_pc_vault_account, false)); + accounts.push(AccountMeta::new(serum_vault_signer, false)); + accounts.push(AccountMeta::new( + serum_bids.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_asks.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_event_queue.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } + } + _ => { + unreachable!(); + } + } + } + + // fill in farm related accounts + if step == 1 || step == 3 { + match farm.route { + FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } => { + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(farm_id, false)); + accounts.push(AccountMeta::new_readonly(farm_authority, false)); + + accounts.push(AccountMeta::new(farm_lp_token_account, false)); + accounts.push(AccountMeta::new(farm_reward_token_a_account, false)); + accounts.push(AccountMeta::new( + farm_reward_token_b_account + .or_else(|| Some(zero::id())) + .unwrap(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + } + _ => { + unreachable!(); + } + } + } + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + + Ok((accounts, data)) + } +} diff --git a/farms/farm-client/src/client/vault_stc_accounts_saber.rs b/farms/farm-client/src/client/vault_stc_accounts_saber.rs new file mode 100644 index 00000000000..e1d147b7453 --- /dev/null +++ b/farms/farm-client/src/client/vault_stc_accounts_saber.rs @@ -0,0 +1,912 @@ +//! Solana Farm Client Vault Instructions + +use { + crate::error::FarmClientError, + solana_farm_sdk::{ + farm::FarmRoute, id::zero, instruction::vault::VaultInstruction, pool::PoolRoute, + vault::VaultStrategy, + }, + solana_sdk::{ + instruction::AccountMeta, program_error::ProgramError, pubkey::Pubkey, system_program, + sysvar, + }, + std::vec::Vec, +}; + +use super::FarmClient; + +impl FarmClient { + /// Returns accounts and data for initializing a new User for the Vault + pub fn get_stc_user_init_accounts_saber( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + // fill in accounts and instruction data + let data = VaultInstruction::UserInit.to_vec()?; + let accounts = vec![ + AccountMeta::new_readonly(*wallet_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ]; + Ok((accounts, data)) + } + + /// Returns accounts and data for adding liquidity to the Vault + pub fn get_stc_add_liquidity_accounts_saber( + &self, + wallet_address: &Pubkey, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let data; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + .. + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + // get user accounts info + let user_lp_token_account = self.get_token_account(wallet_address, &lp_token); + // fill in pool related accounts + match pool.route { + PoolRoute::Saber { + swap_account, + swap_authority, + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } => { + let wrapped_token_a = + self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let user_token_a_account = if wrapped_token_a.is_some() { + self.get_token_account(wallet_address, &wrapped_token_a) + } else { + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + self.get_token_account(wallet_address, &token_a) + }; + let wrapped_token_b = + self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + let user_token_b_account = if wrapped_token_b.is_some() { + self.get_token_account(wallet_address, &wrapped_token_b) + } else { + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + self.get_token_account(wallet_address, &token_b) + }; + + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_lp_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + } + _ => { + unreachable!(); + } + } + + data = VaultInstruction::AddLiquidity { + max_token_a_amount: self + .to_token_amount_option(max_token_a_ui_amount, &token_a)?, + max_token_b_amount: self + .to_token_amount_option(max_token_b_ui_amount, &token_b)?, + } + .to_vec()?; + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for locking liquidity in the Vault + pub fn get_stc_lock_liquidity_accounts_saber( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + + // fill in accounts and instruction data + let data; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(vault_token.unwrap().mint, false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody: _, + token_b_custody: _, + token_a_reward_custody: _, + token_b_reward_custody: _, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + let user_vt_token_account = self.get_token_account(wallet_address, &vault_token); + + // fill in farm related accounts + match farm.route { + FarmRoute::Saber { + quarry, rewarder, .. + } => { + let vault_miner_account = self + .get_token_account(&vault_stake_info, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new( + user_vt_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(vault_miner_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + } + _ => { + unreachable!(); + } + } + + data = VaultInstruction::LockLiquidity { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for removing liquidity from the Vault + pub fn get_stc_remove_liquidity_accounts_saber( + &self, + wallet_address: &Pubkey, + vault_name: &str, + ui_amount: f64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self.get_token_by_ref_from_cache(&Some(vault.vault_token_ref))?; + + // fill in accounts and instruction data + let data; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new(vault_token.unwrap().mint, false)); + accounts.push(AccountMeta::new( + self.get_vault_user_info_account(wallet_address, vault_name)?, + false, + )); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody: _, + token_b_custody: _, + token_a_reward_custody: _, + token_b_reward_custody: _, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + + // get user accounts info + let user_vt_token_account = self.get_token_account(wallet_address, &vault_token); + + // fill in pool related accounts + match pool.route { + PoolRoute::Saber { + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } => { + let wrapped_token_a = + self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let user_token_a_account = if wrapped_token_a.is_some() { + self.get_token_account(wallet_address, &wrapped_token_a) + } else { + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + self.get_token_account(wallet_address, &token_a) + }; + let wrapped_token_b = + self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + let user_token_b_account = if wrapped_token_b.is_some() { + self.get_token_account(wallet_address, &wrapped_token_b) + } else { + let token_b = self.get_token_by_ref_from_cache(&pool.token_b_ref)?; + self.get_token_account(wallet_address, &token_b) + }; + + accounts.push(AccountMeta::new( + user_token_a_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_token_b_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + user_vt_token_account.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + accounts.push(AccountMeta::new(fees_account_a, false)); + accounts.push(AccountMeta::new(fees_account_b, false)); + } + _ => { + unreachable!(); + } + } + + // fill in farm related accounts + match farm.route { + FarmRoute::Saber { + quarry, rewarder, .. + } => { + let vault_miner_account = self + .get_token_account(&vault_stake_info, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(vault_miner_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + } + _ => { + unreachable!(); + } + } + + data = VaultInstruction::RemoveLiquidity { + amount: self.to_token_amount_option(ui_amount, &lp_token)?, + } + .to_vec()?; + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Init Instruction + pub fn get_stc_init_accounts_saber( + &self, + admin_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + let vault_token = self + .get_token_by_ref_from_cache(&Some(vault.vault_token_ref))? + .unwrap(); + + // fill in accounts and instruction data + let data = VaultInstruction::Init { step }.to_vec()?; + let mut accounts = vec![AccountMeta::new(*admin_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_program_id, false)); + accounts.push(AccountMeta::new_readonly(system_program::id(), false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + accounts.push(AccountMeta::new_readonly( + spl_associated_token_account::id(), + false, + )); + accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false)); + + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + // get pools + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + // get tokens info + let token_a = self + .get_token_by_ref_from_cache(&pool.token_a_ref)? + .unwrap(); + let token_b = self + .get_token_by_ref_from_cache(&pool.token_b_ref)? + .unwrap(); + let usdc_token = self.get_token("USDC")?; + let token_a_usdc = if token_a.mint == usdc_token.mint { + true + } else if token_b.mint == usdc_token.mint { + false + } else { + return Err(FarmClientError::ValueError( + "Only USDC pools are supported".to_string(), + )); + }; + let lp_token = self + .get_token_by_ref_from_cache(&pool.lp_token_ref)? + .unwrap(); + let token_a_reward = self + .get_token_by_ref_from_cache(&farm.reward_token_a_ref)? + .unwrap(); + let token_b_reward = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let wrapped_token_mint = match pool.route { + PoolRoute::Saber { + wrapped_token_a_ref, + wrapped_token_b_ref, + .. + } => { + let wrapped_token_a = + self.get_token_by_ref_from_cache(&wrapped_token_a_ref)?; + let wrapped_token_b = + self.get_token_by_ref_from_cache(&wrapped_token_b_ref)?; + if wrapped_token_a.is_some() && wrapped_token_b.is_some() { + // no such pools + unreachable!(); + } else if wrapped_token_a.is_some() { + wrapped_token_a.unwrap().mint + } else if let Some(token) = wrapped_token_b { + token.mint + } else { + zero::id() + } + } + _ => { + unreachable!() + } + }; + + let vault_miner_account = self + .get_token_account(&vault_stake_info, &Some(lp_token)) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_token.mint, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_token_ref, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(vault_miner_account, false)); + accounts.push(AccountMeta::new( + vault.fees_account_a.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new( + vault.fees_account_b.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(token_a_custody, false)); + accounts.push(AccountMeta::new( + token_b_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new(usdc_token.mint, false)); + if token_a_usdc { + accounts.push(AccountMeta::new(token_b.mint, false)); + } else { + accounts.push(AccountMeta::new(token_a.mint, false)); + } + accounts.push(AccountMeta::new(wrapped_token_mint, false)); + accounts.push(AccountMeta::new(lp_token.mint, false)); + + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + accounts.push(AccountMeta::new(token_a_reward.mint, false)); + if let Some(token) = token_b_reward { + accounts.push(AccountMeta::new(token.mint, false)); + } else { + accounts.push(AccountMeta::new(zero::id(), false)); + } + // fill in farm related accounts + match farm.route { + FarmRoute::Saber { + quarry, rewarder, .. + } => { + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new(rewarder, false)); + } + _ => { + unreachable!(); + } + } + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Shutdown Instruction + pub fn get_stc_shutdown_accounts_saber( + &self, + admin_address: &Pubkey, + vault_name: &str, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let data = VaultInstruction::Shutdown.to_vec()?; + let accounts = vec![ + AccountMeta::new_readonly(*admin_address, true), + AccountMeta::new_readonly(vault_ref, false), + AccountMeta::new(vault.info_account, false), + ]; + + Ok((accounts, data)) + } + + /// Returns accounts and data for a Vault Crank Instruction + pub fn get_stc_crank_accounts_saber( + &self, + wallet_address: &Pubkey, + vault_name: &str, + step: u64, + ) -> Result<(Vec, Vec), FarmClientError> { + // get vault info + let vault = self.get_vault(vault_name)?; + let vault_ref = self.get_vault_ref(vault_name)?; + + // fill in accounts and instruction data + let data = VaultInstruction::Crank { step }.to_vec()?; + let mut accounts = vec![AccountMeta::new_readonly(*wallet_address, true)]; + + // general accounts + accounts.push(AccountMeta::new_readonly(vault_ref, false)); + accounts.push(AccountMeta::new(vault.info_account, false)); + accounts.push(AccountMeta::new_readonly(vault.vault_authority, false)); + accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); + + // strategy related accounts + match vault.strategy { + VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } => { + let pool = self.get_pool_by_ref(&pool_id_ref)?; + let farm = self.get_farm_by_ref(&farm_id_ref)?; + + // get tokens info + let sbr_token = self.get_token_by_ref_from_cache(&farm.reward_token_a_ref)?; + let iou_token = self.get_token_by_ref_from_cache(&farm.reward_token_b_ref)?; + let token_a = self.get_token_by_ref_from_cache(&pool.token_a_ref)?; + let lp_token = self.get_token_by_ref_from_cache(&pool.lp_token_ref)?; + let farm_token = self.get_token_by_ref_from_cache(&farm.lp_token_ref)?; + assert_eq!(farm_token, lp_token); + let (is_token_a_wrapped, is_token_b_wrapped) = + self.pool_has_saber_wrapped_tokens(&pool.name)?; + let usdc_mint = self.get_token("USDC")?.mint; + + match farm.route { + FarmRoute::Saber { + quarry, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + iou_fees_account, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info, + } => match step { + 1 => { + accounts.push(AccountMeta::new( + token_b_reward_custody.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(mint_wrapper, false)); + accounts.push(AccountMeta::new_readonly(mint_wrapper_program, false)); + accounts.push(AccountMeta::new(minter, false)); + accounts.push(AccountMeta::new( + iou_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new(iou_fees_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + accounts.push(AccountMeta::new(zero::id(), false)); + } + 2 => { + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new( + token_b_reward_custody.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + vault + .fees_account_a + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new_readonly(redeemer, false)); + accounts.push(AccountMeta::new_readonly(redeemer_program, false)); + accounts.push(AccountMeta::new( + sbr_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new( + iou_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts.push(AccountMeta::new(sbr_vault, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_program, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_authority, false)); + accounts.push(AccountMeta::new_readonly(mint_proxy_state, false)); + accounts.push(AccountMeta::new(minter_info, false)); + } + 3 => { + accounts.push(AccountMeta::new(token_a_reward_custody, false)); + accounts.push(AccountMeta::new(token_a_custody, false)); + accounts.push(AccountMeta::new( + token_b_custody.or_else(|| Some(zero::id())).unwrap(), + false, + )); + + match pool.route { + PoolRoute::Saber { + decimal_wrapper_program, + wrapped_token_a_ref, + wrapped_token_a_vault, + decimal_wrapper_token_a, + wrapped_token_b_ref, + wrapped_token_b_vault, + decimal_wrapper_token_b, + .. + } => { + let (wrapped_token_mint, wrapped_token_vault, decimal_wrapper) = + if is_token_a_wrapped { + let wrapped_token_a = self + .get_token_by_ref_from_cache( + &wrapped_token_a_ref, + )?; + ( + wrapped_token_a + .ok_or(ProgramError::UninitializedAccount)? + .mint, + wrapped_token_a_vault + .ok_or(ProgramError::UninitializedAccount)?, + decimal_wrapper_token_a + .ok_or(ProgramError::UninitializedAccount)?, + ) + } else if is_token_b_wrapped { + let wrapped_token_b = self + .get_token_by_ref_from_cache( + &wrapped_token_b_ref, + )?; + ( + wrapped_token_b + .ok_or(ProgramError::UninitializedAccount)? + .mint, + wrapped_token_b_vault + .ok_or(ProgramError::UninitializedAccount)?, + decimal_wrapper_token_b + .ok_or(ProgramError::UninitializedAccount)?, + ) + } else { + (zero::id(), zero::id(), zero::id()) + }; + + accounts.push(AccountMeta::new_readonly(usdc_mint, false)); + accounts.push(AccountMeta::new(wrapped_token_mint, false)); + accounts.push(AccountMeta::new(wrapped_token_vault, false)); + accounts + .push(AccountMeta::new_readonly(decimal_wrapper, false)); + accounts.push(AccountMeta::new_readonly( + decimal_wrapper_program, + false, + )); + } + _ => { + unreachable!(); + } + } + + let usdc_pool = self.get_pool("RDM.SBR-USDC")?; + match usdc_pool.route { + PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue: _, + pool_temp_lp_token_account: _, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue, + } => { + accounts.push(AccountMeta::new_readonly( + usdc_pool.pool_program_id, + false, + )); + accounts.push(AccountMeta::new( + usdc_pool + .token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + usdc_pool + .token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new(amm_id, false)); + accounts.push(AccountMeta::new_readonly(amm_authority, false)); + accounts.push(AccountMeta::new(amm_open_orders, false)); + accounts.push(AccountMeta::new(amm_target, false)); + accounts.push(AccountMeta::new(serum_market, false)); + accounts + .push(AccountMeta::new_readonly(serum_program_id, false)); + accounts + .push(AccountMeta::new(serum_coin_vault_account, false)); + accounts.push(AccountMeta::new(serum_pc_vault_account, false)); + accounts + .push(AccountMeta::new_readonly(serum_vault_signer, false)); + accounts.push(AccountMeta::new( + serum_bids.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_asks.ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + serum_event_queue + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } + _ => { + unreachable!(); + } + } + } + 4 => match pool.route { + PoolRoute::Saber { + swap_account, + swap_authority, + .. + } => { + let usdc_token = self.get_token("USDC")?; + if token_a.ok_or(ProgramError::UninitializedAccount)?.mint + != usdc_token.mint + { + accounts.push(AccountMeta::new( + vault + .fees_account_b + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } + if is_token_a_wrapped || is_token_b_wrapped { + accounts.push(AccountMeta::new( + token_b_custody + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } else { + accounts.push(AccountMeta::new(token_a_custody, false)); + } + if token_a.ok_or(ProgramError::UninitializedAccount)?.mint + == usdc_token.mint + { + accounts.push(AccountMeta::new( + vault + .fees_account_b + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + } + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts + .push(AccountMeta::new_readonly(pool.pool_program_id, false)); + accounts.push(AccountMeta::new( + pool.token_a_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + pool.token_b_account + .ok_or(ProgramError::UninitializedAccount)?, + false, + )); + accounts.push(AccountMeta::new( + lp_token.ok_or(ProgramError::UninitializedAccount)?.mint, + false, + )); + accounts + .push(AccountMeta::new_readonly(sysvar::clock::id(), false)); + accounts.push(AccountMeta::new_readonly(swap_account, false)); + accounts.push(AccountMeta::new_readonly(swap_authority, false)); + } + _ => { + unreachable!(); + } + }, + 5 => { + let vault_miner_account = self + .get_token_account(&vault_stake_info, &lp_token) + .ok_or(ProgramError::UninitializedAccount)?; + + accounts.push(AccountMeta::new(lp_token_custody, false)); + accounts.push(AccountMeta::new_readonly(farm.farm_program_id, false)); + accounts.push(AccountMeta::new(vault_stake_info, false)); + accounts.push(AccountMeta::new(vault_miner_account, false)); + accounts.push(AccountMeta::new(quarry, false)); + accounts.push(AccountMeta::new_readonly(rewarder, false)); + } + _ => { + panic!("Crank step must be 1-5"); + } + }, + _ => { + unreachable!(); + } + } + } + VaultStrategy::DynamicHedge { .. } => { + unreachable!(); + } + } + + Ok((accounts, data)) + } +} diff --git a/farms/farm-client/src/error.rs b/farms/farm-client/src/error.rs new file mode 100644 index 00000000000..c912841a401 --- /dev/null +++ b/farms/farm-client/src/error.rs @@ -0,0 +1,31 @@ +use { + solana_account_decoder::parse_account_data::ParseAccountError, + solana_client::client_error::ClientError, + solana_sdk::{program_error::ProgramError, pubkey::PubkeyError}, + thiserror::Error, +}; + +/// Farm Client Errors +#[derive(Debug, Error)] +pub enum FarmClientError { + #[error(transparent)] + RpcClientError(#[from] ClientError), + #[error(transparent)] + ProgramError(#[from] ProgramError), + #[error(transparent)] + ParseAccountError(#[from] ParseAccountError), + #[error(transparent)] + PubkeyError(#[from] PubkeyError), + #[error("Record not found: {0}")] + RecordNotFound(String), + #[error("ArrayString error: {0}")] + ArrayStringError(String), + #[error("I/O error: {0}")] + IOError(String), + #[error("Parse error: {0}")] + ParseError(String), + #[error("Value error: {0}")] + ValueError(String), + #[error("Insufficient balance: {0}")] + InsufficientBalance(String), +} diff --git a/farms/farm-client/src/lib.rs b/farms/farm-client/src/lib.rs new file mode 100644 index 00000000000..9594afd75f9 --- /dev/null +++ b/farms/farm-client/src/lib.rs @@ -0,0 +1,3 @@ +mod cache; +pub mod client; +pub mod error; diff --git a/farms/farm-client/tests/orca_pools.rs b/farms/farm-client/tests/orca_pools.rs new file mode 100644 index 00000000000..80fa1964d60 --- /dev/null +++ b/farms/farm-client/tests/orca_pools.rs @@ -0,0 +1,118 @@ +mod pool_actions; +mod utils; + +#[test] +#[ignore] +fn test_pool_atlas_usdc_v1() { + pool_actions::run_test( + "ORC.ATLAS-USDC-V1", + vec![ + utils::Swap { + protocol: "ORC", + from_token: "SOL", + to_token: "USDC", + amount: 0.222, + }, + utils::Swap { + protocol: "ORC", + from_token: "USDC", + to_token: "ATLAS", + amount: -0.5, + }, + ], + vec![ + utils::Swap { + protocol: "ORC", + from_token: "ATLAS", + to_token: "USDC", + amount: 0.0, + }, + utils::Swap { + protocol: "ORC", + from_token: "USDC", + to_token: "SOL", + amount: 0.0, + }, + ], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_ray_sol_latest() { + pool_actions::run_test( + "ORC.RAY-SOL", + vec![utils::Swap { + protocol: "ORC", + from_token: "SOL", + to_token: "RAY", + amount: 0.111, + }], + vec![utils::Swap { + protocol: "ORC", + from_token: "RAY", + to_token: "SOL", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_sol_usdc_latest() { + pool_actions::run_test( + "ORC.SOL-USDC", + vec![utils::Swap { + protocol: "ORC", + from_token: "SOL", + to_token: "USDC", + amount: 0.111, + }], + vec![utils::Swap { + protocol: "ORC", + from_token: "USDC", + to_token: "SOL", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_msol_sol_latest() { + pool_actions::run_test( + "ORC.MSOL-SOL", + vec![ + utils::Swap { + protocol: "ORC", + from_token: "SOL", + to_token: "USDC", + amount: 0.119, + }, + utils::Swap { + protocol: "ORC", + from_token: "USDC", + to_token: "MSOL", + amount: -0.5, + }, + ], + vec![ + utils::Swap { + protocol: "ORC", + from_token: "MSOL", + to_token: "USDC", + amount: 0.0, + }, + utils::Swap { + protocol: "ORC", + from_token: "USDC", + to_token: "SOL", + amount: 0.0, + }, + ], + false, + ); +} diff --git a/farms/farm-client/tests/pool_actions/mod.rs b/farms/farm-client/tests/pool_actions/mod.rs new file mode 100644 index 00000000000..21c8140566b --- /dev/null +++ b/farms/farm-client/tests/pool_actions/mod.rs @@ -0,0 +1,317 @@ +use { + solana_farm_client::client::FarmClient, + solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, signer::Signer}, +}; + +use crate::{utils, utils::Swap}; + +const MAX_SOL_BALANCE_TO_USE: f64 = 0.1; + +pub fn do_swap(client: &FarmClient, keypair: &Keypair, swap: &Swap) { + let amount = if swap.amount == 0.0 { + utils::get_token_or_native_balance(client, &keypair.pubkey(), swap.from_token) + } else if swap.amount < 0.0 { + -1.0 * swap.amount + * utils::get_token_or_native_balance(client, &keypair.pubkey(), swap.from_token) + } else { + swap.amount + }; + if amount < 0.0001 { + return; + } + println!( + ">> Swap {} {} to {}", + amount, swap.from_token, swap.to_token + ); + println!( + " Done: {}", + client + .swap( + keypair, + swap.protocol, + swap.from_token, + swap.to_token, + amount, + 0.0, + ) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + swap.from_token, + swap.to_token, + "After swap", + ); +} + +pub fn do_add_liquidity( + client: &FarmClient, + keypair: &Keypair, + pool_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, +) -> f64 { + println!( + ">> Add liquidity to {}: {}, {}", + pool_name, max_token_a_ui_amount, max_token_b_ui_amount + ); + let (token_a_str, token_b_str, lp_token_name) = client.get_pool_token_names(pool_name).unwrap(); + let lp_balance = utils::get_token_or_native_balance(client, &keypair.pubkey(), &lp_token_name); + println!( + " Done: {}", + client + .add_liquidity_pool( + keypair, + pool_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After add liquidity", + ); + let _ = utils::get_balance(client, &keypair.pubkey(), &lp_token_name, "LP"); + utils::get_token_or_native_balance(client, &keypair.pubkey(), &lp_token_name) - lp_balance +} + +pub fn do_stake(client: &FarmClient, keypair: &Keypair, farm_name: &str, amount: f64) { + println!(">> Stake liquidity to {}: {}", farm_name, amount); + let (token_a_str, token_b_str, lp_token_name) = client.get_farm_token_names(farm_name).unwrap(); + println!( + " Done: {}", + client.stake(keypair, farm_name, amount).unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After stake", + ); + let _ = utils::get_balance(client, &keypair.pubkey(), &lp_token_name, "LP after stake"); +} + +pub fn do_harvest(client: &FarmClient, keypair: &Keypair, farm_name: &str) { + println!(">> Harvest from {}", farm_name); + let (token_a_str, token_b_str, lp_token_name) = client.get_farm_token_names(farm_name).unwrap(); + println!(" Done: {}", client.harvest(keypair, farm_name).unwrap()); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After harvest", + ); + let _ = utils::get_balance( + client, + &keypair.pubkey(), + &lp_token_name, + "LP after harvest", + ); +} + +pub fn do_unstake(client: &FarmClient, keypair: &Keypair, farm_name: &str, amount: f64) { + println!(">> Unstake liquidity from {}: {}", farm_name, amount); + let (token_a_str, token_b_str, lp_token_name) = client.get_farm_token_names(farm_name).unwrap(); + println!( + " Done: {}", + client.unstake(keypair, farm_name, amount).unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After unstake", + ); + let _ = utils::get_balance( + client, + &keypair.pubkey(), + &lp_token_name, + "LP after unstake", + ); +} + +pub fn do_remove_liquidity(client: &FarmClient, keypair: &Keypair, pool_name: &str, amount: f64) { + println!(">> Remove liquidity from {}: {}", pool_name, amount); + let (token_a_str, token_b_str, lp_token_name) = client.get_pool_token_names(pool_name).unwrap(); + println!( + " Done: {}", + client + .remove_liquidity_pool(keypair, pool_name, amount) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After remove liquidity", + ); + let _ = utils::get_balance(client, &keypair.pubkey(), &lp_token_name, "LP"); +} + +pub fn cleanup( + client: &FarmClient, + keypair: &Keypair, + pool_name: &str, + cleanup_swaps: Vec, + pool_only: bool, +) { + println!("\n>>> Clean-up {}...", pool_name); + let wallet = keypair.pubkey(); + let (token_a_str, token_b_str, lp_token_name) = client.get_pool_token_names(pool_name).unwrap(); + + if !pool_only { + let farms = client.find_farms_with_lp(&lp_token_name).unwrap(); + for farm in farms.iter() { + let farm_token_name = + "LP.".to_string() + &farm.name.as_str()[..farm.name.as_str().len() - 3]; + if let Ok(dd_farms) = client.find_farms_with_lp(&farm_token_name) { + for farm in dd_farms.iter() { + if let Ok(staked_balance) = + client.get_user_stake_balance(&wallet, farm.name.as_str()) + { + if staked_balance > 0.0 { + do_unstake(client, keypair, farm.name.as_str(), staked_balance); + } + } + } + } + + if let Ok(staked_balance) = client.get_user_stake_balance(&wallet, farm.name.as_str()) { + if staked_balance > 0.0 { + do_unstake(client, keypair, farm.name.as_str(), staked_balance); + } + } + } + } + + let lp_token_balance = utils::get_token_or_native_balance(client, &wallet, &lp_token_name); + if lp_token_balance > 0.0 { + do_remove_liquidity(client, keypair, pool_name, lp_token_balance); + } + + for swap in cleanup_swaps { + do_swap(client, keypair, &swap); + } + + if token_a_str != "SOL" { + let token_a_balance = utils::get_token_or_native_balance(client, &wallet, &token_a_str); + if token_a_balance > 0.0 { + do_swap( + client, + keypair, + &Swap { + protocol: "RDM", + from_token: token_a_str.as_str(), + to_token: "SOL", + amount: token_a_balance, + }, + ); + } + } + + if token_b_str != "SOL" { + let token_b_balance = utils::get_token_or_native_balance(client, &wallet, &token_b_str); + if token_b_balance > 0.0 { + do_swap( + client, + keypair, + &Swap { + protocol: "RDM", + from_token: token_b_str.as_str(), + to_token: "SOL", + amount: token_b_balance, + }, + ); + } + } +} + +pub fn run_test(pool_name: &str, swaps: Vec, cleanup_swaps: Vec, pool_only: bool) { + let (endpoint, keypair) = utils::get_endpoint_and_keypair(); + let client = FarmClient::new_with_commitment(&endpoint, CommitmentConfig::confirmed()); + let wallet = keypair.pubkey(); + + cleanup( + &client, + &keypair, + pool_name, + cleanup_swaps.clone(), + pool_only, + ); + + println!("\n>>> Testing {}...", pool_name); + let (token_a_str, token_b_str, lp_token_name) = client.get_pool_token_names(pool_name).unwrap(); + + let (_, _) = utils::get_balances(&client, &wallet, &token_a_str, &token_b_str, "Initial"); + //initial swaps + for swap in swaps { + do_swap(&client, &keypair, &swap); + } + + let token_a_balance = if token_a_str == "SOL" { + MAX_SOL_BALANCE_TO_USE.min(utils::get_token_or_native_balance( + &client, + &wallet, + &token_a_str, + )) + } else { + utils::get_token_or_native_balance(&client, &wallet, &token_a_str) + }; + let token_b_balance = if token_b_str == "SOL" { + MAX_SOL_BALANCE_TO_USE.min(utils::get_token_or_native_balance( + &client, + &wallet, + &token_b_str, + )) + } else { + utils::get_token_or_native_balance(&client, &wallet, &token_b_str) + }; + + assert!(token_a_balance > 0.0 && token_b_balance > 0.0); + + // main tests + let mut lp_received = + do_add_liquidity(&client, &keypair, pool_name, token_a_balance / 3.0, 0.0); + assert!(lp_received > 0.0); + lp_received += do_add_liquidity(&client, &keypair, pool_name, 0.0, token_b_balance / 3.0); + + if !pool_only { + let farms = client.find_farms_with_lp(&lp_token_name).unwrap(); + for farm in farms.iter() { + do_stake(&client, &keypair, farm.name.as_str(), lp_received / 2.0); + do_stake(&client, &keypair, farm.name.as_str(), 0.0); + do_harvest(&client, &keypair, farm.name.as_str()); + + // orca double-dip farms + let farm_token_name = + "LP.".to_string() + &farm.name.as_str()[..farm.name.as_str().len() - 3]; + if let Ok(dd_farms) = client.find_farms_with_lp(&farm_token_name) { + for dd_farm in dd_farms.iter() { + do_stake(&client, &keypair, dd_farm.name.as_str(), lp_received / 2.0); + do_stake(&client, &keypair, dd_farm.name.as_str(), 0.0); + do_harvest(&client, &keypair, dd_farm.name.as_str()); + do_unstake(&client, &keypair, dd_farm.name.as_str(), lp_received / 2.0); + do_unstake(&client, &keypair, dd_farm.name.as_str(), 0.0); + } + } + + do_unstake(&client, &keypair, farm.name.as_str(), lp_received / 2.0); + do_unstake(&client, &keypair, farm.name.as_str(), 0.0); + } + } + do_remove_liquidity(&client, &keypair, pool_name, lp_received / 2.0); + do_remove_liquidity(&client, &keypair, pool_name, 0.0); + + cleanup(&client, &keypair, pool_name, cleanup_swaps, pool_only); + + let (_, _) = utils::get_balances(&client, &wallet, &token_a_str, &token_b_str, "Final"); +} diff --git a/farms/farm-client/tests/raydium_pools.rs b/farms/farm-client/tests/raydium_pools.rs new file mode 100644 index 00000000000..e95019864b1 --- /dev/null +++ b/farms/farm-client/tests/raydium_pools.rs @@ -0,0 +1,278 @@ +mod pool_actions; +mod utils; + +#[test] +#[ignore] +fn test_pool_ray_srm() { + pool_actions::run_test( + "RDM.RAY-SRM-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.111, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "SRM", + amount: 0.111, + }, + ], + vec![], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_ray_srm_latest() { + pool_actions::run_test( + "RDM.RAY-SRM", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.111, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "SRM", + amount: 0.111, + }, + ], + vec![], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_polis_ray() { + pool_actions::run_test( + "RDM.POLIS-RAY-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "POLIS", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "POLIS", + to_token: "RAY", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_polis_ray_latest() { + pool_actions::run_test( + "RDM.POLIS-RAY", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "POLIS", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "POLIS", + to_token: "RAY", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_grape_usdc() { + pool_actions::run_test( + "RDM.GRAPE-USDC-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "USDC", + to_token: "GRAPE", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "GRAPE", + to_token: "USDC", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_fida_ray() { + pool_actions::run_test( + "RDM.FIDA-RAY-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.21111111, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "FIDA", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "FIDA", + to_token: "RAY", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_ray_sol() { + pool_actions::run_test( + "RDM.RAY-SOL-V4", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.09999999, + }], + vec![], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_ray_sol_latest() { + pool_actions::run_test( + "RDM.RAY-SOL", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.09999999, + }], + vec![], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_sol_usdc() { + pool_actions::run_test( + "RDM.SOL-USDC-V4", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.10000001, + }], + vec![], + true, + ); +} + +#[test] +#[ignore] +fn test_pool_sol_usdc_latest() { + pool_actions::run_test( + "RDM.SOL-USDC", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.10000001, + }], + vec![], + true, + ); +} + +#[test] +#[ignore] +fn test_pool_msol_usdc() { + pool_actions::run_test( + "RDM.MSOL-USDC-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "MSOL", + amount: 0.10000001, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.1111, + }, + ], + vec![], + true, + ); +} + +#[test] +#[ignore] +fn test_pool_ray_usdc() { + pool_actions::run_test( + "RDM.RAY-USDC-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.10000001, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.09999999, + }, + ], + vec![], + true, + ); +} diff --git a/farms/farm-client/tests/raydium_vaults.rs b/farms/farm-client/tests/raydium_vaults.rs new file mode 100644 index 00000000000..161aee86e72 --- /dev/null +++ b/farms/farm-client/tests/raydium_vaults.rs @@ -0,0 +1,375 @@ +mod utils; +mod vault_actions; + +#[test] +#[ignore] +fn test_vault_polis_ray() { + vault_actions::run_test( + "RDM.STC.POLIS-RAY-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "POLIS", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "POLIS", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_polis_ray_latest() { + vault_actions::run_test( + "RDM.STC.POLIS-RAY", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "POLIS", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "POLIS", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_sny_ray() { + vault_actions::run_test( + "RDM.STC.SNY-RAY-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "SNY", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "SNY", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_atlas_ray() { + vault_actions::run_test( + "RDM.STC.ATLAS-RAY-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "ATLAS", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "ATLAS", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_srm_v3() { + vault_actions::run_test( + "RDM.STC.RAY-SRM-V3", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.123, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "SRM", + amount: 0.123, + }, + ], + vec![], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_srm_v5() { + vault_actions::run_test( + "RDM.STC.RAY-SRM-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.123, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "SRM", + amount: 0.123, + }, + ], + vec![], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_srm_latest() { + vault_actions::run_test( + "RDM.STC.RAY-SRM", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.123, + }, + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "SRM", + amount: 0.123, + }, + ], + vec![], + ); +} + +#[test] +#[ignore] +fn test_vault_grape_usdc() { + vault_actions::run_test( + "RDM.STC.GRAPE-USDC-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.222, + }, + utils::Swap { + protocol: "RDM", + from_token: "USDC", + to_token: "GRAPE", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "GRAPE", + to_token: "USDC", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_samo_ray() { + vault_actions::run_test( + "RDM.STC.SAMO-RAY-V5", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.211, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "SAMO", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "SAMO", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_oxy_ray() { + vault_actions::run_test( + "RDM.STC.OXY-RAY-V4", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.233, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "OXY", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "OXY", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_oxy_ray_latest() { + vault_actions::run_test( + "RDM.STC.OXY-RAY", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.233, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "OXY", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "RDM", + from_token: "OXY", + to_token: "RAY", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_sol() { + vault_actions::run_test( + "RDM.STC.RAY-SOL-V3", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.091111, + }], + vec![], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_sol_latest() { + vault_actions::run_test( + "RDM.STC.RAY-SOL", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.091111, + }], + vec![], + ); +} + +#[test] +#[ignore] +fn test_vault_ray_usdt() { + vault_actions::run_test( + "RDM.STC.RAY-USDT-V3", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "RAY", + amount: 0.223, + }, + utils::Swap { + protocol: "RDM", + from_token: "RAY", + to_token: "USDT", + amount: -0.5, + }, + ], + vec![], + ); +} + +/* +#[test] +#[ignore] +fn all_vault_tests() { + // dual v5 + test_vault_polis_ray(); + test_vault_polis_ray_latest(); + test_vault_atlas_ray(); + test_vault_sny_ray(); + test_vault_ray_srm_v5(); + test_vault_ray_srm_latest(); + + // single reward b v5 + test_vault_grape_usdc(); + test_vault_samo_ray(); + + // dual v4 + test_vault_oxy_ray(); + test_vault_oxy_ray_latest(); + + // single reward a v3 + test_vault_ray_sol(); + test_vault_ray_sol_latest(); + test_vault_ray_usdt(); + test_vault_ray_srm_v3(); +}*/ diff --git a/farms/farm-client/tests/saber_pools.rs b/farms/farm-client/tests/saber_pools.rs new file mode 100644 index 00000000000..fa3ea18ba9c --- /dev/null +++ b/farms/farm-client/tests/saber_pools.rs @@ -0,0 +1,174 @@ +mod pool_actions; +mod utils; + +#[test] +#[ignore] +fn test_pool_xsol_sol_v1() { + pool_actions::run_test( + "SBR.XSOL-SOL-V1", + vec![utils::Swap { + protocol: "SBR", + from_token: "SOL", + to_token: "XSOL", + amount: 0.111, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "XSOL", + to_token: "SOL", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_xsol_sol_latest() { + pool_actions::run_test( + "SBR.XSOL-SOL", + vec![utils::Swap { + protocol: "SBR", + from_token: "SOL", + to_token: "XSOL", + amount: 0.111, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "XSOL", + to_token: "SOL", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_renbtc_btc_latest() { + pool_actions::run_test( + "SBR.RENBTC-BTC", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.111, + }, + utils::Swap { + protocol: "RDM", + from_token: "USDC", + to_token: "BTC", + amount: 0.0, + }, + utils::Swap { + protocol: "SBR", + from_token: "BTC", + to_token: "RENBTC", + amount: -0.5, + }, + ], + vec![ + utils::Swap { + protocol: "SBR", + from_token: "RENBTC", + to_token: "BTC", + amount: 0.0, + }, + utils::Swap { + protocol: "RDM", + from_token: "BTC", + to_token: "USDC", + amount: 0.0, + }, + ], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_usdc_wust_v1_latest() { + pool_actions::run_test( + "SBR.USDC-WUST_V1", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.111, + }, + utils::Swap { + protocol: "SBR", + from_token: "USDC", + to_token: "WUST_V1", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "SBR", + from_token: "WUST_V1", + to_token: "USDC", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_wust_usdc_latest() { + pool_actions::run_test( + "SBR.WUST-USDC", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.111, + }, + utils::Swap { + protocol: "SBR", + from_token: "USDC", + to_token: "WUST", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "SBR", + from_token: "WUST", + to_token: "USDC", + amount: 0.0, + }], + false, + ); +} + +#[test] +#[ignore] +fn test_pool_whusd_v1_usdc_latest() { + pool_actions::run_test( + "SBR.WHUSD_V1-USDC", + vec![ + utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.111, + }, + utils::Swap { + protocol: "SBR", + from_token: "USDC", + to_token: "WHUSD_V1", + amount: -0.5, + }, + ], + vec![utils::Swap { + protocol: "SBR", + from_token: "WHUSD_V1", + to_token: "USDC", + amount: 0.0, + }], + false, + ); +} diff --git a/farms/farm-client/tests/saber_vaults.rs b/farms/farm-client/tests/saber_vaults.rs new file mode 100644 index 00000000000..69044cdb2f2 --- /dev/null +++ b/farms/farm-client/tests/saber_vaults.rs @@ -0,0 +1,82 @@ +mod utils; +mod vault_actions; + +#[test] +#[ignore] +fn test_vault_usdc_usdt() { + vault_actions::run_test( + "SBR.STC.USDC-USDT", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.222, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "USDT", + to_token: "USDC", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_usdc_wust_v1() { + vault_actions::run_test( + "SBR.STC.USDC-WUST_V1", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.221, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "WUST_V1", + to_token: "USDC", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_acusd_usdc() { + vault_actions::run_test( + "SBR.STC.ACUSD-USDC", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.223, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "ACUSD", + to_token: "USDC", + amount: 0.0, + }], + ); +} + +#[test] +#[ignore] +fn test_vault_wdai_usdc() { + vault_actions::run_test( + "SBR.STC.WDAI-USDC", + vec![utils::Swap { + protocol: "RDM", + from_token: "SOL", + to_token: "USDC", + amount: 0.224, + }], + vec![utils::Swap { + protocol: "SBR", + from_token: "WDAI", + to_token: "USDC", + amount: 0.0, + }], + ); +} diff --git a/farms/farm-client/tests/utils/mod.rs b/farms/farm-client/tests/utils/mod.rs new file mode 100644 index 00000000000..e16e50574db --- /dev/null +++ b/farms/farm-client/tests/utils/mod.rs @@ -0,0 +1,84 @@ +//! Common functions for tests + +use { + solana_farm_client::client::FarmClient, + solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::keypair::read_keypair_file}, +}; + +#[derive(Copy, Clone)] +pub struct Swap<'a> { + pub protocol: &'a str, + pub from_token: &'a str, + pub to_token: &'a str, + pub amount: f64, +} + +#[allow(dead_code)] +pub fn get_endpoint_and_keypair() -> (String, Keypair) { + let cli_config = if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { + solana_cli_config::Config::load(config_file).unwrap() + } else { + solana_cli_config::Config::default() + }; + + ( + cli_config.json_rpc_url.to_string(), + read_keypair_file(&cli_config.keypair_path).unwrap_or_else(|_| { + panic!("Filed to read keypair from \"{}\"", cli_config.keypair_path) + }), + ) +} + +#[allow(dead_code)] +pub fn get_token_or_native_balance(client: &FarmClient, wallet: &Pubkey, token_name: &str) -> f64 { + if token_name != "SOL" { + if let Ok(balance) = client.get_token_account_balance(wallet, token_name) { + balance + } else { + 0.0 + } + } else if let Ok(balance) = client.get_account_balance(wallet) { + balance + } else { + 0.0 + } +} + +#[allow(dead_code)] +pub fn get_balance( + client: &FarmClient, + wallet: &Pubkey, + token_name: &str, + description: &str, +) -> f64 { + let token_balance = get_token_or_native_balance(client, wallet, token_name); + println!( + " {} balance. {}: {}", + description, token_name, token_balance + ); + token_balance +} + +#[allow(dead_code)] +pub fn get_balances( + client: &FarmClient, + wallet: &Pubkey, + token_a: &str, + token_b: &str, + description: &str, +) -> (f64, f64) { + let token_a_balance = get_token_or_native_balance(client, wallet, token_a); + let token_b_balance = get_token_or_native_balance(client, wallet, token_b); + println!( + " {} balances. {}: {}, {}: {}", + description, token_a, token_a_balance, token_b, token_b_balance + ); + (token_a_balance, token_b_balance) +} + +#[allow(dead_code)] +pub fn get_vault_stake_balance(client: &FarmClient, vault_name: &str) -> f64 { + let stake_balance = client.get_vault_stake_balance(vault_name).unwrap(); + println!(" Stake balance. {}", stake_balance); + stake_balance +} diff --git a/farms/farm-client/tests/vault_actions/mod.rs b/farms/farm-client/tests/vault_actions/mod.rs new file mode 100644 index 00000000000..10b6a61c6a5 --- /dev/null +++ b/farms/farm-client/tests/vault_actions/mod.rs @@ -0,0 +1,257 @@ +use { + crate::{utils, utils::Swap}, + solana_farm_client::client::FarmClient, + solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, signer::Signer}, + std::{thread, time}, +}; + +const MAX_SOL_BALANCE_TO_USE: f64 = 0.1; +const INITIAL_CRANK_DELAY: u64 = 400; +const CRANK_INTERVAL: u64 = 100; + +pub fn do_swap(client: &FarmClient, keypair: &Keypair, swap: &Swap) { + let amount = if swap.amount == 0.0 { + utils::get_token_or_native_balance(client, &keypair.pubkey(), swap.from_token) + } else if swap.amount < 0.0 { + -1.0 * swap.amount + * utils::get_token_or_native_balance(client, &keypair.pubkey(), swap.from_token) + } else { + swap.amount + }; + if amount < 0.0001 { + return; + } + println!( + ">> Swap {} {} to {}", + amount, swap.from_token, swap.to_token + ); + println!( + " Done: {}", + client + .swap( + keypair, + swap.protocol, + swap.from_token, + swap.to_token, + amount, + 0.0, + ) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + swap.from_token, + swap.to_token, + "After swap", + ); +} + +pub fn do_add_liquidity( + client: &FarmClient, + keypair: &Keypair, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, +) -> f64 { + println!( + ">> Add liquidity to {}: {}, {}", + vault_name, max_token_a_ui_amount, max_token_b_ui_amount + ); + let (token_a_str, token_b_str, vt_token_name) = + client.get_vault_token_names(vault_name).unwrap(); + let vt_balance = utils::get_token_or_native_balance(client, &keypair.pubkey(), &vt_token_name); + println!( + " Done: {}", + client + .add_liquidity_vault( + keypair, + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After add liquidity", + ); + let _ = utils::get_balance(client, &keypair.pubkey(), &vt_token_name, "VT"); + let _ = utils::get_vault_stake_balance(client, vault_name); + utils::get_token_or_native_balance(client, &keypair.pubkey(), &vt_token_name) - vt_balance +} + +pub fn do_crank(client: &FarmClient, keypair: &Keypair, vault_name: &str, step: u64) { + println!(">> Crank {} with step {}", vault_name, step); + let initial_info = client.get_vault_info(vault_name).unwrap(); + println!( + " Done: {}", + client.crank_vault(keypair, vault_name, step).unwrap() + ); + let after_crank_info = client.get_vault_info(vault_name).unwrap(); + println!( + " Rewards received: {}, {}", + after_crank_info.tokens_a_rewards - initial_info.tokens_a_rewards, + after_crank_info.tokens_b_rewards - initial_info.tokens_b_rewards + ); + let _ = utils::get_vault_stake_balance(client, vault_name); +} + +pub fn do_remove_liquidity(client: &FarmClient, keypair: &Keypair, vault_name: &str, amount: f64) { + println!(">> Remove liquidity from {}: {}", vault_name, amount); + let (token_a_str, token_b_str, vt_token_name) = + client.get_vault_token_names(vault_name).unwrap(); + println!( + " Done: {}", + client + .remove_liquidity_vault(keypair, vault_name, amount) + .unwrap() + ); + let _ = utils::get_balances( + client, + &keypair.pubkey(), + &token_a_str, + &token_b_str, + "After remove liquidity", + ); + let _ = utils::get_balance(client, &keypair.pubkey(), &vt_token_name, "VT"); + let _ = utils::get_vault_stake_balance(client, vault_name); +} + +pub fn cleanup(client: &FarmClient, keypair: &Keypair, vault_name: &str, cleanup_swaps: Vec) { + println!("\n>>> Clean-up {}...", vault_name); + let wallet = keypair.pubkey(); + let (token_a_str, token_b_str, vt_token_name) = + client.get_vault_token_names(vault_name).unwrap(); + + let vt_token_balance = utils::get_token_or_native_balance(client, &wallet, &vt_token_name); + if vt_token_balance > 0.0 { + do_remove_liquidity(client, keypair, vault_name, vt_token_balance); + } + + for swap in cleanup_swaps { + do_swap(client, keypair, &swap); + } + + if token_a_str != "SOL" { + let token_a_balance = utils::get_token_or_native_balance(client, &wallet, &token_a_str); + if token_a_balance > 0.0 { + do_swap( + client, + keypair, + &Swap { + protocol: "RDM", + from_token: token_a_str.as_str(), + to_token: "SOL", + amount: token_a_balance, + }, + ); + } + } + + if token_b_str != "SOL" { + let token_b_balance = utils::get_token_or_native_balance(client, &wallet, &token_b_str); + if token_b_balance > 0.0 { + do_swap( + client, + keypair, + &Swap { + protocol: "RDM", + from_token: token_b_str.as_str(), + to_token: "SOL", + amount: token_b_balance, + }, + ); + } + } + + let _ = utils::get_vault_stake_balance(client, vault_name); +} + +pub fn run_test(vault_name: &str, swaps: Vec, cleanup_swaps: Vec) { + let (endpoint, keypair) = utils::get_endpoint_and_keypair(); + let client = FarmClient::new_with_commitment(&endpoint, CommitmentConfig::confirmed()); + let wallet = keypair.pubkey(); + + cleanup(&client, &keypair, vault_name, cleanup_swaps.clone()); + + println!("\n>>> Testing {}...", vault_name); + let (token_a_str, token_b_str, _) = client.get_vault_token_names(vault_name).unwrap(); + + let (_, _) = utils::get_balances(&client, &wallet, &token_a_str, &token_b_str, "Initial"); + let _ = utils::get_vault_stake_balance(&client, vault_name); + //initial swaps + for swap in swaps { + do_swap(&client, &keypair, &swap); + } + + let token_a_balance = if token_a_str == "SOL" { + MAX_SOL_BALANCE_TO_USE.min(utils::get_token_or_native_balance( + &client, + &wallet, + &token_a_str, + )) + } else { + utils::get_token_or_native_balance(&client, &wallet, &token_a_str) + }; + let token_b_balance = if token_b_str == "SOL" { + MAX_SOL_BALANCE_TO_USE.min(utils::get_token_or_native_balance( + &client, + &wallet, + &token_b_str, + )) + } else { + utils::get_token_or_native_balance(&client, &wallet, &token_b_str) + }; + + // main tests + let mut vt_received; + if vault_name.starts_with("SBR.") { + if token_a_str == "USDC" { + assert!(token_a_balance > 0.0); + vt_received = do_add_liquidity( + &client, + &keypair, + vault_name, + token_a_balance * 2.0 / 3.0, + 0.0, + ); + } else { + assert!(token_b_balance > 0.0); + vt_received = do_add_liquidity( + &client, + &keypair, + vault_name, + 0.0, + token_b_balance * 2.0 / 3.0, + ); + } + } else { + assert!(token_a_balance > 0.0 && token_b_balance > 0.0); + vt_received = do_add_liquidity(&client, &keypair, vault_name, token_a_balance / 3.0, 0.0); + assert!(vt_received > 0.0); + vt_received += do_add_liquidity(&client, &keypair, vault_name, 0.0, token_b_balance / 3.0); + } + + println!("Waiting {} secs for rewards...", INITIAL_CRANK_DELAY); + thread::sleep(time::Duration::from_secs(INITIAL_CRANK_DELAY)); + do_crank(&client, &keypair, vault_name, 1); + + let cranks = if vault_name.starts_with("SBR.") { 6 } else { 4 }; + for step in 2..cranks { + println!("Waiting {} secs before next crank...", CRANK_INTERVAL); + thread::sleep(time::Duration::from_secs(CRANK_INTERVAL)); + do_crank(&client, &keypair, vault_name, step); + } + + do_remove_liquidity(&client, &keypair, vault_name, vt_received / 2.0); + do_remove_liquidity(&client, &keypair, vault_name, 0.0); + + cleanup(&client, &keypair, vault_name, cleanup_swaps); + + let (_, _) = utils::get_balances(&client, &wallet, &token_a_str, &token_b_str, "Final"); + let _ = utils::get_vault_stake_balance(&client, vault_name); +} diff --git a/farms/farm-ctrl/.gitignore b/farms/farm-ctrl/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/farms/farm-ctrl/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/farms/farm-ctrl/Cargo.toml b/farms/farm-ctrl/Cargo.toml new file mode 100644 index 00000000000..839b5c4cda3 --- /dev/null +++ b/farms/farm-ctrl/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "solana-farm-ctrl" +version = "0.0.1" +description = "Solana Farm Control" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +debug = [] + +[dependencies] +log = "0.4.14" +clap = "2.33.3" +serde = "1.0.130" +serde_derive = "1.0.130" +serde_json = "1.0.69" +serde_yaml = "0.8.21" +solana-client = "1.8.1" +solana-logger = "1.8.1" +solana-version = "1.8.1" +solana-clap-utils = "1.8.1" +solana-sdk = "1.8.1" +solana-farm-sdk = { path = "../farm-sdk" } +solana-farm-client = { path = "../farm-client" } +spl-associated-token-account = { version = "1.0.3", features = ["no-entrypoint"] } +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } +spl-governance = { version = "2.1.4", features = ["no-entrypoint"] } +solana-cli-config = "1.8.1" +quarry-mine = { version = "1.10.0", features = ["no-entrypoint"] } +url = "2.2.2" diff --git a/farms/farm-ctrl/src/config.rs b/farms/farm-ctrl/src/config.rs new file mode 100644 index 00000000000..daa34297f98 --- /dev/null +++ b/farms/farm-ctrl/src/config.rs @@ -0,0 +1,445 @@ +//! Configuration and command line arguments management. + +use { + clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand}, + solana_clap_utils::{input_validators::is_url, keypair::signer_from_path}, + solana_farm_sdk::refdb, + solana_sdk::{commitment_config::CommitmentConfig, signature::Signer}, + std::str::FromStr, +}; + +#[derive(Debug)] +pub struct Config { + pub farm_client_url: String, + pub commitment: CommitmentConfig, + pub keypair: Box, + pub max_instructions: u32, + pub no_pretty_print: bool, + pub skip_existing: bool, +} + +impl Config { + pub fn new(matches: &ArgMatches) -> Self { + let cli_config = if let Some(config_file) = matches.value_of("config_file") { + match solana_cli_config::Config::load(config_file) { + Err(e) => { + panic!( + "Failed to load config file \"{}\":{}", + config_file, + e.to_string() + ); + } + Ok(config) => config, + } + } else { + solana_cli_config::Config::default() + }; + + let farm_client_url = matches + .value_of("farm_client_url") + .unwrap_or(&cli_config.json_rpc_url); + let keypair_path = matches + .value_of("keypair") + .unwrap_or(&cli_config.keypair_path); + let commitment = matches + .value_of("commitment") + .unwrap_or(&cli_config.commitment); + let max_instructions = matches + .value_of("max_instructions") + .unwrap() + .parse() + .unwrap(); + + Self { + farm_client_url: farm_client_url.to_string(), + commitment: CommitmentConfig::from_str(commitment).unwrap(), + keypair: signer_from_path(matches, keypair_path, "this transaction", &mut None) + .unwrap(), + max_instructions, + no_pretty_print: matches.is_present("no_pretty_print"), + skip_existing: matches.is_present("skip_existing"), + } + } +} + +pub fn get_target(matches: &ArgMatches) -> refdb::StorageType { + let target = matches.value_of("target").unwrap(); + let res = target + .parse() + .unwrap_or_else(|_| panic!("Invalid target type \"{}\"", target)); + if res == refdb::StorageType::Other { + panic!("Invalid target type: {}", res); + } + res +} + +pub fn get_objectname(matches: &ArgMatches) -> String { + matches + .value_of("objectname") + .unwrap() + .parse::() + .unwrap() + .to_uppercase() +} + +pub fn get_objectname_raw(matches: &ArgMatches) -> String { + matches.value_of("objectname").unwrap().parse().unwrap() +} + +pub fn get_vaultname(matches: &ArgMatches) -> String { + matches + .value_of("vaultname") + .unwrap() + .parse::() + .unwrap() + .to_uppercase() +} + +pub fn get_vaultparam(matches: &ArgMatches) -> f64 { + matches.value_of("vaultparam").unwrap().parse().unwrap() +} + +pub fn get_step(matches: &ArgMatches) -> u64 { + matches.value_of("step").unwrap().parse().unwrap() +} + +pub fn get_filename(matches: &ArgMatches) -> String { + matches.value_of("filename").unwrap().parse().unwrap() +} + +pub fn get_clap_app<'a, 'b>(version: &'b str) -> App<'a, 'b> { + let target = Arg::with_name("target") + .value_name("TARGET_TYPE") + .required(true) + .takes_value(true) + .help("Target object type (program, vault, etc.)"); + + let filename = Arg::with_name("filename") + .value_name("FILE_NAME") + .required(true) + .takes_value(true) + .help("Input file name"); + + let objectname = Arg::with_name("objectname") + .value_name("OBJECT_NAME") + .required(true) + .takes_value(true) + .help("Target object name"); + + let vaultname = Arg::with_name("vaultname") + .value_name("VAULT_NAME") + .required(true) + .takes_value(true) + .help("Vault name"); + + let vaultparam = Arg::with_name("vaultparam") + .value_name("VAULT_PARAM") + .required(true) + .takes_value(true) + .help("Vault param"); + + let step = Arg::with_name("step") + .value_name("STEP") + .required(true) + .takes_value(true) + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned integer")), + Ok(_) => Ok(()), + }) + .help("Instruction step"); + + App::new(crate_name!()) + .about(crate_description!()) + .version(version) + .arg( + Arg::with_name("log_level") + .short("L") + .long("log-level") + .takes_value(true) + .default_value("info") + .global(true) + .help("Log verbosity level (debug, info, warning, error)") + .validator(|p| { + let allowed = ["debug", "info", "warning", "error"]; + if allowed.contains(&p.as_str()) { + Ok(()) + } else { + Err(String::from("Must be one of: debug, info, warning, error")) + } + }), + ) + .arg({ + let arg = Arg::with_name("config_file") + .short("C") + .long("config") + .value_name("PATH") + .takes_value(true) + .global(true) + .help("Configuration file to use"); + if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { + arg.default_value(config_file) + } else { + arg + } + }) + .arg( + Arg::with_name("farm_client_url") + .short("f") + .long("farm-client-url") + .value_name("STR") + .takes_value(true) + .global(true) + .validator(is_url) + .help("RPC URL to use with Farm Client"), + ) + .arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("KEYPAIR") + .global(true) + .takes_value(true) + .help("Filepath or URL to a keypair"), + ) + .arg( + Arg::with_name("max_instructions") + .short("m") + .long("max-instructions") + .value_name("NUM") + .global(true) + .takes_value(true) + .default_value("1") + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned integer")), + Ok(_) => Ok(()), + }) + .help("Max instructions per transaction"), + ) + .arg( + Arg::with_name("commitment") + .long("commitment") + .short("c") + .takes_value(true) + .possible_values(&[ + "processed", + "confirmed", + "finalized", + ]) + .value_name("COMMITMENT_LEVEL") + .hide_possible_values(true) + .global(true) + .help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"), + ) + .arg( + Arg::with_name("no_pretty_print") + .short("n") + .long("no-pretty-print") + .global(true) + .takes_value(false) + .help("Print entire record in one line"), + ) + .arg( + Arg::with_name("skip_existing") + .short("s") + .long("skip-existing") + .global(true) + .takes_value(false) + .help("Do not update existing records on-chain"), + ) + .subcommand( + SubCommand::with_name("init") + .about("Initialize Reference DB on-chain") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("init-all") + .about("Initialize Reference DB of all storage types on-chain"), + ) + .subcommand( + SubCommand::with_name("drop") + .about("Drop on-chain Reference DB") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("drop-all") + .about("Drop on-chain Reference DB for all storage types"), + ) + .subcommand( + SubCommand::with_name("load") + .about("Load objects from file and send to blockchain") + .arg(target.clone()) + .arg(filename.clone()), + ) + .subcommand( + SubCommand::with_name("load-all") + .about("Same as \"load\"") + .arg(target.clone()) + .arg(filename.clone()), + ) + .subcommand( + SubCommand::with_name("remove") + .about("Remove specified object from blockchain") + .arg(target.clone()) + .arg(objectname.clone()), + ) + .subcommand( + SubCommand::with_name("remove-all") + .about("Remove all objects of the given type from blockchain") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("remove-all-with-file") + .about("Remove all objects in the file from blockchain") + .arg(target.clone()) + .arg(filename.clone()), + ) + .subcommand( + SubCommand::with_name("get") + .about("Query specified object in blockchain and print") + .arg(target.clone()) + .arg(objectname.clone()), + ) + .subcommand( + SubCommand::with_name("get-ref") + .about("Query specified object by reference address and print") + .arg(target.clone()) + .arg(objectname.clone()), + ) + .subcommand( + SubCommand::with_name("get-all") + .about("Query all objects of the given type and print") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("list-all") + .about("Query all objects of the given type and print") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("vault-init") + .about("Initialize the Vault") + .arg(vaultname.clone()) + .arg(step.clone()) + ) + .subcommand( + SubCommand::with_name("vault-shutdown") + .about("Shutdown the Vault") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-crank") + .about("Crank the Vault") + .arg(vaultname.clone()) + .arg(step.clone()) + ) + .subcommand( + SubCommand::with_name("vault-crank-all") + .about("Crank all Vaults") + .arg(step.clone()) + ) + .subcommand( + SubCommand::with_name("vault-set-fee") + .about("Set new fee percent for the Vault") + .arg(vaultname.clone()) + .arg(vaultparam.clone()), + ) + .subcommand( + SubCommand::with_name("vault-set-external-fee") + .about("Set new external fee percent for the Vault") + .arg(vaultname.clone()) + .arg(vaultparam.clone()), + ) + .subcommand( + SubCommand::with_name("vault-set-min-crank-interval") + .about("Set new min crank interval in seconds for the Vault") + .arg(vaultname.clone()) + .arg(vaultparam.clone()), + ) + .subcommand( + SubCommand::with_name("vault-disable-deposit") + .about("Disable deposits for the specified object") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-enable-deposit") + .about("Enable deposits for the specified object") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-disable-withdrawal") + .about("Disable withdrawals for the specified object") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-enable-withdrawal") + .about("Enable withdrawals for the specified object") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("vault-get-info") + .about("Print current stats for the Vault") + .arg(vaultname.clone()), + ) + .subcommand( + SubCommand::with_name("print-pda-all") + .about("Derive Reference DB addresses for all objects"), + ) + .subcommand( + SubCommand::with_name("print-size") + .about("Print Reference DB and specified object sizes") + .arg(target.clone()), + ) + .subcommand( + SubCommand::with_name("print-size-all") + .about("Print Reference DB and all object sizes"), + ) + .subcommand( + SubCommand::with_name("generate") + .about("Generate json boilerplate for the specified object") + .arg(target.clone()) + .arg(objectname.clone()) + .arg( + Arg::with_name("param1") + .index(3) + .value_name("PARAM1") + .required(true) + .takes_value(true) + .help("Object specific parameter 1"), + ) + .arg( + Arg::with_name("param2") + .index(4) + .value_name("PARAM2") + .required(true) + .takes_value(true) + .help("Object specific parameter 2"), + ), + ) + .subcommand( + SubCommand::with_name("governance") + .about("Governance commands. See `solana-farm-ctrl governance help`") + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand( + SubCommand::with_name("init") + .about("Initialize a new DAO") + .arg( + Arg::with_name("governance-program-address") + .value_name("DAO-PROGRAM") + .required(true) + .takes_value(true) + .help("Address of the governance program"), + ) + .arg( + Arg::with_name("mint-ui-amount") + .value_name("MINT_UI_AMOUNT") + .required(true) + .takes_value(true) + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned integer")), + Ok(_) => Ok(()), + }) + .help("Amount of governance tokens to mint") + ) + ) + ) +} diff --git a/farms/farm-ctrl/src/generate.rs b/farms/farm-ctrl/src/generate.rs new file mode 100644 index 00000000000..0e7852c5cb6 --- /dev/null +++ b/farms/farm-ctrl/src/generate.rs @@ -0,0 +1,344 @@ +//! Handlers for generate command + +use { + crate::config::Config, + log::info, + serde_json::Value, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + farm::{FarmRoute, FarmType}, + git_token::GitToken, + id::main_router_admin, + program::pda::find_target_pda, + refdb::StorageType, + string::{str_to_as64, to_pretty_json}, + vault::{Vault, VaultStrategy, VaultType}, + }, + solana_sdk::pubkey::Pubkey, + std::collections::HashMap, + std::str::FromStr, +}; + +pub fn generate_rdm_stc_vault( + client: &FarmClient, + _config: &Config, + vault_address: &Pubkey, + vault_name: &str, + token_name: &str, +) { + let farm_name = "RDM.".to_string() + vault_name.split('.').collect::>()[2]; + let farm = client.get_farm(&farm_name).unwrap(); + let lp_token = client + .get_token_by_ref(&farm.lp_token_ref.unwrap()) + .unwrap(); + let pool = client.find_pools_with_lp(lp_token.name.as_str()).unwrap()[0]; + let farm_reward_token_b_account = match farm.route { + FarmRoute::Raydium { + farm_reward_token_b_account, + .. + } => farm_reward_token_b_account, + _ => None, + }; + let vault = Vault { + name: str_to_as64(vault_name).unwrap(), + version: 1, + vault_type: VaultType::AmmStake, + official: true, + refdb_index: None, + refdb_counter: 0, + metadata_bump: find_target_pda(StorageType::Vault, &str_to_as64(vault_name).unwrap()).1, + authority_bump: Pubkey::find_program_address( + &[b"vault_authority", vault_name.as_bytes()], + vault_address, + ) + .1, + vault_token_bump: Pubkey::find_program_address( + &[b"vault_token_mint", vault_name.as_bytes()], + vault_address, + ) + .1, + lock_required: false, + unlock_required: true, + vault_program_id: *vault_address, + vault_authority: Pubkey::find_program_address( + &[b"vault_authority", vault_name.as_bytes()], + vault_address, + ) + .0, + vault_token_ref: find_target_pda(StorageType::Token, &str_to_as64(token_name).unwrap()).0, + info_account: Pubkey::find_program_address( + &[b"info_account", vault_name.as_bytes()], + vault_address, + ) + .0, + admin_account: main_router_admin::id(), + fees_account_a: Some( + Pubkey::find_program_address( + &[b"fees_account_a", vault_name.as_bytes()], + vault_address, + ) + .0, + ), + fees_account_b: if farm.farm_type == FarmType::DualReward + || farm_reward_token_b_account.is_some() + { + Some( + Pubkey::find_program_address( + &[b"fees_account_b", vault_name.as_bytes()], + vault_address, + ) + .0, + ) + } else { + None + }, + strategy: VaultStrategy::StakeLpCompoundRewards { + pool_id_ref: client.get_pool_ref(pool.name.as_str()).unwrap(), + farm_id_ref: client.get_farm_ref(&farm_name).unwrap(), + lp_token_custody: Pubkey::find_program_address( + &[b"lp_token_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_a_custody: Pubkey::find_program_address( + &[b"token_a_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_b_custody: Some( + Pubkey::find_program_address( + &[b"token_b_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + ), + token_a_reward_custody: Pubkey::find_program_address( + &[b"token_a_reward_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_b_reward_custody: if farm.farm_type == FarmType::DualReward + || farm_reward_token_b_account.is_some() + { + Some( + Pubkey::find_program_address( + &[b"token_b_reward_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + ) + } else { + None + }, + vault_stake_info: if farm.version < 4 { + Pubkey::find_program_address( + &[b"vault_stake_info", vault_name.as_bytes()], + vault_address, + ) + .0 + } else { + Pubkey::find_program_address( + &[b"vault_stake_info_v4", vault_name.as_bytes()], + vault_address, + ) + .0 + }, + }, + }; + println!("{}", to_pretty_json(&vault).unwrap()); + + let token = GitToken { + chain_id: 101, + address: Pubkey::find_program_address( + &[b"vault_token_mint", vault_name.as_bytes()], + vault_address, + ) + .0 + .to_string(), + symbol: token_name.to_string(), + name: "Raydium ".to_string() + + token_name.split('.').collect::>()[3] + + " Stake Compound Vault Token", + decimals: client + .get_token_by_ref(&farm.lp_token_ref.unwrap()) + .unwrap() + .decimals as i32, + logo_uri: String::default(), + tags: vec!["vt-token".to_string()], + extra: HashMap::::default(), + }; + println!("{}", to_pretty_json(&token).unwrap()); +} + +pub fn generate_sbr_stc_vault( + client: &FarmClient, + _config: &Config, + vault_address: &Pubkey, + vault_name: &str, + token_name: &str, +) { + let farm_name = "SBR.".to_string() + vault_name.split('.').collect::>()[2]; + let farm = client.get_farm(&farm_name).unwrap(); + let lp_token = client + .get_token_by_ref(&farm.lp_token_ref.unwrap()) + .unwrap(); + let pool = client.find_pools_with_lp(lp_token.name.as_str()).unwrap()[0]; + let (is_token_a_wrapped, is_token_b_wrapped) = client + .pool_has_saber_wrapped_tokens(pool.name.as_str()) + .unwrap(); + let quarry = match farm.route { + FarmRoute::Saber { quarry, .. } => quarry, + _ => unreachable!(), + }; + let (vault_authority, authority_bump) = + Pubkey::find_program_address(&[b"vault_authority", vault_name.as_bytes()], vault_address); + + let vault = Vault { + name: str_to_as64(vault_name).unwrap(), + version: 1, + vault_type: VaultType::AmmStake, + official: true, + refdb_index: None, + refdb_counter: 0, + metadata_bump: find_target_pda(StorageType::Vault, &str_to_as64(vault_name).unwrap()).1, + authority_bump, + vault_token_bump: Pubkey::find_program_address( + &[b"vault_token_mint", vault_name.as_bytes()], + vault_address, + ) + .1, + lock_required: true, + unlock_required: false, + vault_program_id: *vault_address, + vault_authority, + vault_token_ref: find_target_pda(StorageType::Token, &str_to_as64(token_name).unwrap()).0, + info_account: Pubkey::find_program_address( + &[b"info_account", vault_name.as_bytes()], + vault_address, + ) + .0, + admin_account: main_router_admin::id(), + fees_account_a: Some( + Pubkey::find_program_address( + &[b"fees_account_a", vault_name.as_bytes()], + vault_address, + ) + .0, + ), + fees_account_b: Some( + Pubkey::find_program_address( + &[b"fees_account_b", vault_name.as_bytes()], + vault_address, + ) + .0, + ), + strategy: VaultStrategy::StakeLpCompoundRewards { + pool_id_ref: client.get_pool_ref(pool.name.as_str()).unwrap(), + farm_id_ref: client.get_farm_ref(&farm_name).unwrap(), + lp_token_custody: Pubkey::find_program_address( + &[b"lp_token_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_a_custody: Pubkey::find_program_address( + &[b"token_a_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_b_custody: if is_token_a_wrapped || is_token_b_wrapped { + Some( + Pubkey::find_program_address( + &[b"token_b_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + ) + } else { + None + }, + token_a_reward_custody: Pubkey::find_program_address( + &[b"token_a_reward_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + token_b_reward_custody: Some( + Pubkey::find_program_address( + &[b"token_b_reward_custody", vault_name.as_bytes()], + vault_address, + ) + .0, + ), + vault_stake_info: Pubkey::find_program_address( + &[b"Miner", &quarry.to_bytes(), &vault_authority.to_bytes()], + &quarry_mine::id(), + ) + .0, + }, + }; + println!("{},", to_pretty_json(&vault).unwrap()); + + let token = GitToken { + chain_id: 101, + address: Pubkey::find_program_address( + &[b"vault_token_mint", vault_name.as_bytes()], + vault_address, + ) + .0 + .to_string(), + symbol: token_name.to_string(), + name: "Saber ".to_string() + + token_name.split('.').collect::>()[3] + + " Stake Compound Vault Token", + decimals: client + .get_token_by_ref(&farm.lp_token_ref.unwrap()) + .unwrap() + .decimals as i32, + logo_uri: String::default(), + tags: vec!["vt-token".to_string()], + extra: HashMap::::default(), + }; + println!("{}", to_pretty_json(&token).unwrap()); +} + +pub fn generate( + client: &FarmClient, + config: &Config, + target: StorageType, + object: &str, + param1: &str, + param2: &str, +) { + info!( + "Generating json boilerplate for {} {} {}...", + target, object, param1 + ); + + match target { + StorageType::Vault => { + if param1.starts_with("RDM.") { + generate_rdm_stc_vault( + client, + config, + &Pubkey::from_str(object).unwrap(), + param1, + param2, + ); + } else if param1.starts_with("SBR.") { + generate_sbr_stc_vault( + client, + config, + &Pubkey::from_str(object).unwrap(), + param1, + param2, + ); + } else { + panic!("Unexpected Vault name: {}", param1); + } + } + _ => { + panic!("Target is not supported: {}", target); + } + } + + info!("Done.") +} diff --git a/farms/farm-ctrl/src/get.rs b/farms/farm-ctrl/src/get.rs new file mode 100644 index 00000000000..eef0abb5adc --- /dev/null +++ b/farms/farm-ctrl/src/get.rs @@ -0,0 +1,178 @@ +//! Handlers for get and get_all command + +use { + crate::config::Config, + log::info, + serde::Serialize, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{refdb::StorageType, string::to_pretty_json}, + solana_sdk::pubkey::Pubkey, + std::str::FromStr, +}; + +pub fn get(client: &FarmClient, config: &Config, target: StorageType, object: &str) { + info!("Querying {} object {}...", target, object); + + match target { + StorageType::Program => { + println!("{}: {}", object, client.get_program_id(object).unwrap()); + } + StorageType::Vault => { + print_object( + config, + &client.get_vault_ref(object).unwrap(), + &client.get_vault(object).unwrap(), + ); + } + StorageType::Farm => { + print_object( + config, + &client.get_farm_ref(object).unwrap(), + &client.get_farm(object).unwrap(), + ); + } + StorageType::Pool => { + print_object( + config, + &client.get_pool_ref(object).unwrap(), + &client.get_pool(object).unwrap(), + ); + } + StorageType::Token => { + print_object( + config, + &client.get_token_ref(object).unwrap(), + &client.get_token(object).unwrap(), + ); + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} + +pub fn get_ref(client: &FarmClient, config: &Config, target: StorageType, object: &str) { + info!("Querying {} object {}...", target, object); + + let pubkey = Pubkey::from_str(object).unwrap(); + + match target { + StorageType::Program => { + println!("{}: {}", client.get_program_name(&pubkey).unwrap(), object); + } + StorageType::Vault => { + print_object(config, &pubkey, &client.get_vault_by_ref(&pubkey).unwrap()); + } + StorageType::Farm => { + print_object(config, &pubkey, &client.get_farm_by_ref(&pubkey).unwrap()); + } + StorageType::Pool => { + print_object(config, &pubkey, &client.get_pool_by_ref(&pubkey).unwrap()); + } + StorageType::Token => { + print_object(config, &pubkey, &client.get_token_by_ref(&pubkey).unwrap()); + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} + +pub fn get_all(client: &FarmClient, config: &Config, target: StorageType) { + info!("Querying all {} objects...", target); + + match target { + StorageType::Program => { + let storage = client.get_program_ids().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + StorageType::Vault => { + let storage = client.get_vaults().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_vault_ref(name).unwrap(), key); + } + } + StorageType::Farm => { + let storage = client.get_farms().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_farm_ref(name).unwrap(), key); + } + } + StorageType::Pool => { + let storage = client.get_pools().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_pool_ref(name).unwrap(), key); + } + } + StorageType::Token => { + let storage = client.get_tokens().unwrap(); + for (name, key) in storage.iter() { + print_object(config, &client.get_token_ref(name).unwrap(), key); + } + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} + +pub fn list_all(client: &FarmClient, _config: &Config, target: StorageType) { + info!("Querying all {} objects...", target); + + match target { + StorageType::Program => { + let storage = client.get_program_ids().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + StorageType::Vault => { + let storage = client.get_vault_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + StorageType::Farm => { + let storage = client.get_farm_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + StorageType::Pool => { + let storage = client.get_pool_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + StorageType::Token => { + let storage = client.get_token_refs().unwrap(); + for (name, key) in storage.iter() { + println!("{}: {}", name, key); + } + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} + +fn print_object(config: &Config, key: &Pubkey, object: &T) +where + T: ?Sized + Serialize + std::fmt::Display, +{ + if config.no_pretty_print { + println!("{}: {}", key, object); + } else { + println!("{}: {}", key, to_pretty_json(object).unwrap()); + } +} diff --git a/farms/farm-ctrl/src/governance.rs b/farms/farm-ctrl/src/governance.rs new file mode 100644 index 00000000000..5fe0ddf8802 --- /dev/null +++ b/farms/farm-ctrl/src/governance.rs @@ -0,0 +1,297 @@ +//! Handler for the governance commands + +use { + crate::config::Config, + log::info, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + id::{main_router_admin, ProgramIDType, DAO_MINT_NAME, DAO_PROGRAM_NAME, DAO_TOKEN_NAME}, + refdb::StorageType, + string::str_to_as64, + token::{Token, TokenType}, + }, + solana_sdk::{program_pack::Pack, pubkey::Pubkey}, + spl_associated_token_account::{create_associated_token_account, get_associated_token_address}, + spl_governance::instruction as dao_instruction, + spl_governance::state::{ + enums::{MintMaxVoteWeightSource, VoteThresholdPercentage, VoteWeightSource}, + governance::GovernanceConfig, + realm::get_realm_address, + token_owner_record::get_token_owner_record_address, + }, +}; + +pub fn init(client: &FarmClient, config: &Config, dao_program: &Pubkey, mint_ui_amount: f64) { + info!("Initializing DAO..."); + + let wallet = config.keypair.pubkey(); + if main_router_admin::id() != wallet { + panic!( + "DAO must be initialized with the admin account {}", + main_router_admin::id() + ); + } + if mint_ui_amount < 100.0 { + panic!("Mint amount must be >= 100"); + } + + let mut inst = vec![]; + + info!("Writing Program \"{}\" to on-chain RefDB...", dao_program); + client + .add_program_id( + config.keypair.as_ref(), + DAO_PROGRAM_NAME, + dao_program, + ProgramIDType::System, + None, + ) + .unwrap(); + + let mint_address = Pubkey::create_with_seed(&wallet, DAO_MINT_NAME, &spl_token::id()).unwrap(); + let mint_size = spl_token::state::Mint::get_packed_len(); + let dao_token_address = get_associated_token_address(&wallet, &mint_address); + + if client.rpc_client.get_account_data(&mint_address).is_err() { + info!( + " Creating governance tokens mint at {} and minting {} tokens...", + mint_address, mint_ui_amount + ); + + // record token info to the refdb + let (index, counter) = if let Ok(token) = client.get_token(DAO_TOKEN_NAME) { + (token.refdb_index, token.refdb_counter) + } else { + ( + Some( + client + .get_refdb_last_index(&StorageType::Token.to_string()) + .expect("Token RefDB query error"), + ), + 0u16, + ) + }; + let token = Token { + name: str_to_as64(DAO_TOKEN_NAME).unwrap(), + description: str_to_as64("Solana Farms Governance Token").unwrap(), + token_type: TokenType::SplToken, + refdb_index: index, + refdb_counter: counter, + decimals: 6, + chain_id: 101, + mint: mint_address, + }; + + inst.push(client.new_instruction_add_token(&wallet, token).unwrap()); + + // initialize governance tokens mint + inst.push( + client + .new_instruction_create_system_account_with_seed( + &wallet, + &wallet, + DAO_MINT_NAME, + 0, + mint_size, + &spl_token::id(), + ) + .unwrap(), + ); + + inst.push( + spl_token::instruction::initialize_mint( + &spl_token::id(), + &mint_address, + &wallet, + Some(&wallet), + 6, + ) + .unwrap(), + ); + + if client + .rpc_client + .get_account_data(&dao_token_address) + .is_err() + { + inst.push(create_associated_token_account( + &wallet, + &wallet, + &mint_address, + )); + } + + // mint governance tokens to admin account first + inst.push( + spl_token::instruction::mint_to( + &spl_token::id(), + &mint_address, + &dao_token_address, + &wallet, + &[], + client.ui_amount_to_tokens_with_decimals(mint_ui_amount, 6), + ) + .unwrap(), + ); + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + } + + info!(" Creating realm and depositing DAO tokens..."); + + // create realm + inst.clear(); + let realm_address = get_realm_address(dao_program, DAO_PROGRAM_NAME); + + if client.rpc_client.get_account_data(&realm_address).is_err() { + inst.push(dao_instruction::create_realm( + dao_program, + &wallet, + &mint_address, + &wallet, + None, + None, + DAO_PROGRAM_NAME.to_string(), + client.ui_amount_to_tokens_with_decimals(1.0, 6), + MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION, + )); + } + + // deposit governance tokens + inst.push(dao_instruction::deposit_governing_tokens( + dao_program, + &realm_address, + &dao_token_address, + &wallet, + &wallet, + &wallet, + client.ui_amount_to_tokens_with_decimals(1.0, 6), + &mint_address, + )); + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + + // create router program governances + info!(" Creating router program governances..."); + inst.clear(); + let dao_config = GovernanceConfig { + vote_threshold_percentage: VoteThresholdPercentage::YesVote(60), + min_community_tokens_to_create_proposal: (mint_ui_amount as f64 * 0.01) as u64, + min_instruction_hold_up_time: 0, + max_voting_time: 259200, + vote_weight_source: VoteWeightSource::Deposit, + proposal_cool_off_time: 0, + min_council_tokens_to_create_proposal: 0, + }; + let token_owner = + get_token_owner_record_address(dao_program, &realm_address, &mint_address, &wallet); + for program_name in &[ + DAO_PROGRAM_NAME, + "MainRouter", + "RaydiumRouter", + "SaberRouter", + "OrcaRouter", + ] { + let program = if program_name == &DAO_PROGRAM_NAME { + *dao_program + } else { + client.get_program_id(program_name).unwrap() + }; + inst.push(dao_instruction::create_program_governance( + dao_program, + &realm_address, + &program, + &wallet, + &token_owner, + &wallet, + &wallet, + None, + dao_config.clone(), + true, + )); + } + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + + // create vault program governances + info!(" Creating vault program governances..."); + inst.clear(); + let vaults = client.get_vaults().unwrap(); + for (_vault_name, vault) in vaults { + inst.push(dao_instruction::create_program_governance( + dao_program, + &realm_address, + &vault.vault_program_id, + &wallet, + &token_owner, + &wallet, + &wallet, + None, + dao_config.clone(), + true, + )); + } + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + + // create DAO mint governance + info!(" Creating DAO mint governance..."); + inst.clear(); + inst.push(dao_instruction::create_mint_governance( + dao_program, + &realm_address, + &mint_address, + &wallet, + &token_owner, + &wallet, + &wallet, + None, + dao_config, + true, + )); + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + // remove realm authority + info!(" Removing realm authority..."); + inst.clear(); + inst.push(dao_instruction::set_realm_authority( + dao_program, + &realm_address, + &wallet, + &None, + )); + + info!( + " Signature: {}", + client + .sign_and_send_instructions(&[config.keypair.as_ref()], inst.as_slice()) + .unwrap() + ); + + info!("Done."); +} diff --git a/farms/farm-ctrl/src/load.rs b/farms/farm-ctrl/src/load.rs new file mode 100644 index 00000000000..a46af89363e --- /dev/null +++ b/farms/farm-ctrl/src/load.rs @@ -0,0 +1,51 @@ +//! Handler for the load command + +use { + crate::{config::Config, loaders}, + log::info, + solana_farm_client::client::FarmClient, + solana_farm_sdk::refdb::StorageType, + std::fs, +}; + +pub fn load( + client: &FarmClient, + config: &Config, + target: StorageType, + filename: &str, + remove_mode: bool, +) { + if !remove_mode { + info!("Loading {} objects from {}...", target, filename); + } else { + info!( + "Removing all {} objects listed in file {}...", + target, filename + ); + } + + let data = fs::read_to_string(filename).unwrap(); + + match target { + StorageType::Program => { + loaders::program::load(client, config, &data, remove_mode); + } + StorageType::Vault => { + loaders::vault::load(client, config, &data, remove_mode); + } + StorageType::Farm => { + loaders::farm::load(client, config, &data, remove_mode); + } + StorageType::Pool => { + loaders::pool::load(client, config, &data, remove_mode); + } + StorageType::Token => { + loaders::token::load(client, config, &data, remove_mode); + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} diff --git a/farms/farm-ctrl/src/loaders/farm.rs b/farms/farm-ctrl/src/loaders/farm.rs new file mode 100644 index 00000000000..0cac6b5191c --- /dev/null +++ b/farms/farm-ctrl/src/loaders/farm.rs @@ -0,0 +1,375 @@ +//! Farms loader. + +use { + crate::{config::Config, loaders::utils::*}, + log::info, + serde::Deserialize, + serde_json::{json, Value}, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + farm::{Farm, FarmRoute, FarmType}, + git_token::GitToken, + pack::{optional_pubkey_deserialize, pubkey_deserialize}, + program::protocol::orca::OrcaFarmState, + refdb::StorageType, + string::str_to_as64, + }, + solana_sdk::{hash::Hasher, pubkey::Pubkey}, +}; + +#[derive(Deserialize, Debug)] +struct JsonRaydiumFarm { + name: String, + lp: String, + reward: String, + #[serde(rename = "rewardB", default)] + reward_b: String, + #[serde(rename = "isStake")] + is_stake: bool, + fusion: bool, + legacy: bool, + dual: bool, + version: u8, + #[serde(rename = "programId")] + program_id: String, + #[serde(rename = "poolId", deserialize_with = "pubkey_deserialize")] + farm_id: Pubkey, + #[serde(rename = "poolAuthority", deserialize_with = "pubkey_deserialize")] + farm_authority: Pubkey, + #[serde(rename = "poolLpTokenAccount", deserialize_with = "pubkey_deserialize")] + farm_lp_token_account: Pubkey, + #[serde( + rename = "poolRewardTokenAccount", + deserialize_with = "pubkey_deserialize" + )] + farm_reward_token_account: Pubkey, + #[serde( + rename = "poolRewardTokenAccountB", + deserialize_with = "optional_pubkey_deserialize", + default + )] + farm_reward_token_account_b: Option, +} + +#[derive(Deserialize, Debug)] +struct JsonSaberFarm { + name: String, + tokens: Vec, + #[serde(rename = "lpToken")] + lp_token: GitToken, + #[serde(deserialize_with = "pubkey_deserialize")] + quarry: Pubkey, +} + +#[derive(Deserialize, Debug)] +pub struct JsonOrcaFarm { + pub name: String, + #[serde(deserialize_with = "pubkey_deserialize")] + pub address: Pubkey, + #[serde(rename = "farmTokenMint", deserialize_with = "pubkey_deserialize")] + pub farm_token_mint: Pubkey, + #[serde(rename = "rewardTokenMint", deserialize_with = "pubkey_deserialize")] + pub reward_token_mint: Pubkey, + #[serde(rename = "rewardTokenDecimals")] + pub reward_token_decimals: u8, + #[serde(rename = "baseTokenMint", deserialize_with = "pubkey_deserialize")] + pub base_token_mint: Pubkey, + #[serde(rename = "baseTokenDecimals")] + pub base_token_decimals: u8, +} + +pub fn load(client: &FarmClient, config: &Config, data: &str, remove_mode: bool) { + let parsed: Value = serde_json::from_str(data).unwrap(); + let last_index = client + .get_refdb_last_index(&StorageType::Farm.to_string()) + .expect("Farm RefDB query error"); + + if parsed["name"] == "Raydium Farms" { + load_raydium_farm(client, config, remove_mode, &parsed, last_index); + } else if parsed["name"] == "Orca Farms" { + load_orca_farm(client, config, remove_mode, &parsed, last_index); + } else if parsed["pools"] != json!(null) && parsed["addresses"] != json!(null) { + load_saber_farm(client, config, remove_mode, &parsed, last_index); + } else { + panic!("Unsupported farms file"); + } +} + +fn load_raydium_farm( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let router_id = client.get_program_id(&"RaydiumRouter".to_string()).unwrap(); + let farms = parsed["farms"].as_array().unwrap(); + for val in farms { + let json_farm: JsonRaydiumFarm = serde_json::from_value(val.clone()).unwrap(); + let name = format!( + "RDM.{}-V{}", + json_farm.name.to_uppercase(), + json_farm.version + ); + if !remove_mode { + if json_farm.legacy { + info!("Skipping legacy Farm \"{}\"...", name); + continue; + } + if config.skip_existing && client.get_farm(&name).is_ok() { + info!("Skipping existing Farm \"{}\"...", name); + continue; + } + info!("Writing Farm \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Farm \"{}\" from on-chain RefDB...", name); + client.remove_farm(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(farm) = client.get_farm(&name) { + (farm.refdb_index, farm.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let farm = Farm { + name: str_to_as64(&name).unwrap(), + version: json_farm.version as u16, + farm_type: if json_farm.dual { + FarmType::DualReward + } else if json_farm.is_stake { + FarmType::ProtocolTokenStake + } else { + FarmType::SingleReward + }, + official: true, + refdb_index: index, + refdb_counter: counter, + lp_token_ref: Some(client.get_token_ref(&json_farm.lp.to_uppercase()).unwrap()), + reward_token_a_ref: Some( + client + .get_token_ref(&json_farm.reward.to_uppercase()) + .unwrap(), + ), + reward_token_b_ref: if json_farm.reward_b.is_empty() { + None + } else { + Some( + client + .get_token_ref(&json_farm.reward_b.to_uppercase()) + .unwrap(), + ) + }, + router_program_id: router_id, + farm_program_id: convert_raydium_program_id(client, &json_farm.program_id), + route: FarmRoute::Raydium { + farm_id: json_farm.farm_id, + farm_authority: json_farm.farm_authority, + farm_lp_token_account: json_farm.farm_lp_token_account, + farm_reward_token_a_account: json_farm.farm_reward_token_account, + farm_reward_token_b_account: json_farm.farm_reward_token_account_b, + }, + }; + + client.add_farm(config.keypair.as_ref(), farm).unwrap(); + } +} + +fn load_saber_farm( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let pools = parsed["pools"].as_array().unwrap(); + let router_id = client.get_program_id(&"SaberRouter".to_string()).unwrap(); + + let farm_program_id = client + .get_program_id(&"SaberQuarryMine".to_string()) + .unwrap(); + let redeemer_program = client.get_program_id(&"SaberRedeemer".to_string()).unwrap(); + let mint_proxy_program = client + .get_program_id(&"SaberMintProxy".to_string()) + .unwrap(); + let redeemer = json_to_pubkey(&parsed["addresses"]["redeemer"]); + let sbr_mint = client.get_token("SBR").unwrap().mint; + let sbr_vault = + spl_associated_token_account::get_associated_token_address(&redeemer, &sbr_mint); + let rewarder = json_to_pubkey(&parsed["addresses"]["rewarder"]); + let iou_mint = client.get_token("IOU").unwrap().mint; + let iou_fees_account = + spl_associated_token_account::get_associated_token_address(&rewarder, &iou_mint); + let mint_wrapper = json_to_pubkey(&parsed["addresses"]["mintWrapper"]); + let mint_wrapper_program = client + .get_program_id(&"SaberMintWrapper".to_string()) + .unwrap(); + + // minter + let minter = Pubkey::find_program_address( + &[ + b"MintWrapperMinter", + &mint_wrapper.to_bytes(), + &rewarder.to_bytes(), + ], + &mint_wrapper_program, + ) + .0; + + // mint_proxy_authority + let registry_signer = Pubkey::find_program_address(&[], &mint_proxy_program).0; + let mut buffer = vec![]; + buffer.extend_from_slice(®istry_signer.to_bytes()); + buffer.extend_from_slice(b"unversioned"); + buffer.extend_from_slice(&mint_proxy_program.to_bytes()); + let mut hasher = Hasher::default(); + hasher.hash(buffer.as_slice()); + let mint_proxy_authority = Pubkey::new(hasher.result().as_ref()); + + // mint_proxy_state + let mint_proxy_state = Pubkey::find_program_address( + &[b"SaberMintProxy", &mint_proxy_authority.to_bytes()], + &mint_proxy_program, + ) + .0; + + // minter info + let minter_info = + Pubkey::find_program_address(&[b"anchor", &redeemer.to_bytes()], &mint_proxy_program).0; + + for val in pools { + let json_farm: JsonSaberFarm = serde_json::from_value(val.clone()).unwrap(); + let name = get_saber_pool_name(&json_farm.tokens[0], &json_farm.tokens[1]); + if !remove_mode { + if config.skip_existing && client.get_farm(&name).is_ok() { + info!("Skipping existing Farm \"{}\"...", name); + continue; + } + info!("Writing Farm \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Farm \"{}\" from on-chain RefDB...", name); + client.remove_farm(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(farm) = client.get_farm(&name) { + (farm.refdb_index, farm.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let farm_token_name = get_saber_lp_token_name(&json_farm.lp_token.name); + if json_farm.tokens[0].address != val["swap"]["state"]["tokenA"]["mint"] + || json_farm.tokens[1].address != val["swap"]["state"]["tokenB"]["mint"] + { + panic!("Farm metadata mismatch"); + } + let farm = Farm { + name: str_to_as64(&name).unwrap(), + version: 1u16, + farm_type: FarmType::SingleReward, + official: true, + refdb_index: index, + refdb_counter: counter, + lp_token_ref: Some(client.get_token_ref(&farm_token_name).unwrap()), + reward_token_a_ref: Some(client.get_token_ref("SBR").unwrap()), + reward_token_b_ref: Some(client.get_token_ref("IOU").unwrap()), + router_program_id: router_id, + farm_program_id, + route: FarmRoute::Saber { + quarry: json_farm.quarry, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + iou_fees_account, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info, + }, + }; + + client.add_farm(config.keypair.as_ref(), farm).unwrap(); + } +} + +fn load_orca_farm( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let router_id = client.get_program_id(&"OrcaRouter".to_string()).unwrap(); + let farm_program_id = client.get_program_id(&"OrcaStake".to_string()).unwrap(); + let farms = parsed["farms"].as_array().unwrap(); + for val in farms { + let json_farm: JsonOrcaFarm = serde_json::from_value(val.clone()).unwrap(); + let upper_name = json_farm.name.to_uppercase().replace("_", "-"); + let lp_token_name = if upper_name.ends_with("-DD") { + "LP.ORC.".to_string() + &upper_name[..upper_name.len() - 3] + "-AQ" + } else if upper_name.ends_with("-AQ") { + "LP.ORC.".to_string() + &upper_name[..upper_name.len() - 3] + } else { + "LP.ORC.".to_string() + upper_name.as_str() + }; + let name = format!("ORC.{}-V1", upper_name); + if !remove_mode { + if config.skip_existing && client.get_farm(&name).is_ok() { + info!("Skipping existing Farm \"{}\"...", name); + continue; + } + info!("Writing Farm \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Farm \"{}\" from on-chain RefDB...", name); + client.remove_farm(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(farm) = client.get_farm(&name) { + (farm.refdb_index, farm.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let farm_data = client + .rpc_client + .get_account_data(&json_farm.address) + .unwrap(); + let farm_state = OrcaFarmState::unpack(&farm_data).unwrap(); + let farm = Farm { + name: str_to_as64(&name).unwrap(), + version: 1, + farm_type: FarmType::SingleReward, + official: true, + refdb_index: index, + refdb_counter: counter, + lp_token_ref: Some(client.get_token_ref(&lp_token_name).unwrap()), + reward_token_a_ref: Some(get_token_ref_with_mint( + client, + &json_farm.reward_token_mint, + )), + reward_token_b_ref: None, + router_program_id: router_id, + farm_program_id, + route: FarmRoute::Orca { + farm_id: json_farm.address, + farm_authority: Pubkey::find_program_address( + &[&json_farm.address.to_bytes()], + &farm_program_id, + ) + .0, + farm_token_ref: get_token_ref_with_mint(client, &json_farm.farm_token_mint), + base_token_vault: farm_state.base_token_vault, + reward_token_vault: farm_state.reward_token_vault, + }, + }; + + client.add_farm(config.keypair.as_ref(), farm).unwrap(); + } +} diff --git a/farms/farm-ctrl/src/loaders/mod.rs b/farms/farm-ctrl/src/loaders/mod.rs new file mode 100644 index 00000000000..af54b369453 --- /dev/null +++ b/farms/farm-ctrl/src/loaders/mod.rs @@ -0,0 +1,6 @@ +pub mod farm; +pub mod pool; +pub mod program; +pub mod token; +pub mod utils; +pub mod vault; diff --git a/farms/farm-ctrl/src/loaders/pool.rs b/farms/farm-ctrl/src/loaders/pool.rs new file mode 100644 index 00000000000..7a047408710 --- /dev/null +++ b/farms/farm-ctrl/src/loaders/pool.rs @@ -0,0 +1,497 @@ +//! Pools loader. + +use { + crate::{config::Config, loaders::utils::*}, + log::info, + serde::Deserialize, + serde_json::{json, Value}, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + git_token::GitToken, + pack::{optional_pubkey_deserialize, pubkey_deserialize}, + pool::{Pool, PoolRoute, PoolType}, + refdb::StorageType, + string::str_to_as64, + }, + solana_sdk::pubkey::Pubkey, + std::collections::HashMap, + std::str::FromStr, +}; + +#[derive(Deserialize, Debug)] +struct JsonRaydiumPool { + name: String, + coin: String, + pc: String, + lp: String, + version: u8, + #[serde(rename = "programId")] + program_id: String, + #[serde(rename = "ammId", deserialize_with = "pubkey_deserialize")] + amm_id: Pubkey, + #[serde(rename = "ammAuthority", deserialize_with = "pubkey_deserialize")] + amm_authority: Pubkey, + #[serde(rename = "ammOpenOrders", deserialize_with = "pubkey_deserialize")] + amm_open_orders: Pubkey, + #[serde(rename = "ammTargetOrders", deserialize_with = "pubkey_deserialize")] + amm_target_orders: Pubkey, + #[serde(rename = "ammQuantities", deserialize_with = "pubkey_deserialize")] + amm_quantities: Pubkey, + #[serde( + rename = "poolCoinTokenAccount", + deserialize_with = "pubkey_deserialize" + )] + pool_coin_token_account: Pubkey, + #[serde(rename = "poolPcTokenAccount", deserialize_with = "pubkey_deserialize")] + pool_pc_token_account: Pubkey, + #[serde(rename = "poolWithdrawQueue", deserialize_with = "pubkey_deserialize")] + pool_withdraw_queue: Pubkey, + #[serde( + rename = "poolTempLpTokenAccount", + deserialize_with = "pubkey_deserialize" + )] + pool_temp_lp_token_account: Pubkey, + #[serde(rename = "serumProgramId")] + serum_program_id: String, + #[serde(rename = "serumMarket", deserialize_with = "pubkey_deserialize")] + serum_market: Pubkey, + #[serde( + rename = "serumBids", + deserialize_with = "optional_pubkey_deserialize", + default + )] + serum_bids: Option, + #[serde( + rename = "serumAsks", + deserialize_with = "optional_pubkey_deserialize", + default + )] + serum_asks: Option, + #[serde( + rename = "serumEventQueue", + deserialize_with = "optional_pubkey_deserialize", + default + )] + serum_event_queue: Option, + #[serde( + rename = "serumCoinVaultAccount", + deserialize_with = "pubkey_deserialize" + )] + serum_coin_vault_account: Pubkey, + #[serde( + rename = "serumPcVaultAccount", + deserialize_with = "pubkey_deserialize" + )] + serum_pc_vault_account: Pubkey, + #[serde(rename = "serumVaultSigner", deserialize_with = "pubkey_deserialize")] + serum_vault_signer: Pubkey, + official: bool, +} + +#[derive(Deserialize, Debug)] +struct JsonSaberPool { + name: String, + tokens: Vec, + #[serde(rename = "lpToken")] + lp_token: GitToken, + #[serde(deserialize_with = "pubkey_deserialize")] + quarry: Pubkey, +} + +#[derive(Deserialize, Debug)] +pub struct JsonOrcaToken { + tag: String, + name: String, + #[serde(deserialize_with = "pubkey_deserialize")] + mint: Pubkey, + scale: u8, + #[serde(deserialize_with = "pubkey_deserialize")] + addr: Pubkey, +} + +#[derive(Deserialize, Debug)] +pub struct JsonOrcaPool { + pub name: String, + #[serde(deserialize_with = "pubkey_deserialize")] + pub address: Pubkey, + pub nonce: u8, + #[serde(deserialize_with = "pubkey_deserialize")] + pub authority: Pubkey, + #[serde(rename = "poolTokenMint", deserialize_with = "pubkey_deserialize")] + pub pool_token_mint: Pubkey, + #[serde(rename = "poolTokenDecimals")] + pub pool_token_decimals: u8, + #[serde(rename = "feeAccount", deserialize_with = "pubkey_deserialize")] + pub fee_account: Pubkey, + #[serde(rename = "tokenIds")] + pub token_ids: Vec, + pub tokens: HashMap, + #[serde(rename = "curveType")] + pub curve_type: u8, + #[serde(flatten)] + pub extra: HashMap, +} + +pub fn load(client: &FarmClient, config: &Config, data: &str, remove_mode: bool) { + let parsed: Value = serde_json::from_str(data).unwrap(); + let last_index = client + .get_refdb_last_index(&StorageType::Pool.to_string()) + .expect("Pool RefDB query error"); + + if parsed["name"] == "Raydium Pools" { + load_raydium_pool(client, config, remove_mode, &parsed, last_index); + } else if parsed["name"] == "Orca Pools" { + load_orca_pool(client, config, remove_mode, &parsed, last_index); + } else if parsed["pools"] != json!(null) && parsed["addresses"] != json!(null) { + load_saber_pool(client, config, remove_mode, &parsed, last_index); + } else { + panic!("Unsupported pools file"); + } +} + +fn load_raydium_pool( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let pools = parsed["pools"].as_array().unwrap(); + let router_id = client.get_program_id(&"RaydiumRouter".to_string()).unwrap(); + for val in pools { + let json_pool: JsonRaydiumPool = serde_json::from_value(val.clone()).unwrap(); + let name = format!( + "RDM.{}-V{}", + json_pool.name.to_uppercase(), + json_pool.version + ); + if !remove_mode { + if config.skip_existing && client.get_pool(&name).is_ok() { + info!("Skipping existing Pool \"{}\"...", name); + continue; + } + info!("Writing Pool \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Pool \"{}\" from on-chain RefDB...", name); + client.remove_pool(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(pool) = client.get_pool(&name) { + (pool.refdb_index, pool.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let pool = Pool { + name: str_to_as64(&name).unwrap(), + version: json_pool.version as u16, + pool_type: PoolType::Amm, + official: json_pool.official, + refdb_index: index, + refdb_counter: counter, + token_a_ref: Some( + client + .get_token_ref(&json_pool.coin.to_uppercase()) + .unwrap(), + ), + token_b_ref: Some(client.get_token_ref(&json_pool.pc.to_uppercase()).unwrap()), + lp_token_ref: Some(client.get_token_ref(&json_pool.lp.to_uppercase()).unwrap()), + token_a_account: Some(json_pool.pool_coin_token_account), + token_b_account: Some(json_pool.pool_pc_token_account), + router_program_id: router_id, + pool_program_id: convert_raydium_program_id(client, &json_pool.program_id), + route: PoolRoute::Raydium { + amm_id: json_pool.amm_id, + amm_authority: json_pool.amm_authority, + amm_open_orders: json_pool.amm_open_orders, + amm_target: if json_pool.version == 4 { + json_pool.amm_target_orders + } else { + json_pool.amm_quantities + }, + pool_withdraw_queue: json_pool.pool_withdraw_queue, + pool_temp_lp_token_account: json_pool.pool_temp_lp_token_account, + serum_program_id: convert_serum_program_id(client, &json_pool.serum_program_id), + serum_market: json_pool.serum_market, + serum_coin_vault_account: json_pool.serum_coin_vault_account, + serum_pc_vault_account: json_pool.serum_pc_vault_account, + serum_vault_signer: json_pool.serum_vault_signer, + serum_bids: json_pool.serum_bids, + serum_asks: json_pool.serum_asks, + serum_event_queue: json_pool.serum_event_queue, + }, + }; + + client.add_pool(config.keypair.as_ref(), pool).unwrap(); + } +} + +fn load_saber_pool( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let pools = parsed["pools"].as_array().unwrap(); + let router_id = client.get_program_id(&"SaberRouter".to_string()).unwrap(); + let decimal_wrapper_program = client + .get_program_id(&"SaberDecimalWrapper".to_string()) + .unwrap(); + for val in pools { + let json_pool: JsonSaberPool = serde_json::from_value(val.clone()).unwrap(); + let name = get_saber_pool_name(&json_pool.tokens[0], &json_pool.tokens[1]); + if !remove_mode { + if config.skip_existing && client.get_pool(&name).is_ok() { + info!("Skipping existing Pool \"{}\"...", name); + continue; + } + info!("Writing Pool \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Pool \"{}\" from on-chain RefDB...", name); + client.remove_pool(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(pool) = client.get_pool(&name) { + (pool.refdb_index, pool.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let pool_token_name = get_saber_lp_token_name(&json_pool.lp_token.name); + if json_pool.tokens[0].address != val["swap"]["state"]["tokenA"]["mint"] + || json_pool.tokens[1].address != val["swap"]["state"]["tokenB"]["mint"] + { + panic!("Pool metadata mismatch"); + } + + // check if there are Saber wrapped symbols + let token1_wrapped = is_saber_wrapped(&json_pool.tokens[0]); + let token2_wrapped = is_saber_wrapped(&json_pool.tokens[1]); + let symbol1 = normalize_name(&json_pool.tokens[0].symbol, false); + let symbol2 = normalize_name(&json_pool.tokens[1].symbol, false); + + let token_a_symbol = if token1_wrapped { + let symbol = extract_saber_wrapped_token_name(&symbol1); + if client.get_token(&symbol).unwrap().mint.to_string() + != json_pool.tokens[0].extra["extensions"]["assetContract"] + .as_str() + .unwrap() + { + panic!( + "Unwrapped token address mismatch for token {}", + json_pool.tokens[0].symbol + ); + } + symbol + } else { + symbol1.clone() + }; + + let token_b_symbol = if token2_wrapped { + let symbol = extract_saber_wrapped_token_name(&symbol2); + if client.get_token(&symbol).unwrap().mint.to_string() + != json_pool.tokens[1].extra["extensions"]["assetContract"] + .as_str() + .unwrap() + { + panic!( + "Unwrapped token address mismatch for token {}", + json_pool.tokens[1].symbol + ); + } + symbol + } else { + symbol2.clone() + }; + + // wrapped token refs + let wrapped_token_a_ref = if token1_wrapped { + Some(client.get_token_ref(&symbol1).unwrap()) + } else { + None + }; + let wrapped_token_b_ref = if token2_wrapped { + Some(client.get_token_ref(&symbol2).unwrap()) + } else { + None + }; + + // wrappers + let (decimal_wrapper_token_a, wrapped_token_a_vault) = if token1_wrapped { + let (a, b) = get_saber_wrappers(client, &json_pool.tokens[0].symbol, &token_a_symbol); + (Some(a), Some(b)) + } else { + (None, None) + }; + let (decimal_wrapper_token_b, wrapped_token_b_vault) = if token2_wrapped { + let (a, b) = get_saber_wrappers(client, &json_pool.tokens[1].symbol, &token_b_symbol); + (Some(a), Some(b)) + } else { + (None, None) + }; + + let pool = Pool { + name: str_to_as64(&name).unwrap(), + version: 1u16, + pool_type: PoolType::AmmStable, + official: true, + refdb_index: index, + refdb_counter: counter, + token_a_ref: Some( + client + .get_token_ref(&normalize_name(&token_a_symbol, false)) + .unwrap(), + ), + token_b_ref: Some( + client + .get_token_ref(&normalize_name(&token_b_symbol, false)) + .unwrap(), + ), + lp_token_ref: Some(client.get_token_ref(&pool_token_name).unwrap()), + token_a_account: Some(json_to_pubkey(&val["swap"]["state"]["tokenA"]["reserve"])), + token_b_account: Some(json_to_pubkey(&val["swap"]["state"]["tokenB"]["reserve"])), + router_program_id: router_id, + pool_program_id: json_to_pubkey(&val["swap"]["config"]["swapProgramID"]), + route: PoolRoute::Saber { + swap_account: json_to_pubkey(&val["swap"]["config"]["swapAccount"]), + swap_authority: json_to_pubkey(&val["swap"]["config"]["authority"]), + fees_account_a: json_to_pubkey(&val["swap"]["state"]["tokenA"]["adminFeeAccount"]), + fees_account_b: json_to_pubkey(&val["swap"]["state"]["tokenB"]["adminFeeAccount"]), + decimal_wrapper_program, + wrapped_token_a_ref, + wrapped_token_a_vault, + decimal_wrapper_token_a, + wrapped_token_b_ref, + wrapped_token_b_vault, + decimal_wrapper_token_b, + }, + }; + + client.add_pool(config.keypair.as_ref(), pool).unwrap(); + } +} + +fn load_orca_pool( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let pools = parsed["pools"].as_array().unwrap(); + let router_id = client.get_program_id(&"OrcaRouter".to_string()).unwrap(); + let pool_program_id = client.get_program_id(&"OrcaSwap".to_string()).unwrap(); + for val in pools { + let json_pool: JsonOrcaPool = serde_json::from_value(val.clone()).unwrap(); + let name = format!("ORC.{}-V1", json_pool.name.to_uppercase().replace("_", "-")); + if !remove_mode { + if config.skip_existing && client.get_pool(&name).is_ok() { + info!("Skipping existing Pool \"{}\"...", name); + continue; + } + info!("Writing Pool \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Pool \"{}\" from on-chain RefDB...", name); + client.remove_pool(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(pool) = client.get_pool(&name) { + (pool.refdb_index, pool.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let pool = Pool { + name: str_to_as64(&name).unwrap(), + version: 1, + pool_type: if json_pool.curve_type == 0 { + PoolType::Amm + } else { + PoolType::AmmStable + }, + official: true, + refdb_index: index, + refdb_counter: counter, + token_a_ref: Some(get_token_ref_with_mint( + client, + &convert_pubkey(&json_pool.token_ids[0]), + )), + token_b_ref: Some(get_token_ref_with_mint( + client, + &convert_pubkey(&json_pool.token_ids[1]), + )), + lp_token_ref: Some(get_token_ref_with_mint(client, &json_pool.pool_token_mint)), + token_a_account: Some(json_pool.tokens[&json_pool.token_ids[0]].addr), + token_b_account: Some(json_pool.tokens[&json_pool.token_ids[1]].addr), + router_program_id: router_id, + pool_program_id, + route: PoolRoute::Orca { + amm_id: json_pool.address, + amm_authority: json_pool.authority, + fees_account: json_pool.fee_account, + }, + }; + + client.add_pool(config.keypair.as_ref(), pool).unwrap(); + } +} + +fn get_saber_wrappers( + client: &FarmClient, + saber_symbol: &str, + original_symbol: &str, +) -> (Pubkey, Pubkey) { + let token = client.get_token(original_symbol).unwrap(); + let decimals = saber_symbol + .split('-') + .last() + .unwrap() + .parse::() + .unwrap(); + let decimal_wrapper_program = client + .get_program_id(&"SaberDecimalWrapper".to_string()) + .unwrap(); + + let wrapper = Pubkey::find_program_address( + &[b"anchor", &token.mint.to_bytes(), &[decimals]], + &decimal_wrapper_program, + ) + .0; + // wrapper_vault can be fetched with: + // async function fetch_wrapper_vault(wrapper_program, wrapper) { + // const idl = JSON.parse( + // require("fs").readFileSync( + // "./add_decimals_idl.json", "utf8" + // ) + // ); + // const programId = new anchor.web3.PublicKey(wrapper_program); + // const program = new anchor.Program(idl, programId); + // console.log( + // ( + // await program.account.wrappedToken.fetch(wrapper) + // ).wrapperUnderlyingTokens.toString() + // ); + // } + let wrapper_vault = match saber_symbol { + "swhETH-9" => "4fUL9yLbFZEuG32SaCjWqJXwDTBFNnipteBWxMvvFoC8", + "swFTT-9" => "5yugfArBAUZJJBUCRWPuiLyi6CWp1f67H9xgg3hcgSkx", + "srenBTC-10" => "764FaQrrREvNTpaH2yXyrPZgVBaXA7AXM8vyCaevXitD", + "srenBTC-9" => "C39Wq6X98TLcrnYCMkcHQhwUurkQMUdibUCpf2fVBDsm", + "srenLUNA-9" => "4R6PmC8BJcPDBsEMGpXpLCnFFkUZhEgZy6pMNtc2LqA4", + "sUSDC-8" => "AQhP39mE4o6BYNwnwYqnz7ZobkPBSLpCg8WvEESq1viZ", + "sUSDC-9" => "77XHXCWYQ76E9Q3uCuz1geTaxsqJZf9RfX5ZY7yyLDYt", + "sUSDT-9" => "BSTjdztBrsptuxfz9JHS31Wc9CknpLeL1wqZjeVs1Ths", + "sBTC-8" => "6hYDFhZ5ddfzoqaAbzRHm8mzG2MQzYQV9295sQHsvNBV", + "sBTC-9" => "B22gDMgN2tNWmvyzhb5tamJKanWcUUUw2zN3h3qjgQg8", + "sETH-8" => "4JWyJ4ZYsQ8uiYue2tTEqcHcFXrDuaQ1rsyjNFfrZm65", + "sFTT-9" => "H5tnZcfHCzHueNnfd6foeBBUUW4g7qXKt6rKzT7wg6oP", + _ => { + panic!("Unknown Saber wrapped token {}", saber_symbol); + } + }; + (wrapper, Pubkey::from_str(wrapper_vault).unwrap()) +} diff --git a/farms/farm-ctrl/src/loaders/program.rs b/farms/farm-ctrl/src/loaders/program.rs new file mode 100644 index 00000000000..123f54cfec8 --- /dev/null +++ b/farms/farm-ctrl/src/loaders/program.rs @@ -0,0 +1,57 @@ +//! Program IDs loader. + +use { + crate::config::Config, + log::info, + serde::Deserialize, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{id::ProgramIDType, pack::pubkey_deserialize}, + solana_sdk::pubkey::Pubkey, +}; + +#[derive(Deserialize, Debug)] +struct JsonProgram { + name: String, + description: String, + program_type: ProgramIDType, + #[serde(deserialize_with = "pubkey_deserialize")] + address: Pubkey, +} + +#[derive(Deserialize, Debug)] +struct JsonPrograms { + name: String, + timestamp: String, + programs: Vec, +} + +pub fn load(client: &FarmClient, config: &Config, data: &str, remove_mode: bool) { + let parsed: JsonPrograms = serde_json::from_str(data).unwrap(); + + for program in parsed.programs.iter() { + if remove_mode { + info!( + "Removing Program \"{}\" from on-chain RefDB...", + program.name + ); + client + .remove_program_id(config.keypair.as_ref(), &program.name) + .unwrap(); + } else { + if config.skip_existing && client.get_program_id(&program.name).is_ok() { + info!("Skipping existing Program \"{}\"...", program.name); + continue; + } + info!("Writing Program \"{}\" to on-chain RefDB...", program.name); + client + .add_program_id( + config.keypair.as_ref(), + &program.name, + &program.address, + program.program_type, + None, + ) + .unwrap(); + } + } +} diff --git a/farms/farm-ctrl/src/loaders/token.rs b/farms/farm-ctrl/src/loaders/token.rs new file mode 100644 index 00000000000..7367f9531b7 --- /dev/null +++ b/farms/farm-ctrl/src/loaders/token.rs @@ -0,0 +1,270 @@ +//! Tokens loader. + +use { + crate::{ + config::Config, + loaders::{farm::JsonOrcaFarm, pool::JsonOrcaPool, utils::*}, + }, + log::info, + serde::Deserialize, + serde_json::Value, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + git_token::GitToken, + pack::{as64_deserialize, pubkey_deserialize}, + refdb::StorageType, + string::{str_to_as64, ArrayString64}, + token::Token, + token::TokenType, + }, + solana_sdk::pubkey::Pubkey, + std::collections::HashMap, +}; + +#[derive(Deserialize, Debug)] +struct JsonRaydiumLPToken { + #[serde(deserialize_with = "as64_deserialize")] + symbol: ArrayString64, + #[serde(deserialize_with = "as64_deserialize")] + name: ArrayString64, + coin: String, + pc: String, + #[serde(rename = "mintAddress", deserialize_with = "pubkey_deserialize")] + mint_address: Pubkey, + decimals: u8, +} + +pub fn load(client: &FarmClient, config: &Config, data: &str, remove_mode: bool) { + let parsed: Value = serde_json::from_str(data).unwrap(); + let last_index = client + .get_refdb_last_index(&StorageType::Token.to_string()) + .expect("Token RefDB query error"); + let is_saber = parsed["name"] == "Saber Tokens"; + + if parsed["name"] == "Solana Token List" || is_saber { + load_solana_tokens(client, config, remove_mode, &parsed, last_index); + } else if parsed["name"] == "Raydium LP Tokens" { + load_raydium_tokens(client, config, remove_mode, &parsed, last_index); + } else if parsed["name"] == "Orca Pools" { + load_orca_pool_tokens(client, config, remove_mode, &parsed, last_index); + } else if parsed["name"] == "Orca Farms" { + load_orca_farm_tokens(client, config, remove_mode, &parsed, last_index); + } else { + panic!("Unsupported tokens file"); + } +} + +fn load_solana_tokens( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let is_saber = parsed["name"] == "Saber Tokens"; + let tokens = parsed["tokens"].as_array().unwrap(); + for val in tokens { + let git_token: GitToken = serde_json::from_value(val.clone()).unwrap(); + let token_type = if git_token.symbol.to_uppercase() == "SOL" { + TokenType::WrappedSol + } else { + get_token_type_from_tags(&git_token.tags) + }; + let name = if is_saber && token_type == TokenType::LpToken { + "LP.SBR.".to_string() + + &normalize_name(git_token.name.split(' ').collect::>()[0], true) + } else if token_type == TokenType::VtToken { + git_token.symbol + } else { + normalize_name(&git_token.symbol, false) + }; + + if git_token.chain_id != 101 || (token_type == TokenType::LpToken && !is_saber) { + continue; + } + if !remove_mode { + if config.skip_existing && client.get_token(&name).is_ok() { + info!("Skipping existing Token \"{}\"...", name); + continue; + } + info!("Writing Token \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Token \"{}\" from on-chain RefDB...", name); + client.remove_token(config.keypair.as_ref(), &name).unwrap(); + continue; + } + let (index, counter) = if let Ok(token) = client.get_token(&name) { + (token.refdb_index, token.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let token = Token { + name: str_to_as64(&name).unwrap(), + description: str_to_as64(&git_token.name).unwrap(), + token_type, + refdb_index: index, + refdb_counter: counter, + decimals: git_token.decimals as u8, + chain_id: git_token.chain_id as u16, + mint: convert_pubkey(&git_token.address), + }; + + client.add_token(config.keypair.as_ref(), token).unwrap(); + } +} + +fn load_raydium_tokens( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let tokens: HashMap = + serde_json::from_value(parsed["tokens"].clone()).unwrap(); + for (symbol, token) in tokens.iter() { + let name = "LP.RDM.".to_string() + &normalize_name(symbol, true); + if !remove_mode { + if config.skip_existing && client.get_token(&name).is_ok() { + info!("Skipping existing Token \"{}\"...", name); + continue; + } + info!("Writing Token \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Token \"{}\" from on-chain RefDB...", name); + let _ = client.remove_token(config.keypair.as_ref(), &name); + continue; + } + let (index, counter) = if let Ok(token) = client.get_token(&name) { + (token.refdb_index, token.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let token = Token { + name: str_to_as64(&name).unwrap(), + description: token.name, + token_type: TokenType::LpToken, + refdb_index: index, + refdb_counter: counter, + decimals: token.decimals, + chain_id: 101u16, + mint: token.mint_address, + }; + + client.add_token(config.keypair.as_ref(), token).unwrap(); + } +} + +fn load_orca_pool_tokens( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let pools = parsed["pools"].as_array().unwrap(); + for val in pools { + let json_pool: JsonOrcaPool = serde_json::from_value(val.clone()).unwrap(); + let name = "LP.ORC.".to_string() + &json_pool.name.to_uppercase().replace("_", "-"); + if !remove_mode { + if config.skip_existing && client.get_token(&name).is_ok() { + info!("Skipping existing Token \"{}\"...", name); + continue; + } + info!("Writing Token \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Token \"{}\" from on-chain RefDB...", name); + let _ = client.remove_token(config.keypair.as_ref(), &name); + continue; + } + let (index, counter) = if let Ok(token) = client.get_token(&name) { + (token.refdb_index, token.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let token = Token { + name: str_to_as64(&name).unwrap(), + description: str_to_as64(format!("Orca {} LP Token", json_pool.name).as_str()).unwrap(), + token_type: TokenType::LpToken, + refdb_index: index, + refdb_counter: counter, + decimals: json_pool.pool_token_decimals, + chain_id: 101u16, + mint: json_pool.pool_token_mint, + }; + + client.add_token(config.keypair.as_ref(), token).unwrap(); + } +} + +fn load_orca_farm_tokens( + client: &FarmClient, + config: &Config, + remove_mode: bool, + parsed: &Value, + last_index: u32, +) { + let mut last_index = last_index; + let farms = parsed["farms"].as_array().unwrap(); + for val in farms { + let json_farm: JsonOrcaFarm = serde_json::from_value(val.clone()).unwrap(); + let name = "LP.ORC.".to_string() + &json_farm.name.to_uppercase().replace("_", "-"); + if !remove_mode { + if config.skip_existing && client.get_token(&name).is_ok() { + info!("Skipping existing Token \"{}\"...", name); + continue; + } + info!("Writing Token \"{}\" to on-chain RefDB...", name); + } else { + info!("Removing Token \"{}\" from on-chain RefDB...", name); + let _ = client.remove_token(config.keypair.as_ref(), &name); + continue; + } + let (index, counter) = if let Ok(token) = client.get_token(&name) { + (token.refdb_index, token.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let token = Token { + name: str_to_as64(&name).unwrap(), + description: str_to_as64(format!("Orca {} Farm LP Token", json_farm.name).as_str()) + .unwrap(), + token_type: TokenType::LpToken, + refdb_index: index, + refdb_counter: counter, + decimals: json_farm.base_token_decimals, + chain_id: 101u16, + mint: json_farm.farm_token_mint, + }; + + client.add_token(config.keypair.as_ref(), token).unwrap(); + } +} + +fn get_token_type_from_tags(tags: &[String]) -> TokenType { + if tags.contains(&String::from("Solana tokenized")) { + TokenType::WrappedSol + } else if tags.contains(&String::from("wrapped-sollet")) { + TokenType::WrappedSollet + } else if tags.contains(&String::from("wrapped")) + || tags.contains(&String::from("wormhole-v1")) + || tags.contains(&String::from("wormhole-v2")) + { + TokenType::WrappedWarmhole + } else if tags.contains(&String::from("lp-token")) + || tags.contains(&String::from("saber-stableswap-lp")) + { + TokenType::LpToken + } else if tags.contains(&String::from("vt-token")) { + TokenType::VtToken + } else { + TokenType::SplToken + } +} diff --git a/farms/farm-ctrl/src/loaders/utils.rs b/farms/farm-ctrl/src/loaders/utils.rs new file mode 100644 index 00000000000..88718ba58c8 --- /dev/null +++ b/farms/farm-ctrl/src/loaders/utils.rs @@ -0,0 +1,122 @@ +//! Common helpers. + +use { + serde_json::Value, solana_farm_client::client::FarmClient, + solana_farm_sdk::git_token::GitToken, solana_sdk::pubkey::Pubkey, std::str::FromStr, +}; + +pub fn convert_raydium_program_id(client: &FarmClient, program_id: &str) -> Pubkey { + match program_id { + "LIQUIDITY_POOL_PROGRAM_ID_V2" => client.get_program_id(&"RaydiumV2".to_string()).unwrap(), + "LIQUIDITY_POOL_PROGRAM_ID_V3" => client.get_program_id(&"RaydiumV3".to_string()).unwrap(), + "LIQUIDITY_POOL_PROGRAM_ID_V4" => client.get_program_id(&"RaydiumV4".to_string()).unwrap(), + "STAKE_PROGRAM_ID" => client.get_program_id(&"RaydiumStake".to_string()).unwrap(), + "STAKE_PROGRAM_ID_V4" => client + .get_program_id(&"RaydiumStakeV4".to_string()) + .unwrap(), + "STAKE_PROGRAM_ID_V5" => client + .get_program_id(&"RaydiumStakeV5".to_string()) + .unwrap(), + _ => convert_pubkey(program_id), + } +} + +pub fn convert_serum_program_id(client: &FarmClient, program_id: &str) -> Pubkey { + match program_id { + "SERUM_PROGRAM_ID_V2" => client.get_program_id(&"SerumV2".to_string()).unwrap(), + "SERUM_PROGRAM_ID_V3" => client.get_program_id(&"SerumV3".to_string()).unwrap(), + _ => convert_pubkey(program_id), + } +} + +pub fn convert_pubkey(pubkey_as_string: &str) -> Pubkey { + Pubkey::from_str(pubkey_as_string).unwrap_or_else(|_| { + panic!( + "Failed to convert the string to pubkey {}", + pubkey_as_string + ) + }) +} + +#[allow(dead_code)] +pub fn convert_optional_pubkey(pubkey_as_string: &str) -> Option { + if pubkey_as_string.is_empty() { + None + } else { + Some(Pubkey::from_str(pubkey_as_string).unwrap_or_else(|_| { + panic!( + "Failed to convert the string to pubkey {}", + pubkey_as_string + ) + })) + } +} + +pub fn json_to_pubkey(input: &Value) -> Pubkey { + if let Ok(pubkey) = Pubkey::from_str(input.as_str().unwrap()) { + return pubkey; + } + panic!("Failed to convert the input to a pubkey: {}", input); +} + +pub fn normalize_name(name: &str, allow_dashes: bool) -> String { + if allow_dashes { + name.to_uppercase() + .replace(" ", "_") + .replace("/", "_") + .replace(".", "_") + } else { + name.to_uppercase() + .replace(" ", "_") + .replace("/", "_") + .replace(".", "_") + .replace("-", "_") + } +} + +pub fn get_saber_lp_token_name(lp_token: &str) -> String { + "LP.SBR.".to_string() + &normalize_name(lp_token.split(' ').collect::>()[0], true) +} + +pub fn extract_saber_wrapped_token_name(name: &str) -> String { + if name.len() > 3 + && (&name[..1] == "s" || &name[..1] == "S") + && vec!["_8", "_9", "10"].contains(&&name[name.len() - 2..]) + { + name.split('_').collect::>()[0][1..].to_string() + } else { + panic!("Unexpected Saber wrapped token name {}", name); + } +} + +pub fn is_saber_wrapped(token: &GitToken) -> bool { + token.symbol.len() > 3 && token.tags.contains(&String::from("saber-decimal-wrapped")) +} + +pub fn get_saber_pool_name(token1: &GitToken, token2: &GitToken) -> String { + let token1_symbol_norm = normalize_name(&token1.symbol, false); + let token2_symbol_norm = normalize_name(&token2.symbol, false); + let token1_name = if is_saber_wrapped(token1) { + extract_saber_wrapped_token_name(&token1_symbol_norm) + } else { + token1_symbol_norm + }; + let token2_name = if is_saber_wrapped(token2) { + extract_saber_wrapped_token_name(&token2_symbol_norm) + } else { + token2_symbol_norm + }; + format!("SBR.{}-{}-V1", token1_name, token2_name) +} + +pub fn get_token_ref_with_mint(client: &FarmClient, token_mint: &Pubkey) -> Pubkey { + client + .get_token_ref( + client + .get_token_with_mint(token_mint) + .unwrap() + .name + .as_str(), + ) + .unwrap() +} diff --git a/farms/farm-ctrl/src/loaders/vault.rs b/farms/farm-ctrl/src/loaders/vault.rs new file mode 100644 index 00000000000..db64ad6445e --- /dev/null +++ b/farms/farm-ctrl/src/loaders/vault.rs @@ -0,0 +1,68 @@ +//! Vaults loader. + +use { + crate::config::Config, + log::info, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{refdb::StorageType, vault::Vault}, +}; + +pub fn load(client: &FarmClient, config: &Config, data: &str, remove_mode: bool) { + let parsed: serde_json::Value = serde_json::from_str(data).unwrap(); + let mut last_index = client + .get_refdb_last_index(&StorageType::Vault.to_string()) + .expect("Vault RefDB query error"); + + if parsed["name"] != "Solana Vaults List" { + panic!("Unsupported vaults file"); + } + let vaults = parsed["vaults"].as_array().unwrap(); + for val in vaults { + let json_vault: Vault = serde_json::from_value(val.clone()).unwrap(); + if !remove_mode { + if config.skip_existing && client.get_vault(&json_vault.name).is_ok() { + info!("Skipping existing Vault \"{}\"...", json_vault.name); + continue; + } + info!("Writing Vault \"{}\" to on-chain RefDB...", json_vault.name); + } else { + info!( + "Removing Vault \"{}\" from on-chain RefDB...", + json_vault.name + ); + client + .remove_vault(config.keypair.as_ref(), &json_vault.name.to_string()) + .unwrap(); + continue; + } + let (index, counter) = if let Ok(vault) = client.get_vault(&json_vault.name.to_string()) { + (vault.refdb_index, vault.refdb_counter) + } else { + last_index += 1; + (Some(last_index - 1), 0u16) + }; + let vault = Vault { + name: json_vault.name, + version: json_vault.version as u16, + vault_type: json_vault.vault_type, + official: json_vault.official, + refdb_index: index, + refdb_counter: counter, + metadata_bump: json_vault.metadata_bump, + authority_bump: json_vault.authority_bump, + vault_token_bump: json_vault.vault_token_bump, + lock_required: json_vault.lock_required, + unlock_required: json_vault.unlock_required, + vault_program_id: json_vault.vault_program_id, + vault_authority: json_vault.vault_authority, + vault_token_ref: json_vault.vault_token_ref, + info_account: json_vault.info_account, + admin_account: json_vault.admin_account, + fees_account_a: json_vault.fees_account_a, + fees_account_b: json_vault.fees_account_b, + strategy: json_vault.strategy, + }; + + client.add_vault(config.keypair.as_ref(), vault).unwrap(); + } +} diff --git a/farms/farm-ctrl/src/main.rs b/farms/farm-ctrl/src/main.rs new file mode 100644 index 00000000000..a932b6ce087 --- /dev/null +++ b/farms/farm-ctrl/src/main.rs @@ -0,0 +1,216 @@ +//! Solana Farms control interface. + +mod config; +mod generate; +mod get; +mod governance; +mod load; +mod loaders; +mod print; +mod refdb; +mod remove; +mod vault; + +use { + log::error, solana_farm_client::client::FarmClient, solana_sdk::pubkey::Pubkey, + std::str::FromStr, +}; + +fn main() { + let matches = config::get_clap_app(solana_version::version!()).get_matches(); + + // set log verbosity level + let log_level = "solana=".to_string() + matches.value_of("log_level").unwrap(); + solana_logger::setup_with_default(log_level.as_str()); + + // load config params + let config = config::Config::new(&matches); + let client = FarmClient::new_with_commitment(&config.farm_client_url, config.commitment); + + // parse commands + match matches.subcommand() { + ("init", Some(subcommand_matches)) => { + refdb::init(&client, &config, config::get_target(subcommand_matches)); + } + ("init-all", Some(_subcommand_matches)) => { + refdb::init_all(&client, &config); + } + ("drop", Some(subcommand_matches)) => { + refdb::drop(&client, &config, config::get_target(subcommand_matches)); + } + ("drop-all", Some(_subcommand_matches)) => { + refdb::drop_all(&client, &config); + } + ("load", Some(subcommand_matches)) => { + load::load( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_filename(subcommand_matches), + false, + ); + } + ("load-all", Some(subcommand_matches)) => { + load::load( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_filename(subcommand_matches), + false, + ); + } + ("remove", Some(subcommand_matches)) => { + remove::remove( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_objectname(subcommand_matches), + ); + } + ("remove-all", Some(subcommand_matches)) => { + remove::remove_all(&client, &config, config::get_target(subcommand_matches)); + } + ("remove-all-with-file", Some(subcommand_matches)) => { + load::load( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_filename(subcommand_matches), + true, + ); + } + ("get", Some(subcommand_matches)) => { + get::get( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_objectname(subcommand_matches), + ); + } + ("get-ref", Some(subcommand_matches)) => { + get::get_ref( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_objectname_raw(subcommand_matches), + ); + } + ("get-all", Some(subcommand_matches)) => { + get::get_all(&client, &config, config::get_target(subcommand_matches)); + } + ("list-all", Some(subcommand_matches)) => { + get::list_all(&client, &config, config::get_target(subcommand_matches)); + } + ("vault-init", Some(subcommand_matches)) => { + vault::init( + &client, + &config, + &config::get_vaultname(subcommand_matches), + config::get_step(subcommand_matches), + ); + } + ("vault-shutdown", Some(subcommand_matches)) => { + vault::shutdown(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("vault-crank", Some(subcommand_matches)) => { + vault::crank( + &client, + &config, + &config::get_vaultname(subcommand_matches), + config::get_step(subcommand_matches), + ); + } + ("vault-crank-all", Some(subcommand_matches)) => { + vault::crank_all(&client, &config, config::get_step(subcommand_matches)); + } + ("vault-set-fee", Some(subcommand_matches)) => { + vault::set_fee( + &client, + &config, + &config::get_vaultname(subcommand_matches), + config::get_vaultparam(subcommand_matches) as f32, + ); + } + ("vault-set-external-fee", Some(subcommand_matches)) => { + vault::set_external_fee( + &client, + &config, + &config::get_vaultname(subcommand_matches), + config::get_vaultparam(subcommand_matches) as f32, + ); + } + ("vault-set-min-crank-interval", Some(subcommand_matches)) => { + vault::set_min_crank_interval( + &client, + &config, + &config::get_vaultname(subcommand_matches), + config::get_vaultparam(subcommand_matches) as u32, + ); + } + ("vault-disable-deposit", Some(subcommand_matches)) => { + vault::disable_deposit(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("vault-enable-deposit", Some(subcommand_matches)) => { + vault::enable_deposit(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("vault-disable-withdrawal", Some(subcommand_matches)) => { + vault::disable_withdrawal(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("vault-enable-withdrawal", Some(subcommand_matches)) => { + vault::enable_withdrawal(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("vault-get-info", Some(subcommand_matches)) => { + vault::get_info(&client, &config, &config::get_vaultname(subcommand_matches)); + } + ("print-pda", Some(subcommand_matches)) => { + print::print_pda(&client, &config, config::get_target(subcommand_matches)); + } + ("print-pda-all", Some(_subcommand_matches)) => { + print::print_pda_all(&client, &config); + } + ("print-size", Some(subcommand_matches)) => { + print::print_size(&client, &config, config::get_target(subcommand_matches)); + } + ("print-size-all", Some(_subcommand_matches)) => { + print::print_size_all(&client, &config); + } + ("generate", Some(subcommand_matches)) => { + generate::generate( + &client, + &config, + config::get_target(subcommand_matches), + &config::get_objectname_raw(subcommand_matches), + &subcommand_matches + .value_of("param1") + .unwrap() + .parse::() + .unwrap(), + &subcommand_matches + .value_of("param2") + .unwrap() + .parse::() + .unwrap(), + ); + } + ("governance", Some(subcommand_matches)) => match subcommand_matches.subcommand() { + ("init", Some(subcommand_matches)) => { + let address_str = subcommand_matches + .value_of("governance-program-address") + .unwrap(); + let dao_address = Pubkey::from_str(address_str).unwrap(); + governance::init( + &client, + &config, + &dao_address, + subcommand_matches + .value_of("mint-ui-amount") + .unwrap() + .parse() + .unwrap(), + ); + } + _ => unreachable!(), + }, + _ => error!("Unrecognized command. Use --help to list known commands."), + }; +} diff --git a/farms/farm-ctrl/src/metadata/farms/orca/farms.json b/farms/farm-ctrl/src/metadata/farms/orca/farms.json new file mode 100644 index 00000000000..997a0cc0fd3 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/farms/orca/farms.json @@ -0,0 +1,878 @@ +{ + "name": "Orca Farms", + "farms": [ + { + "name": "SOL_USDC_AQ", + "address": "85HrPbJtrN82aeB74WTwoFxcNgmf5aDNP2ENngbDpd5G", + "farmTokenMint": "FFdjrSvNALfdgxANNpt3x85WpeVMdQSH5SEP2poM8fcK", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "APDFRM3HMr8CAGXwKHiu2f5ePSpaiEJhaURwhsRrUUt9", + "baseTokenDecimals": 6 + }, + { + "name": "SOL_USDT_AQ", + "address": "4RRRJkscV2DmwJUxTQgRdYock75GfwYJn7LTxy9rGTmY", + "farmTokenMint": "71vZ7Jvu8fTyFzpX399dmoSovoz24rVbipLrRn2wBNzW", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "FZthQCuYHhcfiDma7QrX7buDHwrZEd7vL8SjS6LQa3Tx", + "baseTokenDecimals": 6 + }, + { + "name": "ETH_SOL_AQ", + "address": "3ARgavt1NhqLmJWj3wAJy6XBarG6pJbEKRv1wzzRbbaN", + "farmTokenMint": "CGFTRh4jKLPbS9r4hZtbDfaRuC7qcA8rZpbLnVTzJBer", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "71FymgN2ZUf7VvVTLE8jYEnjP3jSK1Frp2XT1nHs8Hob", + "baseTokenDecimals": 6 + }, + { + "name": "ETH_USDC_AQ", + "address": "FpezTR76RRjgpBb9HhR6ap8BgQfkHyNMQSqJDcoXpjAb", + "farmTokenMint": "HDP2AYFmvLz6sWpoSuNS62JjvW4HjMKp7doXucqpWN56", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "3e1W6Aqcbuk2DfHUwRiRcyzpyYRRjg6yhZZcyEARydUX", + "baseTokenDecimals": 6 + }, + { + "name": "RAY_SOL_AQ", + "address": "B1aByG1fU5yUgQT2EtJrp96SC4tJhnipzdXqHx2CXRgj", + "farmTokenMint": "AUkn5f4N4TqPA5BiWirTDHWnG3SePfmeDpDqrFmhSgKb", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "5kimD5W6yJpHRHCyPtnEyDsQRdiiJKivu5AqN3si82Jc", + "baseTokenDecimals": 6 + }, + { + "name": "ROPE_SOL_AQ", + "address": "NFsHa28zxuLnncHme7iLx8TXWV8ypyxPvEPaQh29zDh", + "farmTokenMint": "xpPyQwQ1HXHyEpvFGyTQRLY6rmj6jtAdEgLMV5uoz4m", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "ADrvfPBsRcJfGsN6Bs385zYddH52nuM5FA8UaAkX9o2V", + "baseTokenDecimals": 6 + }, + { + "name": "STEP_SOL_AQ", + "address": "DGtiR1LWNPGV9A5byh7EjAoh9NC2cW5YDcmxCpUJosD2", + "farmTokenMint": "GwrBA1F8rGummDCDd8NY9Eu1cLNuJqbT8WaGxgWpFwGL", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "8nTzqDXHriG2CXKbybeuEh1EqDQMtrbYMFWcP7AkiDaP", + "baseTokenDecimals": 6 + }, + { + "name": "SRM_SOL_AQ", + "address": "5CQVdPpaW95X1atyfqunZf7eBE1rhQBXMfgJ1wRVF1p1", + "farmTokenMint": "D659zwnbeTgquChbaWC3KDHrkYoqMuz1doGLHTFaqTtD", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "9tf8rBSEQYG7AqL896fN2nZi1iYPqpWaLEdpbeQaC1Vy", + "baseTokenDecimals": 6 + }, + { + "name": "FTT_SOL_AQ", + "address": "GzZE2CEemaTGNtRSYs9sArAJFuJi5kwsPJFyz5puYhsj", + "farmTokenMint": "9r9BcPwCon96P5Y6JSdRAog7Uknz9p9GrnuHm4VzuB9k", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "EsYaDKJCmcJtJHFuJYwQZwqohvVMCrFzcg8yo3i328No", + "baseTokenDecimals": 6 + }, + { + "name": "COPE_SOL_AQ", + "address": "CXYEVaqtDs4R9Bpeu4xYf8KT8S6nYis6VWkDEh9Mu1FS", + "farmTokenMint": "7CT19h7n2YBKiCFCaxXqMM79jNM4cmUvjXhNMjJNRYa", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "CzieDbGRdN1QGaGDNpSqzEA18bi881ccvkkGZi51pe1k", + "baseTokenDecimals": 6 + }, + { + "name": "OXY_SOL_AQ", + "address": "ALmRst1DksKXVfY64KXq7pJUUG5PP56kwY18ijVDGAsz", + "farmTokenMint": "G48RkwsNYd3A4rBfuQhCswr9YUE63fFmZGyhgH95dq3S", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "7tYCdLN84EnTMkxM7HNamWJx7F4xgKe9KiiWvLyWjbgT", + "baseTokenDecimals": 6 + }, + { + "name": "BTC_SOL_AQ", + "address": "EeG5AAnFS56AveUrKexzivQeUdWhm6zYq6ubn6fLMCQa", + "farmTokenMint": "GxmjQZvgwNCh3QSRNB8CPED81hzySem62PDDuMp4B379", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Acxs19v6eUMTEfdvkvWkRB4bwFCHm3XV9jABCy7c1mXe", + "baseTokenDecimals": 6 + }, + { + "name": "MER_SOL_AQ", + "address": "8gJggSfM35JpfvXmEsuJoHPLB8D6br15MEWEWdQF81d6", + "farmTokenMint": "CrKVRnH6iGbFXxEnXMn3Emwv3Fe7VwxEqpA8zNbwsgkH", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "HiwRobjfHZ4zsPtqCC4oBS24pSmy4t8GGkXRbQj4yU6L", + "baseTokenDecimals": 6 + }, + { + "name": "FIDA_SOL_AQ", + "address": "4X3U1tpfiwQ5zPPd4oQSPcu3UK7nMSUWyAXxCmcSA6qP", + "farmTokenMint": "4geGcEfgVjzJGZAaT8iTicPm1XLDPjdSpVhtA99sZ7jX", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "EYsNdtyu4gGTaGz8N5m5iQ3G1N6rDyMbR72B3CqbWW4W", + "baseTokenDecimals": 6 + }, + { + "name": "MAPS_SOL_AQ", + "address": "Gobr8FhZDtn8yxkFQDND3KmfuwZseWEuyZ3xpD56hk1i", + "farmTokenMint": "7Dy84zJNHzEM9335BrtFjCuunt2VgxJ7KBT6PJarxKMq", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "99pfC8fWymXgbq3CvrExhx3UxQDC1fMWEWLbNT83F45e", + "baseTokenDecimals": 6 + }, + { + "name": "USDC_USDT_AQ", + "address": "5psKJrxWnPmoAbCxk3An2CGh7wHAX2cWddf5vZuYbbVw", + "farmTokenMint": "GjpXgKwn4VW4J2pZdS3dovM58hiXWLJtopTfqG83zY2f", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "H2uzgruPvonVpCRhwwdukcpXK8TG17swFNzYFr2rtPxy", + "baseTokenDecimals": 6 + }, + { + "name": "ORCA_SOL_AQ", + "address": "F6pi7SyXWx56fP96mYQ4Yfh4yZ7oGNtDjwSYHT5Mz7Ld", + "farmTokenMint": "B5waaKnsmtqFawPspUwcuy1cRjAC7u2LrHSwxPSxK4sZ", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "2uVjAuRXavpM6h1scGQaxqb6HVaNRn6T2X7HHXTabz25", + "baseTokenDecimals": 6 + }, + { + "name": "ORCA_USDC_AQ", + "address": "9S1BsxbDNQXQccjFamVEGgxiYQHTeudvhEYwFr4oWeaf", + "farmTokenMint": "Gc7W5U66iuHQcC1cQyeX9hxkPF2QUVJPTf1NWbW8fNrt", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "n8Mpu28RjeYD7oUX3LG1tPxzhRZh3YYLRSHcHRdS3Zx", + "baseTokenDecimals": 6 + }, + { + "name": "KIN_SOL_AQ", + "address": "9XTA3t3X8KaeLgkv7XuDWATo4oDwBnTZqjnSPcE5ijYK", + "farmTokenMint": "7Ho3ht7krdFELBcPAsGXFfQMyG4PUvYSfpz4aNBRP3Ek", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "HEvnD66WcBfTajS9adUYnGRBMDehFtLySiFHSD6kEBWs", + "baseTokenDecimals": 6 + }, + { + "name": "SAMO_SOL_AQ", + "address": "98EDd1L47pdW1F7ne82dyFXpfhVdEcrCqUF1dYrWLFZi", + "farmTokenMint": "CNf8gZtLahBWxKe3YwsqywLHMTewGqvq6pJ5ecg3cTYU", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "D6N9j8F2DhtzDtrdpT74y3u2YmYAzcggiLc3nTjqux9M", + "baseTokenDecimals": 6 + }, + { + "name": "LIQ_USDC_AQ", + "address": "z2WHNcBJTZqbK5wCuBPtswEq2614T6si1cJmog7vFAL", + "farmTokenMint": "57vGdcMZLnbNr4TZ4hgrpGJZGR9vTPhu8L9bNKDrqxKT", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "3PD9SZFwXKkXr4akLf4ofo37ZUMycwML89R2P3qxcbZG", + "baseTokenDecimals": 6 + }, + { + "name": "SNY_USDC_AQ", + "address": "7qAQZxYQRcmHmz3Wi9NeieujwLSd3N41M4dJipgSCNsB", + "farmTokenMint": "6Qw5Gzf1TkM3YRe7Dh6yMVMo2wnJxRiCUBP8abTTn9Yg", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "AZpo4BJHHRetF96v6SGinFZBMXM4yWMo4RA8C4PriDLk", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_USDC_AQ", + "address": "EvtMzreDMq1U8ytV5fEmfoWNfPhrjZ87za835GuRvZCc", + "farmTokenMint": "5r3vDsNTGXXb9cGQfqyNuYD2bjhRPymGJBfDmKosR9Ev", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "8PSfyiTVwPb6Rr2iZ8F3kNpbg65BCfJM9v8LfB916r44", + "baseTokenDecimals": 6 + }, + { + "name": "SLRS_USDC_AQ", + "address": "5XXw91d4HoLjqhUhXmUcdkCDtQyXvQkLJMUcYJkKn5Dx", + "farmTokenMint": "66xCxkffQZKBZLiHV3PDcfR8ANJTfnDRxPCaBdv4wxB7", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "AtB4nUmdyQfuWWJ9xAHw9xyVnJFfSjSuVWkiYan8y86w", + "baseTokenDecimals": 6 + }, + { + "name": "PORT_USDC_AQ", + "address": "FpEb4eEqhnPBAA2RNvsSe8baCoev4mSNQjxKPGvx5Gjv", + "farmTokenMint": "4CGxvZdwiZgVMLXiTdJHTkJRUTpTSJCtmtCRbSkAxerE", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "F8gPSpwVHj8FdAJAYULDuZBxFEJut87hUbARYYx3471w", + "baseTokenDecimals": 6 + }, + { + "name": "SBR_USDC_AQ", + "address": "EWRNKpLc4Y8r6Aur9mo8GtBYatqfoML9A7bfEmDv2JD4", + "farmTokenMint": "Cum6sRPGpWYQHZapekDtMhbZ1BQ2QkYv9PAwQjypxMVo", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "CS7fA5n4c2D82dUoHrYzS3gAqgqaoVSfgsr18kitp2xo", + "baseTokenDecimals": 6 + }, + { + "name": "scnSOL_USDC_AQ", + "address": "5MzBKRo6YqK1BKBz67sXd42jrb6gYzBuX6R5F8ywC33e", + "farmTokenMint": "7YFfqZGTxkj3Zeq3Et23kMznCaEYZ1WBZDt6CVrxwfqd", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Dkr8B675PGnNwEr9vTKXznjjHke5454EQdz3iaSbparB", + "baseTokenDecimals": 6 + }, + { + "name": "SOCN_USDC_AQ", + "address": "5MzBKRo6YqK1BKBz67sXd42jrb6gYzBuX6R5F8ywC33e", + "farmTokenMint": "7YFfqZGTxkj3Zeq3Et23kMznCaEYZ1WBZDt6CVrxwfqd", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Dkr8B675PGnNwEr9vTKXznjjHke5454EQdz3iaSbparB", + "baseTokenDecimals": 6 + }, + { + "name": "pSOL_USDC_AQ", + "address": "4TK569s8SLgzmEcihyhrH8GmqFsufjZqsvHs8zrPEZYP", + "farmTokenMint": "8kWk6CuCAfaxhWQZvQva6qkB1DkWNHq9LRKKN6n9joUG", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "C2YzN6MymD5HM2kPaH7bzcbqciyjfmpqyVaR3KA5V6z1", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_SOL_AQ", + "address": "JADWjBW1Xs8WhW8kj3GTCRQn3LR4gwvbFTEMwv9ZNxQh", + "farmTokenMint": "3RTGL7gPF4V1ns1AeGFApT7cBEGVDfmJ77DqQi9AC6uG", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "29cdoMgu6MS2VXpcMo1sqRdWEzdUR9tjvoh8fcK8Z87R", + "baseTokenDecimals": 6 + }, + { + "name": "ORCA_PAI_AQ", + "address": "8KBSXu7zFjvbVXiwf1opGNBx3evVwdpaYHWR3uc4HVR6", + "farmTokenMint": "4aEi4A91hRbERJVDYxRWbbSrBrsxoM1Hm33KRoRzWMht", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "C7TH2jEJJaxVwwuvkbcDGfHUiZvEkkeYjyAcdTMi5ujb", + "baseTokenDecimals": 6 + }, + { + "name": "ORCA_mSOL_AQ", + "address": "7EVKT4iqfjiyzeVrafs23JrfhSoLd6XTanVuENNvisq7", + "farmTokenMint": "3Duk5b6fLztPmS4ryV48FM1Q9WXUSMwz9jehAT4UtqpE", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "CVapmQn7HaU1yMDW3q6oUV4hx6XoYv54T4zfGXkuJqkA", + "baseTokenDecimals": 6 + }, + { + "name": "scnSOL_SOL_AQ", + "address": "5cE7V9D13k1P1qC23g5vcQEMsvrDzL5yFHhiesVUyn93", + "farmTokenMint": "CNqmEKGjZUUARVFHcz4w9CvX5pR8Ae2c6imHDNqsbxgj", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "APNpzQvR91v1THbsAyG3HHrUEwvexWYeNCFLQuVnxgMc", + "baseTokenDecimals": 6 + }, + { + "name": "SOCN_SOL_AQ", + "address": "5cE7V9D13k1P1qC23g5vcQEMsvrDzL5yFHhiesVUyn93", + "farmTokenMint": "CNqmEKGjZUUARVFHcz4w9CvX5pR8Ae2c6imHDNqsbxgj", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "APNpzQvR91v1THbsAyG3HHrUEwvexWYeNCFLQuVnxgMc", + "baseTokenDecimals": 6 + }, + { + "name": "ATLAS_USDC_AQ", + "address": "G92aJeZBCFiECwrKSQsbobfykh6cNLCf5Pd3zkiLjGLe", + "farmTokenMint": "HFmY1ggCsCky1zJ1sfdkNR4zb3u5n38YNRdf4vsGu17t", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "FZ8x1LCRSPDeHBDoAc3Gc6Y7ETCynuHEr5q5YWV7uRCJ", + "baseTokenDecimals": 6 + }, + { + "name": "POLIS_USDC_AQ", + "address": "5NkDw3LcFscf5WSxUiquNregP6RaP79Y2pN1jyUYepzc", + "farmTokenMint": "63JUKLnCAuNMPSPioEgbjjzp9Qk8qSEEM8eZqEtPqfLU", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "GteBdo9sqE7T41G8AJsaG9WHW48uXBwsLLznmu2TBdgy", + "baseTokenDecimals": 6 + }, + { + "name": "BOP_USDC_AQ", + "address": "4bmbPAxrf5NLAD6A5fkhKhmuffdGScpu9Ei39YEaPATQ", + "farmTokenMint": "A7vvbqENJj8kED3ABjphe8TvwpasQYtoWGKpjpLArMxa", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "2gXDJZ7XAtQEtf4PRSQZKoq1WMuu1H44tQanbMA3YVpu", + "baseTokenDecimals": 6 + }, + { + "name": "SAMO_USDC_AQ", + "address": "F5ZvDWRVpQP5A17RFogL4dE7gZ2Uda7ZqKUy3DWJDEFx", + "farmTokenMint": "9voVuTq1S9bFZkF2Jo44HoVG63w2xDRT8eBzB23YbQud", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "6VK1ksrmYGMBWUUZfygGF8tHRGpNxQEWv8pfvzQHdyyc", + "baseTokenDecimals": 6 + }, + { + "name": "NINJA_SOL_AQ", + "address": "B8Q8e1hdCw6fhnSKmsMCrsDbvSNLZyzHjnPpwZKBkcSB", + "farmTokenMint": "7YyhptkxY81HPzFVfyCzA5UXxWdsNRD41ofLva3TuSpd", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "4X1oYoFWYtLebk51zuh889r1WFLe8Z9qWApj87hQMfML", + "baseTokenDecimals": 6 + }, + { + "name": "SLIM_USDC_AQ", + "address": "3muHEhQLmt7jmaicMHqe6LqXrrfFtqEuAfBog2fAXhRm", + "farmTokenMint": "3K7aZhtwWJ2JS6GnbbgeDVnxd1q2hwhqasmgRsAMZ4yC", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "BVWwyiHVHZQMPHsiW7dZH7bnBVKmbxdeEjWqVRciHCyo", + "baseTokenDecimals": 6 + }, + { + "name": "wHAPI_USDC_AQ", + "address": "r2MxTnFdnP7ECMxH7F4mcg1p3CowwhPmDmYoZ1syKZh", + "farmTokenMint": "Bfoi3RNnfdP5VeRGqvTA8MRN9ePGJoZgeKfe8WeBHUxE", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "ELfBngAgvLEHVBuJQhhE7AW6eqLX7id2sfrBngVNVAUW", + "baseTokenDecimals": 6 + }, + { + "name": "COPE_USDC_AQ", + "address": "xigyMM8txxzpvjRw3Z1tX9euUshtqeHucW6K3f81KNQ", + "farmTokenMint": "9SDpBrfqNxjXcCzpKWM6yUKdfky975VJBD6xcu5cKf5s", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "HsauTv9s52Zv12eaDuSp6y7BEm4e4BHEyAsbdjyyWzPK", + "baseTokenDecimals": 6 + }, + { + "name": "SUNNY_USDC_AQ", + "address": "Nw3caGfeqM8xQZ45iAf5zWmPMQux5wMWMjxsKEfnNk4", + "farmTokenMint": "9HPn1oREyNA7CEK7B1xwmBmVH6qtQaSfLBXc1JyRsdUE", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "GHuoeq9UnFBsBhMwH43eL3RWX5XVXbSRYJymmyMYpT7n", + "baseTokenDecimals": 6 + }, + { + "name": "GRAPE_USDC_AQ", + "address": "6eeK7PHkUXJ9t2qgdrHfbZfmU5S7zWZwpDw6i46Rf1jU", + "farmTokenMint": "97q89hnoKwqcynvwXcj83YqfqUBuCm4A8f2zHeV6bfZg", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "EorFh8siFyLF1QTZ7cCXQaPGqyo7eb4SAgKtRH8Jcxjd", + "baseTokenDecimals": 6 + }, + { + "name": "ABR_USDC_AQ", + "address": "PyzV3qSzbj98ArVpASj2LQg6zCq6zfAixqUtYDafnRU", + "farmTokenMint": "5uR5STASUmoGVHzqMeut98t26TfVkQqWU9f9dsv3NfJ6", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "GMzPbaCuQmeMUm1opH3oSCgKUjVgJUW14myq99RVPGX5", + "baseTokenDecimals": 6 + }, + { + "name": "KURO_USDC_AQ", + "address": "6bHkoSxnK3aKW3PyG34dUz1naD6T3Pc7143s41nSuuzN", + "farmTokenMint": "6PGoaQdL9e463hdaFxHXsuPcjCHRK32CQ9PFKxvM7XY2", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "DRknxb4ZFxXUTG6UJ5HupNHG1SmvBSCPzsZ1o9gAhyBi", + "baseTokenDecimals": 6 + }, + { + "name": "MEDIA_USDC_AQ", + "address": "jCGam3ptTrFKhCwBGYUcqtrvt8pY4uWXaPoqC9E7uXB", + "farmTokenMint": "3pMYToENTB7jKrJiUPq19FCZCWE35Ph7bkRRMN6kxDXK", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "2toFgkQDoPrTJYGDEVoCasPXuL9uQnjvXJaDwa9LHyTx", + "baseTokenDecimals": 6 + }, + { + "name": "TULIP_USDC_AQ", + "address": "AEemiZ28JTnz3xmj6LSfXruK1et317ZAGY5KCrKdXNCX", + "farmTokenMint": "2KYUwdRbVtaMUgHp1a6NuTomyCb33FxoZ4fkeVdwjaJA", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "4SBx8GXu8HhcVHWydQv1vsDdZs3G93XSL9CtMBny6hS5", + "baseTokenDecimals": 6 + }, + { + "name": "MNGO_USDC_AQ", + "address": "2LM1Y428kPtLAsxcdxv2iKWaPxobt9poD2DAjeGpN6TC", + "farmTokenMint": "CxhcLZtbhfkwjAZ956SEkGxkAvMVQH3hfKTjKpgTV9Q5", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "H9yC7jDng974WwcU4kTGs7BKf7nBNswpdsP5bzbdXjib", + "baseTokenDecimals": 6 + }, + { + "name": "stSOL_wstETH_AQ", + "address": "6eW2skHuzMxxPZjzE7x5fxtn3ZZ1Ak2SawrMH9T5KANp", + "farmTokenMint": "3kT3oYuS1rCfhmqfgy6EKcbZdaJimaVEjoy25QiuEaoj", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Eswigpwm3xsipkTqahGi2PEJsJcULQBwZgxhQpr6yBEa", + "baseTokenDecimals": 6 + }, + { + "name": "SYP_USDC_AQ", + "address": "7GDHcUdLhHxCHeQccpbJr3eNGzQvnoMdLBzkVNYvQgkh", + "farmTokenMint": "Ds4VGZhZzS2PMFzhzKeC3mwcQjdiCG21R76fTVbsSJyJ", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "qJxKN9BhxbYvRNbjfK2uAVWboto6sonj8XC1ZEW5XTB", + "baseTokenDecimals": 6 + }, + { + "name": "stSOL_wLDO_AQ", + "address": "DMECkFUnVp1esox6Yyyfc7vJeN7spUHd5JypAWqEqCRC", + "farmTokenMint": "DQsbebdNDy8yQrwLTpieckhzi7Ewx9LoCPVf7G9KvY2U", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "74B9aMS7SA832xKngt5VLKmWAP3pa3qkUzWncTmQSsGF", + "baseTokenDecimals": 6 + }, + { + "name": "whETH_SOL_AQ", + "address": "BerS3SE5G6FqZER7L7G3BhUJBrZ7BpizmuQRH9LMEYQw", + "farmTokenMint": "FkHQBBZGh5GS4GcXpcVksKYUUkLTNn6Yk1PCMxucR2AK", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "7aYnrdmdCRodDy2Czn6keUquUhjF1jPEmfwZPh488z8U", + "baseTokenDecimals": 6 + }, + { + "name": "whETH_USDC_AQ", + "address": "GdQyNtN9rQWzpcm7mQMNBiXyeKRjjQoobh2waVQq5QyP", + "farmTokenMint": "B11Xp26xU2gzjToJEuGswvr6Jtidfh4GRUyCWzWMNdQZ", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "7NPtjjAP7vhp4t5NCLyY4DY5rurvyc8cgZ2a2rYabRia", + "baseTokenDecimals": 6 + }, + { + "name": "MNDE_mSOL_AQ", + "address": "5wr7m4YrJB38vuz3xyLqvq2DwLCcDkEDH5X97chKpH4T", + "farmTokenMint": "2wPsMuzhEsC6GhV3qtFpmJF6atEgLGbnmQ8U43Y6fPxZ", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "5PHS5w6hQwFNnLz1jJFe7TVTxSQ98cDYC3akmiAoFMXs", + "baseTokenDecimals": 6 + }, + { + "name": "WAG_USDC_AQ", + "address": "4GWmnvMg7EGQZ6LeQtK6rbJrtTTVsQHj2ivwm4vm8mnR", + "farmTokenMint": "8Wu5sJpERA1J5iWcT8aMpt9cTAfKDLPbLpGjNsJoPgLc", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Df6XNHMF3uRVZnz7LCEGiZVax6rXgz76owtVkBHEjSb6", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_USDT_AQ", + "address": "HULY26UFdfVkc2STTt1KREd57BwFV2md1tqdk253QyiK", + "farmTokenMint": "Afvh7TWfcT1E9eEEWJk17fPjnqk36hreTJJK5g3s4fm8", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "9cMWe4UYRPGAUUsTkjShJWVM7bk8DUBgxtwwH8asFJoV", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_whETH_AQ", + "address": "D4Pmc82b9W1UDAqYNNNMGG7UYxBaZckf97AYExGbUK95", + "farmTokenMint": "58nifjPjF3CutGz2xMxvAMk7R9YgbVEc8Cstj4rCcs8j", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "5qoTq3qC4U7vFxo3iCzbXcaD1UEmDeCD63Dsuoct71oV", + "baseTokenDecimals": 6 + }, + { + "name": "BTC_mSOL_AQ", + "address": "GBrpFtiTabs14mc4Hi1RX9YiQY7res6JxrVfMTADfcQV", + "farmTokenMint": "DzpLz78wuwyFsQToin8iDv6YK6aBEymRqQq82swiFh7r", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "8nKJ4z9FSw6wrVZKASqBiS9DS1CiNsRnqwCCKVQjqdkB", + "baseTokenDecimals": 6 + }, + { + "name": "IVN_SOL_AQ", + "address": "FwzqbJZQiL3qzMx88r2o3CNKFxztuW3JC45YYk6ghMLR", + "farmTokenMint": "HqajzzbGMST3yCCVBJuXvNVsWkY2DXqiBz9cTRmmyBMy", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "DfgCnzaiTXfPkAH1C1Z441b5MzjjTCEh134ioxqRZxYf", + "baseTokenDecimals": 6 + }, + { + "name": "LARIX_USDC_AQ", + "address": "HeAQxAGBQdGURFFcLpdPagQ2vb66kXFTVxDfTjme8eGo", + "farmTokenMint": "DNAGfa7tK8csprRQmiDUwDaFfhw6ueHhVFHTCgTJ8HGs", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "8sfThep3io4gvcGeuoAg1Rs8GDwKJjtcdAFHqQSSNAVE", + "baseTokenDecimals": 6 + }, + { + "name": "PRT_USDC_AQ", + "address": "9dCsoLdfkpYw1s7TMNE1HZPNmVSNDZrUkUHfxn2p8675", + "farmTokenMint": "2cYMt26745oFc7PadaQn8Vv3xFUxWBfbip2NyJeVG35F", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "6jCERp5hKj37PCXP3VTjCDJeoPuSpnMDMz5A6jWQv3yS", + "baseTokenDecimals": 6 + }, + { + "name": "JET_USDC_AQ", + "address": "BKdPQziyuKmXwZeVuo8Uj7usfUYpwSfnGBDbG96y266V", + "farmTokenMint": "4DjiLEKADWjYmiY9gzFnu5xews5oCTMRByWHWEzDa3bj", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "GBijunwxa4Ni3JmYC6q6zgaVhSUJU6hVX5qTyJDRpNTc", + "baseTokenDecimals": 6 + }, + { + "name": "stSOL_USDC_AQ", + "address": "2P7FGV8XNXUkEAG6q5LbhfoBFkHJ7PDAjYqmAbwnVHBF", + "farmTokenMint": "3u2dNfGuU6C3vmSg5EvLPUpX57b3niqhWBV5Gc3WDEf5", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "GtQ1NT7R5aaTiST7K6ZWdMhwDdFxsSFvVFhBo8vyHGAq", + "baseTokenDecimals": 6 + }, + { + "name": "wstETH_USDC_AQ", + "address": "2FAzkAgm8EpE7WpWUsEcNyj4kcVeCX2L8SR1BicGWEx9", + "farmTokenMint": "ojpWEdNYa5nGviUc8k8M2XLjHuaCL2EgHFdvTtdkXA1", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "5a6Y1ephcbKSoyLMQyD1JWbtqawCy8p2FtRL9v3zhaG5", + "baseTokenDecimals": 6 + }, + { + "name": "AURY_USDC_AQ", + "address": "FvkyNXLizY4FCNkgbGpr1WA3z4A8iTRU4rPnnNJC3q2V", + "farmTokenMint": "7s7Veo1P8ZRy6z5MCvoAmg2kPFcQnq2Grt6yewWS8LbQ", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "6mJqqT5TMgveDvxzBt3hrjGkPV5VAj7tacxFCT3GebXh", + "baseTokenDecimals": 6 + }, + { + "name": "AVAX_USDC_AQ", + "address": "GkmPgsKU9uYaZtKfzjWeJjBSvX8mkK8gNqoWMYBdcvUx", + "farmTokenMint": "5mZydNG1fBPTXzgp9SoS7Jny6EmSzBPTPrFbB1ttGnvx", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "Hmfrtmo93DpSDmVNLQKcBS5D1ia5JatiRSok9ososubz", + "baseTokenDecimals": 6 + }, + { + "name": "FTT_USDC_AQ", + "address": "2FtSDVRmYq4x6NeTidpZVuLzgN2APzr3zHxpHx8zdgbr", + "farmTokenMint": "2AAzmhZ9Kh9mFcQtHJVTafu69tc5GCGpi6CTgafYta1S", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "FwCombynV2fTVizxPCNA2oZKoWXLZgdJThjE4Xv9sjxc", + "baseTokenDecimals": 6 + }, + { + "name": "RAY_USDC_AQ", + "address": "GCcVYpBCPcPjb11LeeoVhLNJQzS5GXEEd6Dmo5eejBTx", + "farmTokenMint": "9MaXcCERB4DzedPNSBwyHM2P6Yo6jCFLjnatuWCtR3WF", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "4cXw2MYj94TFBXLL73fEpMCr8DPrW68JvrV8mzWgktbD", + "baseTokenDecimals": 6 + }, + { + "name": "SLND_USDC_AQ", + "address": "5NrUSiG134QLY4JkmX82n4hdZQnmqsWMrBapQwfAtpeu", + "farmTokenMint": "GdySZb2nbeEjCLBg65veC5kzfMCfCWgtgqwH9YWDtDXr", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "F59gkD7NnsdJbFKrRZsiBC8PAooN4c56T8QmahfW1iXN", + "baseTokenDecimals": 6 + }, + { + "name": "GOFX_USDC_AQ", + "address": "GmyMx4JtRrkjmxNn4WQCmVNuLfwRjkAmCfMv2AXag5se", + "farmTokenMint": "B95rdqSY4dqPwmt295XwBZZqZJYLmqDNXU6NvBpT4ep4", + "rewardTokenMint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "rewardTokenDecimals": 6, + "baseTokenMint": "7vnps4VE5RTGAr5fmPZu7fSrk2VnM4Up838grZfqmxqE", + "baseTokenDecimals": 6 + }, + { + "name": "LIQ_USDC_DD", + "address": "AraZDjfmkqzDJ3CdbjjYbtpujUYivgTEKKM2TPf6hJ27", + "farmTokenMint": "5rGtJDiJhD5Mx2fvdEYuLrCiWaMD9z3wpmJSxwGHmo4u", + "rewardTokenMint": "4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj", + "rewardTokenDecimals": 6, + "baseTokenMint": "57vGdcMZLnbNr4TZ4hgrpGJZGR9vTPhu8L9bNKDrqxKT", + "baseTokenDecimals": 6 + }, + { + "name": "STEP_SOL_DD", + "address": "FmHGpt2scyJ8NuDuknnzQ7jectLyRRQmTyXxu5tRoD2j", + "farmTokenMint": "Gs1fM7EFS1rXkxhqs4mwu9uvSkupNzZgRbHGxG2NGRh7", + "rewardTokenMint": "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", + "rewardTokenDecimals": 9, + "baseTokenMint": "GwrBA1F8rGummDCDd8NY9Eu1cLNuJqbT8WaGxgWpFwGL", + "baseTokenDecimals": 6 + }, + { + "name": "SLRS_USDC_DD", + "address": "8JkTAVHXChDPzsNkcJYRJ69mXN5nCcyb1qVBmbVhCH1n", + "farmTokenMint": "F3rWkGAtdjWcU1rr16Wq4YPTgFdsyb1oS1xdy5tr9K1r", + "rewardTokenMint": "SLRSSpSLUTP7okbCUBYStWCo1vUgyt775faPqz8HUMr", + "rewardTokenDecimals": 6, + "baseTokenMint": "66xCxkffQZKBZLiHV3PDcfR8ANJTfnDRxPCaBdv4wxB7", + "baseTokenDecimals": 6 + }, + { + "name": "PORT_USDC_DD", + "address": "C8k63XU8xzzvBpSraYWjHCNzvprjYKDadAML3MWSFbxW", + "farmTokenMint": "Zm2dmUuuBicmvHxGAnAzaohZR2Y86gXEV2WMfo8AoCa", + "rewardTokenMint": "PoRTjZMPXb9T7dyU7tpLEZRQj7e6ssfAE62j2oQuc6y", + "rewardTokenDecimals": 6, + "baseTokenMint": "4CGxvZdwiZgVMLXiTdJHTkJRUTpTSJCtmtCRbSkAxerE", + "baseTokenDecimals": 6 + }, + { + "name": "COPE_USDC_DD", + "address": "HW21NT7v6ViM2Cs3S2tVgUnawACF8JRRwd2AA41a8HUh", + "farmTokenMint": "AtcMEt9caZxpunQV99pxED2rhpQmaDykBreEqBsYU11v", + "rewardTokenMint": "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "rewardTokenDecimals": 6, + "baseTokenMint": "9SDpBrfqNxjXcCzpKWM6yUKdfky975VJBD6xcu5cKf5s", + "baseTokenDecimals": 6 + }, + { + "name": "BOP_USDC_DD", + "address": "GYipJSPD7zcDoEABRCZfDSqh7zqYMmjyP8dNKEUnW1iC", + "farmTokenMint": "CjGUbKiH1QmFFjMqhAbJn4DrbjgBWUhQHV4LuzrgpFqi", + "rewardTokenMint": "BLwTnYKqf7u4qjgZrrsKeNs2EzWkMLqVCu6j8iHyrNA3", + "rewardTokenDecimals": 8, + "baseTokenMint": "A7vvbqENJj8kED3ABjphe8TvwpasQYtoWGKpjpLArMxa", + "baseTokenDecimals": 6 + }, + { + "name": "SAMO_USDC_DD", + "address": "8VJT2SYGXgvQ8jYvh1Cq6mg83gkAVuTY3cHiULUy6Cit", + "farmTokenMint": "EdfAy8jwnvU1z61UaFUjwoRPFgD3UkkPvnhEBZjzwhv8", + "rewardTokenMint": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", + "rewardTokenDecimals": 9, + "baseTokenMint": "9voVuTq1S9bFZkF2Jo44HoVG63w2xDRT8eBzB23YbQud", + "baseTokenDecimals": 6 + }, + { + "name": "wHAPI_USDC_DD", + "address": "GFFQdLWvbWzrZBQrPQVyzGQfd6SWuZaiRwmkKA2bJPeY", + "farmTokenMint": "41VBoy8SGJzQnWGcxiBL4yM6H68FiPp74aMvsZGNGCbt", + "rewardTokenMint": "6VNKqgz9hk7zRShTFdg5AnkfKwZUcojzwAkzxSH3bnUm", + "rewardTokenDecimals": 9, + "baseTokenMint": "Bfoi3RNnfdP5VeRGqvTA8MRN9ePGJoZgeKfe8WeBHUxE", + "baseTokenDecimals": 6 + }, + { + "name": "SLIM_USDC_DD", + "address": "3RsBqqz9MiXa1uhFFd6tpGVQ66ptkFPCBAUyqLAXeCTE", + "farmTokenMint": "HhDk3ySWkVbMZjgBsFSnLtAeudDCrfZ6DNSRgxh2oRUp", + "rewardTokenMint": "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW", + "rewardTokenDecimals": 6, + "baseTokenMint": "3K7aZhtwWJ2JS6GnbbgeDVnxd1q2hwhqasmgRsAMZ4yC", + "baseTokenDecimals": 6 + }, + { + "name": "NINJA_SOL_DD", + "address": "HPeAGzDEhYUNm4z4aV2PTjVjbESnNg6jBy6LtFjFJzQj", + "farmTokenMint": "Db7mPGrZbswvFmJ7MgZsM6CFhnXHMnrUDqr2hrzmi7Re", + "rewardTokenMint": "FgX1WD9WzMU3yLwXaFSarPfkgzjLb2DZCqmkx9ExpuvJ", + "rewardTokenDecimals": 6, + "baseTokenMint": "7YyhptkxY81HPzFVfyCzA5UXxWdsNRD41ofLva3TuSpd", + "baseTokenDecimals": 6 + }, + { + "name": "ATLAS_USDC_DD", + "address": "DTP1xr4EzFf1YDu4CeWTtWVsCBzFPk4HDEsL3AzoR3kB", + "farmTokenMint": "894ptAFT7d3inPsWTniCGL2NZpJDiXGvFZFfuHXA1w8F", + "rewardTokenMint": "ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx", + "rewardTokenDecimals": 8, + "baseTokenMint": "HFmY1ggCsCky1zJ1sfdkNR4zb3u5n38YNRdf4vsGu17t", + "baseTokenDecimals": 6 + }, + { + "name": "POLIS_USDC_DD", + "address": "7h1zAHj2xzEw3eKfprYqG36aN5XwcZXBsYwM2haWQVzR", + "farmTokenMint": "FE1QJzi5RA5aKnTfSV3DAMN3z4uHUzSR5Z4drs9S5vB", + "rewardTokenMint": "poLisWXnNRwC6oBu1vHiuKQzFjGL4XDSu4g9qjz9qVk", + "rewardTokenDecimals": 8, + "baseTokenMint": "63JUKLnCAuNMPSPioEgbjjzp9Qk8qSEEM8eZqEtPqfLU", + "baseTokenDecimals": 6 + }, + { + "name": "ABR_USDC_DD", + "address": "98htZRc2QNd8BS9GGHoxkySZ9BiL9MAgHLEQfxzXYKk6", + "farmTokenMint": "7bp7psdaC3DVc86Hmdz5tAMEjgPjmCzgFEVALfqBwMmz", + "rewardTokenMint": "a11bdAAuV8iB2fu7X6AxAvDTo1QZ8FXB3kk5eecdasp", + "rewardTokenDecimals": 9, + "baseTokenMint": "5uR5STASUmoGVHzqMeut98t26TfVkQqWU9f9dsv3NfJ6", + "baseTokenDecimals": 6 + }, + { + "name": "KURO_USDC_DD", + "address": "BK3VXXDA4KxVurHbP3yPytGWp1HifbBtB4ugpzfDeq4v", + "farmTokenMint": "88RCQs9VFvqPjsRe3PKNzBeMtzCS9oS1a1CJuAnGnLZJ", + "rewardTokenMint": "2Kc38rfQ49DFaKHQaWbijkE7fcymUMLY5guUiUsDmFfn", + "rewardTokenDecimals": 6, + "baseTokenMint": "6PGoaQdL9e463hdaFxHXsuPcjCHRK32CQ9PFKxvM7XY2", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_USDC_DD", + "address": "5fhDMuGKRDPWVWXf7BBEwifRFrp6XwXctDQoG7UHGVt6", + "farmTokenMint": "9y3QYM5mcaB8tU7oXRzAQnzHVa75P8riDuPievLp64cY", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "5r3vDsNTGXXb9cGQfqyNuYD2bjhRPymGJBfDmKosR9Ev", + "baseTokenDecimals": 6 + }, + { + "name": "ORCA_mSOL_DD", + "address": "41ZYSekqDNtJ1BdGkTZVR1CJfBiFrud6HcT3HVUdSyWN", + "farmTokenMint": "876yhw4J4GHyynNJUtARYEnWGaejhrWC7Hy3DAm1pZxi", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "3Duk5b6fLztPmS4ryV48FM1Q9WXUSMwz9jehAT4UtqpE", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_SOL_DD", + "address": "2SciNw7cEsKJc1PMRDzWCcEzvuScmEaUgmrJXCi9UFxY", + "farmTokenMint": "576ABEdvLG1iFU3bLC8AMJ3mo5LhfgPPhMtTeVAGG6u7", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "3RTGL7gPF4V1ns1AeGFApT7cBEGVDfmJ77DqQi9AC6uG", + "baseTokenDecimals": 6 + }, + { + "name": "stSOL_wstETH_DD", + "address": "EmWtmApj1PtJMgMfzbYMmnmtuwfcBy7es3Tg2AR8xfW6", + "farmTokenMint": "5WXyG6zL1HmESPCSHHKBtqLuRPZCNgd9mTB25op87FkU", + "rewardTokenMint": "HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p", + "rewardTokenDecimals": 8, + "baseTokenMint": "3kT3oYuS1rCfhmqfgy6EKcbZdaJimaVEjoy25QiuEaoj", + "baseTokenDecimals": 6 + }, + { + "name": "SYP_USDC_DD", + "address": "gpy1dZRbPbLZ2KNr4wd2r9zoxERbWV8gWTqTL47KNnh", + "farmTokenMint": "BpHfwFwJwkZKWY5xVMC3oifMvWRy42R4VE1vPeBzg2G1", + "rewardTokenMint": "FnKE9n6aGjQoNWRBZXy4RW6LZVao7qwBonUbiD7edUmZ", + "rewardTokenDecimals": 9, + "baseTokenMint": "Ds4VGZhZzS2PMFzhzKeC3mwcQjdiCG21R76fTVbsSJyJ", + "baseTokenDecimals": 6 + }, + { + "name": "MNDE_mSOL_DD", + "address": "C9AfeUkti1ykMTebw9N1WoiEhNqwNiXinfXBwuLUv1BT", + "farmTokenMint": "2FMpVEhvxiFxhfideFUMNxCoUZK3TfhezzajoHGTQKP2", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "2wPsMuzhEsC6GhV3qtFpmJF6atEgLGbnmQ8U43Y6fPxZ", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_USDT_DD", + "address": "FNV9pGMWTYSMq5dRhmjpRwHpDJKV6JD4HTKsqZndKvuY", + "farmTokenMint": "7iKG16aukdXXw43MowbfrGqXhAoYe51iVR9u2Nf2dCEY", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "Afvh7TWfcT1E9eEEWJk17fPjnqk36hreTJJK5g3s4fm8", + "baseTokenDecimals": 6 + }, + { + "name": "mSOL_whETH_DD", + "address": "D6oqo3F2KkJcePDoNZfbb8F7SPnRhP7WCC9FNktzVCDT", + "farmTokenMint": "3kFeVJUxhQS7PE7vV8pt9bhTCQrUDqeGf6AU4sjkLzVt", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "58nifjPjF3CutGz2xMxvAMk7R9YgbVEc8Cstj4rCcs8j", + "baseTokenDecimals": 6 + }, + { + "name": "BTC_mSOL_DD", + "address": "Cn7QNyosNQ8DyKEeMDPmtg66R7vKMXigcQ561kTkFD8E", + "farmTokenMint": "6uA1ADUJbvwYJZpzUn9z9LuyKoRVngBKcQTKdXsSivA8", + "rewardTokenMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "rewardTokenDecimals": 9, + "baseTokenMint": "DzpLz78wuwyFsQToin8iDv6YK6aBEymRqQq82swiFh7r", + "baseTokenDecimals": 6 + }, + { + "name": "IVN_SOL_DD", + "address": "9DNgTpphCRXhkf8ySiSnf1L2CHwACZybgPNwsnLUdzfA", + "farmTokenMint": "5X71f6zUnVYWSxWM8wf942pzWLv1ZtCDhGCYqZipYutD", + "rewardTokenMint": "iVNcrNE9BRZBC9Aqf753iZiZfbszeAVUoikgT9yvr2a", + "rewardTokenDecimals": 6, + "baseTokenMint": "HqajzzbGMST3yCCVBJuXvNVsWkY2DXqiBz9cTRmmyBMy", + "baseTokenDecimals": 6 + }, + { + "name": "LARIX_USDC_DD", + "address": "GnFV3S7H6eM9V12EJwptv111VmV7W3AnEEfzdHJL25n7", + "farmTokenMint": "Huy453KXTaWaA3AiJeqLrAWWSMwoGjFU8nsEi7GiPY7n", + "rewardTokenMint": "Lrxqnh6ZHKbGy3dcrCED43nsoLkM1LTzU2jRfWe8qUC", + "rewardTokenDecimals": 6, + "baseTokenMint": "DNAGfa7tK8csprRQmiDUwDaFfhw6ueHhVFHTCgTJ8HGs", + "baseTokenDecimals": 6 + }, + { + "name": "GOFX_USDC_DD", + "address": "EVDvbsD5f1qz7FpVyknrzB3bLK86FHfqVbcH9WqpWZtK", + "farmTokenMint": "BzZ3mLyXEaGt1Na1zxfZYjRJBFDbDckurc2LDq46irUx", + "rewardTokenMint": "GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD", + "rewardTokenDecimals": 9, + "baseTokenMint": "B95rdqSY4dqPwmt295XwBZZqZJYLmqDNXU6NvBpT4ep4", + "baseTokenDecimals": 6 + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/farms/raydium/farms.json b/farms/farm-ctrl/src/metadata/farms/raydium/farms.json new file mode 100644 index 00000000000..faf4cf6c7b2 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/farms/raydium/farms.json @@ -0,0 +1,759 @@ +{ + "name": "Raydium Farms", + "farms": [ + { + "name": "RAY-USDC", + "lp": "LP.RDM.RAY-USDC-V3", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "8nEWqxeDNZ2yo1izbPzY4nwR55isBZRaQk7CM8ntwUwR", + "poolAuthority": "6vQGZLsHgpJdqh1ER7q2q6mjZ43QwzhtTofTzb2sUhNh", + "poolLpTokenAccount": "77ujS15hjUfFZkM8QAw4HMLvMGZg95Gcm6ixjA1bnk3M", + "poolRewardTokenAccount": "3ejmkn5HpXR9KdVWkai1Ngo87sQSUyKXrx8wSakipkno" + }, + { + "name": "RAY-SRM", + "lp": "LP.RDM.RAY-SRM-V3", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "HwEgvS79S53yzYUTRHShU6EuNmhR3WTX5tTZPUzBmwky", + "poolAuthority": "9B3XWm89zX7NwaBB8VmT5mrWvxVpd9eyfQMeqkuLkcCF", + "poolLpTokenAccount": "F4zXXzqkyT1GP5CVdEgC7qTcDfR8ox5Akm6RCbBdBsRp", + "poolRewardTokenAccount": "FW7omPaCCvgBgUFKwvwU2jf1w1wJGjDrJqurr3SeXn14" + }, + { + "name": "RAY-SOL", + "lp": "LP.RDM.RAY-SOL-V3", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "ECqG3sxwJiq9TTYsRBd7fPGsBKYF4fyogo6Df7c13qdJ", + "poolAuthority": "4Wf4om12g9xzEeeD139ffCuXn4W2huMcXziiSAzf7Nig", + "poolLpTokenAccount": "9kWnkQtMAW2bzKeLQsTdan1rEoypDHaAVnZRcoBPDBfQ", + "poolRewardTokenAccount": "8z4kQbgQFe4zXE4NSozWJTJV14gD4evNq4CKn5ryB6S3" + }, + { + "name": "RAY-ETH", + "lp": "LP.RDM.RAY-ETH-V3", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "CYKDTwojSLVFEShB3tcTTfMjtBxUNtYfCTM4PiMFGkio", + "poolAuthority": "Azmucec2jdgWagFkbnqmwYcsrtKPf1v1kcM95v6s1zxu", + "poolLpTokenAccount": "EncPBQhpc5KLmcgRD2PutQz7wBBNQkVN2s8jjFWEw9no", + "poolRewardTokenAccount": "8q8BHw7fP7mitLrb2jzw78qcSEgCvM7GTB5PzbSQobUt" + }, + { + "name": "RAY-WUSDT", + "lp": "LP.RDM.RAY-WUSDT-V3", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "6d3vDYvk6VFVacEAGA1NDyxkQPRiNxXQRkeKpTPMJwe4", + "poolAuthority": "EcPc2KUDFMyPNAVPE6PsMkzneBFKNqRjUhfhyM2da9go", + "poolLpTokenAccount": "Gx4kLpTirc3Lr3GEYojYt1zUmsCcWajjBZTFVA3tzyDg", + "poolRewardTokenAccount": "J144vsbPdLa9V6JpvGFH63bQw8QhQckUNe48YjPKwcZo" + }, + { + "name": "RAY-USDT", + "lp": "LP.RDM.RAY-USDT-V4", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": false, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "AvbVWpBi2e4C9HPmZgShGdPoNydG4Yw8GJvG9HUcLgce", + "poolAuthority": "8JYVFy3pYsPSpPRsqf43KSJFnJzn83nnRLQgG88XKB8q", + "poolLpTokenAccount": "4u4AnMBHXehdpP5tbD6qzB5Q4iZmvKKR5aUr2gavG7aw", + "poolRewardTokenAccount": "HCHNuGzkqSnw9TbwpPv1gTnoqnqYepcojHw9DAToBrUj" + }, + { + "name": "RAY-WUSDT", + "lp": "LP.RDM.RAY-WUSDT", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 2, + "programId": "STAKE_PROGRAM_ID", + "poolId": "5w3itB5PVAPAiPFpBcMyGZJWukmcuRtwFRkQJF3WzHdj", + "poolAuthority": "4qgEHMtCAA4Z3rY4C1ihz9JHETHFhQVqj81Q1qyB83WP", + "poolLpTokenAccount": "n1gotGPqeUxJnA4yE7QCCsNG8AVqQ1HuATkAhAfVMVV", + "poolRewardTokenAccount": "h8uQ293dPdJd7qFRFE1pvMbpFmxrtD64QaxUWwis4Wv" + }, + { + "name": "RAY-USDC", + "lp": "LP.RDM.RAY-USDC", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 2, + "programId": "STAKE_PROGRAM_ID", + "poolId": "3j7qWosyu3cVNgbwdWRxEf4SxJKNWoWqgpAEn4RLpMrR", + "poolAuthority": "BZhcMxjRy9oXSgghLN52uhsML5ooXS377yTJhkw96bYX", + "poolLpTokenAccount": "6qsk4PmATtiu132YJuUgVt4zekbTYV3xRZWxoc1rAg9U", + "poolRewardTokenAccount": "Aucgi2G2ufXTGGYf2ng3ZyQXLu6RH6ioL1R7mGfhUcbQ" + }, + { + "name": "RAY-SRM", + "lp": "LP.RDM.RAY-SRM", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": true, + "dual": false, + "version": 2, + "programId": "STAKE_PROGRAM_ID", + "poolId": "GLQwyMF1txnAdEnoYuPTPsWdXqUuxgTMsWEV38njk48C", + "poolAuthority": "5ddsMftKDoaT5qHnHKnfkGCexJhiaNz1E4mMagy6qMku", + "poolLpTokenAccount": "HFYPGyBW5hsQnrtQntg4d6Gzyg6iaehVTAVNqQ6f5f28", + "poolRewardTokenAccount": "ETwFtP1dYCbvbARNPfKuJFxoGFDTTsqB6j3pRquPE7Fq" + }, + { + "name": "RAY", + "lp": "RAY", + "reward": "RAY", + "isStake": true, + "fusion": false, + "legacy": false, + "dual": false, + "version": 2, + "programId": "STAKE_PROGRAM_ID", + "poolId": "4EwbZo8BZXP5313z5A2H11MRBP15M5n6YxfmkjXESKAW", + "poolAuthority": "4qD717qKoj3Sm8YfHMSR7tSKjWn5An817nArA6nGdcUR", + "poolLpTokenAccount": "8tnpAECxAT9nHBqR1Ba494Ar5dQMPGhL31MmPJz1zZvY", + "poolRewardTokenAccount": "BihEG2r7hYax6EherbRmuLLrySBuSXx4PYGd9gAsktKY" + }, + { + "name": "FIDA-RAY", + "lp": "LP.RDM.FIDA-RAY-V4", + "reward": "RAY", + "rewardB": "FIDA", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 4, + "programId": "STAKE_PROGRAM_ID_V4", + "poolId": "8rAdapvcC5vYNLXzChMgt56s6HCQGE6Lbo469g3WRTUh", + "poolAuthority": "EcCKf3mgPtL6dNNAVG4gQQtLkAoTAUdf5vzFukkrviWq", + "poolLpTokenAccount": "H6kzwNNg9zbgC1YBjvCN4BdebtA4NusvgUhUSDZoz8rP", + "poolRewardTokenAccount": "7vnPTB2HAXFUAV5iiVZTNHgAnVYjgXcdumbbqfeK6ugp", + "poolRewardTokenAccountB": "EGHdQm9KGLz6nw7W4rK13DyAMMJcGP9RpzCJaXiq75kQ" + }, + { + "name": "OXY-RAY", + "lp": "LP.RDM.OXY-RAY-V4", + "reward": "RAY", + "rewardB": "OXY", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 4, + "programId": "STAKE_PROGRAM_ID_V4", + "poolId": "7Hug9fKfTrasG3hHonXTfSnvv37mDeyoBHbVwyDjw693", + "poolAuthority": "CcD7KXVhjoeFpbkXeBgPpZChafEfTZ4zJL47LqmKdqwz", + "poolLpTokenAccount": "GtXoFnVRATaasBP6sroNaC54uLQfVAwGXsfKzgFqNiUc", + "poolRewardTokenAccount": "GKC7BcGs1515CQx6hiK562u29dFQxBw8HWwJUxqi7xf1", + "poolRewardTokenAccountB": "DXDjRiC7EUUh9cj93tgBtX2jRkmnwtCMEAQD9GrYK2f6" + }, + { + "name": "MAPS-RAY", + "lp": "LP.RDM.MAPS-RAY-V4", + "reward": "RAY", + "rewardB": "MAPS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 4, + "programId": "STAKE_PROGRAM_ID_V4", + "poolId": "Chb6atEWGmH2NitCqrCEMHB8uKWYQiiVaBnmJQDudm87", + "poolAuthority": "BcmgQZXCDPCduv3reT8LDQNqvGeGMZtFhBxyLYdrnCjE", + "poolLpTokenAccount": "5uaBAwu1Sff58KNKGTwfacsjsrMU3wg6jtGtMWwiZd5B", + "poolRewardTokenAccount": "4LVikvk3gZEHaTUNh7L8bsx5By6NNnkqpKfcdJTWTD7Z", + "poolRewardTokenAccountB": "3UWGpEe2NLD9oWPW1zdXGZRCvJxkNSC2puUWooNEugdS" + }, + { + "name": "KIN-RAY", + "lp": "LP.RDM.KIN-RAY-V4", + "reward": "RAY", + "rewardB": "KIN", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "FgApVk6mASrkuWNxmsFvsaAYkFKqdiwMTvYZK36A2DaC", + "poolAuthority": "7kEx8qnkZPkRXV6f4ztf27zYjCACBHY3PUMfuiYJsoML", + "poolLpTokenAccount": "7fgDjhZn9GqRZbbCregr9tpkbWSKjibdCsJNBYbLhLir", + "poolRewardTokenAccount": "5XZjRyEo8Wr2CtSE5bpoKioThT9czK1dUebbK87Lqkaa", + "poolRewardTokenAccountB": "8jGJ3ST1j9eemfC6N2qQevtUdwxT7TpXW1NmvWyvLLVs" + }, + { + "name": "xCOPE-USDC", + "lp": "LP.RDM.xCOPE-USDC-V4", + "reward": "RAY", + "rewardB": "xCOPE", + "isStake": false, + "fusion": true, + "legacy": true, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "XnRBbNMf6YcWvC1u2vBXXuMcagmRBRLu1y84mpqnKwW", + "poolAuthority": "AnYvA5H7oBeA1otnWHSu8ud3waFsEmfUbdAoM1VzdVvt", + "poolLpTokenAccount": "6tXWzm8nLVtNtvqDH8bZNfUwpSjEcKZoJFpcV4hC5rLD", + "poolRewardTokenAccount": "8GoDpozsDk3U3J36vvPiq3YpnA6MeJb1QPVJFiupe2wR", + "poolRewardTokenAccountB": "7niS4ngxgZ3oynHwH82PnwJXicTnY3fo9Vubi1PnjzJq" + }, + { + "name": "STEP-USDC", + "lp": "LP.RDM.STEP-USDC-V4", + "reward": "RAY", + "rewardB": "STEP", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "8xhjCzfzVcP79jE7jXR2xtNaSL6aJYoDRLVT9FMjpRTC", + "poolAuthority": "6wRMPrHKFzj3qB4j5yj4y9mDF89fZ6w7gD1cEzCJwT9B", + "poolLpTokenAccount": "CP3wdgdSygYGLJMjKfbJMiANnYuAxXHPiLTtB124tzVX", + "poolRewardTokenAccount": "3zSiR4XrrRPhsom2hh9iigYZZ7uCpMucfJnZRgREgH8j", + "poolRewardTokenAccountB": "4n3vRUk3wdtbGWgMFSaxUcnGLKwa2wiWVhqw7kv9JDVS" + }, + { + "name": "MEDIA-USDC", + "lp": "LP.RDM.MEDIA-USDC-V4", + "reward": "RAY", + "rewardB": "MEDIA", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "Ef1gD9JMzWF6PNw2uc4744zouh57GyWAeVTjHHbQ2nsu", + "poolAuthority": "3dhU2g3MSHK3LwjuE1VsEJCsNeWKyBJUMHt4EUXepTjs", + "poolLpTokenAccount": "DGjRtqsjeubLCLPD3yH8fj1d7TnrD3jKBpwa1UbVk7E6", + "poolRewardTokenAccount": "Uen8f9Rn42i8sDTK5vEttrnX9AUwXV3yf6DFU63mKDb", + "poolRewardTokenAccountB": "Ek6n7Myojb6pSpQuqk5AyS7KXQdXkJyZT7ki9baYCxds" + }, + { + "name": "COPE-USDC", + "lp": "LP.RDM.COPE-USDC-V4", + "reward": "RAY", + "rewardB": "COPE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "AxVvbT9fDFEkmdLwKUJRY5HsG2RXAZbe1dRAgJ2bDDwg", + "poolAuthority": "3n1Vdmqu1MBUpBYMpYbpJAVFv4MeNMEa82waruLy7BDu", + "poolLpTokenAccount": "BHLzrd5MgQy4NgmUsn542yXRZWkz1iV5bfWg8s8D4tVL", + "poolRewardTokenAccount": "7nGY6xHCUR2MxJnHT1qvArRUEnpo2DsGGf6Pdu3tt9gv", + "poolRewardTokenAccountB": "6ezx1EivkxsJcZLYhSJFLc3nUs25iyubf8PPyRNEX3pL" + }, + { + "name": "MER-USDC", + "lp": "LP.RDM.MER-USDC-V4", + "reward": "RAY", + "rewardB": "MER", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "D4pYuD4tbir9KBsb7Kr63v9e86JY2UoUZeFK9eHKQFZM", + "poolAuthority": "2T46saTyTYeEFWyesRzLWj6y1ha9ngwcyWyGNn9q4zu4", + "poolLpTokenAccount": "EV3wsqiMiNcBmo2mFkUuCtib36NpBCsC2vfkW3By1sSu", + "poolRewardTokenAccount": "5gEH5Uq2QrqiEhdZ8YFAMY1HoYnKMiuu71f6BC25UXee", + "poolRewardTokenAccountB": "FTP4hnN5GPtPYvkrscTkKWYVVQ56hV3f4wGgpEXgrDUD" + }, + { + "name": "ROPE-USDC", + "lp": "LP.RDM.ROPE-USDC-V4", + "reward": "RAY", + "rewardB": "ROPE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "BLy8KuRck5bcJkQdMDLSZnL1Ka4heAZSGiwTJfEfY727", + "poolAuthority": "8xPzoFPHKWZHWmwKaxFUyVBf2V13HMbCrMDgaCZCLjgx", + "poolLpTokenAccount": "DiebAVak6cub1Mn3yhhvgSvGhkAP1JTtyRGoAei4wrWE", + "poolRewardTokenAccount": "4F9FaFewwsSF8Bsxukyj9NiEdPFQQ38dNKEDpZugYfdi", + "poolRewardTokenAccountB": "4tvLbnZEPZLuDf636DHEzrUxW8bDoZ5XyfVwk7ppDhbC" + }, + { + "name": "ALEPH-USDC", + "lp": "LP.RDM.ALEPH-USDC-V4", + "reward": "RAY", + "rewardB": "ALEPH", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "JAP8SFagJBm6vt2LoFGNeSJ1hKDZ2p3yXb3CvBx11How", + "poolAuthority": "DVtR63sAnJPM9wdt1hYBqA5GTyFzjfcfdLTfsSzV85Ss", + "poolLpTokenAccount": "feCzxSvVX4EboJV4cubjqoPTK41noaHUanz8ZNJmiBp", + "poolRewardTokenAccount": "4mAhgUY8XGMY4743wuzVbLw7d5bqqTaxME8jmbC2YfH4", + "poolRewardTokenAccountB": "3sGDa8ir8GrkKbnBH6HP63JaYSs7nskmmVHpF2vuzaZr" + }, + { + "name": "TULIP-USDC", + "lp": "LP.RDM.TULIP-USDC-V4", + "reward": "RAY", + "rewardB": "TULIP", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "7U8Z6TWQMtsMcHV2htALnF9VQonnD1MrVm17YtmGEGEw", + "poolAuthority": "9ZVNLEiBZ2u23P7rEJf5sXY7TZK723cmVs46pBRSbRnU", + "poolLpTokenAccount": "B6xn6doS3Qfy1LJLbdcJa5MpJ4po2bgut1rKFvmmq6Ut", + "poolRewardTokenAccount": "GtPTgCr6nXiogRCWqGvLa8P6dJgZpHfAX3KxGMpxnGMJ", + "poolRewardTokenAccountB": "8qgijAifBGx2EAJ7zKAzk6z7dVpcDV9eHvTBwofmdTP5" + }, + { + "name": "SNY-USDC", + "lp": "LP.RDM.SNY-USDC-V4", + "reward": "RAY", + "rewardB": "SNY", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "31QSh1TwgoA9GbvkgfEEwKEm11t8CR4KiQr6WCyJr7EN", + "poolAuthority": "BbebocNt4ySwkufrY1X3wRG8PVefCRLFR2E2TGzZPkne", + "poolLpTokenAccount": "2t1qozn7xtWjuCqnnTx4PaKikajN2AQK3CVH6A5JqagY", + "poolRewardTokenAccount": "GXZq2zNPZ9odPWAPinxXK8B7cMaAN9CpbcaLicksJsbt", + "poolRewardTokenAccountB": "DdSL2stD9UXfY2nj9MKrNPx8QTro1GGAY6rsBd9kJXMX" + }, + { + "name": "BOP-RAY", + "lp": "LP.RDM.BOP-RAY-V4", + "reward": "RAY", + "rewardB": "BOP", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "EEe8b72w5q6T86nYRNJdFcY25tznPzrd1jGjuxZ7f9mX", + "poolAuthority": "7d99wJT2nRjWe2eKF7FpzMFb7934KoRhLP7pp2bjRm9m", + "poolLpTokenAccount": "FWMHgA5iUxz3zMYf7jRJk8Z9ebWNWpvd7358rGCPFr7M", + "poolRewardTokenAccount": "DhvRSrQUio8LpCJH4uFCvvK4MEYVrBA6xaj1hu9jVxZn", + "poolRewardTokenAccountB": "3c6552swYV5nBTKTCWfKURjN1uGjtceanfb3vRbHNXpN" + }, + { + "name": "SLRS-USDC", + "lp": "LP.RDM.SLRS-USDC-V4", + "reward": "RAY", + "rewardB": "SLRS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "5PVVwSqwzkCvuiKEZwWkM35ApBnoWqF8XopsVZjPwA8z", + "poolAuthority": "7jNUxDiLLyke8ECShavvPPQz4D1abj4aCZwQfZ3TCTAX", + "poolLpTokenAccount": "HTr2pYDBQZP13YTzLdsPzmh6e4hsNeqoGy3B777ejqTT", + "poolRewardTokenAccount": "Ef1tQ2E2Fe92xPVpQGzZFHmT7g7dh2hzVfWYVJJQPdbu", + "poolRewardTokenAccountB": "Ffmv9Ximzk8D9oKwHkkgdq9cVxv5P5Y9LxEJdu1N1jSJ" + }, + { + "name": "SAMO-RAY", + "lp": "LP.RDM.SAMO-RAY-V4", + "reward": "RAY", + "rewardB": "SAMO", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "Bw932pURVJRYjEJwRZGWjfUNpeyz18kjMNdb833eMxoj", + "poolAuthority": "FzTbGLdzgWCRkq8hbS8tLf5HjfU7JzUbtRmTkjGQB9Vz", + "poolLpTokenAccount": "GUVKfYMiGEyp41CUw2j2NsoQJ5zDQ3Q6uSdApM8W46Ba", + "poolRewardTokenAccount": "J99YW5wnfgBJcG17BgSbp1S8RNJ39JAb7kg9RGHyb3Hq", + "poolRewardTokenAccountB": "GhctEMRSwvdZF7aFeCLdK9X1sAAeGVPjr12iVLjQNvhy" + }, + { + "name": "LIKE-USDC", + "lp": "LP.RDM.LIKE-USDC-V4", + "reward": "RAY", + "rewardB": "LIKE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "BRM5bdX2mjmFGg2RAent1Whd61o9asQD16BXsC6QvEni", + "poolAuthority": "9rThpjxEgNR5xi2z2QgXenS2RwRrrN1GqrudegT32Ygy", + "poolLpTokenAccount": "FzVu8n4UCf3o1KH4X8khM9KgKA96dJQdQMPtLvmbHyNi", + "poolRewardTokenAccount": "3G1cbktUU79CT3zskP16VYmEhwVQq2RYxVWV7fcjmkTX", + "poolRewardTokenAccountB": "2Ks41qfN2GZffbd1cqrNGuXJYJbShHhz6aHQvq8SaYYr" + }, + { + "name": "RAY-SOL", + "lp": "LP.RDM.RAY-SOL-V4", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": false, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "HUDr9BDaAGqi37xbQHzxCyXvfMCKPTPNF8g9c9bPu1Fu", + "poolAuthority": "9VbmvaaPeNAke2MAL3h2Fw82VubH1tBCzwBzaWybGKiG", + "poolLpTokenAccount": "A4xQv2BQPB1WxsjiCC7tcMH7zUq255uCBkevFj8qSCyJ", + "poolRewardTokenAccount": "6zA5RAQYgazm4dniS8AigjGFtRi4xneqjL7ehrSqCmhr" + }, + { + "name": "RAY-USDC", + "lp": "LP.RDM.RAY-USDC-V4", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": false, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "CHYrUBX2RKX8iBg7gYTkccoGNBzP44LdaazMHCLcdEgS", + "poolAuthority": "5KQFnDd33J5NaMC9hQ64P5XzaaSz8Pt7NBCkZFYn1po", + "poolLpTokenAccount": "BNnXLFGva3K8ACruAc1gaP49NCbLkyE6xWhGV4G2HLrs", + "poolRewardTokenAccount": "DpRueBHHhrQNvrjZX7CwGitJDJ8eZc3AHcyFMG4LqCQR" + }, + { + "name": "RAY-ETH", + "lp": "LP.RDM.RAY-ETH-V4", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": false, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "B6fbnZZ7sbKHR18ffEDD5Nncgp54iKN1GbCgjTRdqhS1", + "poolAuthority": "6amoZ7YBbsz3uUUbkeEH4vDTNwjvgjxTiu6nGi9z1JGe", + "poolLpTokenAccount": "BjAfXpHTHz2kipraNddS6WwQvGGtbvyobn7MxLEEYfrH", + "poolRewardTokenAccount": "7YfTgYQFGEJ4kb8jCF8cBrrUwEFskLin3EbvE1crqiQh" + }, + { + "name": "RAY-SRM", + "lp": "LP.RDM.RAY-SRM-V4", + "reward": "RAY", + "isStake": false, + "fusion": false, + "legacy": false, + "dual": false, + "version": 3, + "programId": "STAKE_PROGRAM_ID", + "poolId": "5DFbcYNLLy5SJiBpCCDzNSs7cWCsUbYnCkLXzcPQiKnR", + "poolAuthority": "DdFXxCbn5vpxPRaGmurmefCTTSUa5XZ9Kh6Noc4bvrU9", + "poolLpTokenAccount": "792c58UHPPuLJcYZ6nawcD5F5NQXGbBos9ZGczTrLSdb", + "poolRewardTokenAccount": "5ihtMmeTAx3kdf459Yt3bqos5zDe4WBBcSZSB6ooNxLt" + }, + { + "name": "MNGO-USDC", + "lp": "LP.RDM.MNGO-USDC-V4", + "reward": "RAY", + "rewardB": "MNGO", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "GzEDEkHSFFfxKMu3Toww1nrEjtbQGJKRPNRK1Pfd59Zn", + "poolAuthority": "9AMvw1TUJ9gX1kUAvcmHt2ZjokBLepXQbN8EJxBVZu2s", + "poolLpTokenAccount": "gjrMLKsNwXYzJnX9DT8Lc7HeC1AT52jQKtDkPiRRuEP", + "poolRewardTokenAccount": "4czqUC2ebdvqxPXfRniknLk7Cr2TosTabQSRnUeFia9v", + "poolRewardTokenAccountB": "6K1AE1wnTNaMgcAgQPvrTbnWEHB7nW6uTtv7ZbXWgMtn" + }, + { + "name": "COPE-RAY", + "lp": "LP.RDM.COPE-RAY-V4", + "reward": "RAY", + "rewardB": "COPE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "CM9XTJfXEHceGPXhmXxheR87Ng9CZ4jiBoTVQHhs9DVN", + "poolAuthority": "AWYeNgCErUafmBU2TtZgzNwixpKd3BxRTmvYDw7U1jgN", + "poolLpTokenAccount": "FLqRe3W9Lp59uNgzkACsXpEZkWUxBBstMtUyGSzqFhXD", + "poolRewardTokenAccount": "Ex23TUPEarZepXdHgjm7LVy35HDWY2VgeKao5kjYRZGE", + "poolRewardTokenAccountB": "JDjSMCSK9s9dDsiiXeT3HVaX48k7WewyKBoMPax3TZxL" + }, + { + "name": "LIKE-RAY", + "lp": "LP.RDM.LIKE-RAY-V4", + "reward": "RAY", + "rewardB": "LIKE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "AMcVWK66iexwwCHn8drxywdNr2UgH3vmRzLXQFdErGmL", + "poolAuthority": "32yVBkvq29AmXKu1A3xUtgHrMGFnLvxF18fhd4JLKfJs", + "poolLpTokenAccount": "6f72fpk4WDeqpTJZ4dLSvAacfwmCAfEk7RtuPQ5oyNd7", + "poolRewardTokenAccount": "4oPdHXXdRmjtKMLCcK8rtp3vMmq9y9LJ6W83mqrqMjCt", + "poolRewardTokenAccountB": "E49fLhK6Wv43FySZB1xybPghzK2cjr9hgfpcmcVSLeYm" + }, + { + "name": "MEDIA-RAY", + "lp": "LP.RDM.MEDIA-RAY-V4", + "reward": "RAY", + "rewardB": "MEDIA", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "DJfvL6srBht8XFMWYuuKHYGainqvwXyA5icVsDne3pwN", + "poolAuthority": "69PxTdPaRSofBJkwT9mYW14cPUEe7fU2AYEDvt3q5Fkt", + "poolLpTokenAccount": "3Kaibb6xYpKjFejtkgH8tBrMWShWzwBd7WfcGygZ4Vcw", + "poolRewardTokenAccount": "28kE8Erc2uFThiUr8RifoUEc9Kv8V54To6DJLgCuJEPp", + "poolRewardTokenAccountB": "3kofbYH2hPefwHSgMburaGN5XmJx7sD94jo5CsMCXzLE" + }, + { + "name": "MER-RAY", + "lp": "LP.RDM.MER-RAY-V4", + "reward": "RAY", + "rewardB": "MER", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "21xhrT4j8QnaBvj3QjhP5kZu8sXJMCE7hzHKGtWEkdKr", + "poolAuthority": "6GrjogDgJ56mPcNu1nFw7MVLMALoNzd6RsZiXrQAuTvh", + "poolLpTokenAccount": "Ee4zr6okPiyG6ia8kZfPwoNRDtNsrn4YfPc7MMmTqufR", + "poolRewardTokenAccount": "FnSG5cBXyEqo3DxKrcjhj7wo8un3HrxABQrxfA5uKWsg", + "poolRewardTokenAccountB": "8yL9QK96Ag3NnvqZmcaupb7c4NeP5hJXraGS3jCzMzT" + }, + { + "name": "SLRS-RAY", + "lp": "LP.RDM.SLRS-RAY-V4", + "reward": "RAY", + "rewardB": "SLRS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "J61AnYYSwjtJ4wDqEqqWSBuZbiR2SDDrtF7FFobutM6a", + "poolAuthority": "BHGHqkJomVD5tKNMZFajA1PZEJaZW5Yywyp6UAcvf1Wq", + "poolLpTokenAccount": "H8NEHvqm43DxWbMfvLMvUqoKrjG4B4EJXEYBz2DYhRHd", + "poolRewardTokenAccount": "5g1ox4cwcfNFsqPiGH2zhsHYpaBf6rkigL6YR5ZBQA5k", + "poolRewardTokenAccountB": "95b2zMqRGsovcR69XXfRPcvLdyvLCH5M5nd4z27yC8Q1" + }, + { + "name": "SNY-RAY", + "lp": "LP.RDM.SNY-RAY-V4", + "reward": "RAY", + "rewardB": "SNY", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "JEC3P83x2GEijYDwXiksuh5H6YrQt5xW6MC2GDKkMoe", + "poolAuthority": "9qwnkx2gRMLVoYkJVkyH2Yza5e5E7LoZEpx9jZ9r3CBY", + "poolLpTokenAccount": "7JrCLqrhH9kb78St4dAncBYE9VhZdB4P1tFAdxwzDrH5", + "poolRewardTokenAccount": "HmovkXKsso8xHwPYmMYF5bmP5CCwCtReQVb8ETTSSoyN", + "poolRewardTokenAccountB": "GXJSX1JNjjAK6jEEjujvzhCjMeVnZmpJ5fng3daynCnY" + }, + { + "name": "TULIP-RAY", + "lp": "LP.RDM.TULIP-RAY-V4", + "reward": "RAY", + "rewardB": "TULIP", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "BmbG9hv5PazcW3rYWvatA6HpNPkozEdkWBiU64pZxuwr", + "poolAuthority": "956MvcyRBPMZ6waK3bdD4dn3XfaganoKed1NUQ9NaFAg", + "poolLpTokenAccount": "HMgHKCLetHYDUJZEXKRJCiSeQs4Udwy6MNXHoLruMctH", + "poolRewardTokenAccount": "5ih22SsrffDjygZHF8ADyJa4TNKQZqANg7dXyBJN9V8P", + "poolRewardTokenAccountB": "3zK56FmEqeH93BuH5K7JY9ZaEfFMdo3YjAasFikCmDB1" + }, + { + "name": "ALEPH-RAY", + "lp": "LP.RDM.ALEPH-RAY-V4", + "reward": "RAY", + "rewardB": "ALEPH", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "4wvZ9SwWaHKTpshQbCSKQoPosZp9KGwUzuQdESi39qPn", + "poolAuthority": "G3tniqor4UrtE29UQLGcBBuk4ScvonDpXiPSDTK3RioJ", + "poolLpTokenAccount": "Fx32reDAB5MyJJwr8CjCM1fNgFsmnjhaxjC9pJswpUok", + "poolRewardTokenAccount": "34gWdzwgj1zWQG4iwSbTeUDbQkoR8DXzLFQJsSpPDXLa", + "poolRewardTokenAccountB": "Gm4v69FCZ33HZsHAgtdezAUJK6n5fQ3zHpTZxAAzeyoJ" + }, + { + "name": "RAY-SRM", + "lp": "LP.RDM.RAY-SRM-V4", + "reward": "RAY", + "rewardB": "SRM", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "BnYoq5y2MoH4TsBHeEZrEPowhwebHxQq7nJW1vTjPTWu", + "poolAuthority": "8JMnGryLkzSYdnTUPGRgxHoAmP5soH8L8TRre91Gjgni", + "poolLpTokenAccount": "6tuhozgcTA25fq5Lp11QX9HsG8MVspUjtcn7EgYP1cs5", + "poolRewardTokenAccount": "ED6Ak5wnnegeVz6jWMzGEEnFQ7HY55uPdxR8Ha6hk7gz", + "poolRewardTokenAccountB": "G4zqVtnHSK9Sp3SVdiQ5K56m46BdAoE2uQqpgVsmRG9d" + }, + { + "name": "ATLAS-USDC", + "lp": "LP.RDM.ATLAS-USDC-V4", + "reward": "RAY", + "rewardB": "ATLAS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "93wRz2LeQ3TJoair827VTng62MjCzYDgJjG9Q5GmQ3Pd", + "poolAuthority": "4yrRmmckKKGsPbCSFFupGqZrJhAFxQ4hN2DMC9Bh2pHo", + "poolLpTokenAccount": "HmE21hdD32ZjDnR5DvuNz7uS5q4bWbqf8jV2shx8kXmA", + "poolRewardTokenAccount": "9iQsupP7JagNLkp1bvdWWGVkzsLFfHUwDbh9KZPoXbw5", + "poolRewardTokenAccountB": "5oQU1hU6qggyT4CU2AMPcWTcZdSRZeQBy7How5WuEp7A" + }, + { + "name": "POLIS-USDC", + "lp": "LP.RDM.POLIS-USDC-V4", + "reward": "RAY", + "rewardB": "POLIS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "7qcihXTsRW5wS5BgK7iuD84W43ECByoJP45R3hu2r6mF", + "poolAuthority": "3MAzzKcBPJ2ykDHX1CBHzUJafy41FaTaLymg8z6SgX2Q", + "poolLpTokenAccount": "FwLD6rHMwm5H6edDPuGjxdBMk3u38frsnytTkPmVZVP3", + "poolRewardTokenAccount": "AWQr1eX2RZiMadfeEpgPEQJBJq88f7dPLK3nqriKCPJp", + "poolRewardTokenAccountB": "DfofnRgWFPHVaxaLGSdXvFGhr4TRwjdwQQvgkjNNkJfZ" + }, + { + "name": "ATLAS-RAY", + "lp": "LP.RDM.ATLAS-RAY-V4", + "reward": "RAY", + "rewardB": "ATLAS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "BHHhNLdJn69K1XPJcpcw4MBY3TPetpLxhj8s4K4ydsDV", + "poolAuthority": "DjYd34HtSwwAGfTfK13onUyq975akjzfW2abaK5YTRaS", + "poolLpTokenAccount": "5RPJHt2V4baK7gY1E99xCRBtEzScuNEVPr9vA9PapLhs", + "poolRewardTokenAccount": "AQwjpEoLwnHYnsdSnzwRpSkTSeLDNYZ6tv6odVGzXJvZ", + "poolRewardTokenAccountB": "DBXQnchh5zQuiEfaE8JBPTre8G1mksVTsHXoSqRPfA3r" + }, + { + "name": "POLIS-RAY", + "lp": "LP.RDM.POLIS-RAY-V4", + "reward": "RAY", + "rewardB": "POLIS", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "HHm8Pgnzc56fTUYkicPv4DqGYp5fcPZFV1V1uhixSrMk", + "poolAuthority": "GHPg6z7HYx1bsdK4W9WpdmV8BcjpPBBsRGMmj9dAD3yq", + "poolLpTokenAccount": "4wGbaNEGeGjqqgW5S9AAWvQL3LwWZioH1JWMZFBdPFge", + "poolRewardTokenAccount": "4xrr44aG4kkgqQPZhBre93vg5fFY2htkkEEmTQjx5hiG", + "poolRewardTokenAccountB": "EanBQNubTJs2fNgeosUcESCfBnvk6bci391U5SH4Kzoo" + }, + { + "name": "GRAPE-USDC", + "lp": "LP.RDM.GRAPE-USDC-V4", + "reward": "RAY", + "rewardB": "GRAPE", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": false, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "8GBa1cK1NxevoxiRNK6YW9tWuo2xftcA3as9Cu4nhFL7", + "poolAuthority": "Gab4kPHmj5Hqn1KWEDsKt6Ta8jPtpc53oCPULszMNtyj", + "poolLpTokenAccount": "eoVzVdFEkKPKY3djJ47RZjvNr5oujYY25uxXwNvrsfg", + "poolRewardTokenAccount": "AYoDAc5ndfts4Aw6vzH7XUB2GsXamj72aunzBcBCnz2f", + "poolRewardTokenAccountB": "5i2qZN5UH4UyF3t6HNeC1bXeXhWBZy1pwpCjLDG7AdJJ" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/farms/raydium/farms_dev.json b/farms/farm-ctrl/src/metadata/farms/raydium/farms_dev.json new file mode 100644 index 00000000000..4dbe99aad16 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/farms/raydium/farms_dev.json @@ -0,0 +1,22 @@ +{ + "name": "Raydium Farms", + "farms": [ + { + "name": "COIN-PC", + "lp": "LP.RDM.COIN-PC-V4", + "reward": "COIN", + "rewardB": "PC", + "isStake": false, + "fusion": true, + "legacy": false, + "dual": true, + "version": 5, + "programId": "STAKE_PROGRAM_ID_V5", + "poolId": "2Bsexc5j6vk4r9RhBYz2ufPrRWhumXQk6efXucqUKsyr", + "poolAuthority": "BxAtWJ4g6xguPsR9xNvXTK7EjuzwiKNbmKbhoXDZ3EsY", + "poolLpTokenAccount": "83BEhzv7eV4HeJuuPtYmHkhTjZEpNpK83mHnHfX5Krwj", + "poolRewardTokenAccount": "HVtAJ1uRiWJ7tNU9uqAzpPv14B3fN9SVEW9G4PtM77Ci", + "poolRewardTokenAccountB": "39Ea6rMGGrsNmEsYToqQfEyNSqv7hcUJa646qBYLY4yq" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/pools/orca/pools.json b/farms/farm-ctrl/src/metadata/pools/orca/pools.json new file mode 100644 index 00000000000..e20d92a5201 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/pools/orca/pools.json @@ -0,0 +1,2850 @@ +{ +"name": "Orca Pools", +"pools": + [ + { + "name": "SOL_USDC", + "address": "EGZ7tiLeH62TPV1gL8WwbXGzEPa9zmcpVnnkPKKnrE2U", + "nonce": 252, + "authority": "JU8kmKzDHF9sXWsnoznaFDFezLsE5uomX2JkRMbmsQP", + "poolTokenMint": "APDFRM3HMr8CAGXwKHiu2f5ePSpaiEJhaURwhsRrUUt9", + "poolTokenDecimals": 6, + "feeAccount": "8JnSiuvQq3BVuCU3n4DrSTw9chBSPvEMswrhtifVkr1o", + "tokenIds": [ + "So11111111111111111111111111111111111111112", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "ANP74VNsHwSrq9uUSjiSNyNWvf6ZPrKTmE4gHoNd13Lg" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "75HgnSvXbWKZBpZHveX68ZzAhDqMzNDS29X6BGLtxMo1" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SOL_USDT", + "address": "Dqk7mHQBx2ZWExmyrR2S8X6UG75CrbbpK2FSBZsNYsw6", + "nonce": 255, + "authority": "2sxKY7hxVFrY5oNE2DgaPAJFamMzsmFLM2DgVcjK5yTy", + "poolTokenMint": "FZthQCuYHhcfiDma7QrX7buDHwrZEd7vL8SjS6LQa3Tx", + "poolTokenDecimals": 6, + "feeAccount": "BBKgw75FivTYXj85D2AWyVdaTdTWuSuHVXRm1Xu7fipb", + "tokenIds": [ + "So11111111111111111111111111111111111111112", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "tokens": { + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "DTb8NKsfhEJGY1TrA7RXN6MBiTrjnkdMAfjPEjtmTT3M" + }, + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": { + "tag": "USDT", + "name": "Tether USD", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "scale": 6, + "addr": "E8erPjPEorykpPjFV9yUYMYigEWKQUxuGfL2rJKLJ3KU" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ETH_SOL", + "address": "EuK3xDa4rWuHeMQCBsHf1ETZNiEQb5C476oE9u9kp8Ji", + "nonce": 255, + "authority": "DffrDbzPiswDJaiicBBo9CjqztKgFLrqXGwNJH4XQefZ", + "poolTokenMint": "71FymgN2ZUf7VvVTLE8jYEnjP3jSK1Frp2XT1nHs8Hob", + "poolTokenDecimals": 6, + "feeAccount": "unxKgWEc71ZiHwMqZs3VLqjcjmZhfTZEg94ZLGvjdMP", + "tokenIds": [ + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk": { + "tag": "ETH", + "name": "Ethereum", + "mint": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "scale": 6, + "addr": "7F2cLdio3i6CCJaypj9VfNDPW2DwT3vkDmZJDEfmxu6A" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "5pUTGvN2AA2BEzBDU4CNDh3LHER15WS6J8oJf5XeZFD8" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ETH_USDC", + "address": "FgZut2qVQEyPBibaTJbbX2PxaMZvT1vjDebiVaDp5BWP", + "nonce": 253, + "authority": "4dfCZR32xXhoTgMRhnViNaTFwiKP9A34TDjHCR3xM5rg", + "poolTokenMint": "3e1W6Aqcbuk2DfHUwRiRcyzpyYRRjg6yhZZcyEARydUX", + "poolTokenDecimals": 6, + "feeAccount": "DLWewB12jzGn4wXJmFCddWDeof1Ma4cZYNRv9CP5hTvX", + "tokenIds": [ + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk": { + "tag": "ETH", + "name": "Ethereum", + "mint": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "scale": 6, + "addr": "H9h5yTBfCHcb4eRP87fXczzXgNaMzKihr7bf1sjw7iuZ" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "JA98RXv2VdxQD8pRQq4dzJ1Bp4nH8nokCGmxvPWKJ3hx" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "RAY_SOL", + "address": "3THMPkPmcHJ54JtHRyazhs7UN7HbV5KiNJVLJs6hSPSC", + "nonce": 255, + "authority": "EUc3MtHPLL956pTDfM5q25jp5Fk9zW7omEzh1uyDY7s6", + "poolTokenMint": "5kimD5W6yJpHRHCyPtnEyDsQRdiiJKivu5AqN3si82Jc", + "poolTokenDecimals": 6, + "feeAccount": "65XNtnUsP1TMzKMGhMoD3GUFMNbmnZQwDaxDLE3jncUC", + "tokenIds": [ + "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R": { + "tag": "RAY", + "name": "Raydium", + "mint": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", + "scale": 6, + "addr": "GZaUNWf4ov6VZaD5MqZtc5pHB3mWTqczNUB4sstt8CSR" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "GNSZ1rr57QQ6qLcmZnhMcoBymenVezhNpzcFSfJP37h9" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ROPE_SOL", + "address": "DCENobjFZK59nLeMCVRkQtnkAWrJkWAVpmVnwVNc8gqH", + "nonce": 251, + "authority": "C2DDX95TK3uC9MQXhHp3LWRv9kWtFTp36Ub9VPCKKiCV", + "poolTokenMint": "ADrvfPBsRcJfGsN6Bs385zYddH52nuM5FA8UaAkX9o2V", + "poolTokenDecimals": 6, + "feeAccount": "88rKjeeDQYJxGVCG39znDvbxzjPc5H37XqHRQCSNJvED", + "tokenIds": [ + "8PMHT4swUMtBzgHnh5U564N5sjPSiUz2cjEQzFnnP1Fo", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "8PMHT4swUMtBzgHnh5U564N5sjPSiUz2cjEQzFnnP1Fo": { + "tag": "ROPE", + "name": "Rope", + "mint": "8PMHT4swUMtBzgHnh5U564N5sjPSiUz2cjEQzFnnP1Fo", + "scale": 9, + "addr": "HLHPVwgzYjTHmu1fmV9eZzdE8H3fZwhuCBRNNN2Z5miA" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "7Be3aStQmKgeXC1xqfJnA8qaGzw6keQTLqHYAJprZK2H" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "STEP_SOL", + "address": "2sNtf8wLZMR7XJm2pzvvVUNPTNRFsGTiC5vgDSGv5QGD", + "nonce": 255, + "authority": "HzAJLVqZ7fnmc2LfRPtz2GHu93RpAPQsdDTg6DRoTTmf", + "poolTokenMint": "8nTzqDXHriG2CXKbybeuEh1EqDQMtrbYMFWcP7AkiDaP", + "poolTokenDecimals": 6, + "feeAccount": "5FEmPmAk72NycwzMXLD3hc2f47zRXocv4mvd3HEUZ5io", + "tokenIds": [ + "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT": { + "tag": "STEP", + "name": "Step", + "mint": "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", + "scale": 9, + "addr": "35opuEpVvggzfV361hQVNXiC7KAKS1HCeDoVpfVybo8k" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "A3DSsYZJWHiwXSQb7P2AbEoaWhpauJLU1PVdTPnzV5s9" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SRM_SOL", + "address": "EJRXAkKyDhDgzdZz1Ss5CpWKWSK9xTR5S6GDLAer8mdh", + "nonce": 255, + "authority": "2pQbngWBSWUjBHWVWQ3tppKxW3Y5NzUcye1822itKyzZ", + "poolTokenMint": "9tf8rBSEQYG7AqL896fN2nZi1iYPqpWaLEdpbeQaC1Vy", + "poolTokenDecimals": 6, + "feeAccount": "APyc1s8wES4Q2FTqHN8jXZtRuWQyRWZ82u7EFfras2iZ", + "tokenIds": [ + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt": { + "tag": "SRM", + "name": "Serum", + "mint": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "scale": 6, + "addr": "2JCxZv6LaFjtWqBXSC2brsWE9WryS4Cp3VwwDeNGvLyv" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "EdhG3vQbTVVAARZB4AbhU2HsVbvfFqX2yhBAfFV22nzA" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "FTT_SOL", + "address": "68Bg6yQxWm3mrUYk3XzMiF5ycE41HwPhyEdaB1cp6wuo", + "nonce": 255, + "authority": "BpshqwEmPXmJwJfFgTFafmXoHN8Lc7SouvFsh6jyYQAm", + "poolTokenMint": "EsYaDKJCmcJtJHFuJYwQZwqohvVMCrFzcg8yo3i328No", + "poolTokenDecimals": 6, + "feeAccount": "FWBCbjZnypLKz7uHGJXpBAEez2FurQXi9J3js7ZT1xDe", + "tokenIds": [ + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3": { + "tag": "FTT", + "name": "FTX Token", + "mint": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "scale": 6, + "addr": "3eVE92aEAsLYcBACXNu1yxoHVfTM8e8vmQC2zSApGRJX" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "BbsiNbFfJsRDwqF4JaiJ6sKecNuY4eWmEaDHcY6h6HuD" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "COPE_SOL", + "address": "BRx4dJecxzeGYc1BskCWonfGCeMyv9G7tk66cf2QGhK6", + "nonce": 254, + "authority": "JAJr1D6BQHFj9qJ8pfXhvJgLfn9vTcviU9sTA8MhKzN4", + "poolTokenMint": "CzieDbGRdN1QGaGDNpSqzEA18bi881ccvkkGZi51pe1k", + "poolTokenDecimals": 6, + "feeAccount": "9X8VRnxk6EVKGA7TErdSZYFC8oLUM569pDbZTtycjvXw", + "tokenIds": [ + "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh": { + "tag": "COPE", + "name": "Cope", + "mint": "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "scale": 6, + "addr": "7v5GCdHH439SztxcqL4wpfWdPvv9EfMm8GYTHSUQoGoY" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "5tSgRUK6f2x1nAXA4gdcHNXiWdToqni9pr5xvq2Fq82u" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "OXY_SOL", + "address": "Cq4EZrvuFQpsCz8L3gS6t7iQ7VWzTtqDgSxD4AgVAAfd", + "nonce": 255, + "authority": "FxxcJPunf6Vj9Ve5zyM9yatMbmkrzTQ1QSk4NqKW9DvK", + "poolTokenMint": "7tYCdLN84EnTMkxM7HNamWJx7F4xgKe9KiiWvLyWjbgT", + "poolTokenDecimals": 6, + "feeAccount": "Ch8i2A1GAspivXYfdme7vSxh1mhRjmRgeiKN8KpWhVqo", + "tokenIds": [ + "z3dn17yLaGMKffVogeFHQ9zWVcXgqgf3PQnDsNs2g6M", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "z3dn17yLaGMKffVogeFHQ9zWVcXgqgf3PQnDsNs2g6M": { + "tag": "OXY", + "name": "Oxygen", + "mint": "z3dn17yLaGMKffVogeFHQ9zWVcXgqgf3PQnDsNs2g6M", + "scale": 6, + "addr": "BoZQMfTLTPcXnevJxNFkECVbKesfhXnTUg4kxLgzV9BX" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "8ZrEcJbgg7BdoBga5RYDR8aMujLf5cAQp8zdPZqk7nNC" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "BTC_SOL", + "address": "7N2AEJ98qBs4PwEwZ6k5pj8uZBKMkZrKZeiC7A64B47u", + "nonce": 255, + "authority": "GqnLhu3bPQ46nTZYNFDnzhwm31iFoqhi3ntXMtc5DPiT", + "poolTokenMint": "Acxs19v6eUMTEfdvkvWkRB4bwFCHm3XV9jABCy7c1mXe", + "poolTokenDecimals": 6, + "feeAccount": "4yPG4A9jB3ibDMVXEN2aZW4oA1e1xzzA3z5VWjkZd18B", + "tokenIds": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E": { + "tag": "BTC", + "name": "Bitcoin", + "mint": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "scale": 6, + "addr": "9G5TBPbEUg2iaFxJ29uVAT8ZzxY77esRshyHiLYZKRh8" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "5eqcnUasgU2NRrEAeWxvFVRTTYWJWfAJhsdffvc6nJc2" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "MER_SOL", + "address": "UJBm2tHwDbQZpjQvaHozg5qkjh6prSKhnWheqR5T76Q", + "nonce": 255, + "authority": "C8HcMrC9WRqqXVbHRhZWjuZPbbWmszDobKFxevCtGhpT", + "poolTokenMint": "HiwRobjfHZ4zsPtqCC4oBS24pSmy4t8GGkXRbQj4yU6L", + "poolTokenDecimals": 6, + "feeAccount": "3Fdj69449GhiDmqyvWWTSafjRphGdiDhZ5i5rqfHBdio", + "tokenIds": [ + "MERt85fc5boKw3BW1eYdxonEuJNvXbiMbs6hvheau5K", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "MERt85fc5boKw3BW1eYdxonEuJNvXbiMbs6hvheau5K": { + "tag": "MER", + "name": "Mercurial", + "mint": "MERt85fc5boKw3BW1eYdxonEuJNvXbiMbs6hvheau5K", + "scale": 6, + "addr": "DHdkRWTa9SRJNMtWZQYvNNbjrDP3n92EWLHezjFGPipb" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "DC5RwjB3VkXdt2PoWKTA4Ub9KbtcY8xXpmPNKsFjALwq" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "FIDA_SOL", + "address": "4SUBbivGMvMhem3ajVtkmuPvL4GuQ8kfYTJFuQfG4F8t", + "nonce": 255, + "authority": "owuNLod7H14GpQCUodcdBBeD4LiZ2T5U9KpS2sAbTp6", + "poolTokenMint": "EYsNdtyu4gGTaGz8N5m5iQ3G1N6rDyMbR72B3CqbWW4W", + "poolTokenDecimals": 6, + "feeAccount": "J2s1GpduscTTvMYt3os8LdvT24uhr9bPnTbxSKZZhEma", + "tokenIds": [ + "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp": { + "tag": "FIDA", + "name": "Bonfida", + "mint": "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp", + "scale": 6, + "addr": "9ofyx5yFzjH1XWmJzfiGCDfhq6ho8yFbszGQrrJXe54" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "55moYcq91pXbSRpL2DR8P3BehqSNWiJrdTn5SZFc2STn" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "MAPS_SOL", + "address": "4rkgbbCPKk5zx3KiwcCNiSpNSSLgAkswKGfX7CJjGgtC", + "nonce": 255, + "authority": "8uuBxVsGf2bqH5t8mct5NfpgcTDb7czXuWVEm6Boia4x", + "poolTokenMint": "99pfC8fWymXgbq3CvrExhx3UxQDC1fMWEWLbNT83F45e", + "poolTokenDecimals": 6, + "feeAccount": "8w3gx1GQ1UN5sodEVP14qUwzCcgopHWUeWbT26hgK3xh", + "tokenIds": [ + "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb": { + "tag": "MAPS", + "name": "MAPS", + "mint": "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb", + "scale": 6, + "addr": "BqExNTYk7YdeiaREHqnqN2q1F3dBCTPhkwrrWBFD4F1m" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "HdEQ99E979aXn2xTcg5UXEfLynwFkqpPTxLaNkH7Nz7P" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "USDC_USDT", + "address": "F13xvvx45jVGd84ynK3c8T89UejQVxjCLtmHfPmAXAHP", + "nonce": 255, + "authority": "3cGHDS8uWhdxQj14vTmFtYHX3NMouPpE4o9MjQ43Bbf4", + "poolTokenMint": "H2uzgruPvonVpCRhwwdukcpXK8TG17swFNzYFr2rtPxy", + "poolTokenDecimals": 6, + "feeAccount": "B4RNxMJGRzKFQyTq2Uwkmpyjtew13n7KtdqZy6qgENTu", + "tokenIds": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "tokens": { + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "6uUn2okWk5v4x9Gc4n2LLGHtWoa9tmizHq1363dW7t9W" + }, + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": { + "tag": "USDT", + "name": "Tether USD", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "scale": 6, + "addr": "AiwmnLy7xPT28dqZpkRm6i1ZGwELUCzCsuN92v4JkSeU" + } + }, + "curveType": 2, + "amp": 100, + "feeStructure": { + "traderFee": { + "numerator": "06", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "01", + "denominator": "2710" + } + } + }, + { + "name": "ORCA_SOL", + "address": "2ZnVuidTHpi5WWKUwFXauYGhvdT9jRKYv5MDahtbwtYr", + "nonce": 255, + "authority": "2PH1quJj9MHQXATCmNZ6qQ2gZqM8R236DpKaz99ggVpm", + "poolTokenMint": "2uVjAuRXavpM6h1scGQaxqb6HVaNRn6T2X7HHXTabz25", + "poolTokenDecimals": 6, + "feeAccount": "4Zc4kQZhRQeGztihvcGSWezJE1k44kKEgPCAkdeBfras", + "tokenIds": [ + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE": { + "tag": "ORCA", + "name": "Orca", + "mint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "scale": 6, + "addr": "AioST8HKQJRqjE1mknk4Rydc8wVADhdQwRJmAAYX1T6Z" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "73zdy95DynZP4exdpuXTDsexcrWbDJX9TFi2E6CDzXh4" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ORCA_USDC", + "address": "2p7nYbtPBgtmY69NsE8DAW6szpRJn7tQvDnqvoEWQvjY", + "nonce": 254, + "authority": "3fr1AhdiAmWLeNrS24CMoAu9pPgbzVhwLtJ6QUPmw2ob", + "poolTokenMint": "n8Mpu28RjeYD7oUX3LG1tPxzhRZh3YYLRSHcHRdS3Zx", + "poolTokenDecimals": 6, + "feeAccount": "7CXZED4jfRp3qdHB9Py3up6v1C4UhHofFvfT6RXbJLRN", + "tokenIds": [ + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE": { + "tag": "ORCA", + "name": "Orca", + "mint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "scale": 6, + "addr": "9vYWHBPz817wJdQpE8u3h8UoY3sZ16ZXdCcvLB7jY4Dj" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "6UczejMUv1tzdvUzKpULKHxrK9sqLm8edR1v9jinVWm9" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "KIN_SOL", + "address": "Ez52BLSoZw3MxWxMK4ADsJXqzUiYW9sUnwcrrQwQGdLT", + "nonce": 253, + "authority": "C6WisvFQzqxTBF3DV6RFbPMPBiVHE816CZHoctB3WzrW", + "poolTokenMint": "HEvnD66WcBfTajS9adUYnGRBMDehFtLySiFHSD6kEBWs", + "poolTokenDecimals": 6, + "feeAccount": "5bLeJU66qTopjZBa48BUd7EXWcj4UeCMfHjjrvt8Zcgs", + "tokenIds": [ + "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6": { + "tag": "KIN", + "name": "Kin", + "mint": "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6", + "scale": 5, + "addr": "2Ssm5Dd1Zc5QpGSZzuqt3Ef4bADteuBbCGiEZJ5n48rV" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "HCS9EpKRxWDS9GCRFStNbPWgRQpvV6EyBWChJja2UbCm" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SAMO_SOL", + "address": "DiAP9qmp5foN7fLTWfBWjo9KBS1jgvKUJLWi1ZhqKaja", + "nonce": 255, + "authority": "3uZcofBKVBYFrQ7jVjTFLEMWAQiZcih4AES5tKBqdo7m", + "poolTokenMint": "D6N9j8F2DhtzDtrdpT74y3u2YmYAzcggiLc3nTjqux9M", + "poolTokenDecimals": 6, + "feeAccount": "BYAjG645fRRHZ5JFnZKnXc4dzK9WppcoVWDMYj3zm3KF", + "tokenIds": [ + "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU": { + "tag": "SAMO", + "name": "Samoyedcoin", + "mint": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", + "scale": 9, + "addr": "AFcbD7UTzk9d1njRxyDHNbQ5Q6miPNAE1GctjD96JYAi" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "6kYbPDsYLQUwptV7ZvQKG3gjNreEEgaWh2CM4DQPYTpq" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "LIQ_USDC", + "address": "AXSeEafwPUGSamiZWH8m2PJtvpDVtrofxvycNwxiysdh", + "nonce": 254, + "authority": "6Y5TnCwgifc8Z7QYo672nT9uNd2hcivVR1NT6oDkJx6P", + "poolTokenMint": "3PD9SZFwXKkXr4akLf4ofo37ZUMycwML89R2P3qxcbZG", + "poolTokenDecimals": 6, + "feeAccount": "FSVPcprrTkQLRtDACFEpa2rhEVx4kBvjPuQaxj572SaC", + "tokenIds": [ + "4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj": { + "tag": "LIQ", + "name": "LIQ Protocol", + "mint": "4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj", + "scale": 6, + "addr": "CVspL8Tj5YdqntXJegNeDXHiBn3648QDNB7gex6D9MgY" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "8YzLsZ1FtsruswkBogEaXwmRTf5PMuyVcfSZXRAdi8qA" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SNY_USDC", + "address": "BDn3Fj9UXVi2mRVMR2jzpCrAZZphnwfkvFhs192yhCTu", + "nonce": 255, + "authority": "FvjsfbbzZAcrVdfrGtZUjGWAjWHXrfMG8Bjwc17vVSK3", + "poolTokenMint": "AZpo4BJHHRetF96v6SGinFZBMXM4yWMo4RA8C4PriDLk", + "poolTokenDecimals": 6, + "feeAccount": "DiULDJAYXdbtX8CfFsU2jCgHvQWT7u3gwRwpvQxfEMvr", + "tokenIds": [ + "4dmKkXNHdgYsXqBHCuMikNQWwVomZURhYvkkX5c4pQ7y", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "4dmKkXNHdgYsXqBHCuMikNQWwVomZURhYvkkX5c4pQ7y": { + "tag": "SNY", + "name": "SNY", + "mint": "4dmKkXNHdgYsXqBHCuMikNQWwVomZURhYvkkX5c4pQ7y", + "scale": 6, + "addr": "14RHMRzwx9Y4Z41qpr9sTwJZ58wXqV1R9WTkUA7ybmKG" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "Dw9D9T4bBC3oGdMqxE1xWfPSCJ27SYwWD8rFfUxU99QG" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "mSOL_USDC", + "address": "Hme4Jnqhdz2jAPUMnS7jGE5zv6Y1ynqrUEhmUAWkXmzn", + "nonce": 255, + "authority": "9Z7E42k46kxnBjAh8YGXDw3rRGwwxQUBYM7Ccrmwg6ZP", + "poolTokenMint": "8PSfyiTVwPb6Rr2iZ8F3kNpbg65BCfJM9v8LfB916r44", + "poolTokenDecimals": 6, + "feeAccount": "3W3Skj2vQsNEMhGRQprFXQy3Q8ZbM6ojdgiDCokVPWno", + "tokenIds": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "GBa7G5f1FqAXEgByuHXsqsEdpyMjRgT9SNxZwmmnEJAY" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "7hFgNawzzmpDM8TTVCKm8jykBrym8C3TQdb8TDAfAVkD" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SLRS_USDC", + "address": "87Xz6RK1uzP5NEhSjMewZtDAZyg4V1tYAa1KHnvge17b", + "nonce": 254, + "authority": "5D9v9y6Kbswe6k1wnVceuqRXHMsRQJ8xzx8hadSH6EAM", + "poolTokenMint": "AtB4nUmdyQfuWWJ9xAHw9xyVnJFfSjSuVWkiYan8y86w", + "poolTokenDecimals": 6, + "feeAccount": "CLxeBxNq42AtmD43F5BTqCHTnkVHX8sP9cVPdtRkTL7D", + "tokenIds": [ + "SLRSSpSLUTP7okbCUBYStWCo1vUgyt775faPqz8HUMr", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "SLRSSpSLUTP7okbCUBYStWCo1vUgyt775faPqz8HUMr": { + "tag": "SLRS", + "name": "Solrise Finance", + "mint": "SLRSSpSLUTP7okbCUBYStWCo1vUgyt775faPqz8HUMr", + "scale": 6, + "addr": "CM7oGYHy1oxzHoum8Cxv4pwnndm6Jbs3NkBZkc6v9S9d" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "3QqPbMcUMZu3Rz762g7JgvpUxhRHPtE9HFk2MSDRmPqa" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "PORT_USDC", + "address": "4if9Gy7dvjU7XwunKxdnCcPsaT3yAHPXdz2XS1eo19LG", + "nonce": 254, + "authority": "BshtCZRCHj2RZYC7u5sW3ioRJo9ZiYA4T5p8muFwrKnb", + "poolTokenMint": "F8gPSpwVHj8FdAJAYULDuZBxFEJut87hUbARYYx3471w", + "poolTokenDecimals": 6, + "feeAccount": "5JZXUbCfaSo3y9PYq47Hj5Yc6hVFa4j7MkDzBJfMSRSN", + "tokenIds": [ + "PoRTjZMPXb9T7dyU7tpLEZRQj7e6ssfAE62j2oQuc6y", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "PoRTjZMPXb9T7dyU7tpLEZRQj7e6ssfAE62j2oQuc6y": { + "tag": "PORT", + "name": "Port Finance", + "mint": "PoRTjZMPXb9T7dyU7tpLEZRQj7e6ssfAE62j2oQuc6y", + "scale": 6, + "addr": "2wuSqR5z2Guft2yt57Hx7K6i1AYNoUi8fjxHUeAgaKXo" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "AvP1Db3SyUxLGMSc4nSXjJkjm1kAjiLjog7cup19eWa3" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SBR_USDC", + "address": "HiYggjP2fN53Jw46e5UuskqNP3HH98jceRxEgVoeRwNw", + "nonce": 255, + "authority": "ATkEV1nEkdp7zgaGpzFCsJ5WAyejcJbxqzGhQpfcDW4S", + "poolTokenMint": "CS7fA5n4c2D82dUoHrYzS3gAqgqaoVSfgsr18kitp2xo", + "poolTokenDecimals": 6, + "feeAccount": "7S3KKuvcHfcKWBGLDwmoTgtB97JE8LHruP8jbmQkGfH", + "tokenIds": [ + "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1": { + "tag": "SBR", + "name": "Saber", + "mint": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1", + "scale": 6, + "addr": "DrJTQqNZqNCf2HDLpYg9zRCMRwnhZEVQuGjeaWtX6CA7" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "DEVLUv1uiUSukQoBdy9fDQyehi4N2Boojy8J2LQ8bK2E" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "scnSOL_USDC", + "address": "6Gh36sNXrGWYiWr999d9iZtqgnipJbWuBohyHBN1cJpS", + "nonce": 255, + "authority": "GXWEpRURaQZ9E62Q23EreTUfBy4hfemXgWFUWcg7YFgv", + "poolTokenMint": "Dkr8B675PGnNwEr9vTKXznjjHke5454EQdz3iaSbparB", + "poolTokenDecimals": 6, + "feeAccount": "HsC1Jo38jK3EpoNAkxfoUJhQVPa28anewZpLfeouUNk7", + "tokenIds": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm": { + "tag": "scnSOL", + "name": "Socean Staked Sol", + "mint": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "scale": 9, + "addr": "7xs9QsrxQDVoWQ8LQ8VsVjfPKBrPGjvg8ZhaLnU1i2VR" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "FZFJK64Fk1t619zmVPqCx8Uy29zJ3WuvjWitCQuxXRo3" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SOCN_USDC", + "address": "6Gh36sNXrGWYiWr999d9iZtqgnipJbWuBohyHBN1cJpS", + "nonce": 255, + "authority": "GXWEpRURaQZ9E62Q23EreTUfBy4hfemXgWFUWcg7YFgv", + "poolTokenMint": "Dkr8B675PGnNwEr9vTKXznjjHke5454EQdz3iaSbparB", + "poolTokenDecimals": 6, + "feeAccount": "HsC1Jo38jK3EpoNAkxfoUJhQVPa28anewZpLfeouUNk7", + "tokenIds": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm": { + "tag": "scnSOL", + "name": "Socean Staked Sol", + "mint": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "scale": 9, + "addr": "7xs9QsrxQDVoWQ8LQ8VsVjfPKBrPGjvg8ZhaLnU1i2VR" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "FZFJK64Fk1t619zmVPqCx8Uy29zJ3WuvjWitCQuxXRo3" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "pSOL_USDC", + "address": "GW1Xt9HHtvcnky8X7aBA3BoTgiirJKP5XwC5REFcZSsc", + "nonce": 254, + "authority": "GXueH9K1MzRncoTYbpLiXXC3WrKkmHUFxV5JEu8oADbw", + "poolTokenMint": "C2YzN6MymD5HM2kPaH7bzcbqciyjfmpqyVaR3KA5V6z1", + "poolTokenDecimals": 6, + "feeAccount": "BhHd49JYH3Hk6TV5kCjmUgf7fQSQKDjaWTokMmBhTx9o", + "tokenIds": [ + "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX": { + "tag": "pSOL", + "name": "pSOL", + "mint": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "scale": 9, + "addr": "F7XioZaGe99nosYJQCahx25TKgdUGufYf6sudm1JSgu" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "BT14DfFyNS7qcBGc8TY4HAzDev4vvqsoFBJgjtQpdM2Z" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "mSOL_SOL", + "address": "9EQMEzJdE2LDAY1hw1RytpufdwAXzatYfQ3M2UuT9b88", + "nonce": 250, + "authority": "6cwehd4xhKkJ2s7iGh4CaDb7KhMgqczSBnyNJieUYbHn", + "poolTokenMint": "29cdoMgu6MS2VXpcMo1sqRdWEzdUR9tjvoh8fcK8Z87R", + "poolTokenDecimals": 6, + "feeAccount": "6j2tt2UVYMQwqG3hRtyydW3odzBFwy3pN33tyB3xCKQ6", + "tokenIds": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "6xmki5RtGNHrfhTiHFfp9k3RQ9t8qgL1cYP2YCG2h179" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "Ew2coQtVGLeca31vqB2ssHntjzZgUy1ad9VuuAX8yw7p" + } + }, + "curveType": 2, + "amp": 100, + "feeStructure": { + "traderFee": { + "numerator": "06", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "01", + "denominator": "2710" + } + } + }, + { + "name": "ORCA_PAI", + "address": "7LfLiCnoLPefaCVuh6z92TK2tPZUa9bPjW7gHT4jqrec", + "nonce": 254, + "authority": "AwUWHxHyQHomqCGJJvagiSDhb2xMqJUiE25qDytdMw49", + "poolTokenMint": "C7TH2jEJJaxVwwuvkbcDGfHUiZvEkkeYjyAcdTMi5ujb", + "poolTokenDecimals": 6, + "feeAccount": "DrC2aGWrUmsnK6yAphSXEs8GW5nKUCkityaG8Bikn5Ne", + "tokenIds": [ + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS" + ], + "tokens": { + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE": { + "tag": "ORCA", + "name": "Orca", + "mint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "scale": 6, + "addr": "HSUFpGyNXEogXQLgEMQ7aMTxE4HZneRaBovbi9btXepM" + }, + "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS": { + "tag": "PAI", + "name": "Parrot Stable", + "mint": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "scale": 6, + "addr": "4c9rkBiqAY6fXpVvCbDwpDD44AGQ3MXSaCLcpmLUFtrX" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ORCA_mSOL", + "address": "49tTgthTYLMPEqozZNyEQifqkGYxHqqDie9YxVczS3iB", + "nonce": 255, + "authority": "9FQ9gDtS6uNr5SMPafuzkDit2rMftHfQuz5mg2X3TqHT", + "poolTokenMint": "CVapmQn7HaU1yMDW3q6oUV4hx6XoYv54T4zfGXkuJqkA", + "poolTokenDecimals": 6, + "feeAccount": "Hq9xxKdMavJd4teMZusF4PiGNGV3hxdcMZwAdngkHCg7", + "tokenIds": [ + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" + ], + "tokens": { + "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE": { + "tag": "ORCA", + "name": "Orca", + "mint": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "scale": 6, + "addr": "7hoYJc4aqttctANrNe75gscdmQD9HcXZED6AjdDdZMQ9" + }, + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "7MuvRUFT1wWiL7uJKdZqNwk9Fmz2HJ36bEArhDTnyFij" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "scnSOL_SOL", + "address": "2q6UMko5kTnv866W9MTeAFau94pLpsdeNjDdSYSgZUXr", + "nonce": 255, + "authority": "Gyd77CwV23qq937x9UDa4TDkxEeQF9tp8ifotYxqW3Kd", + "poolTokenMint": "APNpzQvR91v1THbsAyG3HHrUEwvexWYeNCFLQuVnxgMc", + "poolTokenDecimals": 6, + "feeAccount": "42Xzazs9EvjtidvEDrj3JXbDtf6fpTq5XHh96mPctvBV", + "tokenIds": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm": { + "tag": "scnSOL", + "name": "Socean Staked Sol", + "mint": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "scale": 9, + "addr": "C8DRXUqxXtUgvgBR7BPAmy6tnRJYgVjG27VU44wWDMNV" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "DzdxH5qJ68PiM1p5o6PbPLPpDj8m1ZshcaMFATcxDZix" + } + }, + "curveType": 2, + "amp": 100, + "feeStructure": { + "traderFee": { + "numerator": "06", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "01", + "denominator": "2710" + } + } + }, + { + "name": "SOCN_SOL", + "address": "2q6UMko5kTnv866W9MTeAFau94pLpsdeNjDdSYSgZUXr", + "nonce": 255, + "authority": "Gyd77CwV23qq937x9UDa4TDkxEeQF9tp8ifotYxqW3Kd", + "poolTokenMint": "APNpzQvR91v1THbsAyG3HHrUEwvexWYeNCFLQuVnxgMc", + "poolTokenDecimals": 6, + "feeAccount": "42Xzazs9EvjtidvEDrj3JXbDtf6fpTq5XHh96mPctvBV", + "tokenIds": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm": { + "tag": "scnSOL", + "name": "Socean Staked Sol", + "mint": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "scale": 9, + "addr": "C8DRXUqxXtUgvgBR7BPAmy6tnRJYgVjG27VU44wWDMNV" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "DzdxH5qJ68PiM1p5o6PbPLPpDj8m1ZshcaMFATcxDZix" + } + }, + "curveType": 2, + "amp": 100, + "feeStructure": { + "traderFee": { + "numerator": "06", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "01", + "denominator": "2710" + } + } + }, + { + "name": "ATLAS_USDC", + "address": "3V5sjXj1mrWjjB1Xt6Xwp554QwHE5fppGSxbk4GzAtEW", + "nonce": 254, + "authority": "8UYN675AJn5htWydDs724xqintBZ4XzsCWqMozUSDU8m", + "poolTokenMint": "FZ8x1LCRSPDeHBDoAc3Gc6Y7ETCynuHEr5q5YWV7uRCJ", + "poolTokenDecimals": 6, + "feeAccount": "CFN4DQ2p3qroX92pPNy3mov3Dw1aCNGLrU5AXHpHxbko", + "tokenIds": [ + "ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx": { + "tag": "ATLAS", + "name": "Star Atlas", + "mint": "ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx", + "scale": 8, + "addr": "xotXsNCx4tBhnwhrajGTaVgKq1sfuMkeYHc77ZegCqE" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "8YswVYsTi66umBF2Bnkh4LB2VWMKPssDpe54VAgiuJZQ" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "POLIS_USDC", + "address": "CdKPtCb5fBRaGFS4bJgytfReeHuFyhpe9YUyWHPnEWZG", + "nonce": 251, + "authority": "8XB9V3VuHtPBzHqvxzcmpkpaoXNXjZMD8VBHC79SxcEL", + "poolTokenMint": "GteBdo9sqE7T41G8AJsaG9WHW48uXBwsLLznmu2TBdgy", + "poolTokenDecimals": 6, + "feeAccount": "3gZQ2YnrXbnRwJj5poffLirF7CwacatvtfGCNRFrbJdr", + "tokenIds": [ + "poLisWXnNRwC6oBu1vHiuKQzFjGL4XDSu4g9qjz9qVk", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "poLisWXnNRwC6oBu1vHiuKQzFjGL4XDSu4g9qjz9qVk": { + "tag": "POLIS", + "name": "Star Atlas DAO", + "mint": "poLisWXnNRwC6oBu1vHiuKQzFjGL4XDSu4g9qjz9qVk", + "scale": 8, + "addr": "EbXNEUiKxSU1vwwhrbVNVk3qX4o1yU3p75SQUUMfc1zH" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "CLCj9b1vdPutrkvZS8ACTM5q42SXB2Q7khnMLVxDMGEK" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "BOP_USDC", + "address": "9pheQ8EX2wDKHZYm75G9haEbTyAcg8F2gFWybw2w6Vyc", + "nonce": 253, + "authority": "3rxJPYuiRijipJbciewUDacab2Wo3yAe1yWBGzmqn5f1", + "poolTokenMint": "2gXDJZ7XAtQEtf4PRSQZKoq1WMuu1H44tQanbMA3YVpu", + "poolTokenDecimals": 6, + "feeAccount": "2bei4349W8FUcu5gvP5Zt8yhkpwqcCngZxUkb3xRMJZJ", + "tokenIds": [ + "BLwTnYKqf7u4qjgZrrsKeNs2EzWkMLqVCu6j8iHyrNA3", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "BLwTnYKqf7u4qjgZrrsKeNs2EzWkMLqVCu6j8iHyrNA3": { + "tag": "BOP", + "name": "Boring Protocol", + "mint": "BLwTnYKqf7u4qjgZrrsKeNs2EzWkMLqVCu6j8iHyrNA3", + "scale": 8, + "addr": "HkHjLSaP9yyWTzMGD1DKyoc1Dfvhvw4vakRhyjcVUCKs" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "CqRoKMF4kh2o568nFdDNHs7cszBjrkQME4RtCuTqcjCe" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SAMO_USDC", + "address": "Epvp7qMYAF21VVjacdB3VfKn6nnXQSF4rGYu8sD6Bkow", + "nonce": 252, + "authority": "AB4rTE2JiKFhnfynUQCovbW75CUxT9LxcJX2SDTbY9gy", + "poolTokenMint": "6VK1ksrmYGMBWUUZfygGF8tHRGpNxQEWv8pfvzQHdyyc", + "poolTokenDecimals": 6, + "feeAccount": "9U8UF7d8kBvsS25XoZnjmVQ9vGkP4BUnHJgfc615BvG1", + "tokenIds": [ + "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU": { + "tag": "SAMO", + "name": "Samoyedcoin", + "mint": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", + "scale": 9, + "addr": "7jwHW4Lw3nVaSJXskN5pUoKU6YB9RBVfZtGBp3VbR43U" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "G7Gqjxk9EaJMeFfoFTSy9WfH8uurgQkbNQCREWAc56DZ" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "NINJA_SOL", + "address": "3ECUtPokme1nimJfuAkMtcm7QYjDEfXRQzmGC16LuYnz", + "nonce": 255, + "authority": "H8f9n2PfehUc73gRWSRTPXvqUhUHVywdAxcfEtYmmyAo", + "poolTokenMint": "4X1oYoFWYtLebk51zuh889r1WFLe8Z9qWApj87hQMfML", + "poolTokenDecimals": 6, + "feeAccount": "43ViAbUVujnYtJyzGP4AhabMYCbLsExenT3WKsZjqJ7N", + "tokenIds": [ + "FgX1WD9WzMU3yLwXaFSarPfkgzjLb2DZCqmkx9ExpuvJ", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "FgX1WD9WzMU3yLwXaFSarPfkgzjLb2DZCqmkx9ExpuvJ": { + "tag": "NINJA", + "name": "NINJA", + "mint": "FgX1WD9WzMU3yLwXaFSarPfkgzjLb2DZCqmkx9ExpuvJ", + "scale": 6, + "addr": "6Y9VyEYHgxVahiixzphNh4HAywpab9zVoD4S8q1sfuL8" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "9SxzphwrrDVDkwkyvmtag9NLgpjSkTw35cRwg9rLMYWk" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SLIM_USDC", + "address": "8JPid6GtND2tU3A7x7GDfPPEWwS36rMtzF7YoHU44UoA", + "nonce": 255, + "authority": "749y4fXb9SzqmrLEetQdui5iDucnNiMgCJ2uzc3y7cou", + "poolTokenMint": "BVWwyiHVHZQMPHsiW7dZH7bnBVKmbxdeEjWqVRciHCyo", + "poolTokenDecimals": 6, + "feeAccount": "E6aTzkZKdCECgpDtBZtVpqiGjxRDSAFh1SC9CdSoVK7a", + "tokenIds": [ + "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW": { + "tag": "SLIM", + "name": "Solanium", + "mint": "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW", + "scale": 6, + "addr": "ErcxwkPgLdyoVL6j2SsekZ5iysPZEDRGfAggh282kQb8" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "EFYW6YEiCGpavuMPS1zoXhgfNkPisWkQ3bQz1b4UfKek" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "wHAPI_USDC", + "address": "2Y1Jmpkf5wt1X5zcFHNBDoHxqjTXbMJfj1UFtrYQwUbG", + "nonce": 254, + "authority": "8K4eRHeyPhBGB9zCjKtyBHoPPZ75zLN64oxBF6GyF4Qg", + "poolTokenMint": "ELfBngAgvLEHVBuJQhhE7AW6eqLX7id2sfrBngVNVAUW", + "poolTokenDecimals": 6, + "feeAccount": "Bx3ZhEBFedDqCBzuzKVS4eMKTtW1MmHkcMGU45FcyxRT", + "tokenIds": [ + "6VNKqgz9hk7zRShTFdg5AnkfKwZUcojzwAkzxSH3bnUm", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "6VNKqgz9hk7zRShTFdg5AnkfKwZUcojzwAkzxSH3bnUm": { + "tag": "wHAPI", + "name": "HAPI", + "mint": "6VNKqgz9hk7zRShTFdg5AnkfKwZUcojzwAkzxSH3bnUm", + "scale": 9, + "addr": "DRYADMQevoJHDFCYbDQeS4p551MpsDN2d7CJU3LxfNHa" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HzsECCX6RZ2ccbR3FarRSEfc5rkuETfywXJnRZut5JzU" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "COPE_USDC", + "address": "DhGTKyT6RVkpvPxXAJodi4Z41RSvQZxV1f5eRvJ5bE4r", + "nonce": 254, + "authority": "Hu8AWoRBa7ZaYQFdHQyRHeEZGB16Me86fA5vAZzjVUmv", + "poolTokenMint": "HsauTv9s52Zv12eaDuSp6y7BEm4e4BHEyAsbdjyyWzPK", + "poolTokenDecimals": 6, + "feeAccount": "JEGcxfGxWJpRkGtvA6J6kEgTBebDz6kxURoeB3SX8vaW", + "tokenIds": [ + "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh": { + "tag": "COPE", + "name": "Cope", + "mint": "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "scale": 6, + "addr": "6N3P3etaUYGeBs2C67ZQTDRqHsExNsfj85dDWPwHtQBS" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "36VVz3eY8YmWBGskQVjvGGBfyUKHHCEDBgkFtzMpFqeU" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SUNNY_USDC", + "address": "3Ukqqshh3kZ8UPbcYYFSRaeJcsgttcmShtNNn12F1rj2", + "nonce": 255, + "authority": "7NP8DTzPdpbQofhNyhLW3j2khutmfy1kuFp2AjaD8rrp", + "poolTokenMint": "GHuoeq9UnFBsBhMwH43eL3RWX5XVXbSRYJymmyMYpT7n", + "poolTokenDecimals": 6, + "feeAccount": "CCuSVbnnq8SUj7cpPe7BbHLuKanyxfvfrwypzCBnaDdf", + "tokenIds": [ + "SUNNYWgPQmFxe9wTZzNK7iPnJ3vYDrkgnxJRJm1s3ag", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "SUNNYWgPQmFxe9wTZzNK7iPnJ3vYDrkgnxJRJm1s3ag": { + "tag": "SUNNY", + "name": "Sunny Aggregator", + "mint": "SUNNYWgPQmFxe9wTZzNK7iPnJ3vYDrkgnxJRJm1s3ag", + "scale": 6, + "addr": "F6nCAMYEFxsyRDVonQXLNufXgAHsgAa1Br8DhBoX3KAV" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HWCTHmQppFSsKQEk1bHUqPC2WLaidgnfTG9MQGD4XKEt" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "GRAPE_USDC", + "address": "6MxUhBLXHCqpdYaFPTmw1D9fQ7zYnm9grZyJvpGiqr15", + "nonce": 255, + "authority": "SvWmpVVUkv8cwoRnBQ5Gqt2FFYjdpWLS665gE2ZLNQp", + "poolTokenMint": "EorFh8siFyLF1QTZ7cCXQaPGqyo7eb4SAgKtRH8Jcxjd", + "poolTokenDecimals": 6, + "feeAccount": "6vWYnRDEHu7kRLbA2dnBgEfbdba72iDMDD9k3munyPaP", + "tokenIds": [ + "8upjSpvjcdpuzhfR1zriwg5NXkwDruejqNE9WNbPRtyA", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "8upjSpvjcdpuzhfR1zriwg5NXkwDruejqNE9WNbPRtyA": { + "tag": "GRAPE", + "name": "Grape", + "mint": "8upjSpvjcdpuzhfR1zriwg5NXkwDruejqNE9WNbPRtyA", + "scale": 6, + "addr": "686KiYDMMkbredNoWx8yqvAdKSiHuWSG3dnbL6yWYmZp" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "9i14ZKzaDmzKCAQb8hCv4h5GCo2Xiq83JcL7bofk4Ddj" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "ABR_USDC", + "address": "rxwsjytcEBvXpXrXBL1rpsjhoh78imBn8WbxjKmLRge", + "nonce": 252, + "authority": "AcaxutE6Rh9vRxipTLdqinEdRK6R4ayUAAv2bZPh6UU9", + "poolTokenMint": "GMzPbaCuQmeMUm1opH3oSCgKUjVgJUW14myq99RVPGX5", + "poolTokenDecimals": 6, + "feeAccount": "7pPJGwd8Vq7aYmHaocQpQSfTn3UWYGKUgFkFhpMmRdDF", + "tokenIds": [ + "a11bdAAuV8iB2fu7X6AxAvDTo1QZ8FXB3kk5eecdasp", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "a11bdAAuV8iB2fu7X6AxAvDTo1QZ8FXB3kk5eecdasp": { + "tag": "ABR", + "name": "Allbridge", + "mint": "a11bdAAuV8iB2fu7X6AxAvDTo1QZ8FXB3kk5eecdasp", + "scale": 9, + "addr": "6FRxhbY7bvSiDojPiqoidjTyDjxaUyCoPQk3ifEdfFbm" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "8aTapFecZRZmC2bTeKr2voHFW2twNvbrh8nWYdXYQWkZ" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "KURO_USDC", + "address": "HdeYs4bpJKN2oTb7PHxbqq4kzKiLr772A5N2gWjY57ZT", + "nonce": 250, + "authority": "2KRcBDQJWEPygxcMMFMvR6dMTVtMkJV6kbxr5e9Kdj5Q", + "poolTokenMint": "DRknxb4ZFxXUTG6UJ5HupNHG1SmvBSCPzsZ1o9gAhyBi", + "poolTokenDecimals": 6, + "feeAccount": "5XuLrZqpX9gW3pJw7274EYwAft1ciTXndU4on96ERi9J", + "tokenIds": [ + "2Kc38rfQ49DFaKHQaWbijkE7fcymUMLY5guUiUsDmFfn", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "2Kc38rfQ49DFaKHQaWbijkE7fcymUMLY5guUiUsDmFfn": { + "tag": "KURO", + "name": "Kurobi", + "mint": "2Kc38rfQ49DFaKHQaWbijkE7fcymUMLY5guUiUsDmFfn", + "scale": 6, + "addr": "DBckbD9CoRBFE8WdbbnFLDz6WdDDSZ7ReEeqdjL62fpG" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "B252w7ZkUX4WyLUJKLEymEpRkYMqJhgv2PSj2Z2LWH34" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "MEDIA_USDC", + "address": "5L2aVMnNsmrnkxU4B25ajb2pR5AJWBRfUa73wjasjyaB", + "nonce": 255, + "authority": "HX3JKg5HtboRw9nQRWm47rSJkBHczdcXwBgWASyHi3Wk", + "poolTokenMint": "2toFgkQDoPrTJYGDEVoCasPXuL9uQnjvXJaDwa9LHyTx", + "poolTokenDecimals": 6, + "feeAccount": "5BkyYnBWnzBWQKnU9AcUaDrmyhjLpAcFxCvVTTnRGyYk", + "tokenIds": [ + "ETAtLmCmsoiEEKfNrHKJ2kYy3MoABhU6NQvpSfij5tDs", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "ETAtLmCmsoiEEKfNrHKJ2kYy3MoABhU6NQvpSfij5tDs": { + "tag": "MEDIA", + "name": "Media Network", + "mint": "ETAtLmCmsoiEEKfNrHKJ2kYy3MoABhU6NQvpSfij5tDs", + "scale": 6, + "addr": "BFAyLvCbMhgF7CQ9fsWWK46jD9mPXfBMDWvXgk5LTgsT" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "7CvBsWsfEif4sAo9dnsf1JKVAfBGcZUVTktqtxBSkgwB" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "TULIP_USDC", + "address": "BNwrRN23RCoq5MAneJ6Cot7iN2FLtyt9rtcjaVfAXTLt", + "nonce": 253, + "authority": "EFZs7veYVdWBHt7RcAPvXQc46gDzccpZTxAcEm6NyXFg", + "poolTokenMint": "4SBx8GXu8HhcVHWydQv1vsDdZs3G93XSL9CtMBny6hS5", + "poolTokenDecimals": 6, + "feeAccount": "8BiqDTCBQ77qjGpED2was7C4iHJrQx9bXhzRt3Wz9xJG", + "tokenIds": [ + "TuLipcqtGVXP9XR62wM8WWCm6a9vhLs7T1uoWBk6FDs", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "TuLipcqtGVXP9XR62wM8WWCm6a9vhLs7T1uoWBk6FDs": { + "tag": "TULIP", + "name": "Tulip Protocol", + "mint": "TuLipcqtGVXP9XR62wM8WWCm6a9vhLs7T1uoWBk6FDs", + "scale": 6, + "addr": "5CKd5M2nXdPM1TMXxqK6Up6GZehKL5uU9Z9Ytm2sFCiz" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HjMQnuxjVRodoaAg9WcNXb9TAssDaFNpgwcUUKNjWdh5" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "MNGO_USDC", + "address": "Hk9ZCvmqVT1FHNkWJMrtMkkVnH1WqssWPAvmio5Vs3se", + "nonce": 254, + "authority": "5RyiYaHFDVupwnwxzKCRk7JY1CKhsczZXefMs3UUmx4Z", + "poolTokenMint": "H9yC7jDng974WwcU4kTGs7BKf7nBNswpdsP5bzbdXjib", + "poolTokenDecimals": 6, + "feeAccount": "FWKcKaMfaVezLRFr946MdgmpTZHG4A2GgqehAxjTyDAB", + "tokenIds": [ + "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac": { + "tag": "MNGO", + "name": "Mango Markets", + "mint": "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac", + "scale": 6, + "addr": "J8bQnhcNyixFGBskQoJ2aSPXPWjvSzaaxF4YPs96XHDJ" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "5yMoAhjfFaCPwEwKM2VeFFh2iBs5mHWLTJ4LuqZifsgN" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "stSOL_wstETH", + "address": "B32UuhPSp6srSBbRTh4qZNjkegsehY9qXTwQgnPWYMZy", + "nonce": 255, + "authority": "EtwQJxu8wih29vMpdTa74K9W9tgtL4LT6hbWBkhHwvU5", + "poolTokenMint": "Eswigpwm3xsipkTqahGi2PEJsJcULQBwZgxhQpr6yBEa", + "poolTokenDecimals": 6, + "feeAccount": "74b9j23njRpt3PYPxoze2XS29ZgGmucziLB7WrsDpBdD", + "tokenIds": [ + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo" + ], + "tokens": { + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": { + "tag": "stSOL", + "name": "Lido Staked SOL", + "mint": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "scale": 9, + "addr": "CeSEpgqc3zV8xDr7Q6PiwJju6a6e92wpAv7Kg6QyFfQB" + }, + "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo": { + "tag": "wstETH", + "name": "Lido Staked Ether", + "mint": "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo", + "scale": 8, + "addr": "Fb3XpEJgghTURUGd1wphWr93ruX5egnesfdZtjWCxJFy" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SYP_USDC", + "address": "5DnwMqYAGEKekYXJdN8Bue6vN1p5zrEnBpmd53jEK61S", + "nonce": 255, + "authority": "4NfadURWeSDPJBGcKQRt39mPhbG9M7EJx6FZDwwcFB9f", + "poolTokenMint": "qJxKN9BhxbYvRNbjfK2uAVWboto6sonj8XC1ZEW5XTB", + "poolTokenDecimals": 6, + "feeAccount": "57L2bEFecsAv4jnaM2PBaeAVyPZEYtTmXBi7eaG2xWXw", + "tokenIds": [ + "FnKE9n6aGjQoNWRBZXy4RW6LZVao7qwBonUbiD7edUmZ", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "FnKE9n6aGjQoNWRBZXy4RW6LZVao7qwBonUbiD7edUmZ": { + "tag": "SYP", + "name": "Sypool", + "mint": "FnKE9n6aGjQoNWRBZXy4RW6LZVao7qwBonUbiD7edUmZ", + "scale": 9, + "addr": "6d19CQA1FP2MLLAzA7XoZEc9Agc32FaKUS175UVWLGtv" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HpPnUHyo19VjmXbP6FbbKXu7WQCUEn6h7be76fZdHVmf" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "stSOL_wLDO", + "address": "CqwyVxWeaiikQ3VhvEZSEmupmG1Wmc2FeaUjsCV492Sx", + "nonce": 255, + "authority": "213QoNt5dR56Ye2sx9cwPwpR3NpJUEStQXn8EbbWKkfJ", + "poolTokenMint": "74B9aMS7SA832xKngt5VLKmWAP3pa3qkUzWncTmQSsGF", + "poolTokenDecimals": 6, + "feeAccount": "D4kH4fcwwDtGMj4LpcynB977YVnmvDUcuDQoo5sqAgRz", + "tokenIds": [ + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p" + ], + "tokens": { + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": { + "tag": "stSOL", + "name": "Lido Staked SOL", + "mint": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "scale": 9, + "addr": "GDprNAcXeR5GVGnCtkS5UqyPGMm2Sy5Lk15qqN36faMT" + }, + "HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p": { + "tag": "wLDO", + "name": "Lido DAO", + "mint": "HZRCwxP2Vq9PCpPXooayhJ2bxTpo5xfpQrwB1svh332p", + "scale": 8, + "addr": "VCgdcsExfmxUDQwusLP2xqZ3ap7VuYyQMMHDPSva2hx" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "whETH_SOL", + "address": "FcEro2uFpHcb7Z785CBs6q12KMJqUJKa8VTXPi4TTBMf", + "nonce": 252, + "authority": "HMxZz8fv2uR9suzAPRbJGNB3wZL1eT3eKL3cpYWUbM8K", + "poolTokenMint": "7aYnrdmdCRodDy2Czn6keUquUhjF1jPEmfwZPh488z8U", + "poolTokenDecimals": 6, + "feeAccount": "YCVJDKdHNi1mhJtWz7QGbBRreMmw1soeipz7wZbQKEK", + "tokenIds": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs": { + "tag": "whETH", + "name": "Ethereum", + "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "scale": 8, + "addr": "3uQytDKNd5H6XK8FhTei4wCUmj2eTbLTbiLAtWk2SmbA" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "GR3g8Wej3jmv92hYM1t22kaXog2xjkGjQ7V1XzLd1efT" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "whETH_USDC", + "address": "4reGGLbesqpAeAZdAJv9hhgA2tgj45oGcyRuEvhATdMm", + "nonce": 252, + "authority": "8uLtzZ1iTLTCPsm3b4QttRmDXcFjhVHRuMS9VTVEwo7E", + "poolTokenMint": "7NPtjjAP7vhp4t5NCLyY4DY5rurvyc8cgZ2a2rYabRia", + "poolTokenDecimals": 6, + "feeAccount": "AVw52spXtzFh4bb5ghhpJaDbLx3XWuY85eQNDEo3X1yN", + "tokenIds": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs": { + "tag": "whETH", + "name": "Ethereum", + "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "scale": 8, + "addr": "9KpjcpKwhoFPbixvKDfcAhBQcVXk1CSBTGsJdzojDPRv" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "5HaG31FQS4McBVcHxVfwaKaWXE3VCGqvJ1ZDkTxs94cQ" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "MNDE_mSOL", + "address": "vjHagYsgZwG9icyFLHu2xWHWdtiS5gfeNzRhDcPt5xq", + "nonce": 255, + "authority": "3HWcojnC1ruEMmsE92Ez1BoebdDXzYQa4USaeWX7eTuM", + "poolTokenMint": "5PHS5w6hQwFNnLz1jJFe7TVTxSQ98cDYC3akmiAoFMXs", + "poolTokenDecimals": 6, + "feeAccount": "46mdANZ2DCA2sTFchvD7WwbffbLQa4jCFkkRL23WuYG8", + "tokenIds": [ + "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" + ], + "tokens": { + "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey": { + "tag": "MNDE", + "name": "Marinade Governance", + "mint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", + "scale": 9, + "addr": "2LferrWvYWtHFfdkmixzt9g3aKa3yBNfgbRrP1CcWMMp" + }, + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "GimsuZjYqMXM6xK6S3e9JpGvX6jaMPuNeR6s2piDESmy" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "WAG_USDC", + "address": "B76e3wtCDTKBgKQjvx87EBkDLPGcCY9w1SGiwjD5kaK7", + "nonce": 254, + "authority": "FRUmMZDiZrDrwioiUYi3tdqF7SEBeT219bBu54PGxoCo", + "poolTokenMint": "Df6XNHMF3uRVZnz7LCEGiZVax6rXgz76owtVkBHEjSb6", + "poolTokenDecimals": 6, + "feeAccount": "BCuRKfsM99LJFCchKUBLBZ26UuziDewJDRkkKMwx2qnd", + "tokenIds": [ + "5tN42n9vMi6ubp67Uy4NnmM5DMZYN8aS8GeB3bEDHr6E", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "5tN42n9vMi6ubp67Uy4NnmM5DMZYN8aS8GeB3bEDHr6E": { + "tag": "WAG", + "name": "Waggle", + "mint": "5tN42n9vMi6ubp67Uy4NnmM5DMZYN8aS8GeB3bEDHr6E", + "scale": 9, + "addr": "8voSogytL9jLgE73GS3WuujBinKFRQJjvUFsVGYexWZd" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HEP7mACuN13cT95eDAYTNjgwriqJnMQVhnyRctqnBRe4" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "mSOL_USDT", + "address": "Afofkb7JTc32rdpqiyc3RDmGF5s9N6W1ujcdYVfGZ5Je", + "nonce": 251, + "authority": "8vrC1FAnW6hQMwJuU5waZdRrBbDJTULqjpdc4GjDtKR6", + "poolTokenMint": "9cMWe4UYRPGAUUsTkjShJWVM7bk8DUBgxtwwH8asFJoV", + "poolTokenDecimals": 6, + "feeAccount": "7GPvi21QbwMyBoXU5Zqf8VhnuEh7VH4A1SRPgHJ36eE7", + "tokenIds": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "tokens": { + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "RTXKRxghfWJpE344UG7UhKnCwN2Gyv6KnNSTFDnaASF" + }, + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": { + "tag": "USDT", + "name": "Tether USD", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "scale": 6, + "addr": "J15KntYr6iout4ce2kcD2QEdkVbLN4EHHFLfCtke3f6Y" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "mSOL_whETH", + "address": "A71DQffTzgxBSzXjPL3tmf8XXTNtS5mR2D78Y8rmV2hk", + "nonce": 250, + "authority": "FPWpe7QEQnDMivnHksQW2uvcw9tvX1oxejKBX136WRkr", + "poolTokenMint": "5qoTq3qC4U7vFxo3iCzbXcaD1UEmDeCD63Dsuoct71oV", + "poolTokenDecimals": 6, + "feeAccount": "FSqUYVzF3XZzLcnj132eT6ed3bK95G1yG4MazcHZi99Q", + "tokenIds": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "tokens": { + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "Fcp5u8bL3V24MXjA4noSfMpcEAP2vSj1WTaA1ZNxACZL" + }, + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs": { + "tag": "whETH", + "name": "Ethereum", + "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "scale": 8, + "addr": "DuBCBX3y2FjDWUn2ncK5EKQh229JiJ7HTCjYJhNC87K8" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "BTC_mSOL", + "address": "8DRw5wQE1pyg6RB1UwypGNFgb2Pzp2hpyDDNwo76Lcc8", + "nonce": 255, + "authority": "3X1aLdyvcQNc8TvBMPiucMsRCnGMBnGsjJHpZEyCf3pn", + "poolTokenMint": "8nKJ4z9FSw6wrVZKASqBiS9DS1CiNsRnqwCCKVQjqdkB", + "poolTokenDecimals": 6, + "feeAccount": "AqiLHbUAy4UWWKGVVgbHsaUVCMg1zemNkgsYBPSirT92", + "tokenIds": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" + ], + "tokens": { + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E": { + "tag": "BTC", + "name": "Bitcoin", + "mint": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "scale": 6, + "addr": "6D3sxC6yEe84FUnF5Kpbgx6gN57N9poJCKAtrCeCWdJo" + }, + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So": { + "tag": "mSOL", + "name": "Marinade.finance", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "scale": 9, + "addr": "EPoVJLhi9QtVPVo8n31M5k5Knvb48j8zbYyRrUbrHwC5" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "IVN_SOL", + "address": "CFCivUWXBuULVNfJezj1fAhX6hdwVFi2BsCtpu6m96bR", + "nonce": 255, + "authority": "JGhNs5r7YNnJokzzXZWE3REKV8x4GiUvn2xSg7XGg59", + "poolTokenMint": "DfgCnzaiTXfPkAH1C1Z441b5MzjjTCEh134ioxqRZxYf", + "poolTokenDecimals": 6, + "feeAccount": "HwwgrSjJGFBtRN8h2daWnVLBciwoo79wNeKi6b5SZmE2", + "tokenIds": [ + "iVNcrNE9BRZBC9Aqf753iZiZfbszeAVUoikgT9yvr2a", + "So11111111111111111111111111111111111111112" + ], + "tokens": { + "iVNcrNE9BRZBC9Aqf753iZiZfbszeAVUoikgT9yvr2a": { + "tag": "IVN", + "name": "Investin", + "mint": "iVNcrNE9BRZBC9Aqf753iZiZfbszeAVUoikgT9yvr2a", + "scale": 6, + "addr": "C5yDeB3jBz5yZPa6FgP6b7HNoFxLP63Pyzpaosnkikis" + }, + "So11111111111111111111111111111111111111112": { + "tag": "SOL", + "name": "Solana", + "mint": "So11111111111111111111111111111111111111112", + "scale": 9, + "addr": "CCm846T6xj9VAhSAifuUJAXYCR3kaGp5KqhXFHCaeWUh" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "LARIX_USDC", + "address": "DaNULZAv2VyLk75pW3QD5szVzx5dT6iNvoih94Bttqf5", + "nonce": 255, + "authority": "FUVkG7fM3i8T49qV7WsJd68rBaYKvqTkAQCdftqTWWNj", + "poolTokenMint": "8sfThep3io4gvcGeuoAg1Rs8GDwKJjtcdAFHqQSSNAVE", + "poolTokenDecimals": 6, + "feeAccount": "AVb9Bvu4rjFYNCHygEnAYCjwnkgtC8C6UmJ7at3dsfdz", + "tokenIds": [ + "Lrxqnh6ZHKbGy3dcrCED43nsoLkM1LTzU2jRfWe8qUC", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "Lrxqnh6ZHKbGy3dcrCED43nsoLkM1LTzU2jRfWe8qUC": { + "tag": "LARIX", + "name": "Larix", + "mint": "Lrxqnh6ZHKbGy3dcrCED43nsoLkM1LTzU2jRfWe8qUC", + "scale": 6, + "addr": "AAjjSJsZM3AKK4h9cbGTHkquEZ2fWjgo9A9Pmrj2ynTH" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "82Fs8dSpMxPPfN1ULsXGFREHWz3JizREpTxwz2MaZ1n1" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "PRT_USDC", + "address": "CRm5uRBsVmUVHVqKfCCvCUX1RLUkjfcBWAeMQ5D9VuCM", + "nonce": 255, + "authority": "A9eqdCXYys7jeoroMFEnkGLoYLa2q5gGbg8RSKHkR2ne", + "poolTokenMint": "6jCERp5hKj37PCXP3VTjCDJeoPuSpnMDMz5A6jWQv3yS", + "poolTokenDecimals": 6, + "feeAccount": "FHVidN2ZdGnVaCjYwLjLXrimPbVsaqsUEEiGcVZ6WAPq", + "tokenIds": [ + "PRT88RkA4Kg5z7pKnezeNH4mafTvtQdfFgpQTGRjz44", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "PRT88RkA4Kg5z7pKnezeNH4mafTvtQdfFgpQTGRjz44": { + "tag": "PRT", + "name": "Parrot Protocol", + "mint": "PRT88RkA4Kg5z7pKnezeNH4mafTvtQdfFgpQTGRjz44", + "scale": 6, + "addr": "3oL2GjsUnQLjHw77p78CsRr7t94AVrtsCnW5uf6NYQ3g" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "2EiVwvsH5cvyk4W243zKoywkaEQb9Bwe9WGphRgBSqaP" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "JET_USDC", + "address": "ErWwp9HKjk5ZPLDt8SrHKH5PvSKTwFDdFo5E3zDuE5Be", + "nonce": 253, + "authority": "GYY1t5d4pZnJC4rMXGY9yKMyCzLqxRqbtSguD2KkxghH", + "poolTokenMint": "GBijunwxa4Ni3JmYC6q6zgaVhSUJU6hVX5qTyJDRpNTc", + "poolTokenDecimals": 6, + "feeAccount": "6NhybmW42rdWj5TcobNKQT6JaZispgngcfTDrCsgVq4Q", + "tokenIds": [ + "JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz": { + "tag": "JET", + "name": "JET", + "mint": "JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz", + "scale": 9, + "addr": "GEtZSc8188t2cCAv21UGCyjvxCeyU5Co99GtRtyTkpdh" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "Bi95f8H7o7zHWuYysxDHEubPv4c3NhsHWhaesXJu91NC" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "stSOL_USDC", + "address": "EfK84vYEKT1PoTJr6fBVKFbyA7ZoftfPo2LQPAJG1exL", + "nonce": 252, + "authority": "8PSN1CQxfyZ7T4sM3HM3RAgF2Y6VCf4tKSc8xY73Tnq5", + "poolTokenMint": "GtQ1NT7R5aaTiST7K6ZWdMhwDdFxsSFvVFhBo8vyHGAq", + "poolTokenDecimals": 6, + "feeAccount": "CJhL3UGesECFt6fvLB3csrGMuHf3M3G78pUzTopUiV8T", + "tokenIds": [ + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": { + "tag": "stSOL", + "name": "Lido Staked SOL", + "mint": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "scale": 9, + "addr": "9SEBxqhP8sTAzmfiQfCPim1MqQXuDPb6fkGzJF7Z339i" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "G45yhM5mZ5RXZpLxGWLk3PVzdAp33z8aH6F9mLW8fQj3" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "wstETH_USDC", + "address": "v51xWrRwmFVH6EKe8eZTjgK5E4uC2tzY5sVt5cHbrkG", + "nonce": 254, + "authority": "3Kk8rpjxpc9qv2pJPr1CbmyQqrTDPntpryXActLogQeD", + "poolTokenMint": "5a6Y1ephcbKSoyLMQyD1JWbtqawCy8p2FtRL9v3zhaG5", + "poolTokenDecimals": 6, + "feeAccount": "ACKiRmbiMaPEc73pz4dVMuJGPaa74Vx9sfYADjnHuzvo", + "tokenIds": [ + "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo": { + "tag": "wstETH", + "name": "Lido Staked Ether", + "mint": "ZScHuTtqZukUrtZS43teTKGs2VqkKL8k4QCouR2n6Uo", + "scale": 8, + "addr": "5c4tzhRVaCxpmu8o3HrEZ8PWBDKSR6QNkBdQrUo9oe3e" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "AFNaWHH7ZGFjB7y7jmPM7jVs7QBAciffu7Z5tZidRHPR" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "AURY_USDC", + "address": "6HSguUukDH9zqJBm6oAAmFkg1WK9dJ5iLgnppTCM6jHm", + "nonce": 255, + "authority": "9T1koZp2PNJgspcx3G22yLiChBUfYzAjs2dhj2kgw2LZ", + "poolTokenMint": "6mJqqT5TMgveDvxzBt3hrjGkPV5VAj7tacxFCT3GebXh", + "poolTokenDecimals": 6, + "feeAccount": "JCqbv7r3mtYhzruNFjc21X14fndDVBLMiaNQrsHVpWui", + "tokenIds": [ + "AURYydfxJib1ZkTir1Jn1J9ECYUtjb6rKQVmtYaixWPP", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "AURYydfxJib1ZkTir1Jn1J9ECYUtjb6rKQVmtYaixWPP": { + "tag": "AURY", + "name": "Aurory", + "mint": "AURYydfxJib1ZkTir1Jn1J9ECYUtjb6rKQVmtYaixWPP", + "scale": 9, + "addr": "413s6jiRbayD9didA4VnY8kQVgVBgkYNpYB2tyNf8sbh" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "GdPeogNxRWAZtUj7ZHc7fUpBuGHJosdbukiT2krFtXm8" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "AVAX_USDC", + "address": "7c2CLgatf2TU36PgpS65WLmvWk94rmaHVf1Z1peZ7mcA", + "nonce": 251, + "authority": "Mq46N9EknnxHL9fRkJhS4Eg9YXRifHiWzFJTD11ePWC", + "poolTokenMint": "Hmfrtmo93DpSDmVNLQKcBS5D1ia5JatiRSok9ososubz", + "poolTokenDecimals": 6, + "feeAccount": "7JH76Kw4dHyC5szRXkx6MFkJ3BEViodfNy15uFJst1cX", + "tokenIds": [ + "AUrMpCDYYcPuHhyNX8gEEqbmDPFUpBpHrNW3vPeCFn5Z", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "AUrMpCDYYcPuHhyNX8gEEqbmDPFUpBpHrNW3vPeCFn5Z": { + "tag": "AVAX", + "name": "Avalanche", + "mint": "AUrMpCDYYcPuHhyNX8gEEqbmDPFUpBpHrNW3vPeCFn5Z", + "scale": 9, + "addr": "5rU6M2jAXQMSmgrsn14BPoVVhoBdCU6y5cP7XMjN4ZYy" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "D28rzq246bcXBrYiCeALY86y8NwvCUmuJGNggvKsh4WR" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "FTT_USDC", + "address": "8npdwWX2BR39kcFLtTJABbcjNq7NWQvipfqxgsfk9mTX", + "nonce": 255, + "authority": "8zU13KiLb1e87skt4rf8q1LhamEKKecyu6Xxb4Hqnm7e", + "poolTokenMint": "FwCombynV2fTVizxPCNA2oZKoWXLZgdJThjE4Xv9sjxc", + "poolTokenDecimals": 6, + "feeAccount": "C8D52rGuZcsBENhWtR9aqJVRU62cL7jyyEhxesKwc1k8", + "tokenIds": [ + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3": { + "tag": "FTT", + "name": "FTX Token", + "mint": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "scale": 6, + "addr": "SasuKsATA2ATrMfFfSJr86wAGVgdS69PkQT3jFASBB8" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "3wADiuUqoakdoYYYxKqwoA4VN3uWZy5UwvLePox1mEsK" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "RAY_USDC", + "address": "2R2VhohRc5WoNHtRdwnjovAQaZRAmr1DE3QFW5jfgb6v", + "nonce": 252, + "authority": "9B9ZcYT8jDQ6XLe6gRLDCFv1zz3uHVKdbZT9DFhsYSQW", + "poolTokenMint": "4cXw2MYj94TFBXLL73fEpMCr8DPrW68JvrV8mzWgktbD", + "poolTokenDecimals": 6, + "feeAccount": "HURhvCRsrwwR5TiG75Hn274WwL76kaKgjgC6n9h4FEHj", + "tokenIds": [ + "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R": { + "tag": "RAY", + "name": "Raydium", + "mint": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", + "scale": 6, + "addr": "9ASj9zDg7cT6wtvn4euSUiZte8yN2U3Tn6cTVZvMHbU7" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "HGTxSWbb62nxk4oGkLkHUvrEzR5D4GKYRb8ZDcA2dpki" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "SLND_USDC", + "address": "GhosXH9yZPxqSyTHqJtXQt6w65YfiGjKXcEXciX1P3z8", + "nonce": 255, + "authority": "ChmSHndtXRsYnFjYA2F7yRRsnyZ8kCpxSogTsCUgCEsh", + "poolTokenMint": "F59gkD7NnsdJbFKrRZsiBC8PAooN4c56T8QmahfW1iXN", + "poolTokenDecimals": 6, + "feeAccount": "GMipxN5pu6F6wwUrq6RhpqgcMjcKLTsnDTeNFCuUm5n7", + "tokenIds": [ + "SLNDpmoWTVADgEdndyvWzroNL7zSi1dF9PC3xHGtPwp", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "SLNDpmoWTVADgEdndyvWzroNL7zSi1dF9PC3xHGtPwp": { + "tag": "SLND", + "name": "Solend", + "mint": "SLNDpmoWTVADgEdndyvWzroNL7zSi1dF9PC3xHGtPwp", + "scale": 6, + "addr": "9RcdfprKxbTzp3erTJMwXKznNCLmbCUaKhibaTMXhToi" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "6wEh8r3Czc3nKkN6JXobShnLG7ZqA5Y5DREGzkirYR36" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + }, + { + "name": "GOFX_USDC", + "address": "C3b5AWQJiyar5g8EWu75zgDE26F55ZJWpqtFVCCVDQQQ", + "nonce": 253, + "authority": "3SphkwoHx3d13Eu9RehVVg4gGMZv7FEaDXvPqWbQF9bm", + "poolTokenMint": "7vnps4VE5RTGAr5fmPZu7fSrk2VnM4Up838grZfqmxqE", + "poolTokenDecimals": 6, + "feeAccount": "CT95CSNqi4nttNW84dDuA8Um7FLAC52PVUvuVRKeCHVK", + "tokenIds": [ + "GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "tokens": { + "GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD": { + "tag": "GOFX", + "name": "GooseFX", + "mint": "GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD", + "scale": 9, + "addr": "5AhPVbtyiTV3SiNRJuq5z9xeaqqwoHQWqohR9HvjJkKS" + }, + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { + "tag": "USDC", + "name": "USD Coin", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "scale": 6, + "addr": "6mtcbtTAadVEdnWJZmsq8woqLea7ef7k5WumVXSHr5KQ" + } + }, + "curveType": 0, + "feeStructure": { + "traderFee": { + "numerator": "19", + "denominator": "2710" + }, + "ownerFee": { + "numerator": "05", + "denominator": "2710" + } + } + } +] +} diff --git a/farms/farm-ctrl/src/metadata/pools/raydium/pools.json b/farms/farm-ctrl/src/metadata/pools/raydium/pools.json new file mode 100644 index 00000000000..17a343afe2a --- /dev/null +++ b/farms/farm-ctrl/src/metadata/pools/raydium/pools.json @@ -0,0 +1,2156 @@ +{ + "name": "Raydium Pools", + "pools": [ + { + "name": "RAY-WUSDT", + "coin": "RAY", + "pc": "WUSDT", + "lp": "LP.RDM.RAY-WUSDT", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "4GygMmZgSoyfM3DEBpA8HvB8pooKWnTp232bKA17ptMG", + "ammAuthority": "E8ddPSxjVUdW8wa5rs3gbscqoXQF1o7sJrkUMFU18zMS", + "ammOpenOrders": "Ht7CkowEPZ5yHQpQQhzhgnN8W7Dq3Gw96Z3Ph8f3tVpY", + "ammTargetOrders": "3FGv6AuhfsxPBsPz4dXRA629W7UF2rW3NjHaihxUNcrB", + "ammQuantities": "EwL1kwav5Z9dGrppUvusjPA4iJ4gVFsD3kGc5gCyAmMt", + "poolCoinTokenAccount": "G2zmxUhRGn12fuePJy9QsmJKem6XCRnmAEkf8G6xcRTj", + "poolPcTokenAccount": "H617sH2JNjMqPhRxsu43C8vDYfjZrFuoMEKdJyMu7V3t", + "poolWithdrawQueue": "2QiXRE5yAfTbTUT9BCfmkahmPPhsmWRox1V88iaJppEX", + "poolTempLpTokenAccount": "5ujWtJVhwzy8P3DJBYwLo4StxiFhJy5q6xHnMx7yrPPb", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "HZyhLoyAnfQ72irTdqPdWo2oFL9zzXaBmAqN72iF3sdX", + "serumCoinVaultAccount": "56KzKfd9LvsY4QuMZcGxcGCd78ZBFQ7JcyMFwgqpXH12", + "serumPcVaultAccount": "GLntTfM7RHeg5RuAuXcudT4NV7d4BGPrEFq7mmMxn29E", + "serumVaultSigner": "6FYUBnwRVxxYCv1kpad4FaFLJAzLYuevFWmpVp7hViTn", + "official": true + }, + { + "name": "RAY-SOL", + "coin": "RAY", + "pc": "SOL", + "lp": "LP.RDM.RAY-SOL", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "5Ytcen7ZQRWA8Dt4EGyVJngyqDL36ZKAGSTVKxbDGZPN", + "ammAuthority": "6LUFae1Ap44GVT9Dhw7NEqibFGSFxijdx4kzKVARsSuL", + "ammOpenOrders": "4JGNm7gSaZguaNJExYsFsL91x4GPtPyHpU7nrb5Jjygh", + "ammTargetOrders": "3rqYVkU3HkSj8XB9c2Y9e1LLPL6BjtNKr187qma6peCc", + "ammQuantities": "BMTLKbmwzsKRxzL45eKgb5or8spaStLZxvycrTGGAhdK", + "poolCoinTokenAccount": "CJukFFmH9FZ98uzFkUNgqRn8xUmSBTUETEDUMxZXk6p8", + "poolPcTokenAccount": "DoZyq9uo3W4WWBZJvPCvfB5cCBFvjU9oq3DdYjNgJNRX", + "poolWithdrawQueue": "9FY699Gpyq4CcL8KFS4rEP76dAR3GQchQnUw7Xg1yaew", + "poolTempLpTokenAccount": "A1BMmYPBXudTXzQExpqy1LrqEkKuoasfwCLjwigiSfRh", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "HTSoy7NCK98pYAkVV6M6n9CTziqVL6z7caS3iWFjfM4G", + "serumCoinVaultAccount": "6dDDqzNsLx8u2Prk384Rs1jUxFPFQsKHne5oQxnf4kog", + "serumPcVaultAccount": "AzxRBcig9mGTfdbUgEdKq48eiNZ2M4ynwQQH4Pvxbcy2", + "serumVaultSigner": "FhTczYTxkXMyofPMDQFJGHxjcnPrjrEGQMexob4BVwXD", + "official": true + }, + { + "name": "LINK-WUSDT", + "coin": "LINK", + "pc": "WUSDT", + "lp": "LP.RDM.LINK-WUSDT", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "Avkh3hMrjRRdGbm7EAmeXaJ1wWrbcwGWDGEroKq5wHJ8", + "ammAuthority": "v1uTXS1hrW2DJkKPcQ3Dm7WwhYbGm7LhHoRE29QrHsJ", + "ammOpenOrders": "HD7VPeJL2Sgict6oBPhb2s3DXvS9uieQmuw7KzhrfD3j", + "ammTargetOrders": "DQ7un7pYeWWcBrt1mpucasb2CaepJQJ3Z3axM3PJ4pJ4", + "ammQuantities": "5KDL4Mtufuhe6Yof9nSPVjXgXgMFMHCXqKETzzbrsGzY", + "poolCoinTokenAccount": "7r5YjMLMnmoYkD1bkyYq374yiTBG9XwBHMwi5ZVDptre", + "poolPcTokenAccount": "6vMeQvJcC3VEGvtZ2TDXcShZerevxkqfW43yjX14vmSz", + "poolWithdrawQueue": "3tgn1n9wMGfryZu37skcMhUuwbNYFWTT5hurWGijikXZ", + "poolTempLpTokenAccount": "EL8G5U28xw9djiEb9AZiEtBUtUdA5YtvaAHJu5hxipCK", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "hBswhpNyz4m5nt4KwtCA7jYXvh7VmyZ4TuuPmpaKQb1", + "serumCoinVaultAccount": "8ZP84HpFb5k4paAgDGgXaMtne537LDFaxEWP89WKBPD1", + "serumPcVaultAccount": "E3X7J1vyogGKZSySEo3WTS9GzipyTGVd5KKiXeFy1YHu", + "serumVaultSigner": "7bwfaV98FDNtWvgPMo7wY3nE7cE8tKfXkFAVzCxtkw6w", + "official": true + }, + { + "name": "ETH-WUSDT", + "coin": "ETH", + "pc": "WUSDT", + "lp": "LP.RDM.ETH-WUSDT", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "7PGNXqdhrpQoVS5uQs9gjT1zfY6MUzEeYHopRnryj7rm", + "ammAuthority": "BFCEvcoD1xY1HK4psbC5wYXVXEvmgwg4wKggk89u1NWw", + "ammOpenOrders": "3QaSNxMuA9zEXazLdD2oJq7jUCfShgtvdaepuq1uJFnS", + "ammTargetOrders": "2exvd2T7yFYhBpi67XSrCVChVwMu23g653ELEnjvv8uu", + "ammQuantities": "BtwQvRXNudUrazbJNhazajSZXEXbrf51ddBrmnje27Li", + "poolCoinTokenAccount": "Gej1jXVRMdDKWSxmEZ78KJp5jruGJfR9dV3beedXe3BG", + "poolPcTokenAccount": "FUDEbQKfMTfAaKS3dGdPEacfcC9bRpa5gmmDW8KNoUKp", + "poolWithdrawQueue": "4q3qXQsQSvzNE1fSyEh249vHGttKfQPJWM7A3AtffEX5", + "poolTempLpTokenAccount": "8i2cZ1UCAjVac6Z76GvQeRqZMKgMyuoZQeNSsjdtEgHG", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "5abZGhrELnUnfM9ZUnvK6XJPoBU5eShZwfFPkdhAC7o", + "serumCoinVaultAccount": "Gwna45N1JGLmUMGhFVP1ELz8szVSajp12RgPqCbk46n7", + "serumPcVaultAccount": "8uqjWjNQiZvoieaGSoCRkGZExrqMpaYJL5huknCEHBcP", + "serumVaultSigner": "4fgnxw343cfYgcNgWvan8H6j6pNBskBmGX4XMbhxtFbi", + "official": true + }, + { + "name": "RAY-USDC", + "coin": "RAY", + "pc": "USDC", + "lp": "LP.RDM.RAY-USDC", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "G2PVNAKAp17xtruKiMwT1S2GWNxptWZfqK6oYrFWCXWX", + "ammAuthority": "2XTg6m9wpuUyPNhHbi8DCGfyo58bpqmAmbujEEpUykSo", + "ammOpenOrders": "HuGmmcqH6ULntUdfaCVrx4uzuhHME55Dczt793EweoTZ", + "ammTargetOrders": "B3UeQ7SK9U9a5vP8fDtZ5gfDv6KRFSsNtawpoH7fziNW", + "ammQuantities": "LEgCPaQhYv9YSnKXvHtc6HixwxdXe9mmvLCuTTxW2Yn", + "poolCoinTokenAccount": "CvcqJtGdS9C1jKKFzgCi5p8qsnR5BZCohWvYMBJXcnJ8", + "poolPcTokenAccount": "AiYm8jzb2WB4HTTFTHX1XCS7uVSQM5XWnMsure5sMeQY", + "poolWithdrawQueue": "rYqeTgbeQvrDxeCg4kjqHA1X6rfjjLQvQTJeYLAgXq7", + "poolTempLpTokenAccount": "4om345FvSd9dqwFpy1SVmPFY9KzeUk8WmKiMzTbQxCQf", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "Bgz8EEMBjejAGSn6FdtKJkSGtvg4cuJUuRwaCBp28S3U", + "serumCoinVaultAccount": "BuMsEd7Ub6MtCCh1eT8pvL6zcBPbiifa1idVWa1BeE2R", + "serumPcVaultAccount": "G7i7ZKx7rfMXGreLYzvR3ZZERgaGK7646nAgi8yjE8iN", + "serumVaultSigner": "Aj6H2siiKsnAdAS5YVwuJPdXrHaLodsSyKs7ZiEtEZQN", + "official": true + }, + { + "name": "RAY-SRM", + "coin": "RAY", + "pc": "SRM", + "lp": "LP.RDM.RAY-SRM", + "version": 2, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V2", + "ammId": "3Y5dpV9DtwkhewxXpiVRscFeQR2dvsHovXQonkKbuDwB", + "ammAuthority": "7iND8ysb6fGUy8tx4C8AS51wbjvRjBxxSoaaL7t1yWXX", + "ammOpenOrders": "4QXs3bK3nyauMYutJjD8qGunFphAw944SsRdSD7n8oUj", + "ammTargetOrders": "5oaHFj1aqz9xLxYwByddXiUfbSwRZ3gmSJsgBF4no7Xx", + "ammQuantities": "His9VQDWu55QdDUFu7tp5CpiCB1fMs6EDk5oC4uTaS4G", + "poolCoinTokenAccount": "5fHS778vozoDDYzzJz2xYG39whTzGGW6bF71GVxRyMXi", + "poolPcTokenAccount": "CzVe191iLM2E31DBW7isXpZBPtcufRRsaxNRc8uShcEs", + "poolWithdrawQueue": "BGmJSiCR7uuahrajWv1RgBJrbUjcQHREFfewqZPhf346", + "poolTempLpTokenAccount": "5aMZAZdab2iS62rfqPYd15AkQ7Y5zSSfz7WxHjV9ZRPw", + "serumProgramId": "SERUM_PROGRAM_ID_V2", + "serumMarket": "HSGuveQDXtvYR432xjpKPgHfzWQxnb3T8FNuAAvaBbsU", + "serumCoinVaultAccount": "6wXCSGvFvWLVoiRaXJheHoXec4LiJhiCWnxmQbYc9kv5", + "serumPcVaultAccount": "G8KH5rE5EqeTpnLjTTNgKhVp47yRHCN28wH27vYFkWCR", + "serumVaultSigner": "EXZnYg9QCzujDwm621N286d4KLAZiMwpUv64GdECcxbm", + "official": true + }, + { + "name": "RAY-WUSDT", + "coin": "RAY", + "pc": "WUSDT", + "lp": "LP.RDM.RAY-WUSDT-V3", + "version": 3, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V3", + "ammId": "FEAkBF4GhYKrYbxMa7tFcujvzxKrueC7xHT2NdyC9vxm", + "ammAuthority": "CgvoNxNc93c91zYkPTAkBsYxjcAn8bRsnLM5ZxNKUpDj", + "ammOpenOrders": "2nzyzD5sdDKkP5pN5V5HGDmacpQJPEkMHqA1vopuRupY", + "ammTargetOrders": "BYCxxFuPB6MjLmpBoA7XMXHKk87LP1V62HPFh5BaobBd", + "ammQuantities": "H8P2YR1MTFgcRKnGHYWk6Aitqf72aXCN3ZKM29mRQqqe", + "poolCoinTokenAccount": "DTQTBTSy3tiy7kZZWgaczWxs9snnTVTi8DBYBzjaVwbj", + "poolPcTokenAccount": "Bk2G4zhjB7VmRsaBwh2ijPwq6tavMHALEq4guogxsosT", + "poolWithdrawQueue": "9JnsD9Pm8YQhMMAKBV7RgPcdVnRTuwJW5PXdWx7T2K8C", + "poolTempLpTokenAccount": "FfNM2Szi8xKWj3SUAjYpsHKuyQsd9NuW8ARkMqyNYPiJ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "C4z32zw9WKaGPhNuU54ohzrV4CE1Uau3cFx6T8RLjxYC", + "serumCoinVaultAccount": "6hCHQufQsxsHDkHYNmw79WvfsAGXvomdZnkzWN7MYz8f", + "serumPcVaultAccount": "7qM644QyBzMvqLLiEYhJksyPzwUpuQj44EodLb1va8aG", + "serumVaultSigner": "2hzqYES4AcwVkuMdNsNNqi1jqjfKSyL2BNus4kimVXNk", + "official": true + }, + { + "name": "RAY-USDC", + "coin": "RAY", + "pc": "USDC", + "lp": "LP.RDM.RAY-USDC-V3", + "version": 3, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V3", + "ammId": "5NMFfbccSpLdre6anA8P8vVy35n2a52AJiNPpQn8tJnE", + "ammAuthority": "Bjhs6Mrvxr34WAKLog2tiU77VMvwNZcrJ1g8UyGoic3e", + "ammOpenOrders": "3Xq4vBd5EWs45v9YwG1Mpfr8Xjng23pDovVUbnAaPce9", + "ammTargetOrders": "7ccgnj4dTuVTaQCwbECDc3GrKrQpuGNA4cETiSNo2cCN", + "ammQuantities": "6ifgXdNx8zKd4bseuya6FEKb49VWx1dDvVTC8f7kc361", + "poolCoinTokenAccount": "DujWhSxnwqFd3TrLfScyUhJ3FdoaHrmoiVE6kU4ETQyL", + "poolPcTokenAccount": "D6F5CDaLDCHHWfE8kMLbMNAFULXLfM572AGDx2a6KeXc", + "poolWithdrawQueue": "76QQPxNT422AL8w5RhssRFQ3gUGy7Y23YxV9BRWqs44Q", + "poolTempLpTokenAccount": "2Q9PevhtVioNFyFFrbkzcGxn1QmzFph5Cpdy1FKe3nYJ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2xiv8A5xrJ7RnGdxXB42uFEkYHJjszEhaJyKKt4WaLep", + "serumCoinVaultAccount": "GGcdamvNDYFhAXr93DWyJ8QmwawUHLCyRqWL3KngtLRa", + "serumPcVaultAccount": "22jHt5WmosAykp3LPGSAKgY45p7VGh4DFWSwp21SWBVe", + "serumVaultSigner": "FmhXe9uG6zun49p222xt3nG1rBAkWvzVz7dxERQ6ouGw", + "official": true + }, + { + "name": "RAY-SRM", + "coin": "RAY", + "pc": "SRM", + "lp": "LP.RDM.RAY-SRM-V3", + "version": 3, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V3", + "ammId": "EGhB6FdyHtJPbPMRoBC8eeUVnVh2iRgnQ9HZBKAw46Uy", + "ammAuthority": "3gSVizZA2BFsWAfW4j1wBSQiQE9Xn3Ds518jPGve31se", + "ammOpenOrders": "6CVRtzecMaPZ1pdfT2ZzJ1qf89yuFsD7MKYGwvjYsy6w", + "ammTargetOrders": "CZYbET8zweaWtWLnFJnt5nouCE9snQxFi7zrTCGYycL1", + "ammQuantities": "3NGwJe5bueAgLp6fMrY5HV2rpHF9xh3HhH97S6LrMLPo", + "poolCoinTokenAccount": "Eg6sR9H28cFaek5DVdgxxDcRKKbS85XvCFEzzkdmYNhq", + "poolPcTokenAccount": "8g2nHtayS2JnRxaAY5ugsYC8CwiZutQrNWA9j2oH8UVM", + "poolWithdrawQueue": "7Yc1P9nyev1uoLtLJu15o5vQugvfXoHcde6x2mm1HeED", + "poolTempLpTokenAccount": "5WHmdyH7CgiezSGcD9PVMYth9hMEWETV1M64zmZ9UT5o", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Cm4MmknScg7qbKqytb1mM92xgDxv3TNXos4tKbBqTDy7", + "serumCoinVaultAccount": "5QDTh4Bpz4wruWMfayMSjUxRgDvMzvS2ifkarhYtjS1B", + "serumPcVaultAccount": "76CofnHCvo5wEKtxNWfLa2jLDz4quwwSHFMne6BWWqx", + "serumVaultSigner": "AorjCaSV1L6NGcaFZXEyUrmbSqY3GdB3YXbQnrh85v6F", + "official": true + }, + { + "name": "RAY-SOL", + "coin": "RAY", + "pc": "SOL", + "lp": "LP.RDM.RAY-SOL-V3", + "version": 3, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V3", + "ammId": "HeRUVkQyPuJAPFXUkTaJaWzimBopWbJ54q5DCMuPpBY4", + "ammAuthority": "63Cw8omVwSQGDPP5nff3a9DakvL8ruaqqEpbQ4uDwPYf", + "ammOpenOrders": "JQEY8R9frhxuvcsewGfgkCVdGWztpHLx4P9zmTAsZFM", + "ammTargetOrders": "7mdd7oqHqULV1Yxaaf5GW52FKFbJz78sZj9ePcfmL5Fi", + "ammQuantities": "HHU2THd3tocaYagZh826KCvLDv7QNWLGKjaJKmtdtTQM", + "poolCoinTokenAccount": "Fy6SnHwAkxoGMhUH2cLu2biqAnHmaAwFDDww9k6gq5ws", + "poolPcTokenAccount": "GoRindEPofTJ3axsonTnbyf7cFwdFdG1A3MG9ENyBZsn", + "poolWithdrawQueue": "3bUwc23vXP9L6XBjVCvG9Mruuu7GRkcfmyXuaH6HdmW2", + "poolTempLpTokenAccount": "9dALTRnKoLmfMn3hPyQoizmSJ5CZSLMLdJy1XMocwXMU", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "C6tp2RVZnxBPFbnAsfTjis8BN9tycESAT4SgDQgbbrsA", + "serumCoinVaultAccount": "6U6U59zmFWrPSzm9sLX7kVkaK78Kz7XJYkrhP1DjF3uF", + "serumPcVaultAccount": "4YEx21yeUAZxUL9Fs7YU9Gm3u45GWoPFs8vcJiHga2eQ", + "serumVaultSigner": "7SdieGqwPJo5rMmSQM9JmntSEMoimM4dQn7NkGbNFcrd", + "official": true + }, + { + "name": "RAY-ETH", + "coin": "RAY", + "pc": "ETH", + "lp": "LP.RDM.RAY-ETH-V3", + "version": 3, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V3", + "ammId": "FrDSSYXGcrJc7ZwY5KMfTmzDfrzjvqdxmSinJFwxLr14", + "ammAuthority": "5Wbe7MYpw8y9iroZKVN8b3fLZNeBUbRKetQwULicDpw2", + "ammOpenOrders": "ugyjEMZLumc1M5c7MNXayMYmxpnuMRYiT4aPwfNb6bq", + "ammTargetOrders": "2M6cT1GvGTiovTj7bRsZBeLMeJzjYoDTHNiTRVJqRFeM", + "ammQuantities": "5YcH7AwHNLdDJd2K6YmZAxqqvGYjgE59NaYAh3pkgVd7", + "poolCoinTokenAccount": "ENjXaFNDiLTh44Gs89ZtfUH2i5MGLLkfYbSY7TmP4Du3", + "poolPcTokenAccount": "9uzWJD2WqJYSmB6UHSyPMskFGoP5L6hB7FxqUdYP4Esm", + "poolWithdrawQueue": "BkrxkmYs1JViXbiBJfnwgns75CJd9yHcqUkFXB8Bz7oB", + "poolTempLpTokenAccount": "CKZ7NMunTef18yKHuizRoNZedzTdDEFwYRUgB3dFDcrd", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6jx6aoNFbmorwyncVP5V5ESKfuFc9oUYebob1iF6tgN4", + "serumCoinVaultAccount": "EVVtYo4AeCbmn2dYS1UnhtfjpzCXCcN26G1HmuHwMo7w", + "serumPcVaultAccount": "6ZT6KwvjLnJLpFdVfiRD9ifVUo4gv4MUie7VvPTuk69v", + "serumVaultSigner": "HXbRDLcX2FyqWJY95apnsTgBoRHyp7SWYXcMYod6EBrQ", + "official": true + }, + { + "name": "FIDA-RAY", + "coin": "FIDA", + "pc": "RAY", + "lp": "LP.RDM.FIDA-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "2dRNngAm729NzLbb1pzgHtfHvPqR4XHFmFyYK78EfEeX", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "DUVSNpoCNyGDP9ef9gJC5Dg53khxTyM1pQrKVetmaW8R", + "ammTargetOrders": "89HcsFvCQaUdorVF712EhNhecvVM7Dk6XAdPbaykB3q2", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "6YeEo7ZTRHotXd89JTBJKRXERBjv3N3ofgsgJ4FoAa39", + "poolPcTokenAccount": "DDNURcWy3CU3CpkCnDoGXwQAeCg1mp2CC8WqvwHp5Fdt", + "poolWithdrawQueue": "H8gZ2f4hp6LfaszDN5uHAeDwZ1qJ4M4s2A59i7nMFFkN", + "poolTempLpTokenAccount": "Bp7LNZH44vecbv69kY35bjmsTjboGbEKy62p7iRT8az", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "9wH4Krv8Vim3op3JAu5NGZQdGxU8HLGAHZh3K77CemxC", + "serumBids": "E2FEkqPVcQZgRaE7KabcHGbpNkpycnvVZMan2MPNGKeM", + "serumAsks": "5TXqn1N2kpCWWV4AcXtFYJw8WqLrXP62qenxiSfhxJiD", + "serumEventQueue": "58qMcacA2Qk4Tc4Rut3Lnao91JvvWJJ26f5kojKnMRen", + "serumCoinVaultAccount": "A2SMhqA1kMTudVeAeWdzCaYYeG6Dts19iEZd4ZQQAcUm", + "serumPcVaultAccount": "GhpccNwfein8qP6uhWnP4vuRva1iLivuQQHUTM7tW58r", + "serumVaultSigner": "F7VdEoWQGmdFK35SD21wAbDWtnkVpcrxM3DPVnmG8Q3i", + "official": true + }, + { + "name": "OXY-RAY", + "coin": "OXY", + "pc": "RAY", + "lp": "LP.RDM.OXY-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "B5ZguAWAGC3GXVtJZVfoMtzvEvDnDKBPCevsUKMy4DTZ", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "FVb13WU1W1vFouhRXZWVZWGkQdK5jo35EnaCrMzFqzyd", + "ammTargetOrders": "FYPP5v8SLHPPcivgBJPE9FgrN6o2QVMB627n3XcZ8rCS", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "6ttf7G7FR9GWqxiyCLFNaBTvwYzTLPdbbrNcRvShaqtS", + "poolPcTokenAccount": "8orrvb6rHB776KbQmszcxPH44cZHdCTYC1fr2a3oHufC", + "poolWithdrawQueue": "4Q9bNJsWreAGhkwhKYL7ApyhEBuwNxiPkcEQNmUjQGHZ", + "poolTempLpTokenAccount": "E12sRQvEHArCULaJu8xppoJKQgJsuDuwPVJZJRrUKYFu", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HcVjkXmvA1815Es3pSiibsRaFw8r9Gy7BhyzZX83Zhjx", + "serumBids": "DaGRz2TAdcVcPwPmYF5JJ7d7kPWvLN68vuBTTMwnoM3T", + "serumAsks": "3ZRtPBQVcjCpVmCt4xPPeJJiUnDDbrc5jommVHGsDLnT", + "serumEventQueue": "C5SGEXUCmN1LxmxapPn2XaHX1FF7fAuQG5Wu4yuu8VK6", + "serumCoinVaultAccount": "FcDWM8eKUEny2wxopDMrZqgmPr3Tmoen9Dckh3MoVX9N", + "serumPcVaultAccount": "9ya4Hv4XdzntjiLwxpgqnX8eP4MtFf8YWEssF6C5Pqhq", + "serumVaultSigner": "Bf9MhS6hwAGSWVJ4uLWKSU6fqPAEroRsHX6ithEjGXiG", + "official": true + }, + { + "name": "MAPS-RAY", + "coin": "MAPS", + "pc": "RAY", + "lp": "LP.RDM.MAPS-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5VyLSjUvaRxsubirbvbfJMbrKZRx1b7JZzuCAfyqgimf", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "HViBtwESRNKLZY7qLQxP68b5vLdUQa1XMAKz19LbSHjx", + "ammTargetOrders": "8Cwm1Z75hQdUpFUxCuoWmWBLcAaZvKMAn2xKeuotC4eC", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "6rYv6kLfhAVKZw1xN2S9NWNgp8EfUVvYKi1Hgzd5x9XE", + "poolPcTokenAccount": "8HfvN4VyAQjX6MhziRxMg5LjbMh9Fw889yf3sDgrXakw", + "poolWithdrawQueue": "HnzkiYgZg22ZaQGdeTHiCgJaoW138CLqCb8tr6QJFkU4", + "poolTempLpTokenAccount": "DnTQwA9PdwLSibsiQFZ35yJJDNJfG9fNbHspPmb8v8TQ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "7Q4hee42y8ZGguqKmwLhpFNqVTjeVNNBqhx8nt32VF85", + "serumBids": "J9ZmfF71eMMzisvaYW12EK87UaopZ4hgND2nr61YwmKw", + "serumAsks": "9ah4Mewrh841gmfaX1v1wCByHU3rbCuUmWUgt2TBAfnb", + "serumEventQueue": "EtimVRtnRUAfv9tXVAHpGCGvtYezcpmzbkwZLuwWAYqe", + "serumCoinVaultAccount": "2zriJ5sVApLD9TC9PxbXK41AkVCQBaRreeXtGx7AGE41", + "serumPcVaultAccount": "2qAKnjzokKR4sL6Xtp1nZYKXTmsraXW9CL3HuBZx3qpA", + "serumVaultSigner": "CH76NgZMpUJ8QQqVNpjyCSpQmZBNZLXW6a5vDHj3aUUC", + "official": true + }, + { + "name": "KIN-RAY", + "coin": "KIN", + "pc": "RAY", + "lp": "LP.RDM.KIN-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6kmMMacvoCKBkBrqssLEdFuEZu2wqtLdNQxh9VjtzfwT", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "DiP4F6FTR5jiTar8fwuwRVuYop5wYRqy2EjbiKTXPrHw", + "ammTargetOrders": "2ak4VVyS19sVESvvBuPZRMAhvY4vVCZCxeELYAybA7wk", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "s7LP6qptF1wufA9neYhekmVPqhav8Ak85AV5ip5h8wK", + "poolPcTokenAccount": "9Q1Xs1s8tCirX3Ky3qo9UjvSqSoGinZvWaUMFXY5r2HF", + "poolWithdrawQueue": "DeHaCJ8KL5uwBGenkUwa39JyhacxPDqDqHAp5HLqgd1i", + "poolTempLpTokenAccount": "T2acWsGDQ4ZRXs4WXVi7vCeH4TxzgjcL6s14xFNuT26", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Fcxy8qYgs8MZqiLx2pijjay6LHsSUqXW47pwMGysa3i9", + "serumBids": "HKWdSptDBeXTURKpQQ2AGPmT2B9LGNBVteq44UzDxKBh", + "serumAsks": "2ceQrRfuNWL8kR2fockPo7C31uDeTyXTs4EyA28FD2kg", + "serumEventQueue": "GwnDyxFnHSnzDdu8dom3vydtTpSu443oZPKepXww5zNB", + "serumCoinVaultAccount": "2sCJ5YZtwEbpXiw7HSXVx8Qot8hwyCpXNEkswZCssi2J", + "serumPcVaultAccount": "H6B59E77WZt4JLfaXdZQBKdATRcWaKy5N6Ki1ZRo1Mcv", + "serumVaultSigner": "5V7FCcvmGtqkMJXHiTSeo61MS5LSMUFK1Esr5kn46cEv", + "official": true + }, + { + "name": "RAY-USDT", + "coin": "RAY", + "pc": "USDT", + "lp": "LP.RDM.RAY-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DVa7Qmb5ct9RCpaU7UTpSaf3GVMYz17vNVU67XpdCRut", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "7UF3m8hDGZ6bNnHzaT2YHrhp7A7n9qFfBj6QEpHPv5S8", + "ammTargetOrders": "3K2uLkKwVVPvZuMhcQAPLF8hw95somMeNwJS7vgWYrsJ", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "3wqhzSB9avepM9xMteiZnbJw75zmTBDVmPFLTQAGcSMN", + "poolPcTokenAccount": "5GtSbKJEPaoumrDzNj4kGkgZtfDyUceKaHrPziazALC1", + "poolWithdrawQueue": "8VuvrSWfQP8vdbuMAP9AkfgLxU9hbRR6BmTJ8Gfas9aK", + "poolTempLpTokenAccount": "FBzqDD1cBgkZ1h6tiZNFpkh4sZyg6AG8K5P9DSuJoS5F", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "teE55QrL4a4QSfydR9dnHF97jgCfptpuigbb53Lo95g", + "serumBids": "AvKStCiY8LTp3oDFrMkiHHxxhxk4sQUWnGVcetm4kRpy", + "serumAsks": "Hj9kckvMX96mQokfMBzNCYEYMLEBYKQ9WwSc1GxasW11", + "serumEventQueue": "58KcficuUqPDcMittSddhT8LzsPJoH46YP4uURoMo5EB", + "serumCoinVaultAccount": "2kVNVEgHicvfwiyhT2T51YiQGMPFWLMSp8qXc1hHzkpU", + "serumPcVaultAccount": "5AXZV7XfR7Ctr6yjQ9m9dbgycKeUXWnWqHwBTZT6mqC7", + "serumVaultSigner": "HzWpBN6ucpsA9wcfmhLAFYqEUmHjE9n2cGHwunG5avpL", + "official": true + }, + { + "name": "SOL-USDC", + "coin": "SOL", + "pc": "USDC", + "lp": "LP.RDM.SOL-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "HRk9CMrpq7Jn9sh7mzxE8CChHG8dneX9p475QKz4Fsfc", + "ammTargetOrders": "CZza3Ej4Mc58MnxWA385itCC9jCo3L1D7zc3LKy1bZMR", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "DQyrAcCrDXQ7NeoqGgDCZwBvWDcYmFCjSb9JtteuvPpz", + "poolPcTokenAccount": "HLmqeL62xR1QoZ1HKKbXRrdN1p3phKpxRMb2VVopvBBz", + "poolWithdrawQueue": "G7xeGGLevkRwB5f44QNgQtrPKBdMfkT6ZZwpS9xcC97n", + "poolTempLpTokenAccount": "Awpt6N7ZYPBa4vG4BQNFhFxDj4sxExAA9rpBAoBw2uok", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumBids": "14ivtgssEBoBjuZJtSAPKYgpUK7DmnSwuPMqJoVTSgKJ", + "serumAsks": "CEQdAFKdycHugujQg9k2wbmxjcpdYZyVLfV9WerTnafJ", + "serumEventQueue": "5KKsLVU6TcbVDK4BS6K1DGDxnh4Q9xjYJ8XaDCG5t8ht", + "serumCoinVaultAccount": "36c6YqAwyGKQG66XEp2dJc5JqjaBNv7sVghEtJv4c7u6", + "serumPcVaultAccount": "8CFo8bL8mZQK8abbFyypFMwEDd8tVJjHTTojMLgQTUSZ", + "serumVaultSigner": "F8Vyqk3unwxkXukZFQeYyGmFfTG3CAX4v24iyrjEYBJV", + "official": true + }, + { + "name": "YFI-USDC", + "coin": "YFI", + "pc": "USDC", + "lp": "LP.RDM.YFI-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "83xxjVczDseaCzd7D61BRo7LcP7cMXut5n7thhB4rL4d", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "DdBAps8e64hpjdWqAAHTThcVFz8mQ6WU2h6s1Kjgb9vk", + "ammTargetOrders": "8BFicQN1AKaVbf1KNoUieULun1bvpdMxsyjrgC15acM6", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "HhhqmQvx2GMQ6SRQh6nZ1A4C5KjCFLQ6yga1ZXDzRJ92", + "poolPcTokenAccount": "4J4Y6qkF9yzxz1EsZYTSqviMz3Lo1VHx9ViCUoJph167", + "poolWithdrawQueue": "FPkMHzDo46vzy1eW9FuQFz7TdAp1MNCkZFgKxrHiuh3W", + "poolTempLpTokenAccount": "DuTzisr6Z2D37yTyY9E4jPMCxhQk3HCNxaL1zKqvwRjR", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "7qcCo8jqepnjjvB5swP4Afsr3keVBs6gNpBTNubd1Kr2", + "serumBids": "8L8kU4H9Ah3fgbczYKFU9WUR1HgAghso1kKwWAPrmLfS", + "serumAsks": "4M9kDzMGsNHT3k31i54wf2ceeApvx3224pLbhDvnoj2s", + "serumEventQueue": "6wKPYgydqNrmcXwbfPeNwkzXmjKMgkUhQcGoGYrm9fS4", + "serumCoinVaultAccount": "2N59Aig7wqhfffAUjMit7T9tk4FmSRzmByMD7mncTesq", + "serumPcVaultAccount": "FcDTYePeh2KJts4nroCghgceiJmSBRgq2Xd3PfpernZm", + "serumVaultSigner": "HDdQQNNf9EoCGWhWUgkQHRJVbG3huDXs2z6Fcow3grCr", + "official": true + }, + { + "name": "SRM-USDC", + "coin": "SRM", + "pc": "USDC", + "lp": "LP.RDM.SRM-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "8tzS7SkUZyHPQY7gLqsMCXZ5EDCgjESUHcB17tiR1h3Z", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "GJwrRrNeeQKY2eGzuXGc3KBrBftYbidCYhmA6AZj2Zur", + "ammTargetOrders": "26LLpo8rscCpMxyAnJsqhqESPnzjMGiFdmXA4eF2Jrk5", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "zuLDJ5SEe76L3bpFp2Sm9qTTe5vpJL3gdQFT5At5xXG", + "poolPcTokenAccount": "4usvfgPDwXBX2ySX11ubTvJ3pvJHbGEW2ytpDGCSv5cw", + "poolWithdrawQueue": "7c1VbXTB7Xqx5eQQeUxAu5o6GHPq3P1ByhDsnRRUWYxB", + "poolTempLpTokenAccount": "2sozAi6zXDUCCkpgG3usphzeCDm4e2jTFngbm5atSdC9", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA", + "serumBids": "AuL9JzRJ55MdqzubK4EutJgAumtkuFcRVuPUvTX39pN8", + "serumAsks": "8Lx9U9wdE3afdqih1mCAXy3unJDfzSaXFqAvoLMjhwoD", + "serumEventQueue": "6o44a9xdzKKDNY7Ff2Qb129mktWbsCT4vKJcg2uk41uy", + "serumCoinVaultAccount": "Ecfy8et9Mft9Dkavnuh4mzHMa2KWYUbBTA5oDZNoWu84", + "serumPcVaultAccount": "hUgoKy5wjeFbZrXDW4ecr42T4F5Z1Tos31g68s5EHbP", + "serumVaultSigner": "GVV4ZT9pccwy9d17STafFDuiSqFbXuRTdvKQ1zJX6ttX", + "official": true + }, + { + "name": "FTT-USDC", + "coin": "FTT", + "pc": "USDC", + "lp": "LP.RDM.FTT-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "4C2Mz1bVqe42QDDTyJ4HFCFFGsH5YDzo91Cen5w5NGun", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "23WS5XY3srvBtnP6hXK64HAsXTuj1kT7dd7srjrJUNTR", + "ammTargetOrders": "CYbPm6BCkMyX8NnnS7AoCUkpxHVwYyxvjQWwZLsrFcLR", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "4TaBaR1ZgHNuQM3QNHnjJdAT4Sws9cz46MtVWVebg7Ax", + "poolPcTokenAccount": "7eDiHvsfcZf1VFC2sUDJwr5EMMr66TpQ2nmAreUjoASV", + "poolWithdrawQueue": "36Aa83kffwBuEP7AqNU1w5c9oB9kLxmR4FMfadXfjNbJ", + "poolTempLpTokenAccount": "8hdJm5bvgXVtb5LA18QgGeKxnXBcp3cYKwRz8vb3fV44", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2Pbh1CvRVku1TgewMfycemghf6sU9EyuFDcNXqvRmSxc", + "serumBids": "9HTDV2r7cQBUKL3fgcJZCUfmJsKA9qCP7nZAXyoyaQou", + "serumAsks": "EpnUJCMCQNZi45nCBoNs6Bugy67Kj3bCSTLYPfz6jkYH", + "serumEventQueue": "2XHxua6ZaPKpCGUNvSvTwc9teJBmexp8iMWCLu4mtzGb", + "serumCoinVaultAccount": "4LXjM6rptNvhBZTcWk4AL49oF4oA8AH7D4CV6z7tmpX3", + "serumPcVaultAccount": "2ycZAqQ3YNPfBZnKTbz2FqPiV7fmTQpzF95vjMUekP5z", + "serumVaultSigner": "B5b9ddFHrjndUieLAKkyzB1xmq8sNqGGZPmbyYWPzCyu", + "official": true + }, + { + "name": "BTC-USDC", + "coin": "BTC", + "pc": "USDC", + "lp": "LP.RDM.BTC-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6kbC5epG18DF2DwPEW34tBy5pGFS7pEGALR3v5MGxgc5", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "L6A7qW935i2HgaiaRx6xNGCGQfFr4myFU51dUSnCshd", + "ammTargetOrders": "6DGjaczWfFthTYW7oBk3MXP2mMwrYq86PA3ki5YF6hLg", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "HWTaEDR6BpWjmyeUyfGZjeppLnH7s8o225Saar7FYDt5", + "poolPcTokenAccount": "7iGcnvoLAxthsXY3AFSgkTDoqnLiuti5fyPNm2VwZ3Wz", + "poolWithdrawQueue": "8g6jrVU7E7eghT3FQa7uPbwHUHwHHLVCEjBh94pA1NVk", + "poolTempLpTokenAccount": "2Nhg2RBqHBx7R74VSEAbfSF8Kmi1x3HxyzCu3oFgpRJJ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw", + "serumBids": "6wLt7CX1zZdFpa6uGJJpZfzWvG6W9rxXjquJDYiFwf9K", + "serumAsks": "6EyVXMMA58Nf6MScqeLpw1jS12RCpry23u9VMfy8b65Y", + "serumEventQueue": "6NQqaa48SnBBJZt9HyVPngcZFW81JfDv9EjRX2M4WkbP", + "serumCoinVaultAccount": "GZ1YSupuUq9kB28kX9t1j9qCpN67AMMwn4Q72BzeSpfR", + "serumPcVaultAccount": "7sP9fug8rqZFLbXoEj8DETF81KasaRA1fr6jQb6ScKc5", + "serumVaultSigner": "GBWgHXLf1fX4J1p5fAkQoEbnjpgjxUtr4mrVgtj9wW8a", + "official": true + }, + { + "name": "SUSHI-USDC", + "coin": "SUSHI", + "pc": "USDC", + "lp": "LP.RDM.SUSHI-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5dHEPTgvscKkAc54R77xUeGdgShdG9Mf6gJ9bwBqyb3V", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "7a8WXaxsvDV9CjSxgSpJG8LZgdxmSps1ehvtgQj2qt4j", + "ammTargetOrders": "9f5b3uy3hQutS6pka2GxcSoKjvKaTcB1ivkj1GK43UAV", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "B8vMKgzKHkapzdDu1jW76ALFvVYzHGGKhR5Afz3A4mZd", + "poolPcTokenAccount": "Hsxi4jvmszcMaWfU3tk98fQa9pVXtRktfKvKJ7rKBQYi", + "poolWithdrawQueue": "AgEspvUPUuaTqyJTjZMCAW3zTuxQBSaU17GhLJoc6Jad", + "poolTempLpTokenAccount": "BHLDqVcYUrAwv8RvDUQ76BQDQzvb2yftFN8UccpA2stx", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "A1Q9iJDVVS8Wsswr9ajeZugmj64bQVCYLZQLra2TMBMo", + "serumBids": "J8JVRuBojWcHFRGosQKRdDtzxwux8fy2dwfk42Z3dCaf", + "serumAsks": "6DScSyKZKBi9cXhD3mRkTkpsxrhw6HABFxebsteCP1zU", + "serumEventQueue": "Hvpz2Cv2LgWUfTtdfjpnefYrjQuaw8gGjKoDAeGxzrwE", + "serumCoinVaultAccount": "BJfPQ2iKTJknyWo2wtCVEpRGWVt8sgpvmSQVNwLioQrk", + "serumPcVaultAccount": "2UN8qfXzoUDAxZMX1KqKut93frkt5hFREL8xcw6Hgtsg", + "serumVaultSigner": "uWhVkK44yR6V5XywVom4oWzDQACSPYHhNjkwXprtUij", + "official": true + }, + { + "name": "TOMO-USDC", + "coin": "TOMO", + "pc": "USDC", + "lp": "LP.RDM.TOMO-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "8mBJC9qdPNDyrpAbrdwGbBpEAjPqwtvZQVmbnKFXXY2P", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "H11WJQWj51KyYU5gdrnsXvpaYZM6ZLGULV93VbTmvaBL", + "ammTargetOrders": "5E9x2QRpTM2oTtwb62C4rDYR8nJZxN8NFhAtnr2uYFKt", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "5swtuQhJQFid8uMd3DsegoxFKXVS8WoiiB3t9Pos9UHj", + "poolPcTokenAccount": "Eqbux46eaW4aZiuy6VUX6z7MJ2TsszeSA7TPnpdw3jVf", + "poolWithdrawQueue": "Hwtv6M9iTJc8SH49WjQx5rbRwzAryGm8f1NSQDmnY2iq", + "poolTempLpTokenAccount": "7YXJQ4rM59A69ow3M21MKbWEEKHbNeZQ1XFESVnbwEPx", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "8BdpjpSD5n3nk8DQLqPUyTZvVqFu6kcff5bzUX5dqDpy", + "serumBids": "DriSFYDLxWCEHcnFVaxKu2NrsWGB2htWhD1wkp39qxwU", + "serumAsks": "jd3YYp9WqjzyPxhBvj4ixa4DY3bCG1b74VquM4oCUbH", + "serumEventQueue": "J82jqHzNAzVYs9ZV3zuRgzRKuu1nGDFMrzJwdxvipjXk", + "serumCoinVaultAccount": "9tQtmWT3LCbVEoHFK5WK93wmDXv4us5s7NRYhficg9ih", + "serumPcVaultAccount": "HRFqUnxuegNbAf2auxqRwECyDijkVGDw25BCJkf5ohM5", + "serumVaultSigner": "7i7rf8LANeECyi8TAwwLTyvfiVUo4x12iJtKeeA6eG53", + "official": true + }, + { + "name": "LINK-USDC", + "coin": "LINK", + "pc": "USDC", + "lp": "LP.RDM.LINK-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "Hr8i6MAm4W5Lwb2fB2CD44A2t3Ag3gGc1rmd6amrWsWC", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "G4WdXwbczwDSs6iQmYt1F3sHDhfL6aD2uBkbAoMaaTt4", + "ammTargetOrders": "Hf3g2Q63UPSLFSCKZBPJvjVVZxVr83rXm1xWR7yC6spn", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "2ueuL35kQShG1ebZz3Cov4ug9Ex6xVXx4Fc4ZKvxFqMz", + "poolPcTokenAccount": "66JxeTwodpafkYLPYYAFoVoTh6ukNYoHvtwMMSzSPBCb", + "poolWithdrawQueue": "AgVo29AiDosdiXysfwMj8bF2AyD1Nvmn971x8PLwaNAA", + "poolTempLpTokenAccount": "58EPUPaefpjDxUppc4oyDeDGc9n7sUo7vapinKXigbd", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3hwH1txjJVS8qv588tWrjHfRxdqNjBykM1kMcit484up", + "serumBids": "GhmGNpJhGDz6zhmJ2kskmETbX9SGxhstRsmUejMXC24t", + "serumAsks": "83KiGivH1w4SiSK9YoN9WZrTSmtwveuCUd1nuZ9AFd2V", + "serumEventQueue": "9ZZ8eGhTEYK3uBNaFWSYo6ugLD6UVvudxpFXff7XSrmx", + "serumCoinVaultAccount": "9BswoEnX3SN7YUnRujZa5ygiL8AXVHXE4xqp8USX4QSY", + "serumPcVaultAccount": "9TibPFxakkdogUYizRhj9Av92fxuY2HxS3nrmme81Sma", + "serumVaultSigner": "8zqs77myZg6wkPjbh9YdSKtNmfPh4FJTzeo9R39mbjCm", + "official": true + }, + { + "name": "ETH-USDC", + "coin": "ETH", + "pc": "USDC", + "lp": "LP.RDM.ETH-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "AoPebtuJC4f2RweZSxcVCcdeTgaEXY64Uho8b5HdPxAR", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "7PwhFjfFaYp7w9N8k2do5Yz7c1G5ebp3YyJRhV4pkUJW", + "ammTargetOrders": "BV2ucC7miDqsmABSkXGzsibCVWBp7gGPcvkhevDSTyZ1", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "EHT99uYfAnVxWHPLUMJRTyhD4AyQZDDknKMEssHDtor5", + "poolPcTokenAccount": "58tgdkogRoMsrXZJubnFPsFmNp5mpByEmE1fF6FTNvDL", + "poolWithdrawQueue": "9qPsKm82ZFacGn4ipV1DH85k7efP21Zbxrxbxm5v3GPb", + "poolTempLpTokenAccount": "2WtX2ow4h5FK1vb8VjwpJ3hmwmYKfJfa1hy1rcDBohBT", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX", + "serumBids": "8tFaNpFPWJ8i7inhKSfAcSestudiFqJ2wHyvtTfsBZZU", + "serumAsks": "2po4TC8qiTgPsqcnbf6uMZRMVnPBzVwqqYfHP15QqREU", + "serumEventQueue": "Eac7hqpaZxiBtG4MdyKpsgzcoVN6eMe9tAbsdZRYH4us", + "serumCoinVaultAccount": "7Nw66LmJB6YzHsgEGQ8oDSSsJ4YzUkEVAvysQuQw7tC4", + "serumPcVaultAccount": "EsDTx47jjFACkBhy48Go2W7AQPk4UxtT4765f3tpK21a", + "serumVaultSigner": "C5v68qSzDdGeRcs556YoEMJNsp8JiYEiEhw2hVUR8Z8y", + "official": true + }, + { + "name": "xCOPE-USDC", + "coin": "xCOPE", + "pc": "USDC", + "lp": "LP.RDM.xCOPE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "3mYsmBQLB8EZSjRwtWjPbbE8LiM1oCCtNZZKiVBKsePa", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "4tN7g8KbPt5bU9YDpeAsUNs2FY4G6GRvajTwCCHXt9Lk", + "ammTargetOrders": "Fe5ZjyEhnB7mCgFhRkSLWNgvtkrut4iRzk1ydfJxwA9b", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "Guw4ErphtZQRC1foic6WweDSvA9AfuqJHKDXDcbrWH4f", + "poolPcTokenAccount": "86WgydpDUFRWa9aHzd9JgcKBELPJZVrkZ3uwxiiC3w2V", + "poolWithdrawQueue": "Gvmc1zR72pdgoWSzNBqMyNoVHe78nxKgd7FSCE422Lcp", + "poolTempLpTokenAccount": "6FpDRYsKds3WkiCLjqpDzNBHWZP2Bz6CK9dZryBLKB9D", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "7MpMwArporUHEGW7quUpkPZp5L5cHPs9eKUfKCdaPHq2", + "serumBids": "5SZ6xDgLzp3QbzkqT68BBAB7orCezSsV5Gb9eAk84zdY", + "serumAsks": "Gwt93Xzp8aFrP8YFV8YSuHmYbkrGURBVVHnE6AqDT4Hp", + "serumEventQueue": "Ea4bQ4wBJ5MXAwTG1hKzEv1zry5WnGY2G58YR8hcZTk3", + "serumCoinVaultAccount": "6LtcYXZVb7zfQG33F5dCDKZ29hyQaUh6BBhWjdHp8moy", + "serumPcVaultAccount": "FCqm5xfy8ZvMxifVFfSz9Gxv1CTRABVMyLXuJrWvzAq7", + "serumVaultSigner": "XoGZnpfyqj539wneBe8xUQyD282mwy5AMUaChz12JCH", + "official": true + }, + { + "name": "SOL-USDT", + "coin": "SOL", + "pc": "USDT", + "lp": "LP.RDM.SOL-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "7XawhbbxtsRcQA8KTkHT9f9nc6d69UwqCDh6U5EEbEmX", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "4NJVwEAoudfSvU5kdxKm5DsQe4AAqG6XxpZcNdQVinS4", + "ammTargetOrders": "9x4knb3nuNAzxsV7YFuGLgnYqKArGemY54r2vFExM1dp", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "876Z9waBygfzUrwwKFfnRcc7cfY4EQf6Kz1w7GRgbVYW", + "poolPcTokenAccount": "CB86HtaqpXbNWbq67L18y5x2RhqoJ6smb7xHUcyWdQAQ", + "poolWithdrawQueue": "52AfgxYPTGruUA9XyE8eF46hdR6gMQiA6ShVoMMsC6jQ", + "poolTempLpTokenAccount": "2JKZRQc92TaH3fgTcUZyxfD7k7V7BMqhF24eussPtkwh", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "serumBids": "2juozaawVqhQHfYZ9HNcs66sPatFHSHeKG5LsTbrS2Dn", + "serumAsks": "ANXcuziKhxusxtthGxPxywY7FLRtmmCwFWDmU5eBDLdH", + "serumEventQueue": "GR363LDmwe25NZQMGtD2uvsiX66FzYByeQLcNFr596FK", + "serumCoinVaultAccount": "29cTsXahEoEBwbHwVc59jToybFpagbBMV6Lh45pWEmiK", + "serumPcVaultAccount": "EJwyNJJPbHH4pboWQf1NxegoypuY48umbfkhyfPew4E", + "serumVaultSigner": "CzZAjoEqA6sjqtaiZiPqDkmxG6UuZWxwRWCenbBMc8Xz", + "official": true + }, + { + "name": "YFI-USDT", + "coin": "YFI", + "pc": "USDT", + "lp": "LP.RDM.YFI-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "81PmLJ8j2P8CC5EJAAhWGYA4HgJvoKs4Y94ALZF2uKKG", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "pxedkTHh23HBYoarBPKML3xWh96EaNzKLW3oXvHHCw5", + "ammTargetOrders": "GUMQZC9SAqynDvoV12sRUzACF8GzLpC5fUtRuzwCbU9S", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "GwY3weBBK4dQFwC96tHAoAQq4pSfMYmMZ4m6Njqq7Wbk", + "poolPcTokenAccount": "Bs3DatsVrDujvjpV1JUVmVgNrPkaVwvp6WtuHz4z1QE6", + "poolWithdrawQueue": "2JJPww9oCvBxTdZaiB2H69Jx4dKWctCEuvbLtFfNCqHd", + "poolTempLpTokenAccount": "B46wMQncJ2Ugp2NwWDxK6Qd4Q9T24NK3naNVdyVYxbug", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3Xg9Q4VtZhD4bVYJbTfgGWFV5zjE3U7ztSHa938zizte", + "serumBids": "7FN1TgMmjQ8iwTdmJZAiwdTM3MddvxmgiF2J4GVHUtQ1", + "serumAsks": "5nudyjGUfjwVYCk1MzzuBeXcj9k59g9mruAUXrsQfcrR", + "serumEventQueue": "4AMp4qKTwE7RwExstg7Pk4JZwJGeRMnjkFmf52tqCHJN", + "serumCoinVaultAccount": "5KgKdCWVyWi9YJ6GipzozhWxAvnbQPpUtaxuMXXEn3Zs", + "serumPcVaultAccount": "29CnTKiFKwGPFfLBXDXGRX6ywGz3ToZfqZuLkoa33dbE", + "serumVaultSigner": "6LRcCMsRoGsye95Ck5oSyNqHJW8kk2iXt9z9YQyi9JkV", + "official": true + }, + { + "name": "SRM-USDT", + "coin": "SRM", + "pc": "USDT", + "lp": "LP.RDM.SRM-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "af8HJg2ffWoKJ6vKvkWJUJ9iWbRR83WgXs8HPs26WGr", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "8E2GLzSgLmzWdpdXjjEaHbPXRXsA5CFehg6FP6N39q2e", + "ammTargetOrders": "8R5TVxXvRfCaYvT493FWAJyLt8rVssUHYVGbGupAbYaQ", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "D6b4Loa4LoidUor2ffouE5BTMt6tLP6MtkNrsfBWG2C3", + "poolPcTokenAccount": "4gNeJniq6yqEygFmbAJa82TQjH1j3Fczm4bdeBHhwGJ1", + "poolWithdrawQueue": "D3JQytXAydpHKUPChDe8JXdmvYRRV4EpnrxsqzMHNjFp", + "poolTempLpTokenAccount": "2dYW9SoJb51YNneQG7AywSB75jmzZa2R8rzzW7gT61h1", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb", + "serumBids": "EE2CYFBSoMvcUR9mkEF6tt8kBFhW9zcuFmYqRM9GmqYb", + "serumAsks": "nkNzrV3ZtkWCft6ykeNGXXCbNSemqcauYKiZdf5JcKQ", + "serumEventQueue": "2i34Kriz23ZaQaJK6FVhzkfLhQj8DSqdQTmMwz4FF9Cf", + "serumCoinVaultAccount": "GxPFMyeb7BUnu2mtGV2Zvorjwt8gxHqwL3r2kVDe6rZ8", + "serumPcVaultAccount": "149gvUQZeip4u8bGra5yyN11btUDahDVHrixzknfKFrL", + "serumVaultSigner": "4yWr7H2p8rt11QnXb2yxQF3zxSdcToReu5qSndWFEJw", + "official": true + }, + { + "name": "FTT-USDT", + "coin": "FTT", + "pc": "USDT", + "lp": "LP.RDM.FTT-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "4fgubfZVL6L8tc5x1j65S14P2Tnxr1YayKtKavQV5MBo", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "BSDKUy73wuGskKDVgzNGLL2k7hzDEwj237nZZ3Ch3bwz", + "ammTargetOrders": "4j1JaKap2s4XrkJeMDaMabfEDsQm9ykeUgJ9CWa9w4JU", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "HHTXo4Q8HFWMSDKnPJWCe1Y5UmYPFNZ6hU4mc8km7Zf4", + "poolPcTokenAccount": "5rbAHV9ufT11XRR5LcvMVsuA5FcpBozLKj91z372wpZR", + "poolWithdrawQueue": "AMU4FFUUahWfaUA6WWzTWNNuiXoNDEgNNsZjFLWhvB8f", + "poolTempLpTokenAccount": "FUVUCrKB6c7x9uVn1zK8qxbVwb6rNLqA2W17TM9Bhvta", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Hr3wzG8mZXNHV7TuL6YqtgfVUesCqMxGYCEyP3otywZE", + "serumBids": "3k5bWdYn9thQmqrye2gSobzFBYTyYosx3bKvMJRcfTTN", + "serumAsks": "DPW1r1p2uyfQxVC7vx3xVQcVvyUeiS2vhAnveQiXs9AT", + "serumEventQueue": "9zMcCfjdHH2Z7iCBtVdkmf9qXUN6y7AhbuWhRMu2DmcV", + "serumCoinVaultAccount": "H1VJqo3piiadyVAUQW6yfZq4an8pgDFvAdqHJkRXMDbq", + "serumPcVaultAccount": "9SQ4Sjsszt59X3aLwRrTqa5SLxonEdXk5jF7KUfAxc8Z", + "serumVaultSigner": "CgV9LcnAukrgDZmqhUwcNQ31z4KEjZEz4DHUSE4bRaVg", + "official": true + }, + { + "name": "BTC-USDT", + "coin": "BTC", + "pc": "USDT", + "lp": "LP.RDM.BTC-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "AMMwkf57c7ZsbbDCXvBit9zFehMr1xRn8ZzaT1iDF18o", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "G5rZ4Qfv5SxpJegVng5FuZftDrJkzLkxQUNjEXuoczX5", + "ammTargetOrders": "DMEasFJLDw27MLkTBFqSX2duvV5GV6LzwtoVqVfBqeGR", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "7KwCHoQ9nqTnGea4XrcfLUr1pwEWp2maGBHWFqBTeoKW", + "poolPcTokenAccount": "HwbXe9YJVez3BKK22jBH1i64YeX2fSKaYny5jrcPDxAk", + "poolWithdrawQueue": "3XUXNx72jcaXB3N56UjrtWwxv99ivqUwLAdkagvop4HF", + "poolTempLpTokenAccount": "8rZSQ23HWfZ1P6qd9ZL4ywTgRYtRZDd3xW3aK1hY7pkR", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4", + "serumBids": "2e2bd5NtEGs6pb758QHUArNxt6X9TTC5abuE1Tao6fhS", + "serumAsks": "F1tDtTDNzusig3kJwhKwGWspSu8z2nRwNXFWc6wJowjM", + "serumEventQueue": "FERWWtsZoSLcHVpfDnEBnUqHv4757kTUUZhLKBCbNfpS", + "serumCoinVaultAccount": "DSf7hGudcxhhegMpZA1UtSiW4RqKgyEex9mqQECWwRgZ", + "serumPcVaultAccount": "BD8QnhY2T96h6KwyJoCT9abMcPBkiaFuBNK9h6FUNX2M", + "serumVaultSigner": "EPzuCsSzHwhYWn2j69HQPKWuWz6wuv4ANZiVigLGMBoD", + "official": true + }, + { + "name": "SUSHI-USDT", + "coin": "SUSHI", + "pc": "USDT", + "lp": "LP.RDM.SUSHI-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DWvhPYVogsEKEsehHApUtjhP1UFtApkAPFJqFh2HPmWz", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "ARZWhFKLtqubNWdotvqeiTTpmBw4XfrySNtY4485Zmq", + "ammTargetOrders": "J8f8p2x3wPTbpaqJydxTY5CvxtiB8HrMdW1DouaEVvRx", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "C77d7jRkxu3WyzL7K2UZZPdWXPzsFrmzLG4uHrsZhGTz", + "poolPcTokenAccount": "BtweN6cYHBntMJiRY2gGB2u4oZFsbapjLz7QJeV3KWF1", + "poolWithdrawQueue": "6WsofMBNdHWacgButviYgn8CCTGyjW19H13vYntkzBzp", + "poolTempLpTokenAccount": "CgaVy8TjkUdxFhi4h3RdszmPtf6MPUyfquqAWUwAnim7", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6DgQRTpJTnAYBSShngAVZZDq7j9ogRN1GfSQ3cq9tubW", + "serumBids": "7U3FPNGvcDkmfnD4u5jKVd2AKwc66RFBZ8GnyjzeNfML", + "serumAsks": "3Zx74FxHwttDuYxeqHzMijitrf25FhSzeoWBT9VeCrVj", + "serumEventQueue": "9PqaWBQ6gSZDZsztbWTnXp6LfrS2TUfVfPTSnf8tbgkE", + "serumCoinVaultAccount": "5LmHe3x8VwGzWZ6rooARZJNMo6AaN1P73478AuhBUjUr", + "serumPcVaultAccount": "iLCNUheHbq3bE1868XwWXs8enoTvjFnwpnmLFmBQGi3", + "serumVaultSigner": "9GN4139oezNfddWhcAc3c8Ke5aU4cwzcxL8cLkqE37Yy", + "official": true + }, + { + "name": "TOMO-USDT", + "coin": "TOMO", + "pc": "USDT", + "lp": "LP.RDM.TOMO-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "GjrXcSvwzGrz1RwKYGVWdbZyXzyotgichSHB95moDmf8", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "6As7AcwxnvawiY4mKnVTYqjTSRe9Uu2yW5hhJB97Ur6y", + "ammTargetOrders": "BPU6CpQ9RVrftpofrXD3Gui5iNXpbiNiCm9ecQUahgH6", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8Ev8a9a8ZQi2xHYa7fwkYqzrmMrwbnUf6D9z762zAWcF", + "poolPcTokenAccount": "DriE8fPjPcTf7jzzyMqnQYqBPAVQPNS6bjZ4EABEJPUd", + "poolWithdrawQueue": "CR4AmK8geX2e1VLdFKgC2raxMwB4JsVUKXd3mBGkv4YW", + "poolTempLpTokenAccount": "GLXgb5oGNHQAVr2t68sET3NGPBtDitE5cQaMG3zgc7D8", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "GnKPri4thaGipzTbp8hhSGSrHgG4F8MFiZVrbRn16iG2", + "serumBids": "7C1XnffUgQVnfRTUPBPxQQT1QKsHwnQ7ogAWmmJqbW9L", + "serumAsks": "Hbd8HWXcZDPUUHYXJLH4vn9t1SfQZ83fqf4jQN65QpYL", + "serumEventQueue": "5AB3QbR7Ck5qsn21fM5zBzxVUnyougXroWHeR33bscwH", + "serumCoinVaultAccount": "P6qAvA6s7DHzzH4i74CUFAzx5bM4Yj3xk5TKmF7eWdb", + "serumPcVaultAccount": "8zFodcf4pKcRBq7Zhdg4tQeB76op7kSjPC2haPjPkDEm", + "serumVaultSigner": "ECTnLdZEaxUiCwyjKcts3CoMfT4kj3CNfVCd9B18hRim", + "official": true + }, + { + "name": "LINK-USDT", + "coin": "LINK", + "pc": "USDT", + "lp": "LP.RDM.LINK-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "E9EvurfzdSQaqCFBUaD4MgV93htuRQ93sghm922Pik88", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "CQ9roBWWPV5efTeZHoqgzJJvTSeVNMca6rteaenNwqF6", + "ammTargetOrders": "DVXgN8m2f8Ggs8zddLZyQdsh49jeUGnLq66s4Lhfd1uj", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BKNf6HxSz9tCmeZts4ABHpYuXwP2wfKf4uRycwdTm3Jh", + "poolPcTokenAccount": "5Uzq3c6rnedxMF7t7s7PJVQkxxZE7YXGFPJUToyhdebY", + "poolWithdrawQueue": "Hj5vcVZCm6JXtkmCa1MPjteoxzkWQCmHQutXxofj2sy6", + "poolTempLpTokenAccount": "7WhsN9LGSeGxhZPT4E4rczauDvhmfquAKHQUESAXYS3k", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3yEZ9ZpXSQapmKjLAGKZEzUNA1rcupJtsDp5mPBWmGZR", + "serumBids": "9fkA2oJQ7BKP5n2WxdLkY7mDA1mzBrGZ9osqVhvdBkH7", + "serumAsks": "G8c3xQURJk1oukLqJd3W4SJykmRq4wq3GrSWJwWipECH", + "serumEventQueue": "4MDEwZYKXuvEdQ58yMsE2zwXLG973aYp4EFvoaUSDMP2", + "serumCoinVaultAccount": "EmS34LncbTGs4yU4GM9bESRYMCFL3JBW6mnAeKB4UtEb", + "serumPcVaultAccount": "AseZZ8ZRqyvkZMMGAAG8dAqM9XFf2xGX2tWWbko7a4hC", + "serumVaultSigner": "FezSC2d6sXEcJ9ah8nYxHC18nh4FZzc4u7ZTtRSrk6Nd", + "official": true + }, + { + "name": "ETH-USDT", + "coin": "ETH", + "pc": "USDT", + "lp": "LP.RDM.ETH-USDT-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "He3iAEV5rYjv6Xf7PxKro19eVrC3QAcdic5CF2D2obPt", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "8x4uasC632WSrk3wgwoCWHy7MK7Xo2WKAe9vV93tj5se", + "ammTargetOrders": "G1eji3rrfRFfvHUbPEEbvnjmJ4eEyXeiJBVbMTUPfKL1", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "DZZwxvJakqbraXTbjRW3QoGbW5GK4R5nmyrrGrFMKWgh", + "poolPcTokenAccount": "HoGPb5Rp44TyR1EpM5pjQQyFUdgteeuzuMHtimGkAVHo", + "poolWithdrawQueue": "EispXkJcfh2PZA2fSXWsAanEGq1GHXzRRtu1DuqADQsL", + "poolTempLpTokenAccount": "9SrcJk8TB4JvutZcA4tMvvkdnxCXda8Gtepre7jcCaQr", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF", + "serumBids": "J8a3dcUkMwrE5kxN86gsL1Mwrg63RnGdvWsPbgdFqC6X", + "serumAsks": "F6oqP13HNZho3bhwuxTmic4w5iNgTdn89HdihMUNR24i", + "serumEventQueue": "CRjXyfAxboMfCAmsvBw7pdvkfBY7XyGxB7CBTuDkm67v", + "serumCoinVaultAccount": "2CZ9JbDYPux5obFXb9sefwKyG6cyteNBSzbstYQ3iZxE", + "serumPcVaultAccount": "D2f4NG1NC1yeBM2SgRe5YUF91w3M4naumGQMWjGtxiiE", + "serumVaultSigner": "CVVGPFejAj3A75qPy2116iJFma7zGEuL8DgnxhwUaFBF", + "official": true + }, + { + "name": "YFI-SRM", + "coin": "YFI", + "pc": "SRM", + "lp": "LP.RDM.YFI-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "GDVhJmDTdSExwHeMT5RvUBUNKLwwXNKhH8ndm1tpTv6B", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "5k2VpDkhbvypWvg9erQTZu4KsKjVLe1VAo3K71THrNM8", + "ammTargetOrders": "4dhnWeEq5aeqDFkEa5CKwS2TYrUmTZs7drFBAS656f6e", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8FufHk1xV2j9RpVztnt9vuw9KJ89rpR7FMT1HTfsqyPH", + "poolPcTokenAccount": "FTuzfUyp6fhLMQ5kUdAkBWd9BjY114DfjkrVocAFKwkQ", + "poolWithdrawQueue": "A266ybcveVZYraGgEKWb9JqVWVp9Tsxa9hTudzvTQJgY", + "poolTempLpTokenAccount": "BXHfb8E4KNVnAVvz1eyVS12QqpvBUimtCnnNiBuoMrRa", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6xC1ia74NbGZdBkySTw93wdxN4Sh2VfULtXh1utPaJDJ", + "serumBids": "EmfyNgr2t1mz6QJoGfs7ytLPpnT3A4kmZj2huGBFHtpr", + "serumAsks": "HQhD6ZoNfCjvUTfsE8KS46PLC8rpeyBYy1tY4FPgEbpQ", + "serumEventQueue": "4QGAwMgfi5PrMUoHvoSbGQV168kuRMURBK4pwGfSV7nC", + "serumCoinVaultAccount": "GzZCBp3Z3fYHZW9b4WusfQhp7p4rZXeSNahCpn8HBD9", + "serumPcVaultAccount": "ANK9Lpi4pUe9SxPvcKvd82jkG6AoKvvgo5kN8BCXukfA", + "serumVaultSigner": "9VAdxQgKNLkHgtQ4fkDetwwTKZG8xVaKeUFQwBVG7c7a", + "official": true + }, + { + "name": "FTT-SRM", + "coin": "FTT", + "pc": "SRM", + "lp": "LP.RDM.FTT-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "21r2zeCacmm5YvbGoPZh9ZoGREuodhcbQHaP5tZmzY14", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "CimwwQH1h2MKbFbodHHByMq8MreFuJznMGVXxYKMpyiB", + "ammTargetOrders": "Fewh6hVTfeduAnbqwNuUx2Cu7uTyJTALP76hjpWCvRoV", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "Atc9Prscs9RLmDEpsCQzFgCqzkscAtTck5ZSZGV9s7hE", + "poolPcTokenAccount": "31ZJVJMap4WpPbzaScPwg5MGRUDjatP2kXVsSgf12yVZ", + "poolWithdrawQueue": "yAZD46BC1Bti2X5FEjveobueuyevi7jFV5ew6DH8Thz", + "poolTempLpTokenAccount": "7Ro1o6Vbh3Ech2zeozNDicRP1gZfHAWcRnxvrzdnLfYi", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "CDvQqnMrt9rmjAxGGE6GTPUdzLpEhgNuNZ1tWAvPsF3W", + "serumBids": "9NfJWy5QNqRDGmNARphS9kJyYtR6nkkWcFyJRLbgECtd", + "serumAsks": "9VEVBJZHVv6N2MzAzNLiCwN2MAdt5GDScCtpE4zkzDFW", + "serumEventQueue": "CbnLQT9Jwo3RHpWBnsPisAybSN4CBuwj4fcF1S9qJchV", + "serumCoinVaultAccount": "8qTUSDRxJ65sGKEUu746xJdCquoP38AqKsQo6ZruSSBk", + "serumPcVaultAccount": "ALe3hiZR35cCjcrzbJi1vKEhNftdVQjwkt4S8rbPZogq", + "serumVaultSigner": "CAAeuJAgnP368num8bCv6VMWCqMZ4pTANCcGTAMAJtm2", + "official": true + }, + { + "name": "BTC-SRM", + "coin": "BTC", + "pc": "SRM", + "lp": "LP.RDM.BTC-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DvxLb4NnQUYq1gErk35HVt9g8kxjNbviJfiZX1wqraMv", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "3CGxjymeKv5wvpVg9unUgbrGUESmeqfJUJkPjVeRuMvT", + "ammTargetOrders": "C8YiDYrk4rfC6sgK93zM3YpGj7SDpGuRbos7DHStSssT", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "5jV7XQ1JnfUg7RvEShyAdV7Gzn1xS54j163x8ZBSzxuh", + "poolPcTokenAccount": "HSKY5r6iqCpC4nWzCGP2oWMQdGEQsx69eBm33PrmZqhg", + "poolWithdrawQueue": "5faTQUz7gmasinkinA7BkC6HsG8hUrD9iukaohF2fuHZ", + "poolTempLpTokenAccount": "9QutovnPtwN9pPxsTdaEWBSCT7iTKc3hwMfF4QJHDXRz", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HfsedaWauvDaLPm6rwgMc6D5QRmhr8siqGtS6tf2wthU", + "serumBids": "GMM36fgidwYvXCAxQhpT1XkGoZ46g1wMc44hY8ds3P8u", + "serumAsks": "BFDQ4WGcEftURk6nrwtQ1GzYdPYj8fx3iBjeJVt6S3jQ", + "serumEventQueue": "94ER3KZeDrYSG8TytGJ56rZK9zM8oz1H8dJ2LP1gHn2s", + "serumCoinVaultAccount": "3ABvHYBeWrpgP82jvHh5TVwid1AjDj9rei7zfY8xh2wz", + "serumPcVaultAccount": "CSpdPdzzbaNWgwhPRTZ4TNoYS6Vco2w1s7jvqUsYQBzf", + "serumVaultSigner": "9o8LaPeTMJBoYyoUVNm6ju6c5rwfphhYReQsp1vTTyRg", + "official": true + }, + { + "name": "SUSHI-SRM", + "coin": "SUSHI", + "pc": "SRM", + "lp": "LP.RDM.SUSHI-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "BLVjPTgzyfiKSgDujTNKKNzW2GXx7HhdMxgr2LQ2g83s", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "Efpi6e4ckqtfaED9gRmadN3RtiTXDtGPrp1szsh7sj7C", + "ammTargetOrders": "BZUFGpRWEsYzpVfLrFpdE7E9fzGhrySQE1TrsX92qWAC", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BjWKHZxVMQykmGGmkhA1m9QQycJTeQFs51kyfP1zQvzv", + "poolPcTokenAccount": "EnWaAD7WAyznuRjg9PqRr2vVaXqQpTje2fBWyFFEvr37", + "poolWithdrawQueue": "GbEc9D11VhEHCDsqcSZ5vPVfnzV7BCS6eTquoVvhSaNz", + "poolTempLpTokenAccount": "AQ4YUkqPSbP8JpnCWEAkYNUWm6AjUSnPucKhVN8ypuiB", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "FGYAizUhNEC9GBmj3UyxdiRWmGjR3TfzMq2dznwYnjtH", + "serumBids": "J9weS4eF3DcSMLttazndEwVtjsqfRf6vBg1FNhdYrKiW", + "serumAsks": "4TCPXw9UBcPfSVtaArzydHvgAXfDbq28iZVjHidbM9rp", + "serumEventQueue": "2eJU3EygyV4SWGAH1g5F57CxtaTj4nL36apaRtnEZ9zH", + "serumCoinVaultAccount": "BSoAoNFKzK65TjcUpY5JZHBvZVMiYnkdo9upy3mLSTpq", + "serumPcVaultAccount": "8U9azb65o1dJuMs7je987i7hKxJfPZnbNRNeH5beJfo7", + "serumVaultSigner": "HZtDGZsz2fdXF75H8tyB8skp5a4rvoawgxwXqHTGEdvU", + "official": true + }, + { + "name": "TOMO-SRM", + "coin": "TOMO", + "pc": "SRM", + "lp": "LP.RDM.TOMO-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DkMAuUCQHC6BNgVnjtM5ZTKm1T8MsriQ6bL3Umi6NBtG", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "34eRiATmb9Ktv1QTDzzckyaFhj4KpC2y94TJXXd34erL", + "ammTargetOrders": "CK2vFsmS2CEZ2Hi6Vf9px8p5DSpoyXST9rkFHwbbHirU", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8BjTHZccnRNZKZpAxsdXx5BEQ4Kpxd9pQLNgeMqMiTZL", + "poolPcTokenAccount": "DxcJXkGo8BUmsky51LuKi4Vs1zW48fHrCXEY6BKuY3TY", + "poolWithdrawQueue": "AoP3EXWypUheq9ZURDBpf8Jd1ijRuhUCQg1uiM5zFpB5", + "poolTempLpTokenAccount": "9go7YtJ6QdG3mWgVhwRcQAfmwPruJk5MmsjyTn2HJisK", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "7jBrpiq3w2ywzzb54K9SoosZKy7nhuSQK9XrsgSMogFH", + "serumBids": "ECdZLJGwcN6fXY9BjiSVNrWssKdWejW9uv8Zs6GkkxBG", + "serumAsks": "J5NN79kpFzGdxj8MGvis3NsGYcrvcdYHNXLtGGn9au5E", + "serumEventQueue": "7FrdprBxpDyM7P1AkeMtEJ75Q6UK6ZE92zgqGg5F4Gxb", + "serumCoinVaultAccount": "8W65Bwb83MYKHf82phS9xPUDsR6RpZbAXnSELxsBb3HH", + "serumPcVaultAccount": "5rjDHBsjFv3Z3Dxr5RMj98vj6LA5DNEwZGDM8wyUF1Hy", + "serumVaultSigner": "EJfMPPTvTKtgj7PUaM17bp2Gbye9CdKjZ5yqonPyY4rB", + "official": true + }, + { + "name": "LINK-SRM", + "coin": "LINK", + "pc": "SRM", + "lp": "LP.RDM.LINK-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "796pvggjoDCPUtUSVFSCLqPRyes5YPvRiu4zFWX582wf", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "3bZB7mZ5hRNZfrJx6BL5C4GhP4nT14rEAGVPXL34hrZg", + "ammTargetOrders": "Ha4yLJU1UrZi8MqCMu2pLK3xXREG1GW1bjjqTsjQnC3c", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "5eTUmVN3kXqBeKHUA2kWU19jB7kFN3wpejWvWYcw6dBa", + "poolPcTokenAccount": "4BsmBxNQtuKgBTNjci8tWd2NqPxXBs2JY38X26epSHYy", + "poolWithdrawQueue": "2jn4FQ2CtYwXDgCcLbNrGUzKFeB5PpPbnMr2x2z2wz3V", + "poolTempLpTokenAccount": "7SxKHHATjgEgfxnLrtKaSU77s2ABqD8BoEr6W6dFMS3a", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "FafaYTnhDbLAFsr5qkD2ZwapRxaPrEn99z59UG4zqRmZ", + "serumBids": "HyKmFiuoWZo7STLjvJJ66YR4V1wauAorCPUaxnnB6umk", + "serumAsks": "8qjKdvjmBPZWjxP3nWjwFCcsrAspCN5EyTD3WfgKbFj4", + "serumEventQueue": "FWZB7PJLwg7WdgoVBRrkvz2A4S7ZctKnoGj1yCSxqs9G", + "serumCoinVaultAccount": "8J7iJ4uidHscVnNGsEgiEPJsUqrfteN7ifMscB9h4dAq", + "serumPcVaultAccount": "Bw7SrqDqvAXHi2yphAniH3uBw9N7J6vVi7jMH9B2KYWM", + "serumVaultSigner": "CvP4Jk6AYBV6Kch6w6FjwuMqHAugQqVrqCNp1eZmGihB", + "official": true + }, + { + "name": "ETH-SRM", + "coin": "ETH", + "pc": "SRM", + "lp": "LP.RDM.ETH-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "3XwxHcbyqcd1xkdczaPv3TNCZsevELD4Zux3pu4sF2D8", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "FBfaqV1RRacEi27E3dm8yLcxpbWYx4BzMXG4zMNx7ZdS", + "ammTargetOrders": "B1gQ6FHLxmBzznDKn8Rj1ZokcJtdSWjkCoXdQLRhz8NS", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "CsFFjzC1hmpqimExTj8g4kregUxGnGrEWX9Jhne172uU", + "poolPcTokenAccount": "ACg55oVWt1a4ZVxnFVCRDEMz1JAeGY13snXufdQAp4pX", + "poolWithdrawQueue": "C6MRGfZ13tstxjcWuLqUseUikidsAjgk7zBEYqM6cFb4", + "poolTempLpTokenAccount": "EVRzNkPU9UAzBf8XhJYD84U7petDZnSMVaaa9mtBQaM6", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3Dpu2kXk87mF9Ls9caWCHqyBiv9gK3PwQkSvnrHZDrmi", + "serumBids": "HBVsrbKLEf1aaUy9oKFkQZVDtgTf54T9H8FQdcGbF7EH", + "serumAsks": "5T3zDaT1XvfEb9jKcgpFyQRze9qWKNTE1iSE5aboxYZy", + "serumEventQueue": "3w11TRux1gX7nqaGUMGpPH9ocDBPudeLTw6k1uhsLo2k", + "serumCoinVaultAccount": "58jqhCZ11r6ZvATqdGfDXPk7LmiR9HS3jQt7kuoBx5CH", + "serumPcVaultAccount": "9NLpT5aZtbbauvEVVFsHqigv2ekTEPK1kojoMMCw6Hhx", + "serumVaultSigner": "EC5JsbaQVp8tM59TqkQBk4Yv7bzLQq3TrzpepjGr9Ecg", + "official": true + }, + { + "name": "SRM-SOL", + "coin": "SRM", + "pc": "SOL", + "lp": "LP.RDM.SRM-SOL-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "EvWJC2mnmu9C9aQrsJLXw8FhUcwBzFEUQsP1E5Y6a5N7", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "9ot4bg8aT2FRKfiRrM2fSPHEr7M1ihBqm1iT4771McqR", + "ammTargetOrders": "AfzGtG3XnMixxJTx2rwoWLXKVaWoFMhsMeYo929BrUBY", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BCNYwsnz3yXvi4mY5e9w2RmZvwUW3pefzYQ4tsoNdDhp", + "poolPcTokenAccount": "7BXPSUXeBVqJGyxW3yvkNxnJjYHuC8mnhyFCDp2abAs6", + "poolWithdrawQueue": "HYo9FfBpm8NCpR8qYMGYFZNqzKkXDRFACLxu8PXCCDc4", + "poolTempLpTokenAccount": "AskrcNfMDKT5c65AYeuEBW6mfMXfT3SG4nDCDRAyEnad", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "jyei9Fpj2GtHLDDGgcuhDacxYLLiSyxU4TY7KxB2xai", + "serumBids": "4ZTJfhgKPizbkFXNvTRNLEncqg85yJ6pyT7NVHBAgvGw", + "serumAsks": "7hLgwZhHD1MRNyiF1qfAjfkMzwvP3VxQMLLTJmKSp4Y3", + "serumEventQueue": "nyZdeD16L5GxJq7Pso8R6KFfLA8R9v7c5A2qNaGWR44", + "serumCoinVaultAccount": "EhAJTsW745jiWjViB7Q4xXcgKf6tMF7RcMX9cbTuXVBk", + "serumPcVaultAccount": "HFSNnAxfhDt4DnmY9yVs2HNFnEMaDJ7RxMVNB9Y5Hgjr", + "serumVaultSigner": "6vBhv2L33KVJvAQeiaW3JEZLrJU7TtGaqcwPdrhytYWG", + "official": true + }, + { + "name": "STEP-USDC", + "coin": "STEP", + "pc": "USDC", + "lp": "LP.RDM.STEP-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "4Sx1NLrQiK4b9FdLKe2DhQ9FHvRzJhzKN3LoD6BrEPnf", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "EXgME2sUuzBxEc2wuyoSZ8FZNZMC3ChhZgFZRAW3nCQG", + "ammTargetOrders": "78bwAGKJjaiPQqmwKmbj4fhrRTLAdzwqNwpFdpTzrhk1", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8Gf8Cc6yrxtfUZqM2vf2kg5uR9bGPfCHfzdYRVBAJSJj", + "poolPcTokenAccount": "ApLc86fHjVbGbU9QFzNPNuWM5VYckZM92q6sgJN1SGYn", + "poolWithdrawQueue": "5bzBcB7cnJYGYvGPFxKcZETn6sGAyBbXgFhUbefbagYh", + "poolTempLpTokenAccount": "CpfWKDYNYfvgk42tqR8HEHUWohGSJjASXfRBm3yaKJre", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "97qCB4cAVSTthvJu3eNoEx6AY6DLuRDtCoPm5Tdyg77S", + "serumBids": "5Xdpf7CMGFDkJj1smcVQAAZG6GY9gqAns18QLKbPZKsw", + "serumAsks": "6Tqwg8nrKJrcqsr4zR9wJuPv3iXsHAMN65FxwJ3RMH8S", + "serumEventQueue": "5frw4m8pEZHorTKVzmMzvf8xLUrj65vN7wA57KzaZFK3", + "serumCoinVaultAccount": "CVNye3Xr9Jv26c8TVqZZHq4F43BhoWWfmrzyp1M9YA67", + "serumPcVaultAccount": "AnGbReAhCDFkR83nB8mXTDX5dQJFB8Pwicu6pGMfCLjt", + "serumVaultSigner": "FbwU5U1Doj2PSKRJi7pnCny4dFPPJURwALkFhHwdHaMW", + "official": true + }, + { + "name": "MEDIA-USDC", + "coin": "MEDIA", + "pc": "USDC", + "lp": "LP.RDM.MEDIA-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "94CQopiGxxUXf2avyMZhAFaBdNatd62ttYGoTVQBRGdi", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "EdS5vqjihxRbRujPkqqzHYwBqcTP9QPbrBc9CDtnBDwo", + "ammTargetOrders": "6Rfew8qvNp97PVN14C9Wg8ybqRdF9HUEUhuqqZBWcAUW", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "7zfTWDFmMi3Tzbbd3FZ2vZDdBm1w7whiZq1DrCxAHwMj", + "poolPcTokenAccount": "FWUnfg1hHuanU8LxJv31TAfEWSvuWWffeMmHpcZ9BYVr", + "poolWithdrawQueue": "F7MUnGrShtQqSvi9DoWyBNRo7FUpRiYPsS9aw77auhiS", + "poolTempLpTokenAccount": "7oX2VcPYwEV6EUUyMUoTKVVxAPAvGQZcGiGzotX43wNM", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "FfiqqvJcVL7oCCu8WQUMHLUC2dnHQPAPjTdSzsERFWjb", + "serumBids": "GmqbTDL5QSAhWL7UsE8MriTHSnodWM1HyGR8Cn8GzZV5", + "serumAsks": "CrTBp7ThkRRYJBL4tprke2VbKYj2wSxJp3Q1LDoHcQwP", + "serumEventQueue": "HomZxFZNGmH2XedBavMsrXgLnWFpMLT95QV8nCYtKszd", + "serumCoinVaultAccount": "D8ToFvpVWmNnfJzjHuumRJ4eoJc39hsWWcLtFZQpzQTt", + "serumPcVaultAccount": "6RSpnBYaegSKisXaJxeP36mkdVPe9SP3p2kDERz8Ahhi", + "serumVaultSigner": "Cz2m3hW2Vcb8oEFz12uoWcdq8mKb9D1N7RTyXpigoFXU", + "official": true + }, + { + "name": "ROPE-USDC", + "coin": "ROPE", + "pc": "USDC", + "lp": "LP.RDM.ROPE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "BuS4ScFcZjEBixF1ceCTiXs4rqt4WDfXLoth7VcM2Eoj", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "ASkE1yKPBei2aUxKHrLRptB2gpC3a6oTSxafMikoHYTG", + "ammTargetOrders": "5isDwR41fBJocfmcrcfwRtTnmSf7CdssdpsmBy2N2Eym", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "3mS8mb1vDrD45v4zoxbSdrvbyVM1pBLM31cYLT2RfS2U", + "poolPcTokenAccount": "BWfzmvvXhQ5V8ZWDMC4u82sEWgc6HyRLnq6nauwrtz5x", + "poolWithdrawQueue": "9T1cwwE5zZr3D2Rim8e5xnJoPJ9yKbTXvaRoxeVoqffo", + "poolTempLpTokenAccount": "FTFx4Vg6hgKLZMLBUvazvPbM7AzDe5GpfeBZexe2S6WJ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "4Sg1g8U2ZuGnGYxAhc6MmX9MX7yZbrrraPkCQ9MdCPtF", + "serumBids": "BDYAnAUSoBTtX7c8TKHeqmSy7U91V2pDg8ojvLs2fnCb", + "serumAsks": "Bdm3R8X7Vt1FpTruE9SQVESSd3BjAyFhcobPwAoK2LSw", + "serumEventQueue": "HVzqLTfcZKVC2PanNpyt8jVRJfDW8M5LgDs5NVVDa4G3", + "serumCoinVaultAccount": "F8PdvS5QFhSqgVdUFo6ivXdXC4nDEiKGc4XU97ZhCKgH", + "serumPcVaultAccount": "61zxdnLpgnFgdk9Jom5f6d6cZ6cTbwnC6QqmJag1N9jB", + "serumVaultSigner": "rCFXUwdmQvRK9jtnCip3SdDm1cLn8nB6HHgEHngzfjQ", + "official": true + }, + { + "name": "MER-USDC", + "coin": "MER", + "pc": "USDC", + "lp": "LP.RDM.MER-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "BkfGDk676QFtTiGxn7TtEpHayJZRr6LgNk9uTV2MH4bR", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "FNwXaqyYNKNwJ8Qc39VGzuGnPcNTCVKExrgUKTLCcSzU", + "ammTargetOrders": "DKgXbNmsm1uCJ2eyh6xcnTe1G6YUav8RgzaxrbkG4xxe", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "6XZ1hoJQZARtyA17mXkfnKSHWK2RvocC3UDNsY7f4Lf6", + "poolPcTokenAccount": "F4opwQUoVhVRaf3CpMuCPpWNcB9k3AXvMMsfQh52pa66", + "poolWithdrawQueue": "8mqpqWGL7W2xh8B1s6XDZJsmPuo5zRedcM5sF55hhEKo", + "poolTempLpTokenAccount": "9ex6kCZsLR4ZbMCN4TcCuFzkw8YhiC9sdsJPavsrqCws", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "G4LcexdCzzJUKZfqyVDQFzpkjhB1JoCNL8Kooxi9nJz5", + "serumBids": "DVjhW8nLFWrpRwzaEi1fgJHJ5heMKddssrqE3AsGMCHp", + "serumAsks": "CY2gjuWxUFGcgeCy3UiureS3kmjgDSRF59AQH6TENtfC", + "serumEventQueue": "8w4n3fcajhgN8TF74j42ehWvbVJnck5cewpjwhRQpyyc", + "serumCoinVaultAccount": "4ctYuY4ZvCVRvF22QDw8LzUis9yrnupoLQNXxmZy1BGm", + "serumPcVaultAccount": "DovDds7NEzFn493DJ2yKBRgqsYgDXg6z38pUGXe1AAWQ", + "serumVaultSigner": "BUDJ4F1ZknbZiwHb6xHEsH6o1LuW394DE8wKT8CoAYNF", + "official": true + }, + { + "name": "COPE-USDC", + "coin": "COPE", + "pc": "USDC", + "lp": "LP.RDM.COPE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DiWxV1SPXPNJRCt5Ao1mJRAxjw97hJVyj8qGzZwFbAFb", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "jg8ayFZLH2cEUJULUirWy7wNggN1eyRnTMt6EjbJUun", + "ammTargetOrders": "8pE4fzFzRT6aje7B3hYHXrZakeEqNF2kFmJtxkrxUK9b", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FhjBg8vpVgsiW9oCUxujqoWWSPSRvnWNXucEF1G1F39Z", + "poolPcTokenAccount": "Dv95skm7AUr33x1p2Bu5EgvE3usB1TxgZoxjBe2rpfm6", + "poolWithdrawQueue": "4An6jy1JocXGUjayXqVTx1jvs79o8LgsRk3VvmRgXxaq", + "poolTempLpTokenAccount": "57hiWKd47VHVD7y8BenqnakSdgQNBvyUrkSpf9BDP6UQ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6fc7v3PmjZG9Lk2XTot6BywGyYLkBQuzuFKd4FpCsPxk", + "serumBids": "FLjCjU5wLUsqF6FeYJaH5JtTTFSTZzTCingxN1uyr9zn", + "serumAsks": "7TcstD7AdWqjuFoRVK24zFv66v1qyMYDNDT1V5RNWKRz", + "serumEventQueue": "2dQ1Spgc7rGSuE1t3Fb9RL7zvGc7F7pH9XwJ46u3QiJr", + "serumCoinVaultAccount": "2ShBow4Bof4dkLjx8VTRjLXXvUydiBNF7bHzDaxPjpKq", + "serumPcVaultAccount": "EFdqJhawpCReiK2DcrbbUUWWc6cd8mqgZm5MSbQ3TR33", + "serumVaultSigner": "A6q5h5Wx9iqeoVsvYWA7xofUcKx6XUPPab8BTVrW91Bs", + "official": true + }, + { + "name": "ALEPH-USDC", + "coin": "ALEPH", + "pc": "USDC", + "lp": "LP.RDM.ALEPH-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "GDHXjn9wF2zxW35DBkCegWQdoTfFBC9LXt7D5ovJxQ5B", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "AtUeUK7MZayoDktjrRSJAFsyPiPwPsbAeTsunM5pSnnK", + "ammTargetOrders": "FMYSGYEL1CPYz8cpgAor5jV2HqeEQRDLMEggoz6wAiFV", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BT3QMKHrha4fhqpisnYKaPDsv42XeHU2Aovhdu5Bazru", + "poolPcTokenAccount": "9L4tXPyuwuLhmtmX4yaRTK6TB7tYFNHupeENoCdPceq", + "poolWithdrawQueue": "4nRbmEUp7DQroG71jXv6cJjrhnh91ePdPhzmBSjinwB8", + "poolTempLpTokenAccount": "9JdpGvmo6aPZYf4hkiZNUjceXgd2RtR1fJgvjuoAuhsM", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "GcoKtAmTy5QyuijXSmJKBtFdt99e6Buza18Js7j9AJ6e", + "serumBids": "HmpcmzzajDvhFSXb4pmJo5mb23zW8Cj9FEeB3hVT78jV", + "serumAsks": "8sfGm6jsFTAcb4oLuqMKr1xNEBd5CXuNPAKZEdbeezA", + "serumEventQueue": "99Cd6D9QnFfTdKpcwtoF3zAZdQAuZQi5NsPMERresj1r", + "serumCoinVaultAccount": "EBRqW7DaUGFBHRbfgRagpSf9jTSS3yp9MAi3RvabdBGz", + "serumPcVaultAccount": "9QTMfdkgPWqLriB9J7FcYvroUEqfw6zW2VCi1dAabdUt", + "serumVaultSigner": "HKt6xFufxTBBs719WQPbro9t1DfDxffurxFhTPntMgoe", + "official": true + }, + { + "name": "TULIP-USDC", + "coin": "TULIP", + "pc": "USDC", + "lp": "LP.RDM.TULIP-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "96hPvuJ3SRT82m7BAc7G1AUVPVcoj8DABAa5gT7wjgzX", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "6GtSWZfdUFtT47RPk2oSxoB6RbNkp9aM6yP77jB4XmZB", + "ammTargetOrders": "9mB928abAihkhqM6AKLMW4cZkHBXFn2TmcxEKhTqs6Yr", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "s9Xp7GV1jGvixdSfY6wPgivsTd3c4TzjW1eJGyojwV4", + "poolPcTokenAccount": "wcyW58QFNfppgm4Wi7cKhSftdVNfpLdn67YvvCNMWrt", + "poolWithdrawQueue": "59NA3khShyZk4dhDjFN564nScNdEi3UR4wrCnLN6rRgX", + "poolTempLpTokenAccount": "71oLQgsHknJVHGJDCaBVUnb6udGepK7kwkHXGy47u2i4", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW", + "serumBids": "69W6zLetZ7FgXPXgHRp4i4wNd422tXeZzDuBzdkjgoBW", + "serumAsks": "42RcphsKYsVWDhaqJRETmx74RHXtHJDjZLFeeDrEL2F9", + "serumEventQueue": "ExbLY71YpFaAGKuHjJKXSsWLA8hf1hGLoUYHNtzvbpGJ", + "serumCoinVaultAccount": "6qH3FNTSGKw34SEEj7GXbQ6kMQXHwuyGsAAeV5hLPhJc", + "serumPcVaultAccount": "6AdJbeH76BBSJ34DeQ6LLdauF6W8fZRrMKEfLt3YcMcT", + "serumVaultSigner": "5uJEd4wfVH84HyFEBf5chfJMTTPHBddXi1S7GmBE6x14", + "official": true + }, + { + "name": "WOO-USDC", + "coin": "WOO", + "pc": "USDC", + "lp": "LP.RDM.WOO-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DSkXJYPZqJ3yHQECyVyh3xiE3HBrt7ARmepwNDA9rREn", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "6WHHLn8ia2eHZnPFPDwBKaW2nt7vTRNsvrbgzS55gVwi", + "ammTargetOrders": "HuSyM774u2zhjbG8rQYCrALBHhK7yVWgUP36rNEtfTs2", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "HeMxCh5SozqLth4QPpU1cbEw29ueqFUKSYP6369GX1HV", + "poolPcTokenAccount": "J3jwx9wsRAq1sBu5tSsKpA4ixQVzLiLyRKdxkjMcRenv", + "poolWithdrawQueue": "FRSDrhT8Q28yZ3dGhVwNoAbzWawsE3qgmAAEwxTNtE6y", + "poolTempLpTokenAccount": "GP8hM7HRSjcsQfTbvHKNAWnwhqdn2Nxthb4UJiKXkfJC", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2Ux1EYeWsxywPKouRCNiALCZ1y3m563Tc4hq1kQganiq", + "serumBids": "34oLSEmDGyH4NyP84mUXCHbpW9JvG5anNd3iPaCF55zE", + "serumAsks": "Lp7h84DcAmWqhDbJ6LpvVX9m45GJQfpvMbWPTg4qtkF", + "serumEventQueue": "8Y7MaACCFcTdjcUSLsGkxqxMLDaJDPSZtT5R1kuUL1Hk", + "serumCoinVaultAccount": "54vv5QSZkmHpQzpvUmpS5ZreDwmbuXPdbGp9ybzgcsTM", + "serumPcVaultAccount": "7PL69dV89XXJg9V6wzzdu9p2ymhVwBWqp82sUzWvjnp2", + "serumVaultSigner": "CTcvsPoWroF2e2iiZWe6ztBwNQHiDyAVCs8EbQ5Annig", + "official": true + }, + { + "name": "SNY-USDC", + "coin": "SNY", + "pc": "USDC", + "lp": "LP.RDM.SNY-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5TgJXpv6H3KJhHCuP7KoDLSCmi8sM8nABizP7CmYAKm1", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "2Nr82a2ZxqsQYwBbpeLWQedy1s9kAi2U2AbeuMKjgFzw", + "ammTargetOrders": "Cts3uDVAgUSaXAHMEfLPnQWF4W5TpGdiB7WhYDAaQbSy", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FaUYbopmMVdNRe3rLnqGPBA2KB96nLHudKaEgAUcvHXn", + "poolPcTokenAccount": "9YiW8N9QdEsAdTQN8asjebwwEmDXAHRnb1E3nvz64vjg", + "poolWithdrawQueue": "HpWzYHXNeQkmW9oxFjHFozyy6sVxetqJBZdhNSTwcNid", + "poolTempLpTokenAccount": "7QAVG74PVZntmFqvnGYwYySRBjB13HSeSNABwMPtfAPR", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "DPfj2jYwPaezkCmUNm5SSYfkrkz8WFqwGLcxDDUsN3gA", + "serumBids": "CFFoYkeUJaAEh6kQyVEbAgkWfABnH7c8Lynr2hk8ycJT", + "serumAsks": "AVQEVeftGzTV6Yj2jEPFGgWHyTYs5uyT3ZFFyTaLgTAP", + "serumEventQueue": "H6UE5r8zMsaHW9fha6Xm7bsWrYbyaL8WbBjhbqbZYPQM", + "serumCoinVaultAccount": "CddTJJj2tDWUk6Kteh3KSBJJh4HvkoWMXcQjZuXaaAzP", + "serumPcVaultAccount": "BGr1LWgHKaekkmScogSU1SYSRUaJBBPFeBAEBvuwf7CE", + "serumVaultSigner": "3APrMUDUQ16iEsL4vTaovTf5fPXAEwtXmWXvD9xQVPaB", + "official": true + }, + { + "name": "BOP-RAY", + "coin": "BOP", + "pc": "RAY", + "lp": "LP.RDM.BOP-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "SJmR8rJgzzCi4sPjGnrNsqY4akQb3jn5nsxZBhyEifC", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "8pt8zWa9hsRSsiCJtVWnApXGBkmzSubjqf9sbgkbj9LS", + "ammTargetOrders": "Gg6gGVaokrVMJWtgDbamPwVG8PBN3VbgHLFghfSn3JxY", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "B345z8QcC2WvCwKjeTveLHAuEghumw2qH2xPxAbW7Awd", + "poolPcTokenAccount": "EPFPMhTRNA6f7J1NzEZ1rkWyhfexZBr9VX3MAn3C6Ce4", + "poolWithdrawQueue": "E8PcDA6vn9WHRsrMYZvKy2D2CxTB28Bp2cKAYcu16JH9", + "poolTempLpTokenAccount": "47GcR2477mHukyTte1LpDShs4RUmkcF2rejJvisRFALB", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6Fcw8aEs7oP7YeuMrM2JgAQUotYxa4WHKHWdLLXssA3R", + "serumBids": "3CNgQ6KpTQYKX9s1CSy5y16ZtnXqYfcTHikmHjEjXKJm", + "serumAsks": "7VxSfKDL7i3FmpJLnK4v7YgidNa1t7SCo84FY7YinQyA", + "serumEventQueue": "9ote3YanmgQgL6vPBUGJVZyFsp6HDJNviTw7ghxzMDLT", + "serumCoinVaultAccount": "CTv9hnW3nbANzJ2yyzmyMCoUxv5s95ndxcBbLzV39z3w", + "serumPcVaultAccount": "GXFttVfXbH7rU6GJnBVs3LyyuiPU8a6sW2tv5K5ZGEAQ", + "serumVaultSigner": "5JEwQ7hM1qFCBwJkZ2JyjkoJ99ojJXRx2bFjLcFobDvC", + "official": true + }, + { + "name": "SLRS-USDC", + "coin": "SLRS", + "pc": "USDC", + "lp": "LP.RDM.SLRS-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "7XXKU8oGDbeGrkPyK5yHKzdsrMJtB7J2TMugjbrXEhB5", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "3wNRVMaot3R2piZkzmKsAqewcZ5ABktqrJZrc4Vz3uWs", + "ammTargetOrders": "BwSmQF7nxRqzzVdfaynxM98dNbXFi94cemDDtxMfV3SB", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "6vjnbp6vhw4RxNqN3e2tfE3VnkbCx8RCLt8RBmHZvuoC", + "poolPcTokenAccount": "2anKifuiizorX69zWQddupMqawGfk3TMPGZs4t7ZZk43", + "poolWithdrawQueue": "Fh5WTfP9jCbkLPzsspCs4WCSPGqE5GYE8v7kqFXijMSA", + "poolTempLpTokenAccount": "9oiniKrJ7r1cHw97gv4XPxTFS9i61vSa7PkpRcm8qGeK", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2Gx3UfV831BAh8uQv1FKSPKS9yajfeeD8GJ4ZNb2o2YP", + "serumBids": "6kMW5vafM4mWZJdBNpH4EsVjFSuSTUokx5meYoVY8GTw", + "serumAsks": "D5asu2BVatxtgGFugwmNubdknAsLSJDZcqRHvkaS8UBd", + "serumEventQueue": "66Go3JcjNJaDHHvJyaFaV8rh8GAciLzvM8WzN7fRE3HM", + "serumCoinVaultAccount": "6B527pfkvbvbLRDgjASLGygdaQ1fFLwmmqyFCgTacsKH", + "serumPcVaultAccount": "Bsa11vdveUhSouxAXSYCE4yXToUP58N9EEeM1P8qbtp3", + "serumVaultSigner": "CjiJdQ9a7dnjTKfVPZ2fwn31NtgJA1kRU55pwDE8HHrM", + "official": true + }, + { + "name": "SAMO-RAY", + "coin": "SAMO", + "pc": "RAY", + "lp": "LP.RDM.SAMO-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "EyDgEU9BdG7m6ZK4bYERxbN4NCJ129WzPtv23dBkfsLg", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "45TD9SmkGoq4hBxBnsQQD2V7pyWK53HkEXz7uNNHpezG", + "ammTargetOrders": "Ave8ozwW9iBGL4SpK1tM1RfrQi8CsLUFj4UGdFkWRPRp", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "9RFqA8EbTTqH3ct1fTGiGgqFAg2hziUdtyGgg1w69LJP", + "poolPcTokenAccount": "ArAyYYib2X8BTcURYNXKhfoUww2DWkzk67PRPGVpFAuJ", + "poolWithdrawQueue": "ASeXk7dri8jz466wCtkCVUYheHFEznX55EMuGivL5WPL", + "poolTempLpTokenAccount": "2pu8zUYpwa9UEPvKkQvZHQUbbTdMg6N2mXi2Vv4DaEJV", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "AAfgwhNU5LMjHojes1SFmENNjihQBDKdDDT1jog4NV8w", + "serumBids": "AYEeLrFWhGDRgX9L428SqBU56iVzDSyP3A6Db4VekcjE", + "serumAsks": "CctHQdpAtxugQNFU7PA4ebb2T5K1ZkwDTvoFrsYrxifY", + "serumEventQueue": "CFtHmFydRBtw1qsoPZ4LufbdX39LKT9Aw5HzUib9JpiL", + "serumCoinVaultAccount": "BpHuL7HNTJDDGiw4ELpnYQdhTNNgZ53ennhtkQjGawGS", + "serumPcVaultAccount": "BzsbZPiwLMJHhSFNVdtGqi9MWKhYijgq34Z6YjYkQJUr", + "serumVaultSigner": "F2f14Nw7kqBeGwgFymm7sEPcZrKWWN56hvN5yx2vc6sE", + "official": true + }, + { + "name": "renBTC-USDC", + "coin": "renBTC", + "pc": "USDC", + "lp": "LP.RDM.renBTC-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "61JtCkTQKSeBU8ztEScByZiBhS6KAHSXfQduVyA4s1h7", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "AtFR9ub2dbNJJod7gPL81F7gRxVtpcR1n4GczqgasqX2", + "ammTargetOrders": "ZVmcXezubm6FXvS8Wtvah66vqZRW6NKD17tea7FcGsB", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "2cA595zqm12sRtsiNvV6AqD8WDYYiJoLwEYNQ1FZG2ep", + "poolPcTokenAccount": "Fxn92YfcVsd9diz32YtKixqmuezgLeSWqd1gypFL5qe", + "poolWithdrawQueue": "ioR3UfTLnz6t9Bzbcu7TPmw1xYQRwXCgGqcpvzRmCQx", + "poolTempLpTokenAccount": "8VEBvPwhBwu9D4e4Zei6X31ZBs5udL5epJHp935LVMv1", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "serumBids": "B1xjpD5EEVtLWnWioHc7pCJLj1WVGyKdyMV1NzY4q5pa", + "serumAsks": "6NZf4f6dxxv83Bdfiyf1R1vMFo5QP8BLB862qrVkmhuS", + "serumEventQueue": "7RbmehbSunJLpg7N6kaCX5SenR1N79xHN8jKnuvXoEHC", + "serumCoinVaultAccount": "EqnX836tGG4PYSBPgzzQecbTP47AZQRVfcy4RqQW8F3D", + "serumPcVaultAccount": "7yiA6p6BXxZwcm38St3vTzyGNEmZjw8x7Ko2nyTfvVx3", + "serumVaultSigner": "9aZNHmGZrNnB3fKmBj5B9oD7moA1nFviZqNUSkx2tctg", + "official": true + }, + { + "name": "renDOGE-USDC", + "coin": "renDOGE", + "pc": "USDC", + "lp": "LP.RDM.renDOGE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "34oD4akb2DeNcCw1smKHPsD3iqQQQWmNy3cY81nz7HP8", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "92QStSTSQHYFg2ZxJjxWETwiS3zYsKnJm9BznJ8JDvrh", + "ammTargetOrders": "EHjwgEneTm6DZWGbictuSxf7NfcirEjyYdzYaSyNkhT1", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "EgNtpEoLCiSJx8TtVLWUBpXhUWmqzBrymgweihtmnd83", + "poolPcTokenAccount": "HZHCa82ezeYegyQWtsWW3vznpoiRaa3ewtxYvm5X6tTz", + "poolWithdrawQueue": "FbWCd9uQfAD5M62Pyceff5S2WFeN9Z5rL6azysGdhais", + "poolTempLpTokenAccount": "H12qWVeehVN6CQGfwCnSH2LxcHJ9we33U6gPmiViueu5", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "5FpKCWYXgHWZ9CdDMHjwxAfqxJLdw2PRXuAmtECkzADk", + "serumBids": "EdXd7dZLfkjz4k38VoP8d8ij7UJdrnZ3EoR9RHr5ThqX", + "serumAsks": "DuGkNca9NtZByzAxQsbt5yPFNF8pyv2PqB2sjSbBGEWi", + "serumEventQueue": "AeRsgcjxerNiMK1wpPyt7TSkH9Ps1mTr9Ac1bbWvYhdp", + "serumCoinVaultAccount": "5UbUbaVLXnZq1eibQSUxdsk6Lp38bgdTjbjQPssXGgwW", + "serumPcVaultAccount": "4KMsmK7gPdKMAKmEcHqtBB5EhNnWVRd71v3a5uBwhQ2T", + "serumVaultSigner": "Gwe1pE3rV4LLviNZqrEFPAeLchwvHrftBUQsnJtEkpSa", + "official": true + }, + { + "name": "RAY-USDC", + "coin": "RAY", + "pc": "USDC", + "lp": "LP.RDM.RAY-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6UmmUiYoBjSrhakAobJw8BvkmJtDVxaeBtbt7rxWo1mg", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "J8u8nTHYtvudyqwLrXZboziN95LpaHFHpd97Jm5vtbkW", + "ammTargetOrders": "3cji8XW5uhtsA757vELVFAeJpskyHwbnTSceMFY5GjVT", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FdmKUE4UMiJYFK5ogCngHzShuVKrFXBamPWcewDr31th", + "poolPcTokenAccount": "Eqrhxd7bDUCH3MepKmdVkgwazXRzY6iHhEoBpY7yAohk", + "poolWithdrawQueue": "ERiPLHrxvjsoMuaWDWSTLdCMzRkQSo8SkLBLYEmSokyr", + "poolTempLpTokenAccount": "D1V5GMf3N26owUFcbz2qR5N4G81qPKQvS2Vc4SM73XGB", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2xiv8A5xrJ7RnGdxXB42uFEkYHJjszEhaJyKKt4WaLep", + "serumBids": "Hf84mYadE1VqSvVWAvCWc9wqLXak4RwXiPb4A91EAUn5", + "serumAsks": "DC1HsWWRCXVg3wk2NndS5LTbce3axwUwUZH1RgnV4oDN", + "serumEventQueue": "H9dZt8kvz1Fe5FyRisb77KcYTaN8LEbuVAfJSnAaEABz", + "serumCoinVaultAccount": "GGcdamvNDYFhAXr93DWyJ8QmwawUHLCyRqWL3KngtLRa", + "serumPcVaultAccount": "22jHt5WmosAykp3LPGSAKgY45p7VGh4DFWSwp21SWBVe", + "serumVaultSigner": "FmhXe9uG6zun49p222xt3nG1rBAkWvzVz7dxERQ6ouGw", + "official": true + }, + { + "name": "RAY-SRM", + "coin": "RAY", + "pc": "SRM", + "lp": "LP.RDM.RAY-SRM-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "GaqgfieVmnmY4ZsZHHA6L5RSVzCGL3sKx4UgHBaYNy8m", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "7XWbMpdyGM5Aesaedh6V653wPYpEswA864sBvodGgWDp", + "ammTargetOrders": "9u8bbHv7DnEbVRXmptz3LxrJsryY1xHqGvXLpgm9s5Ng", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "3FqQ8p72N85USJStyttaohu1EBsTsEZQ9tVqwcPWcuSz", + "poolPcTokenAccount": "384kWWf2Km56EReGvmtCKVo1BBmmt2SwiEizjhwpCmrN", + "poolWithdrawQueue": "58z15NsT3JJyfywFbdYzn2GVeDDC444WHyUrssZ5tCm7", + "poolTempLpTokenAccount": "8jqpuijsM2ne5dkwLyjQxa9oCbYEjM6bE1uBaFXmC3TE", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Cm4MmknScg7qbKqytb1mM92xgDxv3TNXos4tKbBqTDy7", + "serumBids": "G65a5G6xHpc9zV8tGhVSKJtz7AcAJ8Q3hbMqnDJQgMkz", + "serumAsks": "7bKEjcZEqVAWsiRGDnxXvTnNwhZLt2SH6cHi5hpcg5de", + "serumEventQueue": "4afBYfMNsNpLQxFFt72atZsSF4erfU28XvugpX6ugvr1", + "serumCoinVaultAccount": "5QDTh4Bpz4wruWMfayMSjUxRgDvMzvS2ifkarhYtjS1B", + "serumPcVaultAccount": "76CofnHCvo5wEKtxNWfLa2jLDz4quwwSHFMne6BWWqx", + "serumVaultSigner": "AorjCaSV1L6NGcaFZXEyUrmbSqY3GdB3YXbQnrh85v6F", + "official": true + }, + { + "name": "RAY-ETH", + "coin": "RAY", + "pc": "ETH", + "lp": "LP.RDM.RAY-ETH-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "8iQFhWyceGREsWnLM8NkG9GC8DvZunGZyMzuyUScgkMK", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "7iztHknuo7FAXVrrpAjsHBEEjRTaNH4b3hecVApQnSwN", + "ammTargetOrders": "JChSqhn6yyEWqD95t8UR5DaZZtEZ1RGGjdwgMc8S6UUt", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "G3Szi8fUqxfZjZoNx17kQbxeMTyXt2ieRvju4f3eJt9j", + "poolPcTokenAccount": "7MgaPPNa7ySdu5XV7ik29Xoav4qcDk4wznXZ2Muq9MnT", + "poolWithdrawQueue": "C9aijsE3tLbVyYaXXHi45qneDL5jfyN8befuJh8zzpou", + "poolTempLpTokenAccount": "3CDnyBsNnexdvfvo6ASde5Q4e72jzMQFHRRkSQr49vEG", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6jx6aoNFbmorwyncVP5V5ESKfuFc9oUYebob1iF6tgN4", + "serumBids": "Hdvh4ZGL9MkiQApNqfZtdmd4jM6Sz8e9akCUuxxkYhb8", + "serumAsks": "7vWmTv9Mh8XbAxcduEqed2dLtro4N7hFroqch6mMxYKM", + "serumEventQueue": "EgcugBBSwM2FxqLQx5S6zAiU9x9qRS8qMVRMDFFU4Zty", + "serumCoinVaultAccount": "EVVtYo4AeCbmn2dYS1UnhtfjpzCXCcN26G1HmuHwMo7w", + "serumPcVaultAccount": "6ZT6KwvjLnJLpFdVfiRD9ifVUo4gv4MUie7VvPTuk69v", + "serumVaultSigner": "HXbRDLcX2FyqWJY95apnsTgBoRHyp7SWYXcMYod6EBrQ", + "official": true + }, + { + "name": "RAY-SOL", + "coin": "RAY", + "pc": "SOL", + "lp": "LP.RDM.RAY-SOL-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "AVs9TA4nWDzfPJE9gGVNJMVhcQy3V9PGazuz33BfG2RA", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "6Su6Ea97dBxecd5W92KcVvv6SzCurE2BXGgFe9LNGMpE", + "ammTargetOrders": "5hATcCfvhVwAjNExvrg8rRkXmYyksHhVajWLa46iRsmE", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "Em6rHi68trYgBFyJ5261A2nhwuQWfLcirgzZZYoRcrkX", + "poolPcTokenAccount": "3mEFzHsJyu2Cpjrz6zPmTzP7uoLFj9SbbecGVzzkL1mJ", + "poolWithdrawQueue": "FSHqX232PHE4ev9Dpdzrg9h2Tn1byChnX4tuoPUyjjdV", + "poolTempLpTokenAccount": "87CCkBfthmyqwPuCDwFmyqKWJfjYqPFhm5btkNyoALYZ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "C6tp2RVZnxBPFbnAsfTjis8BN9tycESAT4SgDQgbbrsA", + "serumBids": "C1nEbACFaHMUiKAUsXVYPWZsuxunJeBkqXHPFr8QgSj9", + "serumAsks": "4DNBdnTw6wmrK4NmdSTTxs1kEz47yjqLGuoqsMeHvkMF", + "serumEventQueue": "4HGvdannxvmAhszVVig9auH6HsqVH17qoavDiNcnm9nj", + "serumCoinVaultAccount": "6U6U59zmFWrPSzm9sLX7kVkaK78Kz7XJYkrhP1DjF3uF", + "serumPcVaultAccount": "4YEx21yeUAZxUL9Fs7YU9Gm3u45GWoPFs8vcJiHga2eQ", + "serumVaultSigner": "7SdieGqwPJo5rMmSQM9JmntSEMoimM4dQn7NkGbNFcrd", + "official": true + }, + { + "name": "DXL-USDC", + "coin": "DXL", + "pc": "USDC", + "lp": "LP.RDM.DXL-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "asdEJnE7osjgnSyQkSZJ3e5YezbmXuDQPiyeyiBxoUm", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "4zuyAKT81y9mSSrjq8sN872zwgcD5ncQGyCXwRJDn6tC", + "ammTargetOrders": "H2GMj87upPeBQT3ywzqudJodwyTFpPmwuwtiZ7DQB8Md", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FHAqAqqdyZFaxUTCg19hH9pRfKKChwNekFrY428NVPtT", + "poolPcTokenAccount": "7jzwUCSq1R1QX72PKRDjZ4xgUm6Q6iiLW9BY8tnj8wkc", + "poolWithdrawQueue": "3WBnh4HbddG6sMvv6s1GALVLPq6xfwVat3WqufZKKFXa", + "poolTempLpTokenAccount": "9DRSmvcrXC7AtNrhf9tgfBuwT4q5hXyWaAybe5yfRU7q", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "DYfigimKWc5VhavR4moPBibx9sMcWYVSjVdWvPztBPTa", + "serumBids": "2Z6Do29oGtze6dnVMXAVw8mkRxFpLGc8uS2RjfrWoCyy", + "serumAsks": "FosLnuNKUKqfqYviAPdp1doC3dKpXQXvAeRGM5xAoUCJ", + "serumEventQueue": "EW5QgqGUZ7dSmXLXiuWB8AAsjSjpb8kaaoxAUqK1DWyg", + "serumCoinVaultAccount": "9ZaKDVrjCaPRZTqnuteGc8iBmJhdaGVf8JV2HBT67wbX", + "serumPcVaultAccount": "5Y65XyuJemmRU7G1AQQTvWKSge8WDVYhb2knd7htJHoh", + "serumVaultSigner": "y6FHXgMwWvvpoiox6Ut6mUAUHgbJMXNJnXQm7MQkEdE", + "official": true + }, + { + "name": "LIKE-USDC", + "coin": "LIKE", + "pc": "USDC", + "lp": "LP.RDM.LIKE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "GmaDNMWsTYWjaXVBjJTHNmCWAKU6cn5hhtWWYEZt4odo", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "Crn5beRFeyj4Xw13E2wdJ9YkkLLEZzKYmtTV4LFDx3MN", + "ammTargetOrders": "7XjS6MrvBRi9JeFWBMAYPaKhKgR3b7xnVdYDBkFb4CXR", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8LoHX6f6bMdQVs4mThoH2KwX2dQDSkqVFADi4ZjDQv9T", + "poolPcTokenAccount": "2Fwm8M8vuPXEXxvKz98VdawDxsK9W8uRuJyJhvtRdhid", + "poolWithdrawQueue": "CW9zJ2JbBekkdd5SdvPapPcbziR8d1UHBzW7nNn1W3ga", + "poolTempLpTokenAccount": "FVHsnC1nhwMcrAzFwcK4dgUtDdYFM1VrTJ8Rp8Mb1LkY", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3WptgZZu34aiDrLMUiPntTYZGNZ72yT1yxHYxSdbTArX", + "serumBids": "GzHpnQSfS7KdqLKgiEEP7pkYnwEBz9zaE7De2CjmCrNV", + "serumAsks": "FpEBAT9qP1so4ASUTiEWxyXH2SJvgoBYUiZ1AbPimcS7", + "serumEventQueue": "CUMDMV9KtE22RUZECUNHxiq7FmUiRusyKa1rHUJfRptq", + "serumCoinVaultAccount": "Dd9F1fugQj2xtduyNvFS5TtxP9vKnuxVMcrPsHFnLyqp", + "serumPcVaultAccount": "BnXXu8kLUXrwg3MpcVRVPLZw9bpX2mLd95qtCMnSUtu7", + "serumVaultSigner": "MKCHeoqNGWU8TJBkdF1M76nMUteJCwuBRUJfCtR3iV7", + "official": true + }, + { + "name": "mSOL-USDC", + "coin": "mSOL", + "pc": "USDC", + "lp": "LP.RDM.mSOL-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "ZfvDXXUhZDzDVsapffUyXHj9ByCoPjP4thL6YXcZ9ix", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "4zoatXFjMSirW2niUNhekxqeEZujjC1oioKCEJQMLeWF", + "ammTargetOrders": "Kq9Vgb8ntBzZy5doEER2p4Zpt8SqW2GqJgY5BgWRjDn", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8JUjWjAyXTMB4ZXcV7nk3p6Gg1fWAAoSck7xekuyADKL", + "poolPcTokenAccount": "DaXyxj42ZDrp3mjrL9pYjPNyBp5P8A2f37am4Kd4EyrK", + "poolWithdrawQueue": "CfjpUvQAoU4hadb9nReTCAqBFFP7MpJyBW97ezbiWgsQ", + "poolTempLpTokenAccount": "3EdqPYv3hLJFXC3U9LH7yA7HX6Z7gRxT7vGQQJrxScDH", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6oGsL2puUgySccKzn9XA9afqF217LfxP5ocq4B3LWsjy", + "serumBids": "8qyWhEcpuvEsdCmY1kvEnkTfgGeWHmi73Mta5jgWDTuT", + "serumAsks": "PPnJy6No31U45SVSjWTr45R8Q73X6bNHfxdFqr2vMq3", + "serumEventQueue": "BC8Tdzz7rwvuYkJWKnPnyguva27PQP5DTxosHVQrEzg9", + "serumCoinVaultAccount": "2y3BtF5oRBpLwdoaGjLkfmT3FY3YbZCKPbA9zvvx8Pz7", + "serumPcVaultAccount": "6w5hF2hceQRZbaxjPJutiWSPAFWDkp3YbY2Aq3RpCSKe", + "serumVaultSigner": "9dEVMESKXcMQNndoPc5ji9iTeDJ9GfToboy8prkZeT96", + "official": true + }, + { + "name": "mSOL-SOL", + "coin": "mSOL", + "pc": "SOL", + "lp": "LP.RDM.mSOL-SOL-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "EGyhb2uLAsRUbRx9dNFBjMVYnFaASWMvD6RE1aEf2LxL", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "6c1u1cNEELKPmuH352WPNNEPdfTyVPHsei39DUPemC42", + "ammTargetOrders": "CLuMpSesLPqdxewQTxfiLdifQfDfRsxkFhPgiChmdGfk", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "85SxT7AdDQvJg6pZLoDf7vPiuXLj5UYZLVVNWD1NjnFK", + "poolPcTokenAccount": "BtGUR6y7uwJ6UGXNMcY3gCLm7dM3WaBdmgtKVgGnE1TJ", + "poolWithdrawQueue": "7vvoHxA6di9EvzJKL6bmojbZnH3YaRXu2LitufrQhM21", + "poolTempLpTokenAccount": "ACn8TZ27fQ85kgdPKUfkETB4dS5JPFoq53z7uCgtHDai", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "5cLrMai1DsLRYc1Nio9qMTicsWtvzjzZfJPXyAoF4t1Z", + "serumBids": "JAABQk3n6S8W85LC6RpqTvGgP9wJFb8kfqir6kUhBXkQ", + "serumAsks": "psFs3Dm7quZZn3BhvrT1LdWCVtbMqxXanU7ZYdHULj6", + "serumEventQueue": "4bmSJJCrx3dehFQ8kXAE1c4L9kfP8DyHow4tFw6aRJZe", + "serumCoinVaultAccount": "2qmHPJn3URkrboLiJkQ5tBB4bmYWdb6MyhQzZ6ms7wf9", + "serumPcVaultAccount": "A6eEM36Vpyti2PoHK8h8Dqk5zu7YTaSRTQb7XXL8tcrV", + "serumVaultSigner": "EHMK3DdPiPBd9aBjeRU4aZjD7z568rmwHCSAAxRooPq6", + "official": true + }, + { + "name": "MER-PAI", + "coin": "MER", + "pc": "PAI", + "lp": "LP.RDM.MER-PAI-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6GUF8Qb5FWmifzYpRdKomFNbSQAsLShhT45GbTGg34VJ", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "Gh3w9pfjwbZX2FVrMy6PzUQG5rhihKduGCB7UaPGUTZw", + "ammTargetOrders": "37k5Xe8Sej1TrjrGsR2HyRR1EjYECV1HcS3Xh6Jnxggi", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "ApnMY7ahxTMssU1dzxYEfMcag1aSa5s4Axje3nqnnrXH", + "poolPcTokenAccount": "BuQxGhmS82ZhczEGbUyi9R7TjxczXTMRoD4nQ4GvqxCf", + "poolWithdrawQueue": "CrvN8Zi4c6BHVFc3mAB8CZSZRftY73WtpBH2Zade9MKZ", + "poolTempLpTokenAccount": "5W9V96yUqk95zUYawoCfEittj4VT4Nbv8NVjevJ4kN78", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "FtxAV7xEo6DLtTszffjZrqXknAE4wpTSfN6fBHW4iZpE", + "serumBids": "Hi6bo1sodi7X2GrpeVpk5mKKG42Ga8n4Gi3Fxr2WK6rg", + "serumAsks": "75a4ASjShTXZPdxNzm4RoSEVydLBFfDa1V81Wcf7Xw59", + "serumEventQueue": "7WDqc3MAApvgDskQBDKVVPmya3Src228sAk8Lag8ovph", + "serumCoinVaultAccount": "2Duueu4HUnv6e4qUqdM4DKECM9X3XggBsXp5eLYuSLXe", + "serumPcVaultAccount": "3GEqHH6VAnyqrgG9jRB4Qy9PMTYJmSBvg7u3LtBWHEWD", + "serumVaultSigner": "7cBPvLMQvf1X5rzLMNKrx7TY5M186rTR49yJNHNSp81s", + "official": true + }, + { + "name": "PORT-USDC", + "coin": "PORT", + "pc": "USDC", + "lp": "LP.RDM.PORT-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6nJes56KF999Q8VtQTrgWEHJGAfGMuJktGb8x2uWff2u", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "ENfqr7WFKJy9VRwfDkgL4HvMM6GU7pHyowzZsZwx8P39", + "ammTargetOrders": "9wjp6tFY1XNH6KhdCHeDgeUsNLVjTwxA3iC9k5aun2NW", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "GGurDvQctUDgcegSYZetkNGytcWEfLes6yXzYruhLuLP", + "poolPcTokenAccount": "3FmHEQRHaKMS4vA41eYTVmfxX9ErxdAScS2tvgWvNHSz", + "poolWithdrawQueue": "ETie1oDMcoTD8jzrseAcvTqZYyyoWxR92LH15nA6Lfub", + "poolTempLpTokenAccount": "GEJfHTwURq89KcM1RgvFZRweb4f7H8NAsmyMg2kTPBEs", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "8x8jf7ikJwgP9UthadtiGFgfFuyyyYPHL3obJAuxFWko", + "serumBids": "9Y24T3co7Cc7cGbG2mFc9n3LQonAWgtayqfLz3p28JPa", + "serumAsks": "8uQcJBapCnxy3tNEB8tfmssUvqYWvuCsSHYtdNFbFFjm", + "serumEventQueue": "8ptDxtRLWXAKYQYRoRXpKmrJje31p8dsDsxeZHEksqtV", + "serumCoinVaultAccount": "8rNKJFsd9yuGx7xTTm9sb23JLJuWJ29zTSTznGFpUBZB", + "serumPcVaultAccount": "5Vs1UWLxZHHRW6yRYEEK3vpzE5HbQ8BFm27PnAaDjqgb", + "serumVaultSigner": "63ZaXnSj7SxWLFEcjmK79fyGokJxhR3UEXomN7q7Po25", + "official": true + }, + { + "name": "MNGO-USDC", + "coin": "MNGO", + "pc": "USDC", + "lp": "LP.RDM.MNGO-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "34tFULRrRwh4bMcBLPtJaNqqe5pVgGZACi5sR8Xz95KC", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "58G7RrYRntVvVj9rVgDwGhAJoWhMWHNyDCoMydYUwSR6", + "ammTargetOrders": "2qBcjDqDywhB7Kgb1VYq8K5svJh37BB8oC5kBE4VqA7q", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "91fMidHL8Yr8KRcu4Zu2RPRRg1FbXxZ7DV43rAyKRLjn", + "poolPcTokenAccount": "93oFfbcayY2WkcR6d9AyqPcRC121dXmWarFJkwPErRRE", + "poolWithdrawQueue": "FhnSdMoRPj75bLs6yzaDPFfiuucUZhVDiyM78WEhaKJo", + "poolTempLpTokenAccount": "FZAwAb6UxNiwDTbQZ3bPKYA4PkbYpurh8YpAH8G424Lv", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3d4rzwpy9iGdCZvgxcu7B1YocYffVLsQXPXkBZKt2zLc", + "serumBids": "3nAdH9wTEhPoW4e2s8K2cXfn4jZH8FBCkUqtzWpsZaGb", + "serumAsks": "HxbWm3iabHEFeHG9LVGYycTwn7aJVYYHbpQyhZhAYnfn", + "serumEventQueue": "H1VVmwbM96BiBJq46zubSBm6VBhfM2FUhLVUqKGh1ee9", + "serumCoinVaultAccount": "7Ex7id4G37HynuiCAv5hTYM4BnPB9y4NU85QcaNWZy3G", + "serumPcVaultAccount": "9UB1NhGeDuV1apHdtK5LeAEjP7kZFH8vVYGdh2yGFRi8", + "serumVaultSigner": "BFkxdUwW17eANhfs1xNmBqEcegb4EStQxVb5VaMS2dq6", + "official": true + }, + { + "name": "ATLAS-USDC", + "coin": "ATLAS", + "pc": "USDC", + "lp": "LP.RDM.ATLAS-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "2bnZ1edbvK3CK3LTNZ5jH9anvXYCmzPR4W2HQ6Ngsv5K", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "EzYB1U93e8E1KGJdUzmnwgNBFMP9E1XAuyosmiPGLAvD", + "ammTargetOrders": "DVxJDo3E9zfGgvSkC2DYS5fsv5AyXA7gXpcs1fHFrP3y", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FpFV46UVvRtcrRvYtKYgJpJtP1tZkvssjhrLUfoj8Cvo", + "poolPcTokenAccount": "GzwX68f1ZF4dKnAJ58RdET8sPvvnYktbDEHmjoGw7Umk", + "poolWithdrawQueue": "26SuCukyzbYo5kzeufaSoMjRPStAwqfVzTXb4QGynTit", + "poolTempLpTokenAccount": "HcoA8ucDBjEUVMjvURaS9CZgdEUbq8jRieGabq48mCL8", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Di66GTLsV64JgCCYGVcY21RZ173BHkjJVgPyezNN7P1K", + "serumBids": "2UabAccF1AFPcNqv9D46JgyGnErnaYAJuCwyaT5dCkHc", + "serumAsks": "9umNLTbks7S51TEB8XF4jeCxwyq3qmdHrFDMFB8cT1gv", + "serumEventQueue": "EYU32k5waRUxF521k2KFSuhEj11HQvg4MbQ9tFXuixLi", + "serumCoinVaultAccount": "22a8dDQwHmmnW4M4WuSXHC9NdQAufZ2V8at3EtPzBqFj", + "serumPcVaultAccount": "5Wu76Qx7EoiR79zVVV49cZDYZ5csZaKFiHKYtCjF9FNU", + "serumVaultSigner": "FiyZW6n5VE64Yubn2PUFAxbmB2FZXhYce74LzJUhqSZg", + "official": true + }, + { + "name": "POLIS-USDC", + "coin": "POLIS", + "pc": "USDC", + "lp": "LP.RDM.POLIS-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "9xyCzsHi1wUWva7t5Z8eAvZDRmUCVhRrbaFfm3VbU4Mf", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "12A4SGay36i2cSwA4JSdvg7rWSmCz8JzhsoDqMM8Yns7", + "ammTargetOrders": "6bszsB6zxw2YowrEm26XYhh57HKQEVMRx5YMvPSSVQNh", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "7HgvC7GdmUt7kMivdLMovLStW25avFsW9GDXgNr525Uy", + "poolPcTokenAccount": "9FknRLGpWBqYg7fXQaBDyWWdu1v2RwUM6zRV6CiPjWBD", + "poolWithdrawQueue": "6uN62R1i31QVoy9cmQAeDrfLccMZDjQ2gmwv2D4iBTJT", + "poolTempLpTokenAccount": "FJV66MrqZW8VYGmTuAupstwYtqfF6ULLPP9voYtnc8DS", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HxFLKUAmAMLz1jtT3hbvCMELwH5H9tpM2QugP8sKyfhW", + "serumBids": "Bc5wovapX1tRjZfyZVpsGH73Gq5LGN4ANsj8kaEhfY7c", + "serumAsks": "4EHg2ANFFEKLFkpLxgiyinJ1UDWsG2p8rVoAjFfjMDKc", + "serumEventQueue": "qeQC4u5vpo5QMC17V5UMkQfK67vu3DHtBYVT1hFSGCK", + "serumCoinVaultAccount": "5XQ7xYE3ujVA21HGbvFGVG4pLgqVHSfR9anz2EfmZ3nA", + "serumPcVaultAccount": "ArUDWPwzGQFfa7t7nSdkp1Dj6tYA3icXEq8K7goz9WoG", + "serumVaultSigner": "FHX9fPAUVA1MxPme28f4eeVH81QVRHDWofa2V6FUJaiR", + "official": true + }, + { + "name": "ATLAS-RAY", + "coin": "ATLAS", + "pc": "RAY", + "lp": "LP.RDM.ATLAS-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "F73euqPynBwrgcZn3fNSEneSnYasDQohPM5aZazW9hp2", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "2CbuxnkjsBvaQoAubc5MAmbeZSMn36z8sZnfMvZWH1vb", + "ammTargetOrders": "6GZrucFa9hAQW7yHiPt3oZj9GkL6oBipngyY1Hw3zMx", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "33UaaUmmySzxK7q3yhmQiXMrW1tQrwqojyD6ZEFgM6FZ", + "poolPcTokenAccount": "9SYRTwYE5UV2cxEuRz8iiJcV8gMbMnJUYFC8zgDAsUwB", + "poolWithdrawQueue": "6bznLHPLPA3axnRfjh3sFzkxeMUQDLWhDuaHzjGL1EE6", + "poolTempLpTokenAccount": "FnmoaJqFYHotLTG2Ur84jSUmVUACVWrBvBvRHdPzhqvb", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "Bn7n597jMxU4KjBPUo3QwJhbqr5145cHy31p6EPwPHwL", + "serumBids": "9zAgdk4Na8fBKLiTWzsqZwgYQETuHBDjPe2GYqHy17L", + "serumAsks": "Fv6MY3w7PP7A54cuPQHevQNuwekGy8yksXWioBsyVd42", + "serumEventQueue": "75iVJf9QKovBdsvgxcCFfwn2N4QyxEXyKxQdBvZTdzjr", + "serumCoinVaultAccount": "9tBagdm862GCoxZNFvXv7HFjLUFmypxPYxfiT3j9S3h3", + "serumPcVaultAccount": "4oc1kGhKByyxRnh3oXupjTn5P6JwWPnoxwvLxjZzi2vE", + "serumVaultSigner": "EK2TjcyoXzUweNJnJupQf6sZK8756mvBJeGBvi6y18Cq", + "official": true + }, + { + "name": "POLIS-RAY", + "coin": "POLIS", + "pc": "RAY", + "lp": "LP.RDM.POLIS-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5tho4By9RsqTF1rbm9Akiepik3kZBT7ffUzGg8bL1mD", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "UBa61sKev8gr19nqVyN3BZbW2jG7eAGjbjeZvpU4wu8", + "ammTargetOrders": "FgMtC8pDrSQJUovmnrDiRWgLGVrVSq9kui98re6uRz5i", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "Ah9T12tzwnTXWrWVWzLmCrwCEmVHS7HMdWKG4qLUDzJP", + "poolPcTokenAccount": "J7kjQkrpafcLjL7cCpmMamxLAFnCkGApLTC2QrbHe2NQ", + "poolWithdrawQueue": "EgZgi8skDug7YecbFuCFxXx3SPFPhbGSVrGiNzLHErkj", + "poolTempLpTokenAccount": "TYw7qQDt6sqpwUFSRfNBaLHEA1SUxbEWtmZxtZQhojk", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3UP5PuGN6db7NhWf4Q76FLnR4AguVFN14GvgDbDj1u7h", + "serumBids": "4tAuffNhWeF2MDWjMDgrRoR8X8Jg3BLvUAaerXzLsFpG", + "serumAsks": "9W133475h1LZ2ZzY7aJtbJajLDSCn5hNnKcsu6gXgE2G", + "serumEventQueue": "5DX4tJ8jZt91XzM7JUUPhu6CL4o6UDGnfjLJZtkmEfVT", + "serumCoinVaultAccount": "pLD9GMk4LACBXDJAWJSgbT1batbHgunBVyy8BaVBazG", + "serumPcVaultAccount": "Ah3JVyTAGLbH63XPWDDnJUwV1xYwHhFX2J81CDHomkLk", + "serumVaultSigner": "5RqVkFy8hUbYDR81ucZhF6rAwpgYJngLJLSynMTeC4vM", + "official": true + }, + { + "name": "ALEPH-RAY", + "coin": "ALEPH", + "pc": "RAY", + "lp": "LP.RDM.ALEPH-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "8Fr3wxZXLtiSozqms5nF4XXGHNSNqcMC6K6MvRqEfk4a", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "GrTQQTca8U7QpNiThwHfiQuFVihvSkkNPchhkKr7PMy", + "ammTargetOrders": "7WCvFBFN3fjU5hKJjPF2rHLAyXfzGCEqJ8qbqKLBaGTv", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "4WzdFwdKaXLQdFn9i84asMxdr6Fmhmh3qd6uC2xjBXwd", + "poolPcTokenAccount": "yFWn8ji7zq24UDg1mMqP1mA3vWyUdkjARQUPZCS5iCf", + "poolWithdrawQueue": "J9QSrJtasvLydL5dgbfv55eqBoADM9z91kVi5hpxk36Y", + "poolTempLpTokenAccount": "fGohyeWwAGqGdjQsHrE4c6GoTC1xHmyiAxJsgz2uZZ9", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "4qATPNrEGqE4yFJhXXWtppzJj5evmUaZ5LJspjL6TRoU", + "serumBids": "84wPUTporXrCAceD753fXdiysry7WNkpiJH5HwhV5PwC", + "serumAsks": "BDcmopZQkPoxkk1BLAeh4zR3oWeDFUXTkrD2fJgh8xYu", + "serumEventQueue": "4PiUj2EFVq8YNjMd8zWCUe7dV2prLEJCucapjzTeiShv", + "serumCoinVaultAccount": "7dCAQbfwtDFtLwNgoB2WahCubPhFjZRGjfVYJajcF6qJ", + "serumPcVaultAccount": "2DsQ33R4GqqBkmxPdFyBy7WYAzyWYm6BNPqKtENAKXuY", + "serumVaultSigner": "DDyP6zj3GTK3hTRyjPuaEL9yyqgfdstRMMKCkn939pkp", + "official": true + }, + { + "name": "TULIP-RAY", + "coin": "TULIP", + "pc": "RAY", + "lp": "LP.RDM.TULIP-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "Dm1Q15216uRARmQTbo6VfnyEGVzRvLTm4TfCWWX4MF3F", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "2x6JvLToztTWoiYAXFvLw9R8Ump3aDcuiRPBY9ZuzoRL", + "ammTargetOrders": "GZzyFjERxn9CqS5jXq1o2J3zmSNmhPMzn7U4aMJ82wL", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "96VnEN3nhvyb6hLSyP6BGsvSFdTJycQtTr574Kavrje8", + "poolPcTokenAccount": "FhnZ1j8C8d7aXecxQXEGpRycoH6uJ1Fpncj4Sm33J2iS", + "poolWithdrawQueue": "ELX79G4JU2YQrykozCvaRnhU2dBFmxNpSrJD3BoRoxfE", + "poolTempLpTokenAccount": "BagZFcJSYZzQn3iS37sPFDPiaKsfUwo8YD98XsEMKrsd", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "GXde1EjpxVV5fzhHJcZqdLmsA3zmaChGFstZMjWsgKW7", + "serumBids": "2ty8Nq6brwkp74n6EtJkD8msgBnc3fRiavNGrE5d7yE3", + "serumAsks": "GzztpwBixtLW1vqZwtNZH7FvyGJcRmLvCZTffCW2ZoS2", + "serumEventQueue": "4EgxxtAL5zsc1GCR243EU2vpbYpSvsawyfznVuRYbGHm", + "serumCoinVaultAccount": "JD1MfYD2SXiY1j6p3H6DifpG6RAe8cAtmNNLdRAdB1aT", + "serumPcVaultAccount": "UtkM2zbygo9tig18DQJDdRjHSKQiMf5uSuDTR2kf7ov", + "serumVaultSigner": "3yRCDVhumspJgYJnNhyJaXTjRn5jiMqdbQ13rTyHHQgQ", + "official": true + }, + { + "name": "SLRS-RAY", + "coin": "SLRS", + "pc": "RAY", + "lp": "LP.RDM.SLRS-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "J3CoGcJqHquUdSgS7qAwdGbp3so4EpLX8eVDdGuauvi", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "FhtXN2pPZ8JMxGcLKSfRJtGsorSCXBKJyw3n7SsEc1aR", + "ammTargetOrders": "2hdnnbsAu7pCf6nX5fDKPAdThLZmmWFQ7Kcq2cdShPGW", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8QWf745UQeyMyM1qAAsCeb73jTvQvpm2diVgjNvHgbVX", + "poolPcTokenAccount": "5TsxBaazJ7Zdx4x4Zd2zC7TY98EVSwGY7hnioS2omkx1", + "poolWithdrawQueue": "6w9z1TGNkMU2qBHj5wzfaoqCLn7cPLKvPa23qeffsn9U", + "poolTempLpTokenAccount": "39VEjufVUfdASteaQstBT25zQuLUha8ZrqYQfcDdJ47A", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "BkJVRQZ7PjfwevMKsyjjpGZ4j6sBu9j5QTUmKuTLZNrq", + "serumBids": "8KouZyh14hmqurZZd1YRpwZ9pMVkWWHPnKTsETSYUuQQ", + "serumAsks": "NBpY6i9KbWx2V5sS3iP54KYYaHg8aVB6WB43ibVFUPo", + "serumEventQueue": "BMZfHb6CkiYwdgfVkAiiy4SWf6PHuRPFZyZWQNw1uDZx", + "serumCoinVaultAccount": "F71huJuAGZ8Q9xVxQueLQ8vDQD6Nq8MkJJsyM2S937sy", + "serumPcVaultAccount": "AbmAd3LgTowBANXnCNPLctxL7PReirJv5VcizvQ3mfah", + "serumVaultSigner": "E91Pu1z4q4Nr5mGSVcwyDzzbQC3LdDBzmFyLoXfXfg17", + "official": true + }, + { + "name": "MER-RAY", + "coin": "MER", + "pc": "RAY", + "lp": "LP.RDM.MER-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "BKLCqnuk4qc5iHWuJuewMxuvsNZXuTBSUyRT5ftnRb6H", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "qDqpetCPbbV2n8bgcy4urhDcKYkUNVoEn7xaCQSDzKv", + "ammTargetOrders": "7KU9VPAZ8BMXA29gadnpssgtcoo4Tm1LYnc6Sn5HefcL", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "rmnAGzEwFnim88JLhqj66B86QLJL6cgm3tPDfGiKqZf", + "poolPcTokenAccount": "4Lm4c4NqNyobLGULtHRtgoG4hbX7ytuGQFFcdip4jvBb", + "poolWithdrawQueue": "9qwtjaEnTCHFf6GuTNxPf85hFzJVNJAAXJnWNFi4DmkX", + "poolTempLpTokenAccount": "H9uyyChWbaXCmNmQu3g4fqKF5xsa7YVZiMvGcsVrCcNn", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "75yk6hSTuX6n6PoPRxEbXapJbbXj4ynw3gKgub7vRdUf", + "serumBids": "56zkA91Mad1HBJpiq8baMi9XhvvnTRNyd6m8hzeu5arh", + "serumAsks": "BgovKK4YP6ZgLUHsnXeUym1BH5BSjUxDuinTk6shPuzd", + "serumEventQueue": "5NVyybcVeC8wqjgBj3ZxaX3RauWa2iqvdXkUYPJnistu", + "serumCoinVaultAccount": "EaFu94rusrGHjJWhuuUbKWW2AJizDGbpWJXJa4cxmLCP", + "serumPcVaultAccount": "ApZdrWpBu2uLkYAeVLneWnDhVrbR6TjhjbBR78kpg5r2", + "serumVaultSigner": "FCf82FB2TFAfH4YEDkBJtEeSkTK1EQFc27d1iSnvXMjk", + "official": true + }, + { + "name": "MEDIA-RAY", + "coin": "MEDIA", + "pc": "RAY", + "lp": "LP.RDM.MEDIA-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5ZPBHzMr19iQjBaDgFDYGAx2bxaQ3TzWmSS7zAGrHtQJ", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "HhAqmp3r8gaKo9P1ybaEXpwjq5MfmkfD6sRVD4EYs1tU", + "ammTargetOrders": "3Dwo6BD7H2GQMyxoh5nXdmAK7dWfqPMUj3PcrJVqUuEp", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "FGskpuYNgqgHU4kHSibgqDkYCCZhxAtpQxZNqFaKfBDK", + "poolPcTokenAccount": "7AiT1Re8Z8m8eLdy5HWRqWvx6pBZMytdWQ3wL8zCrSNp", + "poolWithdrawQueue": "7reJT6i8tnFjf5vbvmRLw6ikZZxs6ZJ8bsEx4iCU22ot", + "poolTempLpTokenAccount": "6LmFCURzNyEsNpF4fgMDyGPX1xoNAnm2oVcrYJJQGv9Y", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "2STXADodK1iZhGh54g3QNrq2Ap4TMwrAzV3Ja14UXut9", + "serumBids": "FKgbQ8Sdv9d44SMrtLMy58EmP3V59fvjse2UUQ8mNCxd", + "serumAsks": "CNcZwNeBA1QVL1Kzq3n166RSvUocLrKNs4nzTGXgVPuE", + "serumEventQueue": "FwHwAcBc54zm8XjtNxvaZG1t84shzYs68z3BAsKZdoE", + "serumCoinVaultAccount": "Ea7ECm7a3ECLnvJJMpZS9QrWbYnb8LkqVvWCXtmFVzWX", + "serumPcVaultAccount": "54a18egZToocQ2yeCstCrtYZLAj3z82qfLG4Ed1quThb", + "serumVaultSigner": "F1XJJ2fkPiiYg1hWnDD6phMfDd8Sr8XwM6GKFeAZpTmr", + "official": true + }, + { + "name": "SNY-RAY", + "coin": "SNY", + "pc": "RAY", + "lp": "LP.RDM.SNY-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "Am9FpX73ctZ3HzohcRdyCCv84iT7nugevqLjY5yTSUQP", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "BFxUhqhrUWqMMazhef1dwDGXDo1LkQYV2YAgMfY81Evo", + "ammTargetOrders": "AKp1o6Nxe224Z8z4tFzyFKdCRoJDFpCen1xHyGXfyxKu", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BjJMnG8c4zMHHZrvxP6ydKYGPkvXL5fF9gC38rtAu2Sx", + "poolPcTokenAccount": "7dwpWj95qzPoBFCL7qzgoj9zhjmNNoDyncbyJEYiRfv7", + "poolWithdrawQueue": "6g5sTJtMw1r9vx4RP5YkN3ZJpSssh7eH8QdVK986xLS2", + "poolTempLpTokenAccount": "9tHcrwFdxNNzosaTkqrejHNXkr2HasKSwczimjBh2F8Z", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HFAsygpAgFq3f9YQ932ptoEsEdBP2ELJSAK5eYAJrg4K", + "serumBids": "6A6njiM3ByNbopETpEfbqsQci3NZecTzheg2YACVFXjc", + "serumAsks": "8YvHQkUCB7HxCAu3muytUTbEXuDGmroVcnwbkXydzyEH", + "serumEventQueue": "8syFMq2kMQV9beCJ9Y5T9TARgUii6aND5MDgDEAAGF73", + "serumCoinVaultAccount": "F1LcTLXQhFf9ymAHnxFNovSdZttZiVjRBoqQxyPAEipj", + "serumPcVaultAccount": "64UEnruJCyjKUz8vdgZh3FwWwd53oSMY9Knd5dt5oVuK", + "serumVaultSigner": "3enyrrweGCtkVDvaiAkSo2d2tF7B899tWHGSDfEGKtNs", + "official": true + }, + { + "name": "LIKE-RAY", + "coin": "LIKE", + "pc": "RAY", + "lp": "LP.RDM.LIKE-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "DGSnfcE1kw4uDC6jgrsZ3s5CMfsWKN7JNjDNasHdvKfq", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "4hgUQQevH5BauWE1CGGsfsDZbnCUrjd6YsRHB2gQjRUb", + "ammTargetOrders": "AD3TRMfAuTJXTdxsvJ3E9p6YK3GyNAGDSk4DX26mtmFC", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "HXmwydLeUB7JaLWhoPFkDLazQJwUuWCBi3M28p7WfwL7", + "poolPcTokenAccount": "BDbjkVrTezpirdkk24MfXprJrAi3WXazr4L6DHT5buXi", + "poolWithdrawQueue": "FFKXu8Q3kaQjnuZsicVyUQNNBwRRLFAT86WqDN8Yz2UV", + "poolTempLpTokenAccount": "FJguakQVbJmhjVGrzakNGQo5WCm5HG1Uk23X6x75WtZz", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "E4ohEJNB86RkKoveYtQZuDX1GzbxE2xrbdjJ7EddCc5T", + "serumBids": "7vhuHsR1VxAGN4DD5EywRnW9nb7cX3VHcyrAKL1AAJ4v", + "serumAsks": "KXrJ3YVBvSGpCRETy3M2ronxM55PU8xBmQ2wCWVzhpY", + "serumEventQueue": "EMTQJ2v3dn4ndnV7UwZTiGTmSNPsVSCgdSN6w5QvCv2M", + "serumCoinVaultAccount": "EENxPU4YaXqTLBgd5jHBHigpH74MZNq9WxcLaKVsVSvq", + "serumPcVaultAccount": "5c9DtqqCvj5du96cgUCSt2GZp8sreE7uV1Defmb615na", + "serumVaultSigner": "GWnLv7RwJhceF3YNqawMyEJqg6WgZc6XtT7Bi6prjkyC", + "official": true + }, + { + "name": "COPE-RAY", + "coin": "COPE", + "pc": "RAY", + "lp": "LP.RDM.COPE-RAY-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "8hvVAhShYLPThcxrxwMNAWmgRCSjtxygj11EGHp2WHz8", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "HMwERnf6t8JTR8qnrQDDGxGL2PeBgpzzmQBJQgvXL3NS", + "ammTargetOrders": "9y7m8jaURWcehBkMt6ebgQ92mqaJzZfxW51wBv6dtGR8", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "CVCDGPwGmxHyt1HwfJgCYbskEXPTvKxZfR6nkZexFQi5", + "poolPcTokenAccount": "DyHnyEW4MQ1J28JrqvY7AdMq6Djr3TjvczgsokQxj6YB", + "poolWithdrawQueue": "PPCMh17bDnu6sZKhipEfXf4ASK4sTpHkWrEX3SBNKRV", + "poolTempLpTokenAccount": "HReYRwCxu4qEjzkyjsdf67DyEUsWn1Tqf7eisvM3J7ro", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "6y9WTFJRYoqKXQQZftFxzLdnBYStvqrDmLwTFAUarudt", + "serumBids": "Afj14X2pCvbgVzWFAXRC4XBS3B71hZFXiTpVaFEohdCe", + "serumAsks": "GmZTkEYABdUej3QXXZSf8aeZ1UxLB2WaQ4dhVihKZPB8", + "serumEventQueue": "3PveQeVGVfaa4LpTjhuRtm1Xe3Y9q7iW7YQeGJZYKtc4", + "serumCoinVaultAccount": "9mQ22KCPTyFkJ4dp16Fhpd1pFrVmonS6SMa9L8nM6nLn", + "serumPcVaultAccount": "BKGiYU9So4XMYYuYiV2d68kcR2wwLogKbi3rmg8ci4xt", + "serumVaultSigner": "k5mhBL7yqEtAQs1WtUGdMT9eLLZkjambTd1Y4MyGouf", + "official": true + }, + { + "name": "ETH-SOL", + "coin": "ETH", + "pc": "SOL", + "lp": "LP.RDM.ETH-SOL-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "9Hm8QX7ZhE9uB8L2arChmmagZZBtBmnzBbpfxzkQp85D", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "GwFM8qoBwusXVbcdfreKV9q86vqdudnVtvhYfJWgtgB", + "ammTargetOrders": "FQp9HzJKEFfiDSnV6qyQNoz8cEKsWHnV3yFqWrT1ThgN", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "59STNbqDpY1sj6m95jBPRiFwjtigtivHqQeJRUofWY2a", + "poolPcTokenAccount": "HXz1MFnu9ANWfCBesnrzMZMPoFbUyyqPDKT67sqgT4rk", + "poolWithdrawQueue": "GrLKNkFVyAdV1wXoBFYxMSSPJ3BNekggiZJERrPSnAE2", + "poolTempLpTokenAccount": "AtQQZJUBrXs8nBKCHy4L2WovuEEVf7QnVWwgRdVbnKd4", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HkLEttvwk2b4QDAHzNcVtxsvBG35L1gmYY4pecF9LrFe", + "serumBids": "B38zSRMdSHYxnbsWCgY4GvSy4aRytkhqR5qVjaHsNXdA", + "serumAsks": "E4hWT9G64hLDMY7VrGXfJ5cuU8jRzJsUYAi8fqep6Sqy", + "serumEventQueue": "Bdy9encMZ7UpbEbdCgh5qDq8qQn4D31tFR45Bdas3f5y", + "serumCoinVaultAccount": "HMPki4uRhncFhMHpLAacHCDAU4QazjgFTsB8SQgh6bMY", + "serumPcVaultAccount": "BeWaZ85mTxmrYfS3J9E1jQQ5tKgDRA6qmTpksKnGeNps", + "serumVaultSigner": "GPNCigFBsjNhXu3cbmU1uxfbGVuxCA8bJN4bobwDjuTm", + "official": true + }, + { + "name": "stSOL-USDC", + "coin": "stSOL", + "pc": "USDC", + "lp": "LP.RDM.stSOL-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "6a1CsrpeZubDjEJE9s1CMVheB6HWM5d7m1cj2jkhyXhj", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "28NQqHxrqMYMQ67aWyn9AzZ1F16PYd4zvLpiiKnEZpsD", + "ammTargetOrders": "B8nmqinHQjyqAnMWNiqSzs1Jb8VbMpX5k9VUMnDp1gUA", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "DD6oh3HRCvMzqHkGeUW3za4pLgWNPJdV6aNYW3gVjXXi", + "poolPcTokenAccount": "6KR4qkJN91LGko2gdizheri8LMtCwsJrhtsQt6QPwCi5", + "poolWithdrawQueue": "5i9pTTk9x7r8fx8mJMBCEN85URVLAnkLzZXKyoutUJhU", + "poolTempLpTokenAccount": "GiuNbiBirwsBp9GuxGYgNUMMKGM6Qf6wqgnxbJFHTYFa", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "5F7LGsP1LPtaRV7vVKgxwNYX4Vf22xvuzyXjyar7jJqp", + "serumBids": "HjJSzUbis6VhBZLCbSFN1YtvWLLdxutb7WEvymCLrBJt", + "serumAsks": "9e37wf6QUqe2s4J6UUNsuv6REQkwTxd47hXhDanm1adp", + "serumEventQueue": "CQY7LwdZJrfLRZcmEzUYp34XJbxhnxgF4UXmLKqJPLCk", + "serumCoinVaultAccount": "4gqecEySZu6SEgCNhBJm7cEn2TFqCMsMNoiyski5vMTD", + "serumPcVaultAccount": "6FketuhRzyTpevhgjz4fFgd5GL9fHeBeRsq9uJvu8h9m", + "serumVaultSigner": "x1vRSsrhXkSn7xzJfu9mYP2i19SPqG1gjyj3vUWhim1", + "official": true + }, + { + "name": "GRAPE-USDC", + "coin": "GRAPE", + "pc": "USDC", + "lp": "LP.RDM.GRAPE-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "vVXfY15WdPsCmLvbiP4hWWECPFeAvPTuPNq3Q4BXfhy", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "A7RFkvmDFN4Qev8XgGAqSr5W75sNhhtCY3ZcGHZiDDo1", + "ammTargetOrders": "HRiPQyFJfzF7WgC4g2cFbxuKgqn1vKVRjTCuZTNGim36", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "BKqBnj1TLpW4UEBbZn6aVoPLLBHDB6NTEL5nFNRqX7e7", + "poolPcTokenAccount": "AN7XxHrrcFL7629WySWVA2Tq9inczxkbE6YqgZ31rDnG", + "poolWithdrawQueue": "29WgH1suwTnhL4JUwDMUQQpUzypet8PHEh8jQpZtiDBK", + "poolTempLpTokenAccount": "3XCGBJpfHV5VYkz92nqzRtHahTiHXjYzVs4PargSpYwS", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "72aW3Sgp1hMTXUiCq8aJ39DX2Jr7sZgumAvdLrLuCMLe", + "serumBids": "F3PQsAGiFf8fSySjUGgP3NQdAGSnioAThncyfd26GKZ3", + "serumAsks": "6KyB4XprAw7Mgp1YMMsxRGx8T59Y5Lcu6s1FcwFrXy3i", + "serumEventQueue": "Due4ZmGX2u7an9DPMvk3uX3sXYgngRatP1XmwzEgk1tT", + "serumCoinVaultAccount": "8FMjC6yopBVYTXcYSGdFgoh6AFpwTdkJAGXxBeoV8xSq", + "serumPcVaultAccount": "5vgxuCqMn7DUt6Le6EGhdMzZjPQrtD1x4TD9zGw3mPte", + "serumVaultSigner": "FCZkJzztVTx6qKVec25jA3m4XjeGBH1iukGdDqDBHPvG", + "official": true + }, + { + "name": "SBR-USDC", + "coin": "SBR", + "pc": "USDC", + "lp": "LP.RDM.SBR-USDC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "5cmAS6Mj4pG2Vp9hhyu3kpK9yvC7P6ejh9HiobpTE6Jc", + "ammAuthority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", + "ammOpenOrders": "8bEDWrUBqMV7ei55PgySABm8swC9WFW24NB6U5f5sPJT", + "ammTargetOrders": "G2nswHPqZLXtMimXZtsiLHVZ5gJ9GTiKRdLxahDDdYag", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "8vwzjpW7KPGFLQdRuyoBBoiBCsNG6SLRGssKMNsofch2", + "poolPcTokenAccount": "AcK6bv25Q7xofBUiXKwUgueSi3ELS6anMbmNn2NPV8FZ", + "poolWithdrawQueue": "BG59NCoZnxqSU2TQ2DNsENiCZci73BcRvXWtqmQhNrcw", + "poolTempLpTokenAccount": "msNco37chvHeLivUwoetEnHDFZxVNi2KXQzjGAXkRuZ", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "HXBi8YBwbh4TXF6PjVw81m8Z3Cc4WBofvauj5SBFdgUs", + "serumBids": "FdGKYpHxpQEkRitZw6KZ8b21Q2mYiATHXZgJjFDhnRWM", + "serumAsks": "cxqTRyeoGeh6TBEgo3NAieHaMkdmfZiCjSEfkNAe1Y3", + "serumEventQueue": "EUre4VPaLh7B95qG3JPS3atquJ5hjbwtX7XFcTtVNkc7", + "serumCoinVaultAccount": "38r5pRYVzdScrJNZowNyrpjVbtRKQ5JMcQxn7PgKE45L", + "serumPcVaultAccount": "4YqAGXQEQTQbn4uKX981yCiSjUuYPV8aCajc9qQh3QPy", + "serumVaultSigner": "84aqZGKMzbr8ddA267ML7JUTAjieVJe8oR1yGUaKwP53", + "official": false + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/pools/raydium/pools_dev.json b/farms/farm-ctrl/src/metadata/pools/raydium/pools_dev.json new file mode 100644 index 00000000000..6a6eb2dff7e --- /dev/null +++ b/farms/farm-ctrl/src/metadata/pools/raydium/pools_dev.json @@ -0,0 +1,31 @@ +{ + "name": "Raydium Pools", + "pools": [ + { + "name": "COIN-PC", + "coin": "COIN", + "pc": "PC", + "lp": "LP.RDM.COIN-PC-V4", + "version": 4, + "programId": "LIQUIDITY_POOL_PROGRAM_ID_V4", + "ammId": "HeD1cekRWUNR25dcvW8c9bAHeKbr1r7qKEhv7pEegr4f", + "ammAuthority": "DhVpojXMTbZMuTaCgiiaFU7U8GvEEhnYo4G9BUdiEYGh", + "ammOpenOrders": "HboQAt9BXyejnh6SzdDNTx4WELMtRRPCr7pRSLpAW7Eq", + "ammTargetOrders": "6TzAjFPVZVMjbET8vUSk35J9U2dEWFCrnbHogsejRE5h", + "ammQuantities": "11111111111111111111111111111111", + "poolCoinTokenAccount": "3qbeXHwh9Sz4zabJxbxvYGJc57DZHrFgYMCWnaeNJENT", + "poolPcTokenAccount": "FrGPG5D4JZVF5ger7xSChFVFL8M9kACJckzyCz8tVowz", + "poolWithdrawQueue": "DD9nUbJoUbuE3FampcJeLfDPb5zKGvR7Ho7HE5rpcBGx", + "poolTempLpTokenAccount": "249BW2tWhsvwEFtWabpTXXX17Vh7NQSeHS4W7Ku6b27R", + "serumProgramId": "SERUM_PROGRAM_ID_V3", + "serumMarket": "3tsrPhKrWHWMB8RiPaqNxJ8GnBhZnDqL4wcu5EAMFeBe", + "serumBids": "ANHHchetdZVZBuwKWgz8RSfVgCDsRpW9i2BNWrmG9Jh9", + "serumAsks": "ESSri17GNbVttqrp7hrjuXtxuTcCqytnrMkEqr29gMGr", + "serumEventQueue": "FGAW7QqNJGFyhakh5jPzGowSb8UqcSJ95ZmySeBgmVwt", + "serumCoinVaultAccount": "E1E5kQqWXkXbaqVzpY5P2EQUSi8PNAHdCnqsj3mPWSjG", + "serumPcVaultAccount": "3sj6Dsw8fr8MseXpCnvuCSczR8mQjCWNyWDC5cAfEuTq", + "serumVaultSigner": "C2fDkZJqHH5PXyQ7UWBNZsmu6vDXxrEbb9Ex9KF7XsAE", + "official": true + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms.json b/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms.json new file mode 100644 index 00000000000..0d3ff962c0e --- /dev/null +++ b/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms.json @@ -0,0 +1,8792 @@ +{ + "addresses": { + "landlord": "B38L5x5EszUK4iqcNMAZRyaJx8ie8cgGvxxbYmkWkjZe", + "landlordBase": "GHxgjDJgpUkugb2cPvaKQSbkdoHYHCHo2ZZsRFiFt4YL", + "rewarder": "rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk", + "mintWrapper": "EVVDA3ZiAjTizemLGXNUN3gb6cffQFEYkFjFZokPmUPz", + "iouMint": "iouQcQBAiEXe6cKLS85zmZxUqaCqBdeHFpqKoSz615u", + "redeemer": "CL9wkGFT3SZRRNa9dgaovuRV7jrVVigBUZ6DjcgySsCU", + "sbr": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1" + }, + "pools": [ + { + "id": "usdc_usdt", + "name": "USDT-USDC", + "tokens": [ + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrUSDCUSDT", + "name": "USDT-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "2poo1w1DL6yd2WNTCnNTzDqkC6MBXq7axo77P16yrBuf", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_usdt", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + "plotKey": "EZEBiZieuKrMGyCd72696Vm8HuiimfQGjVrejmp7Abjd", + "swap": { + "config": { + "swapAccount": "YAkoNb6HKmSxQN9L8hiBE5tPJRsniSSMzND1boHmZxe", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "5C1k9yV7y4CjMnKv8eGYDgWND8P89Pdfj79Trk2qmfGo" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "GLztedC76MeBXjAmVXMezcHQzdmQaVLiXCZr9KEBSR6Y", + "reserve": "CfWX7o2TswwbxusJ4hCaPobu2jLCb1hfXuXJQjVq3jQF", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "tokenB": { + "adminFeeAccount": "2SL8iP8EjnUr6qTkbkfZt9tauXwJgc4GKXkYCCbLGbVP", + "reserve": "EnTrdMMpdhugeH6Ban6gYZWXughWxKtVGfCwFn78ZmY3", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + }, + "poolTokenMint": "2poo1w1DL6yd2WNTCnNTzDqkC6MBXq7axo77P16yrBuf", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000000" + }, + "adminWithdraw": { + "formatted": "50.0000000000", + "numerator": "5000000", + "denominator": "10000000" + }, + "trade": { + "formatted": "0.0399900000", + "numerator": "3999", + "denominator": "10000000" + }, + "withdraw": { + "formatted": "0.5000000000", + "numerator": "50000", + "denominator": "10000000" + } + } + } + }, + "quarry": "Hs1X5YtXwZACueUtS9azZyXFDWVxAMLvm3tttubpK7ph" + }, + { + "id": "usdc_pai", + "name": "PAI-USDC", + "tokens": [ + { + "chainId": 101, + "address": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "symbol": "PAI", + "name": "PAI (Parrot USD)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS.svg", + "tags": ["utility-token", "stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "coingeckoId": "parrot-usd", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "symbol": "PAI", + "name": "PAI (Parrot USD)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS.svg", + "tags": ["utility-token", "stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "coingeckoId": "parrot-usd", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "symbol": "PAI", + "name": "PAI (Parrot USD)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS.svg", + "tags": ["utility-token", "stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "coingeckoId": "parrot-usd", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrPAIUSDC", + "name": "PAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PaiYwHYxr4SsEWox9YmyBNJmxVG7GdauirbBcYGB7cJ", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_pai", + "underlyingTokens": [ + "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + "plotKey": "GcSFMQ352X8EgX9NpXr2aNeHYoou5iUwv6cD1CeiKWNN", + "swap": { + "config": { + "swapAccount": "B2izdXFYb2sNwbWWHmh75T4aQFtfXsUxem9u2p61HGzf", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "7W9KMACQT6UmjRPEUQKXyVf4NjZ9Ux4PHs1e1P5PxDtA" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "5ERUKXf8w8aa9asB4EDGVQQYb3Awj9mFvgW6WuHmZqjf", + "reserve": "4DYwgJtxwuJdAjkj5RJSNH4e7U329V5cNp7d3a1nLrZv", + "mint": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS" + }, + "tokenB": { + "adminFeeAccount": "AA5bBxPuhCaMDpMq4xXHAMa5RwEUWzdiSYv1ipX9Lc8b", + "reserve": "EXNW64GEf1ACC6xY9BtKRiunrs6GoJSXBdxWN2eTPmrF", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "poolTokenMint": "PaiYwHYxr4SsEWox9YmyBNJmxVG7GdauirbBcYGB7cJ", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000000" + }, + "trade": { + "formatted": "0.0399900000", + "numerator": "3999", + "denominator": "10000000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000000" + } + } + } + }, + "quarry": "FR245AMbpU6Xcpad7JAVicSJRR36nWWxDw4itr2hNDHa" + }, + { + "id": "btc", + "name": "BTC-renBTC", + "tokens": [ + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + }, + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (8 decimals)", + "address": "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv", + "decimals": 8, + "symbol": "sBTC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (8 decimals)", + "address": "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv", + "decimals": 8, + "symbol": "sBTC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E" + ], + "currency": "BTC" + } + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "symbol": "BTC", + "name": "Wrapped Bitcoin (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-btc"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "serumV3Usdc": "A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw", + "serumV3Usdt": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4", + "coingeckoId": "bitcoin", + "currency": "BTC" + } + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrrenBTCsBTC-8", + "name": "BTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "SLPbsNrLHv8xG4cTc4R5Ci8kB9wUPs6yn6f7cKosoxs", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/btc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv" + ], + "source": "saber" + } + }, + "plotKey": "14XHWnimCq8Hx3RBtocT1YsdytfHALUh9H1EfebbbEU4", + "swap": { + "config": { + "swapAccount": "BkwbeSfcX1h4thMDd7obGkHrPevf3QwgzJ4pCEqG18Lu", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "Fekck54VF2MdesR74trJteZbiKj1TD5AVQisXr8E7fjG" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "3TFH39YLZgnAyp3ehDAYckzYCAyFoAXACZCtZeBVvS6J", + "reserve": "35yX27bmurdebhfAb8EPmjLETDiUaEUCn9zHaDPbakH2", + "mint": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + }, + "tokenB": { + "adminFeeAccount": "EXdKdeYzX8uPP7PgzFhjec8VKRvdcVnpEfQHvvqJRaP8", + "reserve": "2CxECn1ZJFoESyUnQysQU8rRgT3iJ5GRs2Mdd6gZjx5g", + "mint": "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv" + }, + "poolTokenMint": "SLPbsNrLHv8xG4cTc4R5Ci8kB9wUPs6yn6f7cKosoxs", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "4pp9vu4oVr61KmyW1byfXg613jhfsDAA6FCuLhw9ycMp" + }, + { + "id": "pbtc", + "name": "pBTC-renBTC", + "tokens": [ + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + }, + { + "address": "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun", + "symbol": "pBTC", + "name": "pBTC (Parrot BTC)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun.svg", + "tags": [ + "stablecoin", + "saber-market-btc", + "saber-swappable", + "saber-market-btc" + ], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "BTC" + }, + "chainId": 101 + } + ], + "tokenIcons": [ + { + "address": "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun", + "symbol": "pBTC", + "name": "pBTC (Parrot BTC)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun.svg", + "tags": [ + "stablecoin", + "saber-market-btc", + "saber-swappable", + "saber-market-btc" + ], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "BTC" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "address": "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun", + "symbol": "pBTC", + "name": "pBTC (Parrot BTC)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun.svg", + "tags": [ + "stablecoin", + "saber-market-btc", + "saber-swappable", + "saber-market-btc" + ], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "BTC" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrrenBTCpBTC", + "name": "pBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "pBTCmyG7FaZx4uk3Q2pT5jHKWmWDn84npdc7gZXpQ1x", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/pbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun" + ], + "source": "saber" + } + }, + "plotKey": "DTiZh4xkDzcre58Apdsnx4wFPC2ankXmVVRwyNHHsrH", + "swap": { + "config": { + "swapAccount": "AyiATPCAx5HZstcZ1jdH9rENwFb3yd9zEhkgspvDrCs4", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2wszCpUdVDFrJcP79wpV3FdBmU38UC1YKuoSUBA5mhWu" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "3YPinq9u33R6PfQzxKdLkpwm5VH4ETnv2A5oVkomqXtq", + "reserve": "DvHVapj4g2Y1tJVSw2ubSPkPBsJPb8fW387ZWXwaKmZq", + "mint": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + }, + "tokenB": { + "adminFeeAccount": "EnEyxfTc1bDTP68L7nshcZP7DTenH8XENft6MpSZEaPX", + "reserve": "DKjqWWgrtDRPKrnMWtZ4UiJk4sGQVCQgFjSo7BvfngvK", + "mint": "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun" + }, + "poolTokenMint": "pBTCmyG7FaZx4uk3Q2pT5jHKWmWDn84npdc7gZXpQ1x", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "8hnBtfEumuBh8Vd19qZ16re6wWgMZypZQoFLum7vx1bf" + }, + { + "id": "xusd", + "name": "xUSD-USDC", + "tokens": [ + { + "chainId": 101, + "address": "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "symbol": "xUSD", + "name": "Synthetic USD", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y.svg", + "tags": ["saber-market-usd"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "symbol": "xUSD", + "name": "Synthetic USD", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y.svg", + "tags": ["saber-market-usd"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "symbol": "xUSD", + "name": "Synthetic USD", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y.svg", + "tags": ["saber-market-usd"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrxUSDUSDC", + "name": "xUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "XUSDfnsgc2QYXRdbPAbMWoXCbBCCspRSvoGJ8o7RV9n", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xusd", + "underlyingTokens": [ + "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + "plotKey": "H7gz192xGR4Yr8oXCjX4rS8FUxRDQKtLC6dYAgyyCkJQ", + "swap": { + "config": { + "swapAccount": "NorjjEePhxfwLF4vZjUHCrVVKLzQjEEGQFd3wgjEJmh", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4MKU6kajSHK6YGt39fAjvW5bPV1uwUdAP5VVEcS7JLRr" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "E6bf5cADDiv8JqYLxNSEbhmc4oEWbo3jKZqqdMmamANP", + "reserve": "34M8pFVqgbV7aqHxvRT5tCv3vu2P1JJYT8J3VaxBK2oG", + "mint": "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y" + }, + "tokenB": { + "adminFeeAccount": "FbEwqK6P6qmRJyKgWoZMCobAPYams7kMmXUiweoQT9hr", + "reserve": "3AZWBSze3ucsY3xSeuLne3b2pauVqiaJ1SZSV5T9WGWw", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "poolTokenMint": "XUSDfnsgc2QYXRdbPAbMWoXCbBCCspRSvoGJ8o7RV9n", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "9XAGs2YwvRqCLupFAQQyH1XnP1MP3dp9PL6JaBD7yCZp" + }, + { + "id": "xsol", + "name": "xSOL-SOL", + "tokens": [ + { + "chainId": 101, + "address": "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "symbol": "xSOL", + "name": "Synthetic SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov.svg", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "solana", + "currency": "SOL", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "symbol": "xSOL", + "name": "Synthetic SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov.svg", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "solana", + "currency": "SOL", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "symbol": "xSOL", + "name": "Synthetic SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov.svg", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "solana", + "currency": "SOL", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrxSOLSOL", + "name": "xSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "xSoLVBNztDTUW8Kou2GJinHoe54Siu9Sk3e2uoU9aUi", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xsol", + "underlyingTokens": [ + "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + "plotKey": "F62bWEaqZDf2npGYF7usA9GH9zCffYsPnGANz4T6YB6v", + "swap": { + "config": { + "swapAccount": "BERTH9caDT1mFBvne1h5hHdE58vxuAVuvh5rs43AYn9r", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "5j93Lq7Txj9nEr9fUw3YJ2C4Pezm6eUJfGsUi6eYXcy" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "8GcLEUyVAeoj3EeY5VxmWN73bdNWf6AGpBHF1WJf8uSs", + "reserve": "GSt31U8nF6WJBRG7j2EALfNTMd3J8jmotGX9szVTrzvN", + "mint": "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov" + }, + "tokenB": { + "adminFeeAccount": "5grxK9RLRdFw5ekweyVxvgzzq9MPif1KNMZmCrCMG2Uu", + "reserve": "7WEqRDS5Dk6kVXFZHrsW5EtdPhy4v6jWK9P5jHhi9oz", + "mint": "So11111111111111111111111111111111111111112" + }, + "poolTokenMint": "xSoLVBNztDTUW8Kou2GJinHoe54Siu9Sk3e2uoU9aUi", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "BB8EYouLWTWnY3cpns7zGpBwcBbWTcczKuqpkvwcdkvi" + }, + { + "id": "xftt", + "name": "xFTT-wFTT", + "tokens": [ + { + "chainId": 101, + "address": "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "symbol": "xFTT", + "name": "Synthetic FTT", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9.svg", + "tags": ["saber-market-ftt"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "synthetify" + } + }, + { + "symbol": "wFTT", + "name": "FTT (Wormhole)", + "address": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "extensions": { + "coingeckoId": "ftx-token", + "source": "wormhole-v2", + "currency": "FTT" + }, + "chainId": 101, + "tags": ["saber-market-ftt", "wormhole-v2"] + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "symbol": "xFTT", + "name": "Synthetic FTT", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9.svg", + "tags": ["saber-market-ftt"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "synthetify" + } + }, + { + "symbol": "wFTT", + "name": "FTT (Wormhole)", + "address": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "extensions": { + "coingeckoId": "ftx-token", + "source": "wormhole-v2", + "currency": "FTT" + }, + "chainId": 101, + "tags": ["saber-market-ftt", "wormhole-v2"] + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "symbol": "xFTT", + "name": "Synthetic FTT", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9.svg", + "tags": ["saber-market-ftt"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "synthetify" + } + }, + { + "symbol": "wFTT", + "name": "FTT (Wormhole)", + "address": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "extensions": { + "coingeckoId": "ftx-token", + "source": "wormhole-v2", + "currency": "FTT" + }, + "chainId": 101, + "tags": ["saber-market-ftt", "wormhole-v2"] + } + ], + "currency": "FTT", + "lpToken": { + "symbol": "sbrxFTTwFTT", + "name": "xFTT-wFTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "xFTTLsMdN28XHtYTTTVWYz5zwXWBm5r1WTuZ7Cc7SyA", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xftt", + "underlyingTokens": [ + "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv" + ], + "source": "saber" + } + }, + "plotKey": "Es4SyfteTBaBFHKNwCh2rdiXD3juxBYjzCMytomhTACp", + "swap": { + "config": { + "swapAccount": "Bodz3SK3pzzskFdjwG5TXoavTzNLJbMV8VDQHvxkhTa1", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "EqG6R7n6dZeRU1fDeCh5RrCAVNERHbwHz5QWjXwMfzoy" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "BYH9fc4FGHVVRSJdWDoX94x5d4GR4nRHAGiM98HF7ZHP", + "reserve": "HHnpxMvDcXaeHXTSnWw9y8gMeUMnnCzxWfACgx839nCt", + "mint": "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9" + }, + "tokenB": { + "adminFeeAccount": "8i5sAU1eage7bP2PebuRRoBr7tQNXBNiQjdUSSLKPus", + "reserve": "Hk8UFqdvVdvJRZjKJV59PkrDrpLnbCCBMgDjVwx53cwi", + "mint": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv" + }, + "poolTokenMint": "xFTTLsMdN28XHtYTTTVWYz5zwXWBm5r1WTuZ7Cc7SyA", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "FdHVdkarMfbpPhiAhHtKK6PgH8ibN8SnGSqWhQkTyD2c" + }, + { + "id": "xeth", + "name": "xETH-whETH", + "tokens": [ + { + "chainId": 101, + "address": "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "symbol": "xETH", + "name": "Synthetic ETH", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK.svg", + "tags": ["saber-market-eth"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ethereum", + "currency": "ETH", + "source": "synthetify" + } + }, + { + "name": "Saber Wrapped Ether (Wormhole) (9 decimals)", + "address": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "decimals": 9, + "symbol": "swhETH-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "tags": ["saber-market-eth", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "currency": "ETH" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "symbol": "xETH", + "name": "Synthetic ETH", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK.svg", + "tags": ["saber-market-eth"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ethereum", + "currency": "ETH", + "source": "synthetify" + } + }, + { + "name": "Saber Wrapped Ether (Wormhole) (9 decimals)", + "address": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "decimals": 9, + "symbol": "swhETH-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "tags": ["saber-market-eth", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "currency": "ETH" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "symbol": "xETH", + "name": "Synthetic ETH", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK.svg", + "tags": ["saber-market-eth"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ethereum", + "currency": "ETH", + "source": "synthetify" + } + }, + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2"] + } + ], + "currency": "ETH", + "lpToken": { + "symbol": "sbrxETHswhETH-9", + "name": "xETH-whETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "xETH89889mVRwsw9tSUnULsdLUPryTpijagy2YXxWyY", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xeth", + "underlyingTokens": [ + "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma" + ], + "source": "saber" + } + }, + "plotKey": "7PpoS69gVG9Y4nXm1K1eZiED1ZdiJkDtVmB27pkmHfvu", + "swap": { + "config": { + "swapAccount": "ionytDfEj1mXodptgrsKEZro7DGsfpzANUfarJdm6Qb", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "Gdnw9zSTsNjvFcYdtpA5efcGbNCeNAqMev4gGEKbvnJ2" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "ES5HG99aBAgngaePAZPQMxCpsCpPSxBHVwt5cWzfzEj6", + "reserve": "3gkSjY95an9xNFSjCfsay6JZez7uv7yfW3Ad4ubc4fMK", + "mint": "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK" + }, + "tokenB": { + "adminFeeAccount": "3ug9QaKPWv8BudjeraohGn6KpJTfinP4XxiCXUyCeHmS", + "reserve": "9wXqANaSuEqkKL9yHzCPEDGHwbkStVAqV5rB3QDs3Jr6", + "mint": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma" + }, + "poolTokenMint": "xETH89889mVRwsw9tSUnULsdLUPryTpijagy2YXxWyY", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "CipQjauQwCa2pBbavhtR67mKUHbd8ymrQsDwJHTxjPFB" + }, + { + "id": "xbtc", + "name": "xBTC-renBTC", + "tokens": [ + { + "chainId": 101, + "address": "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "symbol": "xBTC", + "name": "Synthetic BTC", + "decimals": 10, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D.svg", + "tags": ["saber-market-btc"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "bitcoin", + "currency": "BTC", + "source": "synthetify" + } + }, + { + "name": "Saber Wrapped renBTC (10 decimals)", + "address": "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi", + "decimals": 10, + "symbol": "srenBTC-10", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "symbol": "xBTC", + "name": "Synthetic BTC", + "decimals": 10, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D.svg", + "tags": ["saber-market-btc"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "bitcoin", + "currency": "BTC", + "source": "synthetify" + } + }, + { + "name": "Saber Wrapped renBTC (10 decimals)", + "address": "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi", + "decimals": 10, + "symbol": "srenBTC-10", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "symbol": "xBTC", + "name": "Synthetic BTC", + "decimals": 10, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D.svg", + "tags": ["saber-market-btc"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "bitcoin", + "currency": "BTC", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrxBTCsrenBTC-10", + "name": "xBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 10, + "address": "xBTCPvRuEuRgz5DuuUd3ju3VP5XtR2Dsu1AxyW9JpXK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xbtc", + "underlyingTokens": [ + "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi" + ], + "source": "saber" + } + }, + "plotKey": "H8PDe6QwBdLbmFbPfXe1EEEAnMgrPL2pDDmZ8W4hAfph", + "swap": { + "config": { + "swapAccount": "NorBXypYWTkV5PMs6Q9JsvNkmRwe7H9Zr1Kz2TcJH2Y", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2AdWk8VKBkThFohdpA1RYekfvwGbUe1t12p7hv1YdQT6" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "7yK5mGAxTh8DVvherfc8kQohRWefP9JgH4P7WsAZge5g", + "reserve": "C82XDu1d1UjtuUwnkSTwVuWyH3AqpJ3ftb9W7en8rEUu", + "mint": "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D" + }, + "tokenB": { + "adminFeeAccount": "7hgUbcacMhbehnTmsHSPcDDrKUceKvsoFZjERZkjp4Tn", + "reserve": "F7cZoYpWQSFuYJzW2UUFw6sVbTtaihyD5upeeHRUPABk", + "mint": "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi" + }, + "poolTokenMint": "xBTCPvRuEuRgz5DuuUd3ju3VP5XtR2Dsu1AxyW9JpXK", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "GWTy8S43vAUMCQpjNV9JBp5NPNSuCyCn34wz9Roz63YF" + }, + { + "id": "ust", + "name": "wUST_V1-USDC", + "tokens": [ + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm", + "symbol": "wUST_V1", + "name": "Wrapped UST (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://terra.money", + "address": "0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "coingeckoId": "terrausd", + "currency": "USD", + "source": "wormhole-v1" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm", + "symbol": "wUST_V1", + "name": "Wrapped UST (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://terra.money", + "address": "0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "coingeckoId": "terrausd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm", + "symbol": "wUST_V1", + "name": "Wrapped UST (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://terra.money", + "address": "0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "coingeckoId": "terrausd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrsUSDC-9wUST_V1", + "name": "wUST_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "UST32f2JtPGocLzsL41B3VBBoJzTm1mK1j3rwyM3Wgc", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ust", + "underlyingTokens": [ + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm" + ], + "source": "saber" + } + }, + "plotKey": "2Pyj6DfsYccbLSiLia13yqENrCHybQ85MCW4AfYxvzLS", + "swap": { + "config": { + "swapAccount": "7oAd7xG4m3oC2qeWB1szghTebAZyyGPAFDJ4wwwbRSNi", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "ASpJBf8HtyrNxaMqFNpjYCqi8SsJC5h56hd3HQUNk6M7" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "H8sa4bG8oWg89vHQeZnwzumqTk2gnRWKb19LzZgge3W6", + "reserve": "D9yh4KAysxt9GLacVe4Wwh2XqghhcjTCSTV9HuM7TBJd", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "tokenB": { + "adminFeeAccount": "54BYHhhjjtSnLiYE8QxCWxchRiVcqXssG2deNm1pQKYw", + "reserve": "HDYfJLpZKaMFb84jM4mRytn7XLR8UFZUnQpSfhJJaNEy", + "mint": "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm" + }, + "poolTokenMint": "UST32f2JtPGocLzsL41B3VBBoJzTm1mK1j3rwyM3Wgc", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "FFfgPdrES48xe8UKGgmdc5kvQ9cv49oeu5FUKadh1i1k", + "newPoolID": "wust" + }, + { + "id": "dai", + "name": "wDAI_V1-USDC", + "tokens": [ + { + "chainId": 101, + "address": "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "symbol": "wDAI_V1", + "name": "Dai Stablecoin (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F", + "coingeckoId": "dai", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "symbol": "wDAI_V1", + "name": "Dai Stablecoin (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F", + "coingeckoId": "dai", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "symbol": "wDAI_V1", + "name": "Dai Stablecoin (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F", + "coingeckoId": "dai", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwDAI_V1sUSDC-9", + "name": "wDAI_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "Daimhb91DY4e3aVaa7YCW5GgwaMT9j1ALSi2GriBvDNh", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/dai", + "underlyingTokens": [ + "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "BvYhEvAG5tFqz9SYYe6yvSWi7TqV1mpL4FVmVJPxorsj", + "swap": { + "config": { + "swapAccount": "4xeRVccqvUgiV6VeF6NkWqkAYeewcJPRWFVginvJw1n4", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2hAy2ubWi3PWrgxSoamzonLy1bUL3BfoqW7u7791Qpj9" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "DtM3gSPFCS9EjkjKSsgeoXimvdowveDjHDgYSEp8Vg5m", + "reserve": "A7VkMFrnCCyeZFUrQ3TzDr4xFep7PZtxvy3jJnBjLB2a", + "mint": "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1" + }, + "tokenB": { + "adminFeeAccount": "94rscwtQCYT9FCdzxefmw6JJP8gqHoV8B86RHNTcAc7p", + "reserve": "PhfHJ2Yu99BsEjZrefhApqUnLUiExcECcUT1YLoNUUv", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "Daimhb91DY4e3aVaa7YCW5GgwaMT9j1ALSi2GriBvDNh", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "EYbg27c9Afc1Cbxy8qnCvECw2unWajypuXAYiQq949Yy", + "newPoolID": "wdai" + }, + { + "id": "busd", + "name": "wBUSD_V1-USDC", + "tokens": [ + { + "chainId": 101, + "address": "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "symbol": "wBUSD_V1", + "name": "Binance USD (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "coingeckoId": "binance-usd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "symbol": "wBUSD_V1", + "name": "Binance USD (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "coingeckoId": "binance-usd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "symbol": "wBUSD_V1", + "name": "Binance USD (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "coingeckoId": "binance-usd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwBUSD_V1sUSDC-9", + "name": "wBUSD_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BUSDaZjarCrQJLeHpWi7aLaKptdR1S8DFpwdDuuZu9p3", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/busd", + "underlyingTokens": [ + "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "CMMdwnzEfzbaDgaQnaQY8TFcVDb1vD4mLXQUWkwFh8i2", + "swap": { + "config": { + "swapAccount": "BkMBnJpvHKtWhUvrgRrLesXfi9RUV3ELgdBaJcdH7hcY", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "FDndRkBVpFoNBHY6Jhx7PgNpysvZjt3P2MZ95vmkSfWa" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "DLhk52w7tDsdDvHf3SHnQr7hDhkTkTQ3mrjqqFc7KHbX", + "reserve": "5uerVwBnZQsuVhZ15igs7ZgmcqhHnYWbwoRtLyRqLvR", + "mint": "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX" + }, + "tokenB": { + "adminFeeAccount": "44RJsmGr3nt3hL76FqSrwyuFrhQmKr99KPbVKo3nuZHT", + "reserve": "9YWiQh5d4jCtgMdzcGLv9bWgnLaFtzvDDh2nnhJdzhBX", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "BUSDaZjarCrQJLeHpWi7aLaKptdR1S8DFpwdDuuZu9p3", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "FdefcL2FRLhz9bDnjyypuV8qRJ5p2TRKV2yUDxqFcFRN", + "newPoolID": "webusd" + }, + { + "id": "luna", + "name": "wLUNA_V1-renLUNA", + "tokens": [ + { + "chainId": 101, + "address": "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "symbol": "wLUNA_V1", + "name": "Wrapped LUNA Token (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV.png", + "tags": ["wrapped", "wormhole", "saber-market-luna", "wormhole-v1"], + "extensions": { + "address": "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "coingeckoId": "wrapped-terra", + "currency": "LUNA", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped renLUNA (9 decimals)", + "address": "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT", + "decimals": 9, + "symbol": "srenLUNA-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "underlyingTokens": [ + "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE" + ], + "currency": "LUNA" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "symbol": "wLUNA_V1", + "name": "Wrapped LUNA Token (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV.png", + "tags": ["wrapped", "wormhole", "saber-market-luna", "wormhole-v1"], + "extensions": { + "address": "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "coingeckoId": "wrapped-terra", + "currency": "LUNA", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped renLUNA (9 decimals)", + "address": "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT", + "decimals": 9, + "symbol": "srenLUNA-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "underlyingTokens": [ + "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE" + ], + "currency": "LUNA" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "symbol": "wLUNA_V1", + "name": "Wrapped LUNA Token (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV.png", + "tags": ["wrapped", "wormhole", "saber-market-luna", "wormhole-v1"], + "extensions": { + "address": "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "coingeckoId": "wrapped-terra", + "currency": "LUNA", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "symbol": "renLUNA", + "name": "renLUNA", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna"], + "extensions": { + "website": "https://renproject.io/", + "serumV3Usdc": "CxDhLbbM9uAA2AXfSPar5qmyfmC69NLj3vgJXYAsSVBT", + "currency": "LUNA" + } + } + ], + "currency": "LUNA", + "lpToken": { + "symbol": "sbrwLUNA_V1srenLUNA-9", + "name": "wLUNA_V1-renLUNA Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "LUNkiLcb2wxcqULmJvMjuM6YQhpFBadG5KZBe7qBpSE", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/luna", + "underlyingTokens": [ + "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT" + ], + "source": "saber" + } + }, + "plotKey": "9o2JPTjrxuzF6Skg6jKo5MHCoFrZGLieFx2feukaw5qS", + "swap": { + "config": { + "swapAccount": "4EFeyTtMZZnAv3ZPs2jvps1T1J1JykbpyizrWjBQcA1S", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4HP9xSxLcEK64zALBCP36GdfDLrMXorVk4X6DyLrBjbp" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "8473xzuHPocWB6uo8Ww38JFH9Kg4fFWtLRjP2qhZ39eW", + "reserve": "Au5zcSost9sXpH8AQQjULRXJ9QCJ3kdKehUr1zYzTr6G", + "mint": "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV" + }, + "tokenB": { + "adminFeeAccount": "D7EcrRANNj21tHGsBVvY9WZ45JjmGocxqrpP8UuWdwTg", + "reserve": "Gx1L7n1YhDWLNfUyCeZfzKvwHJSxhppnk4DS5cZLqyd", + "mint": "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT" + }, + "poolTokenMint": "LUNkiLcb2wxcqULmJvMjuM6YQhpFBadG5KZBe7qBpSE", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "FaEnerTg1tsHKtqXfjGKtHMDkUrd4pe6i7d41qoYDUJf", + "newPoolID": "wluna" + }, + { + "id": "frax", + "name": "wFRAX_V1-USDC", + "tokens": [ + { + "chainId": 101, + "address": "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "symbol": "wFRAX_V1", + "name": "Frax (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x853d955aCEf822Db058eb8505911ED77F175b99e", + "coingeckoId": "frax", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "symbol": "wFRAX_V1", + "name": "Frax (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x853d955aCEf822Db058eb8505911ED77F175b99e", + "coingeckoId": "frax", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "symbol": "wFRAX_V1", + "name": "Frax (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "address": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x853d955aCEf822Db058eb8505911ED77F175b99e", + "coingeckoId": "frax", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwFRAX_V1sUSDC-9", + "name": "wFRAX_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FRAXXvt2ucEsxYPK4nufDy5zKhb2xysieqRBE1dQTqnK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/frax", + "underlyingTokens": [ + "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "GdNLkwhFDq7JQMcui1L4qVGHwme32549F37VZyXY5ige", + "swap": { + "config": { + "swapAccount": "ZUCKWFa4LTL833dPhhpeeRWZcaAEoMeYJPi2iKn4F8N", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "GUotxHmyJVsJYWYoL8Vo6SKQweNRUZMRQcoqDe5PswHt" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "5jYdGvmEtatq2ojR2hvxPdxEaFm5hoARSEBVj51pWNKw", + "reserve": "7eEYpq6ShaJ9opZWMxitRFrdCHh6vfyHhGfoSvFht3N2", + "mint": "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU" + }, + "tokenB": { + "adminFeeAccount": "GoVHiVxVgu7YcYRF4dUUJrQtxzmwTirU8fd21ziUUEfu", + "reserve": "H8VggnHmuwd1wvwpT8eg9cUJFEfZ7HAaeYjgXrSm7A2u", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "FRAXXvt2ucEsxYPK4nufDy5zKhb2xysieqRBE1dQTqnK", + "initialAmpFactor": "64", + "targetAmpFactor": "64", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "Cj4Ui8sa4CKSYZt4vVQF8QJsaa8Lgg6ykVseWXQkHTgU", + "newPoolID": "wfrax" + }, + { + "id": "hbtc", + "name": "wHBTC_V1-renBTC", + "tokens": [ + { + "chainId": 101, + "address": "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "symbol": "wHBTC_V1", + "name": "HBTC (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref.png", + "tags": ["wrapped", "wormhole", "saber-market-btc", "wormhole-v1"], + "extensions": { + "address": "0x0316EB71485b0Ab14103307bf65a021042c6d380", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x0316EB71485b0Ab14103307bf65a021042c6d380", + "coingeckoId": "huobi-btc", + "currency": "BTC", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "symbol": "wHBTC_V1", + "name": "HBTC (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref.png", + "tags": ["wrapped", "wormhole", "saber-market-btc", "wormhole-v1"], + "extensions": { + "address": "0x0316EB71485b0Ab14103307bf65a021042c6d380", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x0316EB71485b0Ab14103307bf65a021042c6d380", + "coingeckoId": "huobi-btc", + "currency": "BTC", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "symbol": "wHBTC_V1", + "name": "HBTC (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref.png", + "tags": ["wrapped", "wormhole", "saber-market-btc", "wormhole-v1"], + "extensions": { + "address": "0x0316EB71485b0Ab14103307bf65a021042c6d380", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x0316EB71485b0Ab14103307bf65a021042c6d380", + "coingeckoId": "huobi-btc", + "currency": "BTC", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrwHBTC_V1srenBTC-9", + "name": "wHBTC_V1-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "HBTCNvkwjMsEtwe2PeXUuMcu8C4Hobw6HDP2m6vpWHGo", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/hbtc", + "underlyingTokens": [ + "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + "plotKey": "6YtaMDi2WozuD5LkWoqzdZnTyLR3KwYdwqHNf5bxaCx1", + "swap": { + "config": { + "swapAccount": "DJUn4vhv4YQHhDRfxmLtu7f8sGnZpgnKG5GHzA7wFPB9", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "G4cRef4AxEjaSV32xqQzDmHqi3iz8112LQwx8oPbZhYb" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "9KJ3osjhcYiQkUkg6oCVCZ36BGpkHuJG8t12ax5d6ssZ", + "reserve": "GsizhiRtCs4QDKd2LnSQ9BpzvG8CqERMDtHZcQPDkFQB", + "mint": "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref" + }, + "tokenB": { + "adminFeeAccount": "AFL8U16ZGCtg7iJDUcFpCJ1dtoycwgKiN3CnPz1ekt2B", + "reserve": "CRaJHfCry6JShmF4tMr6siR2D2QNNfcUrLawTqPVCTTJ", + "mint": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + }, + "poolTokenMint": "HBTCNvkwjMsEtwe2PeXUuMcu8C4Hobw6HDP2m6vpWHGo", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "HHs32Mph7a19onVvNyfPTvxs8gdjbwfd576KtRwspX2u" + }, + { + "id": "husd", + "name": "wHUSD_V1-USDC", + "tokens": [ + { + "chainId": 101, + "address": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "symbol": "wHUSD_V1", + "name": "HUSD Stablecoin (Wormhole V1)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://www.stcoins.com/", + "address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "coingeckoId": "husd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "symbol": "wHUSD_V1", + "name": "HUSD Stablecoin (Wormhole V1)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://www.stcoins.com/", + "address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "coingeckoId": "husd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "symbol": "wHUSD_V1", + "name": "HUSD Stablecoin (Wormhole V1)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX.png", + "tags": ["wrapped", "wormhole", "saber-market-usd", "wormhole-v1"], + "extensions": { + "website": "https://www.stcoins.com/", + "address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "coingeckoId": "husd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwHUSD_V1sUSDC-8", + "name": "wHUSD_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "HUSDgP5YieANhAAHD42yivX9aFS1zbodTut2Dvvkj8QS", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/husd", + "underlyingTokens": [ + "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "AckM1mipndQXeBq71pA1mFFVED8DSkxks7zm7Tawt6Ub", + "swap": { + "config": { + "swapAccount": "MNkYtBe2HtgL15osfaMmyqNW3vo4J399FXXCS4a39Wf", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "ELnY6YAb1oSPGuARAV8rBJq44AXgT69GJhvWNfuabq9B" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "BouyhtZfbhfX7zJYhQpTDEjLYf9TtUbF15zRLyCziriY", + "reserve": "2mUxDu8NrhSKhQJMgKfYLxJqZzeEbmwhQdHeHMyohyuk", + "mint": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX" + }, + "tokenB": { + "adminFeeAccount": "2q3RvnehnJzZHJ2Ms9nhM98wnqHKmafzRNdpYT2ZovgP", + "reserve": "AZCBmDBcFsA2jHHhfFJBTsWCHx9XnnKmGfFsue3ZVW1t", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "HUSDgP5YieANhAAHD42yivX9aFS1zbodTut2Dvvkj8QS", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "4DqNHQTQTXkHgcsAQaBVYaqZUiPBqtdf73P4q2octpJH", + "newPoolID": "whusd" + }, + { + "id": "usdk", + "name": "wUSDK_V1-USDC", + "tokens": [ + { + "symbol": "wUSDK_V1", + "name": "USDK (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/usdk.png", + "address": "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "decimals": 9, + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v1", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v1"] + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wUSDK_V1", + "name": "USDK (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/usdk.png", + "address": "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "decimals": 9, + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v1", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v1"] + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wUSDK_V1", + "name": "USDK (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/usdk.png", + "address": "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "decimals": 9, + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v1", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwUSDK_V1sUSDC-9", + "name": "wUSDK_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "uSdKg2Cs5bCtFSeNXs7aRVNzZJauX58eCkdsfssxTdW", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdk", + "underlyingTokens": [ + "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "Gske6XTLwhsJbZgPLcjMWRZfQoHXoidJYHUYU7p8m3Bb", + "swap": { + "config": { + "swapAccount": "okgoAkj7GvBT1Q54j6Qvm6AZPritriGTjcEpkPhdWqf", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "5iGwpfXgTX2zqQuagzwLtMWEg1e8Rju7tkjYUbbHXvgj" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "EsPTcNLF5ceh3S2jybKqGNkCitmSKqat5DKkmeoNLZkY", + "reserve": "5RfXYWvxR9PUaedokXVxgJHDoD4xnLLauVtdJ27shPWG", + "mint": "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak" + }, + "tokenB": { + "adminFeeAccount": "EDA5gjEd9o2f7i9mpGKh4nqnzeZ52u7SjLuHBq8YJU59", + "reserve": "DJcFPaQjyW9Xkt7sXCbnEGj1yfykGYuLRUXFyS4LLZ5F", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "uSdKg2Cs5bCtFSeNXs7aRVNzZJauX58eCkdsfssxTdW", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "AfvTQGrJw4Wk71Lvv1uXtWbb8MF9igxtSe6yVAaXgDf7", + "newPoolID": "wusdk" + }, + { + "id": "ftt", + "name": "wFTT_V1-FTT", + "tokens": [ + { + "chainId": 101, + "address": "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "symbol": "wFTT_V1", + "name": "Wrapped FTT (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi.png", + "tags": ["wrapped", "wormhole", "saber-market-ftt", "wormhole-v1"], + "extensions": { + "address": "0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped Wrapped FTT (Sollet) (9 decimals)", + "address": "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt", + "decimals": 9, + "symbol": "sFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-ftt", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "coingeckoId": "ftx-token", + "underlyingTokens": [ + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3" + ], + "currency": "FTT" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "symbol": "wFTT_V1", + "name": "Wrapped FTT (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi.png", + "tags": ["wrapped", "wormhole", "saber-market-ftt", "wormhole-v1"], + "extensions": { + "address": "0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "wormhole-v1" + } + }, + { + "name": "Saber Wrapped Wrapped FTT (Sollet) (9 decimals)", + "address": "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt", + "decimals": 9, + "symbol": "sFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-ftt", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "coingeckoId": "ftx-token", + "underlyingTokens": [ + "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3" + ], + "currency": "FTT" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "symbol": "wFTT_V1", + "name": "Wrapped FTT (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi.png", + "tags": ["wrapped", "wormhole", "saber-market-ftt", "wormhole-v1"], + "extensions": { + "address": "0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "symbol": "FTT", + "name": "Wrapped FTT (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-ftt"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "serumV3Usdc": "2Pbh1CvRVku1TgewMfycemghf6sU9EyuFDcNXqvRmSxc", + "serumV3Usdt": "Hr3wzG8mZXNHV7TuL6YqtgfVUesCqMxGYCEyP3otywZE", + "coingeckoId": "ftx-token", + "waterfallbot": "https://bit.ly/FTTwaterfall", + "currency": "FTT" + } + } + ], + "currency": "FTT", + "lpToken": { + "symbol": "sbrwFTT_V1sFTT-9", + "name": "wFTT_V1-FTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FTXdV5wFFhceKjcd1JRrRQTT2uB7ruMerAqbj2rj1Mz7", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ftt", + "underlyingTokens": [ + "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt" + ], + "source": "saber" + } + }, + "plotKey": "69JS7H4E6i9TTpcA1UsH4GizxozzMttUbXFyCxiZ62Vf", + "swap": { + "config": { + "swapAccount": "SBFjafzbDXgDRsA5SUypY8FXjJ9sBdb4JbuQq2PHQDG", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "DGPFLHMzcfLeANN5m6gVoMFpo38KuU85tAGFHpLfn3gM" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "8cxvuwMt5rhCAUyTMjUEvqtCaMdMrvm8BnXGxDPj4Wid", + "reserve": "46xwHtnXoQR3wCHUbm2eCAbPYWbioDQ59Te1Db8M5DDL", + "mint": "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi" + }, + "tokenB": { + "adminFeeAccount": "78pq8j6Ku9u6GUTNwKaiWu9Ke7H3AC4AA5GtZ672HZzS", + "reserve": "FC38fiikZwFvDt5zTjNtGfKd7LjaPz2uUAzLwKP5pRJY", + "mint": "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt" + }, + "poolTokenMint": "FTXdV5wFFhceKjcd1JRrRQTT2uB7ruMerAqbj2rj1Mz7", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "AB17JLiVcGQ6DjRy69Pz2ydMcaJW6kNZVX2ajWT4g1Ke", + "newPoolID": "wftt" + }, + { + "id": "srm", + "name": "wSRM_V1-SRM", + "tokens": [ + { + "symbol": "wSRM_V1", + "name": "Serum (Wormhole V1)", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "address": "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "decimals": 6, + "extensions": { + "coingeckoId": "serum", + "source": "wormhole-v1", + "currency": "SRM" + }, + "chainId": 101, + "tags": ["saber-market-srm", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "symbol": "SRM", + "name": "Serum", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "tags": ["saber-market-srm"], + "extensions": { + "website": "https://projectserum.com/", + "serumV3Usdc": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA", + "serumV3Usdt": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb", + "coingeckoId": "serum", + "waterfallbot": "https://bit.ly/SRMwaterfall", + "currency": "SRM" + } + } + ], + "tokenIcons": [ + { + "symbol": "wSRM_V1", + "name": "Serum (Wormhole V1)", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "address": "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "decimals": 6, + "extensions": { + "coingeckoId": "serum", + "source": "wormhole-v1", + "currency": "SRM" + }, + "chainId": 101, + "tags": ["saber-market-srm", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "symbol": "SRM", + "name": "Serum", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "tags": ["saber-market-srm"], + "extensions": { + "website": "https://projectserum.com/", + "serumV3Usdc": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA", + "serumV3Usdt": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb", + "coingeckoId": "serum", + "waterfallbot": "https://bit.ly/SRMwaterfall", + "currency": "SRM" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wSRM_V1", + "name": "Serum (Wormhole V1)", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "address": "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "decimals": 6, + "extensions": { + "coingeckoId": "serum", + "source": "wormhole-v1", + "currency": "SRM" + }, + "chainId": 101, + "tags": ["saber-market-srm", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "symbol": "SRM", + "name": "Serum", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "tags": ["saber-market-srm"], + "extensions": { + "website": "https://projectserum.com/", + "serumV3Usdc": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA", + "serumV3Usdt": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb", + "coingeckoId": "serum", + "waterfallbot": "https://bit.ly/SRMwaterfall", + "currency": "SRM" + } + } + ], + "currency": "SRM", + "lpToken": { + "symbol": "sbrwSRM_V1SRM", + "name": "wSRM_V1-SRM Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "SRMKjSJpBHJ5gSVTrimci49SnXc1LVkBi9TGF9RNYdp", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/srm", + "underlyingTokens": [ + "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" + ], + "source": "saber" + } + }, + "plotKey": "2MQ9cdqpb6NmG9ApgpRBJzyDj13eJ4VKBrwSVh44KTSd", + "swap": { + "config": { + "swapAccount": "TSMFNX73aQNb3yqszDKgyR8AVoigfbaD4cfLjjCYXZf", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "BdvYL4rH3CqJ6eX6d7iC4snNZZvJQXR67T8dHNTUeSmz" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "5mzVc2No73PtnXTdvMg7Q8iqy43dbHP5pCPbtev4X7sb", + "reserve": "C5uYkVHiFduEFq8S3fr4pgUS24oYj1sjZ8WW2cb4j8SU", + "mint": "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP" + }, + "tokenB": { + "adminFeeAccount": "9VbKkWpQofMKdEFnn4etBcwYnKBhNeBu43bcpENXPWbj", + "reserve": "3F5DPU5ScgHiFzePYUHZovvgh3uqmM5keNvbavx2ERqV", + "mint": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" + }, + "poolTokenMint": "SRMKjSJpBHJ5gSVTrimci49SnXc1LVkBi9TGF9RNYdp", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "2FfGDoqghYeaASLD7WbK16B4BCENGVrzxR6VQ21qZ4Xe" + }, + { + "id": "ibbtc", + "name": "wibBTC_V1-BTC", + "tokens": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (9 decimals)", + "address": "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H", + "decimals": 9, + "symbol": "sBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (9 decimals)", + "address": "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H", + "decimals": 9, + "symbol": "sBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": [ + "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E" + ], + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "symbol": "BTC", + "name": "Wrapped Bitcoin (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-btc"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "serumV3Usdc": "A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw", + "serumV3Usdt": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4", + "coingeckoId": "bitcoin", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrwibBTC_V1sBTC-9", + "name": "wibBTC_V1-BTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BADGsQo6rTxKZuqkY1kSoqhriQwZW3ZVgyPjgDk9mvyo", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ibbtc", + "underlyingTokens": [ + "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H" + ], + "source": "saber" + } + }, + "plotKey": "7mZ2N8BSXW5vM1vqxSqahe1ivvZqpChHQRzmBJAqLGmU", + "swap": { + "config": { + "swapAccount": "SAVEEos8BnE9mgk8fw6yV22Ffk1eeHEJq95ewErUhbT", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4PHvSwhw8Gz26UZfgSjDLx8JLpJnh2kpNCtssgGUKQFe" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "AyBzqBeuqbLUQeaVT5wv79FDJVNrYeembnq8QquaN1DT", + "reserve": "Fb2Zdo7hWjMmwMXn7589WbPbjVPxVUQgPgs5cAcStJm8", + "mint": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz" + }, + "tokenB": { + "adminFeeAccount": "DopfcYkSAz3TwgRG3c6cif9dQ31GELqhe4a1PoiNsLdW", + "reserve": "AjVXZPzFEShWkGASQv5dBp9r4Xf6V1mvyHWfH5UWF9c3", + "mint": "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H" + }, + "poolTokenMint": "BADGsQo6rTxKZuqkY1kSoqhriQwZW3ZVgyPjgDk9mvyo", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "9D8qdTZeYupfK51k2fAXmNTSUbkhzKz12VqUVm7Y9pwi", + "newPoolID": "ibbtc_ren" + }, + { + "id": "ibbtc_ren", + "name": "wibBTC_V1-renBTC", + "tokens": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1"] + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbrwibBTC_V1srenBTC-9", + "name": "wibBTC_V1-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BRENm9SgYJZuCxM4ZJiH6CmZqEBn4MLpD9cnBZDnJgeT", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ibbtc_ren", + "underlyingTokens": [ + "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + "plotKey": "4EiuieY6S4a8jAU7MVPzckVixbf3VFLgCyZxQFVYvwak", + "swap": { + "config": { + "swapAccount": "BDGnasCnLUuqHVpHipGuevAsnHuMsBLya1RZSo4ZzMUF", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "3rjYaVP4fkv4BVQsA7aaC7DZUdogkna7ACGaAhiuNYfi" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "2kRjLTbrnDASTtKhw1Yg1iv387PdmVSxDW5RsjE5wEuk", + "reserve": "CZ48nQQ6GK8Z7hGBPdxTtMzRBA8werj5iciVJnc3vz1S", + "mint": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz" + }, + "tokenB": { + "adminFeeAccount": "FkU119vLnKK39xb4aBFE95CQAKNHEgspHzevnHyqyWjb", + "reserve": "G3nLYyvP46npva5MEobZVJhcDNzJ4rrHgLGJMtvyad5c", + "mint": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + }, + "poolTokenMint": "BRENm9SgYJZuCxM4ZJiH6CmZqEBn4MLpD9cnBZDnJgeT", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "FP4zv3sQiVLgr5j2jV6cfvLeMi7ekVQL8tNSJuyUyhdy", + "newPoolID": "ibbtc_ren" + }, + { + "id": "webusd", + "name": "weBUSD-USDC", + "tokens": [ + { + "symbol": "weBUSD", + "name": "BUSD (ETH Wormhole)", + "address": "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX.png", + "extensions": { + "coingeckoId": "binance-usd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "weBUSD", + "name": "BUSD (ETH Wormhole)", + "address": "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX.png", + "extensions": { + "coingeckoId": "binance-usd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "weBUSD", + "name": "BUSD (ETH Wormhole)", + "address": "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX.png", + "extensions": { + "coingeckoId": "binance-usd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrweBUSDsUSDC-8", + "name": "weBUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "BSCNZ4GLnpZYv4BLk5edymk4qty8a6ZpiMbfvtv9gAzL", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/webusd", + "underlyingTokens": [ + "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "4RDTPgZiytiiP7orSnfrSpjjyLbKU1ogxir1Bb37233N", + "swap": { + "config": { + "swapAccount": "CZgvH45CRmoaNJ7395Ao5CQZXkGznvqU7cErD6n7FaRg", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "H73Bq4wPdkMyM1o1RCgFTrmCCTzkK7W9FrfyWWLZ8QCj" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "CfBhqWEx8uY79EQQWoQCnqCHJ6Uk6E2UR8UMekiJ8Pd3", + "reserve": "4ZjU6MKvtqVQKCiowhvZ5yUsPxXX5SCzd5eFsPeXyey5", + "mint": "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX" + }, + "tokenB": { + "adminFeeAccount": "5McQhgfC2v4AJhMdn5eRQWCXGufYYJDvwq43bhVQE2qY", + "reserve": "BsyWeshjDbCvjDojry3vKDCesmFRM37WtwcR4Kfgzqr", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "BSCNZ4GLnpZYv4BLk5edymk4qty8a6ZpiMbfvtv9gAzL", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "4RU52YmyNaxg1zPnzALtv3aeXJJZEy5uaRXaTC7bBSgA" + }, + { + "id": "weusdc", + "name": "weUSDC-USDC", + "tokens": [ + { + "symbol": "weUSDC", + "name": "USDC (ETH Wormhole)", + "address": "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM.png", + "extensions": { + "coingeckoId": "usd-coin", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "weUSDC", + "name": "USDC (ETH Wormhole)", + "address": "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM.png", + "extensions": { + "coingeckoId": "usd-coin", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "weUSDC", + "name": "USDC (ETH Wormhole)", + "address": "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM.png", + "extensions": { + "coingeckoId": "usd-coin", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrweUSDCUSDC", + "name": "weUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USDCgfM1psLGhAbx99iPA72mTySvUcVq33qhCJpm65c", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/weusdc", + "underlyingTokens": [ + "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + "plotKey": "5HtgDfdZwTgFrEZBZh5R1NHFn2ZNeA7StBsUvFpaXF6Z", + "swap": { + "config": { + "swapAccount": "GokA1R67GqSavkd15zR62QD68Tuc5AEfvjssntVDEbM8", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "7XFMgfxhDURuaPwhUkXAy6uQJCoC3HPpjiZBqcot57Ge" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "5WemKHzh1RjGjQGtp79yqP4yCEvmkNRcyN8qt9q6h46r", + "reserve": "3YB7hfpBdbQEuZqLGWVDpRPmeZWCUsrrWyqGXegnQ6Cg", + "mint": "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM" + }, + "tokenB": { + "adminFeeAccount": "AmextuVSihZJ2WzFxW9tzxYihaz54QLci7AxH5y3vNKr", + "reserve": "4DPCj6Z1DsG6HUtwSogBGqXEUxdEV5a8YVrrFtcnz7UW", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "poolTokenMint": "USDCgfM1psLGhAbx99iPA72mTySvUcVq33qhCJpm65c", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "7q5jvR4C6hFrC6tScyVKV4wE8km9koX537bud5iyw8ma" + }, + { + "id": "weusdt", + "name": "weUSDT-USDT", + "tokens": [ + { + "symbol": "weUSDT", + "name": "USDT (ETH Wormhole)", + "address": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1.png", + "extensions": { + "coingeckoId": "tether", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "weUSDT", + "name": "USDT (ETH Wormhole)", + "address": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1.png", + "extensions": { + "coingeckoId": "tether", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "weUSDT", + "name": "USDT (ETH Wormhole)", + "address": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1.png", + "extensions": { + "coingeckoId": "tether", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrweUSDTUSDT", + "name": "weUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USDTJZL2vH92K5QeCvQTTzvMXUYAdvk3v46CwZyfsue", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/weusdt", + "underlyingTokens": [ + "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + "plotKey": "7rM3yGbfGcfTpmyRwebwJd7s4qQ1H5MdVZj2RwkFqZnX", + "swap": { + "config": { + "swapAccount": "vEnkoSUkpnbRrPgBz9DfcVxNz67zjwMYBZraAvDDNSf", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2kHUshxGYQLo7RQYdLTM6MXZq118w3TA4oak9dCwsF6B" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "2VTaCqHxVokTWfAkvaU6mYzYwbHpU1xog9mtEFYjL6Vv", + "reserve": "DtfyjjsZ5yCjWKLKHvwsfXs5QVsmm1QkK6Sh9JZHoq1g", + "mint": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1" + }, + "tokenB": { + "adminFeeAccount": "Evjog1kmUo2xAS7RhZHoHrMbU1UNSy9gX9CwUpxo2TZJ", + "reserve": "e5wrKCRv4GhHvEf1VD5Pq2KGaZDXP77nTL612Jyrxwy", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + }, + "poolTokenMint": "USDTJZL2vH92K5QeCvQTTzvMXUYAdvk3v46CwZyfsue", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "yPKVUwQxzuJ57uxW9rAJF6D1xEEButVjvRBsuN7E77P" + }, + { + "id": "wusdk", + "name": "wUSDK-USDC", + "tokens": [ + { + "symbol": "wUSDK", + "name": "USDK (Wormhole)", + "address": "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F.png", + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wUSDK", + "name": "USDK (Wormhole)", + "address": "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F.png", + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wUSDK", + "name": "USDK (Wormhole)", + "address": "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F.png", + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwUSDKsUSDC-8", + "name": "wUSDK-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "USDKKmk1anWU1aEn6GJ6skL3ZvcB9CBAWVkmPGQEHtz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wusdk", + "underlyingTokens": [ + "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "2SEWtGYG9oAPh2f51S9kW6CpXgZrVZPSbz58zske1ztU", + "swap": { + "config": { + "swapAccount": "oKQqupM9E7SSLAFDLxr7mdsXP7znck9qRbVWc6Yyj7Q", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2pVd6wjKqvNbv5RdLoqaBGRxTMTsCKDV8jvgpYDL3AJX" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "ALK28jM7pXqm25iYNB6CWxXvm3caykezYRqed8FQASdL", + "reserve": "CMR2J7CwfW98RpvnJmFBgRFTLAd3x8CJ1SWs8Bt3uunG", + "mint": "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F" + }, + "tokenB": { + "adminFeeAccount": "3QXDu6FGcXu8S2P1LKqW2BsMzzBQQDysXi5ftZ9RkG88", + "reserve": "FmRb5jHfSNj1YWP3b45d9vvYuhwjp4om8bALmtqZpr1v", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "USDKKmk1anWU1aEn6GJ6skL3ZvcB9CBAWVkmPGQEHtz", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "4g9LQYZnLjvbDyzJJUtUKTdJfUeUe1j3wKKiGfXwUPJF" + }, + { + "id": "whusd", + "name": "wHUSD-USDC", + "tokens": [ + { + "symbol": "wHUSD", + "name": "HUSD (Wormhole)", + "address": "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw.png", + "extensions": { + "coingeckoId": "husd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wHUSD", + "name": "HUSD (Wormhole)", + "address": "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw.png", + "extensions": { + "coingeckoId": "husd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wHUSD", + "name": "HUSD (Wormhole)", + "address": "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw.png", + "extensions": { + "coingeckoId": "husd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwHUSDsUSDC-8", + "name": "wHUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "HUSzWddUQbavKn24cjozm65eps8rq9yhNn5edtTLWfdz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/whusd", + "underlyingTokens": [ + "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "BAeacYdQ9NDWqu4KbzYzTELZSEChg8J7FVVhtC7tSAaS", + "swap": { + "config": { + "swapAccount": "HuonfMR5ivyhX5BZBw2Lr3XuNPSTVrSmZMWgBZrMQJU2", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "Ah5zHaE8fA8GJCopa9jaCmxG8AVUtf7ae7V8znSFURAd" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "FBRf9fnrYjMQPoowt5ihWh8jdpNwU9sF1u2ANe8UYzC7", + "reserve": "E7L8CZxreEeNwM1V8DLxa8AZQKu6oday6RvXxRo27CHi", + "mint": "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw" + }, + "tokenB": { + "adminFeeAccount": "HgSVcTucDZr1WxeUQuqtGWhvwbphygisNkr86ikV4dju", + "reserve": "48UJw19Ra8y5Kv989DsXPrfUHeUWUbHvXZmdPBbGuHdz", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "HUSzWddUQbavKn24cjozm65eps8rq9yhNn5edtTLWfdz", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "41CPKcrV6XAJ7943hN7X9znsSaiN1DEGJNEncSA3hAPw" + }, + { + "id": "wdai", + "name": "wDAI-USDC", + "tokens": [ + { + "symbol": "wDAI", + "name": "DAI (Wormhole)", + "address": "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o.png", + "extensions": { + "coingeckoId": "dai", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wDAI", + "name": "DAI (Wormhole)", + "address": "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o.png", + "extensions": { + "coingeckoId": "dai", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wDAI", + "name": "DAI (Wormhole)", + "address": "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o.png", + "extensions": { + "coingeckoId": "dai", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwDAIsUSDC-8", + "name": "wDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "DAihWEjhBc8LEmV1rEekTaiC2zqE5ex7nEFkmoe1Ppp3", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wdai", + "underlyingTokens": [ + "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "jsDhpC6hBGrUqj7wMfrG1XwESTiZAsTskXcXHgwda3w", + "swap": { + "config": { + "swapAccount": "RajgMbScQbqqrdJfR2rxfHJiTiY5b6bJ2tTXiF4aPxy", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "7mupg3bhALz9TBNz8yWRroVpVqJMKDaJG1u1JasCebe8" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "B3u2djbwCbCn7jddeGWrwVKCF4hkuAnbvVNPaEJMtxDK", + "reserve": "725ZA3fhyBAKb5iopNRrcbSuQa3LXA1656Q3mBvYPxeg", + "mint": "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o" + }, + "tokenB": { + "adminFeeAccount": "Hmd4AoqtDg4bNyQJ11HUVvvM6bktdu84CPgVndrA7CbM", + "reserve": "GLA6i1dSZpUfk2f9Nj8uiG7xyDdAR2reff8oEiZH3617", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "DAihWEjhBc8LEmV1rEekTaiC2zqE5ex7nEFkmoe1Ppp3", + "initialAmpFactor": "02ee", + "targetAmpFactor": "02ee", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "Gr1ijg84eQWG9zsf5PaBpjrNy4iEzP6hBpUgqxWkpb9y" + }, + { + "id": "wfrax", + "name": "wFRAX-USDC", + "tokens": [ + { + "symbol": "wFRAX", + "name": "FRAX (Wormhole)", + "address": "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp.png", + "extensions": { + "coingeckoId": "frax", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wFRAX", + "name": "FRAX (Wormhole)", + "address": "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp.png", + "extensions": { + "coingeckoId": "frax", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wFRAX", + "name": "FRAX (Wormhole)", + "address": "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp.png", + "extensions": { + "coingeckoId": "frax", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwFRAXsUSDC-8", + "name": "wFRAX-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "FRXsjEv4jF3r72FgbCXu8uLbPoZGLmCmg3EN1S3cfC4x", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wfrax", + "underlyingTokens": [ + "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + "plotKey": "BRsZ18RRzAejVEgHRzponHQKZ6sc5kPEJpZxLL2xTYTx", + "swap": { + "config": { + "swapAccount": "KZMu9QBEdL97EFgamLbN5DE1AoKBG4HdrKKJo4Vuon3", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4DEzAUzQCmvd4LsxcQ8X2tjNH89wGby8g9UXqZB55Mwp" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "BeZRrf1CFujZ1ARG7ejQNmbv6cTDRAGAnuFf53DsSaPB", + "reserve": "DD4ACBWyVqdaKWh7v5btsgqQCCMwbKnuL2xd9pv7Gyuw", + "mint": "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp" + }, + "tokenB": { + "adminFeeAccount": "3ArtibkBij4tFNsziDYxn5qNrMxeBH6tVowbkKuUdh2q", + "reserve": "GcYqiJ1MAuYbNTNYXm1F1gQYTfTgkErACAhNU5dpVj9T", + "mint": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + }, + "poolTokenMint": "FRXsjEv4jF3r72FgbCXu8uLbPoZGLmCmg3EN1S3cfC4x", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "CEtNcKPEirS6Ns2znGmvk1Z2UYQBt5GJabwhtrv28UEN" + }, + { + "id": "wheth", + "name": "ETH-whETH", + "tokens": [ + { + "name": "Saber Wrapped Wrapped Ethereum (Sollet) (8 decimals)", + "address": "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj", + "decimals": 8, + "symbol": "sETH-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-eth", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk" + ], + "currency": "ETH" + } + }, + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2"] + } + ], + "tokenIcons": [ + { + "name": "Saber Wrapped Wrapped Ethereum (Sollet) (8 decimals)", + "address": "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj", + "decimals": 8, + "symbol": "sETH-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-eth", + "saber-decimal-wrapped" + ], + "extensions": { + "assetContract": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk" + ], + "currency": "ETH" + } + }, + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2"] + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "symbol": "ETH", + "name": "Wrapped Ethereum (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-eth"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "serumV3Usdc": "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX", + "serumV3Usdt": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF", + "coingeckoId": "ethereum", + "currency": "ETH" + } + }, + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2"] + } + ], + "currency": "ETH", + "lpToken": { + "symbol": "sbrsETH-8whETH", + "name": "ETH-whETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "WTHPuMavN9HBvgUafjrL65WqQytQHDwnTAmdFB9whXA", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wheth", + "underlyingTokens": [ + "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj", + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "source": "saber" + } + }, + "plotKey": "ZRzURa4wGCcwaFmuaeB2tRL2tMMQ5epfpcq9HjJK27E", + "swap": { + "config": { + "swapAccount": "wrmcMSHFi3sWpAEy4rGDvQb3ezh3PhXoV2xNjgLBkKU", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2ctAWUAM4FUWp3iaHC5kEGktx4ThtC4tfnUEXQzxjFKm" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "DgFmPGKTHjZPiPQQVpKrJWXia3S2uZ9zxRAiPqg8YPNy", + "reserve": "8p4mGnARWPBu6P2Gcw4adodLkNDorrXniZMhaY7eJXUn", + "mint": "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj" + }, + "tokenB": { + "adminFeeAccount": "4Effb2Wr3zJJnGLfmvXQZRHGUQcK99kVqdaNqWXHGwWV", + "reserve": "AyY89zccbsgp9LKFgH4CFZMJmpSgqmPSdTfukhP227rc", + "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + }, + "poolTokenMint": "WTHPuMavN9HBvgUafjrL65WqQytQHDwnTAmdFB9whXA", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "4acW2PzwAxoZLSxj6w33Wvn2Af79bwCdJLdJGPY6HD3x" + }, + { + "id": "wftt", + "name": "aeFTT-wFTT", + "tokens": [ + { + "chainId": 101, + "address": "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "symbol": "aeFTT", + "name": "Wrapped FTT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf.png", + "tags": ["saber-market-ftt"], + "extensions": { + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped FTT (Wormhole) (9 decimals)", + "address": "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx", + "decimals": 9, + "symbol": "swFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "tags": ["saber-market-ftt", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "coingeckoId": "ftx-token", + "underlyingTokens": [ + "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv" + ], + "currency": "FTT" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "symbol": "aeFTT", + "name": "Wrapped FTT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf.png", + "tags": ["saber-market-ftt"], + "extensions": { + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped FTT (Wormhole) (9 decimals)", + "address": "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx", + "decimals": 9, + "symbol": "swFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "tags": ["saber-market-ftt", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "coingeckoId": "ftx-token", + "underlyingTokens": [ + "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv" + ], + "currency": "FTT" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "symbol": "aeFTT", + "name": "Wrapped FTT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf.png", + "tags": ["saber-market-ftt"], + "extensions": { + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "allbridge" + } + }, + { + "symbol": "wFTT", + "name": "FTT (Wormhole)", + "address": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "extensions": { + "coingeckoId": "ftx-token", + "source": "wormhole-v2", + "currency": "FTT" + }, + "chainId": 101, + "tags": ["saber-market-ftt", "wormhole-v2"] + } + ], + "currency": "FTT", + "lpToken": { + "symbol": "sbraeFTTswFTT-9", + "name": "aeFTT-wFTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FTXjwjwWqituSXEHnL5VF1mjDhZoAyJqvHiRPsRq3KWK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wftt", + "underlyingTokens": [ + "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx" + ], + "source": "saber" + } + }, + "plotKey": "5EvyWjr7gd89AA5TfzMzZ7bxyhVcY21nwrewFu1CefXJ", + "swap": { + "config": { + "swapAccount": "TSMUBiuZXEpnqd6X21JRskACqA6oad7MsYddvLbxwDH", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "EUVmhYUvcyv4kzRE5heLtsPQWVX47RqGPeX2VMNwVSua" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "2uVQ3f2k6oC8X2o5T7mkmQy3DqAYzLUct7wv54WuY5NE", + "reserve": "CyFL8noR6CKMFBjZPziSmChCKJE1Jtg4hQyMhgWPgH84", + "mint": "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf" + }, + "tokenB": { + "adminFeeAccount": "7oDdgg2z36LL5vt5rpLm72vnvqJMTu4EA9s5WHk5NKBR", + "reserve": "5PfKQpBWUayFWSSkwjp1PLr8LiHBrvVB81qfnSP5Dvkb", + "mint": "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx" + }, + "poolTokenMint": "FTXjwjwWqituSXEHnL5VF1mjDhZoAyJqvHiRPsRq3KWK", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "2o1fDM9y6BbrwfaTxE7MaXxescKm5QeAeknrWscHprNG" + }, + { + "id": "wust", + "name": "wUST-USDC", + "tokens": [ + { + "symbol": "wUST", + "name": "UST (Wormhole)", + "address": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i.png", + "extensions": { + "coingeckoId": "terrausd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "wUST", + "name": "UST (Wormhole)", + "address": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i.png", + "extensions": { + "coingeckoId": "terrausd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wUST", + "name": "UST (Wormhole)", + "address": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i.png", + "extensions": { + "coingeckoId": "terrausd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrwUSTUSDC", + "name": "wUST-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USTCmQpbUGj5iTsXdnTYHZupY1QpftDZhLokSVk6UWi", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wust", + "underlyingTokens": [ + "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + "plotKey": "8sNgWmTK6SZwiZ294Hiy8feFtbtRMfj2KeJzrZBcKGM4", + "swap": { + "config": { + "swapAccount": "KwnjUuZhTMTSGAaavkLEmSyfobY16JNH4poL9oeeEvE", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "9osV5a7FXEjuMujxZJGBRXVAyQ5fJfBFNkyAf6fSz9kw" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "35rkTKhWZ7s7mMFafKikzLFAwWqyczJ4Wh9KpKg1kVuR", + "reserve": "J63v6qEZmQpDqCD8bd4PXu2Pq5ZbyXrFcSa3Xt1HdAPQ", + "mint": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i" + }, + "tokenB": { + "adminFeeAccount": "EX1XJauoedrAzhj1sC66U1h44SEipBWbmyoEuRwTfYsW", + "reserve": "BnKQtTdLw9qPCDgZkWX3sURkBAoKCUYL1yahh6Mw7mRK", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "poolTokenMint": "USTCmQpbUGj5iTsXdnTYHZupY1QpftDZhLokSVk6UWi", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "BYEUtsLjYAVHRiRR3Avjqnd2RQLRL8n933N52p9kSX2y" + }, + { + "id": "wluna", + "name": "wLUNA-renLUNA", + "tokens": [ + { + "symbol": "wLUNA", + "name": "Luna (Wormhole)", + "address": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W.png", + "extensions": { + "coingeckoId": "terra-luna", + "source": "wormhole-v2", + "currency": "LUNA" + }, + "chainId": 101, + "tags": ["saber-market-luna", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "symbol": "renLUNA", + "name": "renLUNA", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna"], + "extensions": { + "website": "https://renproject.io/", + "serumV3Usdc": "CxDhLbbM9uAA2AXfSPar5qmyfmC69NLj3vgJXYAsSVBT", + "currency": "LUNA" + } + } + ], + "tokenIcons": [ + { + "symbol": "wLUNA", + "name": "Luna (Wormhole)", + "address": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W.png", + "extensions": { + "coingeckoId": "terra-luna", + "source": "wormhole-v2", + "currency": "LUNA" + }, + "chainId": 101, + "tags": ["saber-market-luna", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "symbol": "renLUNA", + "name": "renLUNA", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna"], + "extensions": { + "website": "https://renproject.io/", + "serumV3Usdc": "CxDhLbbM9uAA2AXfSPar5qmyfmC69NLj3vgJXYAsSVBT", + "currency": "LUNA" + } + } + ], + "underlyingIcons": [ + { + "symbol": "wLUNA", + "name": "Luna (Wormhole)", + "address": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W.png", + "extensions": { + "coingeckoId": "terra-luna", + "source": "wormhole-v2", + "currency": "LUNA" + }, + "chainId": 101, + "tags": ["saber-market-luna", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "symbol": "renLUNA", + "name": "renLUNA", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna"], + "extensions": { + "website": "https://renproject.io/", + "serumV3Usdc": "CxDhLbbM9uAA2AXfSPar5qmyfmC69NLj3vgJXYAsSVBT", + "currency": "LUNA" + } + } + ], + "currency": "LUNA", + "lpToken": { + "symbol": "sbrwLUNArenLUNA", + "name": "wLUNA-renLUNA Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "LUN1p1dZwSBgTv1JSdn2apdUuLanHKtgNcnpDydVFTU", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wluna", + "underlyingTokens": [ + "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE" + ], + "source": "saber" + } + }, + "plotKey": "5zrQomindJeKFw37hm6xME1FU3teBXeUmmAiVVtiXafk", + "swap": { + "config": { + "swapAccount": "Joji2GxwqNyoXMUqi4z3P4KSSpx31nwcrxxtMSN6aey", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "ubiHGBrW6aYpYdAM4f1WVJjmiC8SAKFcG4hZe1Cksa6" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "F3jxvxorHa6rpykj2vnMdvx8PW8aTdufw6obyni9WGvh", + "reserve": "GgRnQyCm22UkHQME96CqJFpsqV93QJQsWMURdsQSSAdk", + "mint": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W" + }, + "tokenB": { + "adminFeeAccount": "55LZWm3esX3Fwc7wMPpJsatHBRrmbQGAeKq8GxN5MKYJ", + "reserve": "GrWSq33By3uf7Vqqfrscy7oJMuqGgZojd2KSzsxioxQL", + "mint": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE" + }, + "poolTokenMint": "LUN1p1dZwSBgTv1JSdn2apdUuLanHKtgNcnpDydVFTU", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "7DyHgSJ2KvjjreNFefeWxkPe8oucwbdtSb8LkjkVTarx" + }, + { + "id": "acusd", + "name": "acUSD-USDC", + "tokens": [ + { + "symbol": "acUSD", + "name": "Wrapped cUSD (Allbridge from Celo)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e.png", + "address": "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "celo-dollar", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "acUSD", + "name": "Wrapped cUSD (Allbridge from Celo)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e.png", + "address": "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "celo-dollar", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "acUSD", + "name": "Wrapped cUSD (Allbridge from Celo)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e.png", + "address": "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "celo-dollar", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbracUSDsUSDC-9", + "name": "acUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "cUSDDDBZRhpDW7eyUUPMuw6u1SiMnzu6i7movwf5jxk", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/acusd", + "underlyingTokens": [ + "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "JCu76hLaYzExth7DebfmSyiHV3V3BiYi7yajGBzyEq2w", + "swap": { + "config": { + "swapAccount": "moFiBS1R4Lf3E3zzmBPnw3NF4RhWPkhREFviuzUaCAU", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4RcgjJHEndsDpSniNaU392dnfjFZFGhwUT3Fd9rSEcMv" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "EPd5EG4BbwGYSqa4weWGpmEY8ShD9SFLcmQ8D13Vhe55", + "reserve": "44ALBrpqi9PfZkBRTvbtRNtFJGsatfY4QydjtMbFWq8S", + "mint": "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e" + }, + "tokenB": { + "adminFeeAccount": "7efqH89CNXDaHMNdyMEsE74NPFKPPPStCauogXzYwkKy", + "reserve": "2ho8qEEie1UjVwWNBo9y4oAJZMyMLx8zPj9bxyUz6uWs", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "cUSDDDBZRhpDW7eyUUPMuw6u1SiMnzu6i7movwf5jxk", + "initialAmpFactor": "c8", + "targetAmpFactor": "c8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "64Jh71Av4M5QaEyduDJigNsa4HUBwvT2cQyi5YBSp1e4" + }, + { + "id": "apusdt", + "name": "apUSDT-USDT", + "tokens": [ + { + "chainId": 101, + "address": "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "symbol": "apUSDT", + "name": "Wrapped USDT (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "symbol": "apUSDT", + "name": "Wrapped USDT (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "symbol": "apUSDT", + "name": "Wrapped USDT (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrapUSDTUSDT", + "name": "apUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PLYJZgSkcV8UXTWhTyf2WLCMeBoZum1Y4rXgXkoYiNj", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/apusdt", + "underlyingTokens": [ + "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + "plotKey": "Cfn325YmkHmxS6B1rb7WiVBqHRpod5dsafKS8noUTVxD", + "swap": { + "config": { + "swapAccount": "ALBpShJ9NLpE739pcHDF2LNVk5J4VYPvvKuV6jMPitZi", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4S8xo3PeKfs3kY7ecS2amiffJZ4WXCAceDhUseE11q5E" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "BKBj63Q8xReKi8Mhde1dPpyCNxHEuFjnFs27isrQPxUT", + "reserve": "D6d156U1bPiJpDYRLextvDRccgFk7QzkQbJg42ceKEu5", + "mint": "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ" + }, + "tokenB": { + "adminFeeAccount": "2bK5UWmXmP94qj9wTaA8BNt9MWaceLDhBcaARLnWNtpr", + "reserve": "GNcM8U5g8RwTZffwwm3kSQqUNPSwv5d2fbN9sLxPJqt2", + "mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + }, + "poolTokenMint": "PLYJZgSkcV8UXTWhTyf2WLCMeBoZum1Y4rXgXkoYiNj", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "9ngf7fgxie59xAbEcMZF27QbcVgRea88RbXqjFccE4dA" + }, + { + "id": "apusdc", + "name": "apUSDC-USDC", + "tokens": [ + { + "chainId": 101, + "address": "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "symbol": "apUSDC", + "name": "Wrapped USDC (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "symbol": "apUSDC", + "name": "Wrapped USDC (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "symbol": "apUSDC", + "name": "Wrapped USDC (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrapUSDCUSDC", + "name": "apUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "APUVVYA8Xf7T1PqLyDvNxLtwQ9rRDf3RUxfMttreVzHP", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/apusdc", + "underlyingTokens": [ + "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + "plotKey": "E8xah3btxSw7BcizxX7u5PF1fQfNL1jdZYNqKPgbAQ5p", + "swap": { + "config": { + "swapAccount": "AUSejoEUCh2DBs9mof6BNC8Pubs22w4cJa1ccV9h44R", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "8puxJXzTyPXRsQ9PH9niMeje9M83a2bzatTzHxBWjGoP" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "FvLrRpaBWenMvAR2v8NcxBdYV1W6a69VP2Zh3nVLrzZA", + "reserve": "4xkyD7AfGQDwsQMy9MosRRNGoVJ1zosNinesxg1TzHG7", + "mint": "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca" + }, + "tokenB": { + "adminFeeAccount": "VVrMwjEBLTDaomPij4pStWhBy8GeykqJSzFZB2ZwKyx", + "reserve": "Cbm5gsQe3nSiCsFrvepVzMCD9xLK3ri9g3ypS2hdmVJ7", + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "poolTokenMint": "APUVVYA8Xf7T1PqLyDvNxLtwQ9rRDf3RUxfMttreVzHP", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "3eNV6vLGDm9z8vKspbddEUvVcwcuHsFz5mBmVaRqYHqQ" + }, + { + "id": "mai", + "name": "MAI-USDC", + "tokens": [ + { + "symbol": "MAI", + "name": "MAI (miMATIC)", + "logoURI": "https://registry.saber.so/token-icons/mai.svg", + "address": "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "decimals": 9, + "extensions": { + "coingeckoId": "mimatic", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "MAI", + "name": "MAI (miMATIC)", + "logoURI": "https://registry.saber.so/token-icons/mai.svg", + "address": "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "decimals": 9, + "extensions": { + "coingeckoId": "mimatic", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "MAI", + "name": "MAI (miMATIC)", + "logoURI": "https://registry.saber.so/token-icons/mai.svg", + "address": "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "decimals": 9, + "extensions": { + "coingeckoId": "mimatic", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrMAIsUSDC-9", + "name": "MAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "MAiP3Zmjhc6NYiCb2xK2893ifvTTDHciCS57Kga39pC", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/mai", + "underlyingTokens": [ + "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "3c9AVNMwXanJ3CJSimTrDohMuHixPdensTCTJfGhKiyU", + "swap": { + "config": { + "swapAccount": "DFWzVTzNBrQZLjoQ4yQwkj9HuoXmfkuZP9V5vbymZe23", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "41gN5ZP7inB3rgqQikVxWcfofyYsxH4zWQBRWyJrdZCy" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "EjhrU14XgkHBmJpKLnZjy3QDM8bYu4pAoau2rvnm7axN", + "reserve": "DhgiEgiNdqZdRdo195UUHnoEJUtefK8buko8nU97XJUZ", + "mint": "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7" + }, + "tokenB": { + "adminFeeAccount": "CC3i4qg2sW9QvKH92YHfV52gGLguRr1FJ4aTyNnm4EzZ", + "reserve": "2K2kkXsouBHtWVjtcgkyiXd8eP3oVBvq1bTJzcVdLLr2", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "MAiP3Zmjhc6NYiCb2xK2893ifvTTDHciCS57Kga39pC", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "GfhJbBiT4Yfv3FvnGY42EBGReCJpbLgQZbaZ8KuPydK5" + }, + { + "id": "abbusd", + "name": "abBUSD-USDC", + "tokens": [ + { + "symbol": "abBUSD", + "name": "Wrapped BUSD (Allbridge from BSC)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF.png", + "address": "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "binance-usd", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "symbol": "abBUSD", + "name": "Wrapped BUSD (Allbridge from BSC)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF.png", + "address": "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "binance-usd", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "symbol": "abBUSD", + "name": "Wrapped BUSD (Allbridge from BSC)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF.png", + "address": "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "binance-usd", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrabBUSDsUSDC-9", + "name": "abBUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BUSDjE9NEQ15aRFTxKFAjUf5vzqBhEgTNbYevWcSB5qp", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/abbusd", + "underlyingTokens": [ + "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "7JxiFeTn7UrUbmHPvzgUQNz4YupH1GZXGa7Z4qTy2zPT", + "swap": { + "config": { + "swapAccount": "BSCNHw8CmxbZ8hCWWzMf5FaG2ajPT6JxfznKUNuRVrn4", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "ASniuTkvzrbk5gpV4mkibbFvyzL62ACdDBmLqeHxpvaB" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "2SRUBK2GjsJcpgLP1F5xaiPWPRGnREjVVs9WsuH797TS", + "reserve": "7n9kgNL633yGzCYksgnzHyjG73ZLBAwvEae5sbAekbzW", + "mint": "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF" + }, + "tokenB": { + "adminFeeAccount": "GxCcf7gndszNN2BBESWjTdFmEYXVhXBQyBnVL5ALE3Mo", + "reserve": "SscdxE99omdG9AiMVwmaEMQySQnCgeAh1sPj2Leutm2", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "BUSDjE9NEQ15aRFTxKFAjUf5vzqBhEgTNbYevWcSB5qp", + "initialAmpFactor": "96", + "targetAmpFactor": "96", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "8RA7KUL1KJaN2hgexWy6iST8XdqVv2FapB6LbkQZdkfT" + }, + { + "id": "aeeth", + "name": "whETH-aeWETH", + "tokens": [ + { + "name": "Saber Wrapped Ether (Wormhole) (9 decimals)", + "address": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "decimals": 9, + "symbol": "swhETH-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "tags": ["saber-market-eth", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "currency": "ETH" + } + }, + { + "chainId": 101, + "address": "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE", + "symbol": "aeWETH", + "name": "Wrapped ETH (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE.png", + "tags": ["stablecoin", "saber-market-eth"], + "extensions": { + "coingeckoId": "weth", + "currency": "ETH", + "source": "allbridge" + } + } + ], + "tokenIcons": [ + { + "name": "Saber Wrapped Ether (Wormhole) (9 decimals)", + "address": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "decimals": 9, + "symbol": "swhETH-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "tags": ["saber-market-eth", "wormhole-v2", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "coingeckoId": "ethereum", + "underlyingTokens": [ + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "currency": "ETH" + } + }, + { + "chainId": 101, + "address": "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE", + "symbol": "aeWETH", + "name": "Wrapped ETH (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE.png", + "tags": ["stablecoin", "saber-market-eth"], + "extensions": { + "coingeckoId": "weth", + "currency": "ETH", + "source": "allbridge" + } + } + ], + "underlyingIcons": [ + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2"] + }, + { + "chainId": 101, + "address": "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE", + "symbol": "aeWETH", + "name": "Wrapped ETH (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE.png", + "tags": ["stablecoin", "saber-market-eth"], + "extensions": { + "coingeckoId": "weth", + "currency": "ETH", + "source": "allbridge" + } + } + ], + "currency": "ETH", + "lpToken": { + "symbol": "sbrswhETH-9aeWETH", + "name": "whETH-aeWETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AET3m1Mp2SLi7QX3tSypcZWyEtk1d8dUGcwhweDiZdaR", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeeth", + "underlyingTokens": [ + "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE" + ], + "source": "saber" + } + }, + "plotKey": "4seZECHzhFXAF3JjQ3UnV4v6PuYqSFe7PqhJgns1mchW", + "swap": { + "config": { + "swapAccount": "ALLwa5WNZbvQsRKAqe1jdRGemdYQnW8E5k4msbRFcYtu", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "4vKg9TxAmbmBQYvsv9StJYMwyDeWNyfVSumMzmemrXbZ" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "7K7c6yYjywBtCJ6VMwJYrEefgTSsLNhtQP8rbAQcfSed", + "reserve": "CGoM82Hbz7QVqTgvQatYtjvDWBvoUJp5qm21P8iMs9yb", + "mint": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma" + }, + "tokenB": { + "adminFeeAccount": "65LYdn1Youm9cfvB3nhsAUHubcJ9VZTKY9S8Mh2xffUA", + "reserve": "3dxNNww8TivyN5FMy5dCNdi3Zcxv6e4kUfSPxfFwpdNq", + "mint": "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE" + }, + "poolTokenMint": "AET3m1Mp2SLi7QX3tSypcZWyEtk1d8dUGcwhweDiZdaR", + "initialAmpFactor": "0320", + "targetAmpFactor": "0320", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "8vUrnWpraPY42kucjF5xYuFHx34Tn83b7X9TZRhx95Xd" + }, + { + "id": "aeusdc", + "name": "aeUSDC-USDC", + "tokens": [ + { + "chainId": 101, + "address": "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "symbol": "aeUSDC", + "name": "Wrapped USDC (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "symbol": "aeUSDC", + "name": "Wrapped USDC (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "symbol": "aeUSDC", + "name": "Wrapped USDC (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraeUSDCsUSDC-9", + "name": "aeUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AECpyKJWfXVyWnk2d9md5dUj3RuzHRKfQra8MakjuVRz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeusdc", + "underlyingTokens": [ + "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "3MVjy3pn7wu3gQz5pd9GoJZwCKX1XVaikyiPLjzNTvZF", + "swap": { + "config": { + "swapAccount": "WiiQFxXqp8P9Fs1rt9SVcDyA3KUdJmUsog6wnVQAwba", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "7SYgkmnr9nFhDw9BUxFc2x2ZwY98NYfvMtMLcK67vknQ" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "AcHQUvVEGsXnWk8KmcupRbchxccyPHBN6pm4gA4qNPws", + "reserve": "3S2jpqSYxho6wiz3cafhjXruVY8cg4HYa2v3BqBHYFSj", + "mint": "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9" + }, + "tokenB": { + "adminFeeAccount": "FE6JgtYW8CSK12ehEXj5oXtngkZWotVekHejYS9mqbZV", + "reserve": "Dfe67M6TfrG74gKRtdqys4e6E9QirPD53BkF4FujFjuv", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "AECpyKJWfXVyWnk2d9md5dUj3RuzHRKfQra8MakjuVRz", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "HvHCpbooc8xtvAhW5gfByfhJgNbS2G265ecQT1VBBGGw" + }, + { + "id": "aeusdt", + "name": "aeUSDT-USDT", + "tokens": [ + { + "chainId": 101, + "address": "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "symbol": "aeUSDT", + "name": "Wrapped USDT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USDT (9 decimals)", + "address": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV", + "decimals": 9, + "symbol": "sUSDT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "symbol": "aeUSDT", + "name": "Wrapped USDT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USDT (9 decimals)", + "address": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV", + "decimals": 9, + "symbol": "sUSDT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "symbol": "aeUSDT", + "name": "Wrapped USDT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraeUSDTsUSDT-9", + "name": "aeUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "aeTwxcJhujVCq6rwbJri3s6ViYifsJUCFirMjLHgHZ7", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeusdt", + "underlyingTokens": [ + "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + ], + "source": "saber" + } + }, + "plotKey": "EXqaPwXQMvzuVLEK3NvMqyuaSWQ6PvDvFGmCmZKGun9y", + "swap": { + "config": { + "swapAccount": "TETFuZgEmET6d9vYmz9iMFCxK1jKdKYxf3oPz8hSzJK", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "3LKrJtagDoUGTCzHA6JC18uWwAoMWDbDDKGrz3q46wZV" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "BWZUe1ghLSFWXtPkBdErat13uxVZvqG5eB8Up7aDQYUv", + "reserve": "Eu1Ze6Z84LWNkq7SNoAHBJbXx5t1P95EwyqYohrfCRFV", + "mint": "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn" + }, + "tokenB": { + "adminFeeAccount": "57sLf9VC946nXgTw5g2NikKJSfeTpAdsucVstVgcGmAp", + "reserve": "ASMbv7WNu3qe4pkhREmvyEdwFNDZQ6B6iSrjkj4u8bdC", + "mint": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + }, + "poolTokenMint": "aeTwxcJhujVCq6rwbJri3s6ViYifsJUCFirMjLHgHZ7", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "CVjYK5fk69mj2k8ma1yCnvFzzDFGL6fziwEaxberD7n1" + }, + { + "id": "aedai", + "name": "aeDAI-USDC", + "tokens": [ + { + "chainId": 101, + "address": "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "symbol": "aeDAI", + "name": "Wrapped DAI (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "symbol": "aeDAI", + "name": "Wrapped DAI (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "currency": "USD", + "source": "allbridge" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "symbol": "aeDAI", + "name": "Wrapped DAI (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraeDAIsUSDC-9", + "name": "aeDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "aeDebgky5BssqgLo426rXoQTmGrAn1JjEXp6aXFNLic", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aedai", + "underlyingTokens": [ + "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "BmnFpVtEmYAugDUtwZRxSN2sDyFiaRRWnU1rMGruk1sn", + "swap": { + "config": { + "swapAccount": "DA1mL7dfBow33EH5yjUuZE6SH2KeUK5zEXzsyhE5r5j5", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "99Y7Y3DwALuxnbe9YkHJNK5ReLswTq1dwaEs4yRPv7ad" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "3tknZfydUcTyRjD62R6PS6bSKWRBAYT8HdgPXShio7kW", + "reserve": "B4UtLRZND3D83CitRCBUyeiNr7H8m7YFRqBmiPmXjhW4", + "mint": "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs" + }, + "tokenB": { + "adminFeeAccount": "DUizF8snVNbv3yWwkr68es4wzv3U5RnA6niLqNPhcT31", + "reserve": "AAmpYbuixWiSqDaJBCqYEs1pb5SrbdPr9mgXrB58Q2e", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "aeDebgky5BssqgLo426rXoQTmGrAn1JjEXp6aXFNLic", + "initialAmpFactor": "c8", + "targetAmpFactor": "c8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "GBkRWmWtDtHwnjhhDXGuHTDbVNQAs14nbUkwNpWXE2xp" + }, + { + "id": "aausdc", + "name": "aaUSDC-USDC", + "tokens": [ + { + "address": "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "symbol": "aaUSDC", + "name": "Wrapped USDC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "address": "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "symbol": "aaUSDC", + "name": "Wrapped USDC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "address": "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "symbol": "aaUSDC", + "name": "Wrapped USDC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "usd-coin", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraaUSDCsUSDC-9", + "name": "aaUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVC7uVb6R9B34T8zWxQMEK8twvYk26U71gworsujxFNv", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aausdc", + "underlyingTokens": [ + "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "E7FkwDpycCuU7Y9n8J1jnPTxYH5DXMis9gEP9sCEsazJ", + "swap": { + "config": { + "swapAccount": "KEVpg4SYCVrqFxKijQH6hm4QxsRdoE51ud64bEEFUC9", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "BHKzitonCpp1LBEZ2eqs3TZXB1wJxLbisS18LbE3aJos" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "6otgawUjCsDyFsjMto1Ky2Yb897B49Rzu4teniz2Kade", + "reserve": "2bPenuBYfqzD8p5dFjCcggwkHDsc2RQDnx9AoyYGUBjA", + "mint": "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr" + }, + "tokenB": { + "adminFeeAccount": "FVkTSvwhSHQdjWAJXc98XJ4xpv52g88D24gJ3FnsGmKt", + "reserve": "AE6n6iLuX8oYu61vt5hoDYkjswpnxFxrhSehxrnQTnbx", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "AVC7uVb6R9B34T8zWxQMEK8twvYk26U71gworsujxFNv", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "A8NH6Fk92PLzQAGwwAY3b2e9Zuryiisvu8WUceu95QK6" + }, + { + "id": "aausdt", + "name": "aaUSDT-USDT", + "tokens": [ + { + "address": "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "symbol": "aaUSDT", + "name": "Wrapped USDT (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USDT (9 decimals)", + "address": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV", + "decimals": 9, + "symbol": "sUSDT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "address": "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "symbol": "aaUSDT", + "name": "Wrapped USDT (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USDT (9 decimals)", + "address": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV", + "decimals": 9, + "symbol": "sUSDT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "address": "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "symbol": "aaUSDT", + "name": "Wrapped USDT (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "tether", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraaUSDTsUSDT-9", + "name": "aaUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVTrxHq5P57fYZTYjMuCRWFqsrLmom2gGThNtgEgK1ip", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aausdt", + "underlyingTokens": [ + "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + ], + "source": "saber" + } + }, + "plotKey": "EArh8iMeXdec81F6uir7yiVpkGf7amVA8RzScEq7ukZv", + "swap": { + "config": { + "swapAccount": "SEQV6QiTpvTzrceiaYg9Avnwf7oc8ZALpnQMz6TyT4A", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "EPtXq2vDbFdpxVUuS4qzh5dhnWHQeriNyz7Tvyott2DB" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "CQN3BwxS27NQMcQg6RmvcAm73QgZpBoEw3P7HyWrURwm", + "reserve": "3dFMPj4r9mMmPHF32mz1gNDX7f8ftidWof7S8YiYTs1j", + "mint": "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj" + }, + "tokenB": { + "adminFeeAccount": "HYEVa6PQ4j95Bv4WRuuyznhaetWEy6H1hpHSHomm4BrL", + "reserve": "Fc1ED3YPeQfXQosHbd8DTaBd2AV5gHQtwiSAL5C5pUhi", + "mint": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + }, + "poolTokenMint": "AVTrxHq5P57fYZTYjMuCRWFqsrLmom2gGThNtgEgK1ip", + "initialAmpFactor": "03e8", + "targetAmpFactor": "03e8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "9chpeWW6MAjYZrgSB1AHRmJXTzBHKW2agbxqF4qMWNvz" + }, + { + "id": "aadai", + "name": "aaDAI-USDC", + "tokens": [ + { + "address": "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "symbol": "aaDAI", + "name": "Wrapped DAI (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "tokenIcons": [ + { + "address": "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "symbol": "aaDAI", + "name": "Wrapped DAI (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "currency": "USD" + } + } + ], + "underlyingIcons": [ + { + "address": "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "symbol": "aaDAI", + "name": "Wrapped DAI (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbraaDAIsUSDC-9", + "name": "aaDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVDuGckLavyLr5YifViaxnoveY6rwqDezHw5kiKiRQEC", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aadai", + "underlyingTokens": [ + "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + "plotKey": "3Ykczfea6RPUW5oVNiZr7UFn4y9799syMCyhRTiv5jCN", + "swap": { + "config": { + "swapAccount": "SQDY9uoDoCbNq7F3HAHYYQZbrUzpKxDtdWLvYcqBLak", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "J5DTF99jm9xjWwcDcMgYxRcsPT6ydE936Qry76oLKSXe" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "9AZg8dZxNZLLXMEokWnvzq7F6N7hNFQWMjNEBLXrRwVH", + "reserve": "GiehZ4X2uQjhEKuxSm3ACmEFrabxh3RTvjbLfLjw1xGX", + "mint": "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi" + }, + "tokenB": { + "adminFeeAccount": "CKt6LrLXqTWFPDCvt85ge5AMDh6ceEn3DwaVe7T8fEdm", + "reserve": "rxDQYV77NKRKYxCjq4cECX3QQsiSyLv1T1FT2X3zNA2", + "mint": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + }, + "poolTokenMint": "AVDuGckLavyLr5YifViaxnoveY6rwqDezHw5kiKiRQEC", + "initialAmpFactor": "c8", + "targetAmpFactor": "c8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "228rDhwVZrJkwgFiMq7AHp5L9PdHJS8p8Ta8z8DP4ABh" + }, + { + "id": "aawbtc", + "name": "aaWBTC-renBTC", + "tokens": [ + { + "address": "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "symbol": "aaWBTC", + "name": "Wrapped BTC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw.png", + "extensions": { + "coingeckoId": "wrapped-bitcoin", + "source": "allbridge", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc"] + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "tokenIcons": [ + { + "address": "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "symbol": "aaWBTC", + "name": "Wrapped BTC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw.png", + "extensions": { + "coingeckoId": "wrapped-bitcoin", + "source": "allbridge", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc"] + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5" + ], + "currency": "BTC" + } + } + ], + "underlyingIcons": [ + { + "address": "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "symbol": "aaWBTC", + "name": "Wrapped BTC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw.png", + "extensions": { + "coingeckoId": "wrapped-bitcoin", + "source": "allbridge", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc"] + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "sbraaWBTCsrenBTC-9", + "name": "aaWBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVBDpg1UYpDYQLbzEnRY76R3u82PYHtDuc3NBdFS2k39", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aawbtc", + "underlyingTokens": [ + "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + "plotKey": "AxFsScUmiukmLuwdToMCTbAWWCMisV3afc2XdptFttYU", + "swap": { + "config": { + "swapAccount": "SATqdxinH3vFiJBKv3JA6MuCXweEXr6UV1aRcvGjLD5", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "BQsXo2y2KFdPpF9v9ckb1BaxSQxqgx12WevS8BYnkYhY" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "2RcddGUJPE5bH7Ju98XWtQSwB7idmcoKqriY6aseQqVV", + "reserve": "bDVSLHopvEo45R3rR7JXoRRAcnUXKD7r14ijj1apALb", + "mint": "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw" + }, + "tokenB": { + "adminFeeAccount": "9qdRkD6J5x9d142kUknJ4NG29ELDXqSi25hidAJ5G8xt", + "reserve": "33QrhvkyroiTxp21hbXPRowPhsgVHmrgEQbY7eHPp1h4", + "mint": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + }, + "poolTokenMint": "AVBDpg1UYpDYQLbzEnRY76R3u82PYHtDuc3NBdFS2k39", + "initialAmpFactor": "c8", + "targetAmpFactor": "c8", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "6r6zEtUZghQmWsPpLFRwMvnoRja3SNDUaS72NUuQsFia" + }, + { + "id": "port_2pool", + "name": "pUSDT-pUSDC", + "tokens": [ + { + "symbol": "pUSDT", + "name": "Port USDT", + "logoURI": "https://registry.saber.so/token-icons/usdt.svg", + "address": "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "decimals": 6, + "extensions": { + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "symbol": "pUSDC", + "name": "Port USDC", + "logoURI": "https://registry.saber.so/token-icons/usdc.svg", + "address": "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58", + "decimals": 6, + "extensions": { + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + } + ], + "tokenIcons": [ + { + "symbol": "pUSDT", + "name": "Port USDT", + "logoURI": "https://registry.saber.so/token-icons/usdt.svg", + "address": "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "decimals": 6, + "extensions": { + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "symbol": "pUSDC", + "name": "Port USDC", + "logoURI": "https://registry.saber.so/token-icons/usdc.svg", + "address": "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58", + "decimals": 6, + "extensions": { + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + } + ], + "underlyingIcons": [ + { + "symbol": "pUSDT", + "name": "Port USDT", + "logoURI": "https://registry.saber.so/token-icons/usdt.svg", + "address": "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "decimals": 6, + "extensions": { + "coingeckoId": "tether", + "underlyingTokens": [ + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + }, + { + "symbol": "pUSDC", + "name": "Port USDC", + "logoURI": "https://registry.saber.so/token-icons/usdc.svg", + "address": "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58", + "decimals": 6, + "extensions": { + "coingeckoId": "usd-coin", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd"] + } + ], + "currency": "USD", + "lpToken": { + "symbol": "sbrpUSDTpUSDC", + "name": "pUSDT-pUSDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PortuzxBGYMQXeNmM9Kc6AtHLBwqSrb6xWwZ4trQ1en", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-port"], + "extensions": { + "website": "https://app.saber.so/#/pools/port_2pool", + "underlyingTokens": [ + "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58" + ], + "source": "saber" + } + }, + "plotKey": "8cHGxmX41wR3QiTtJCLM4Nx4gQuuGq2DUiNDeMv5RCVe", + "swap": { + "config": { + "swapAccount": "LeonQMdt2TUm3PL358Ny1fa7XEFwMrL4VWheT5PNCVW", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "H9zLktv9iSJz7fMopV4HXKfx4Bi2UDhR7QJ9MjQ6Hej8" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "HJgm8StULdtdGyKPoecFdDxtWWSVBiBjk88XPmqdpuNa", + "reserve": "2tBpLvzYBCs7e4DrRocEnssoJANB8rDh2jt5FbeCAeK1", + "mint": "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ" + }, + "tokenB": { + "adminFeeAccount": "CbxHVoM2NbgAfQGF9WsUHCdLqbkWwvZuouUCLBQg37qd", + "reserve": "DLkWGoaeXw4oeGPnr2ecJze541HVKFhrhaW4iVSFEwET", + "mint": "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58" + }, + "poolTokenMint": "PortuzxBGYMQXeNmM9Kc6AtHLBwqSrb6xWwZ4trQ1en", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "8fneZQffsCH7QmZBaUE6JUPpaNb5Y5ggbEJ7mq6w3UCu" + }, + { + "id": "msol_sol", + "name": "mSOL-SOL", + "tokens": [ + { + "chainId": 101, + "address": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "symbol": "mSOL", + "name": "Marinade staked SOL (mSOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.png", + "tags": ["saber-market-sol"], + "extensions": { + "coingeckoId": "msol", + "website": "https://marinade.finance", + "twitter": "https://twitter.com/MarinadeFinance", + "discord": "https://discord.gg/mGqZA5pjRN", + "medium": "https://medium.com/marinade-finance", + "github": "https://github.com/marinade-finance", + "serumV3Usdc": "6oGsL2puUgySccKzn9XA9afqF217LfxP5ocq4B3LWsjy", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "symbol": "mSOL", + "name": "Marinade staked SOL (mSOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.png", + "tags": ["saber-market-sol"], + "extensions": { + "coingeckoId": "msol", + "website": "https://marinade.finance", + "twitter": "https://twitter.com/MarinadeFinance", + "discord": "https://discord.gg/mGqZA5pjRN", + "medium": "https://medium.com/marinade-finance", + "github": "https://github.com/marinade-finance", + "serumV3Usdc": "6oGsL2puUgySccKzn9XA9afqF217LfxP5ocq4B3LWsjy", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "symbol": "mSOL", + "name": "Marinade staked SOL (mSOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.png", + "tags": ["saber-market-sol"], + "extensions": { + "coingeckoId": "msol", + "website": "https://marinade.finance", + "twitter": "https://twitter.com/MarinadeFinance", + "discord": "https://discord.gg/mGqZA5pjRN", + "medium": "https://medium.com/marinade-finance", + "github": "https://github.com/marinade-finance", + "serumV3Usdc": "6oGsL2puUgySccKzn9XA9afqF217LfxP5ocq4B3LWsjy", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrmSOLSOL", + "name": "mSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "SoLEao8wTzSfqhuou8rcYsVoLjthVmiXuEjzdNPMnCz", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/msol_sol", + "underlyingTokens": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + "plotKey": "3LGuvwvwLJdzsVJ324u4KSEBsJjNV3Xpu7DziXmwfqqu", + "swap": { + "config": { + "swapAccount": "Lee1XZJfJ9Hm2K1qTyeCz1LXNc1YBZaKZszvNY4KCDw", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "2Sj4MZvmLhud4uRmGHJvDxq612nmF4JJsU1R4ZjNNGMS" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "FVqXHUrVCWDVG6z4YFRaSDvVx29MZSMUG3QikEoHgg8r", + "reserve": "9DgFSWkPDGijNKcLGbr3p5xoJbHsPgXUTr6QvGBJ5vGN", + "mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" + }, + "tokenB": { + "adminFeeAccount": "4CzMd4Gy3My2bucNeJ7gmY1TvmXCAoYSeb769nab4Aet", + "reserve": "2hNHZg7XBhuhHVZ3JDEi4buq2fPQwuWBdQ9xkH7t1GQX", + "mint": "So11111111111111111111111111111111111111112" + }, + "poolTokenMint": "SoLEao8wTzSfqhuou8rcYsVoLjthVmiXuEjzdNPMnCz", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "7193EeecxsPPv9TMoQATTN8i1eTqEUSNU8aDLuFCQy68" + }, + { + "id": "psol", + "name": "pSOL-prtSOL", + "tokens": [ + { + "address": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "symbol": "pSOL", + "name": "pSOL (Parrot SOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX.svg", + "tags": ["stablecoin", "saber-market-sol"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "SOL" + }, + "chainId": 101 + }, + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + } + ], + "tokenIcons": [ + { + "address": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "symbol": "pSOL", + "name": "pSOL (Parrot SOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX.svg", + "tags": ["stablecoin", "saber-market-sol"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "SOL" + }, + "chainId": 101 + }, + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + } + ], + "underlyingIcons": [ + { + "address": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "symbol": "pSOL", + "name": "pSOL (Parrot SOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX.svg", + "tags": ["stablecoin", "saber-market-sol"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "SOL" + }, + "chainId": 101 + }, + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrpSOLprtSOL", + "name": "pSOL-prtSOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "PSopTFPXzTRysj2H6W8oTvYBZmJHtRcVaQaDkckifAy", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/psol", + "underlyingTokens": [ + "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3" + ], + "source": "saber" + } + }, + "plotKey": "ABW8Q3Bq71gDRfQPxtU29oMrHq6et8wdnNMWPbc9gnZr", + "swap": { + "config": { + "swapAccount": "parEAJhzYzHW87y9VoZAQ22dzQ2MD2ekEtTReaxbMcz", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "6rxqyX1fD27oepCCnv2uy9uJWmXHjPTeQ5PaP99JZrKx" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "A3Muu6G6A3hyjbrPyop9QCc8cE4gWVnZdKe1VGL3PASK", + "reserve": "3oyc6hpjYSJEquK6dSvG72NXyz3pqLZz5D8AC71pwgQb", + "mint": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX" + }, + "tokenB": { + "adminFeeAccount": "4QTw2UTMpZD8YvLRKtAxdoQ1JgZYxBK5WKpeFfKsEvA2", + "reserve": "Di2vordgFJVZ1aj1aBpvzXtLfZgJsuLLXawGtVFwX6Rz", + "mint": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3" + }, + "poolTokenMint": "PSopTFPXzTRysj2H6W8oTvYBZmJHtRcVaQaDkckifAy", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "8uEjJsJ5cCbz7m4K9jZEmQB6cL3SM3V2suc6fazkgWan" + }, + { + "id": "prtsol", + "name": "prtSOL-SOL", + "tokens": [ + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "tokenIcons": [ + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "underlyingIcons": [ + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol"] + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrprtSOLSOL", + "name": "prtSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "PrsVdKtXDDf6kJQu5Ff6YqmjfE4TZXtBgHM4bjuvRnR", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/prtsol", + "underlyingTokens": [ + "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + "plotKey": "Gp7AahF48oWdoUU4XVb9VK4zuA7p4rYw7qySGtiQV9xQ", + "swap": { + "config": { + "swapAccount": "heyk4SZTeCUV6a65WiVA5bzic3KcxgVf1qxobfeV4tQ", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "EKwQt98muCQJq4ghRFausCbMvyLhExS5g8CxvAFHike1" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "AX91QezedSBhh48ajj4rzc2KrYMomv1Djn1sbJ6Qz5Es", + "reserve": "9ZDpBKPqMABtGfq66FbVribaArMvtk63xxy91onZAtDt", + "mint": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3" + }, + "tokenB": { + "adminFeeAccount": "79WsCvyyHQSVFyUtgkcRHzkeneRaRshvptHwrb3ezuPb", + "reserve": "DaTP4s6N7xxSPinbNZ3FB52EpRDRt87QShTuN3VPCW52", + "mint": "So11111111111111111111111111111111111111112" + }, + "poolTokenMint": "PrsVdKtXDDf6kJQu5Ff6YqmjfE4TZXtBgHM4bjuvRnR", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "3LbfNHhqvJ86u1W8TzoRg8TcozXj7HUhGvZC5gMzwzQa" + }, + { + "id": "stsol", + "name": "stSOL-SOL", + "tokens": [ + { + "chainId": 101, + "address": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "symbol": "stSOL", + "name": "Lido Staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.lido.fi/", + "twitter": "https://twitter.com/LidoFinance/", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "symbol": "stSOL", + "name": "Lido Staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.lido.fi/", + "twitter": "https://twitter.com/LidoFinance/", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "symbol": "stSOL", + "name": "Lido Staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.lido.fi/", + "twitter": "https://twitter.com/LidoFinance/", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrstSOLSOL", + "name": "stSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "stSjCmjQ96BiGhTk8gkU22j1739R8YBQVMq7KXWTqUV", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/stsol", + "underlyingTokens": [ + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + "plotKey": "4zPQjwy9qAxfZT7ACyeaLe6uVNR4FUyCphGGveZCsueX", + "swap": { + "config": { + "swapAccount": "Lid8SLUxQ9RmF7XMqUA8c24RitTwzja8VSKngJxRcUa", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "8eyi347MTDeH5F6eVv2qjPxVnU685FFZLDGcj5QWHZ6y" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "H1tJ1y5CpVv6vxQ3C3jzLixYK6TqjvzaneLhUE2kaapu", + "reserve": "4PgzyzLtds9bKZ2to9PMnKqJzKEUpjvNUaeN23phegax", + "mint": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj" + }, + "tokenB": { + "adminFeeAccount": "AnEme7S1UWtUobCD2X4mgBkvHiYBGpJmJqURiGbxoUpm", + "reserve": "AtymwxoVN9peZo7EXTcDz9jKVc4vRmisJKKrNfe3ewBa", + "mint": "So11111111111111111111111111111111111111112" + }, + "poolTokenMint": "stSjCmjQ96BiGhTk8gkU22j1739R8YBQVMq7KXWTqUV", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "2isVhcqVVpsAeZ94d1DGVErCQ7uZQNSJ85xikN8awHeC" + }, + { + "id": "socean", + "name": "scnSOL-SOL", + "tokens": [ + { + "chainId": 101, + "address": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "symbol": "scnSOL", + "name": "Socean staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm.png", + "tags": ["stake-pool", "saber-market-sol"], + "extensions": { + "discord": "https://discord.gg/k8ZcW27bq9/", + "medium": "https://medium.com/@soceanfinance/", + "twitter": "https://twitter.com/soceanfinance/", + "website": "https://socean.fi/", + "coingeckoId": "socean-staked-sol", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "tokenIcons": [ + { + "chainId": 101, + "address": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "symbol": "scnSOL", + "name": "Socean staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm.png", + "tags": ["stake-pool", "saber-market-sol"], + "extensions": { + "discord": "https://discord.gg/k8ZcW27bq9/", + "medium": "https://medium.com/@soceanfinance/", + "twitter": "https://twitter.com/soceanfinance/", + "website": "https://socean.fi/", + "coingeckoId": "socean-staked-sol", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "underlyingIcons": [ + { + "chainId": 101, + "address": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "symbol": "scnSOL", + "name": "Socean staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm.png", + "tags": ["stake-pool", "saber-market-sol"], + "extensions": { + "discord": "https://discord.gg/k8ZcW27bq9/", + "medium": "https://medium.com/@soceanfinance/", + "twitter": "https://twitter.com/soceanfinance/", + "website": "https://socean.fi/", + "coingeckoId": "socean-staked-sol", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + } + ], + "currency": "SOL", + "lpToken": { + "symbol": "sbrscnSOLSOL", + "name": "scnSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "SoCJs5Qw1D3fjGbTqxxovK15FVnYVrwvTbYcBBrZmWj", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/socean", + "underlyingTokens": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + "plotKey": "aGQEdwDWCj28SppCwBev18XyRqeQ6EHGJ51QYC8HdG3", + "swap": { + "config": { + "swapAccount": "FrnkTEL6yZ5Aycq2TrEgu29dCrwKG4LPUZydfiwqspkc", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "3roMZdhjqzKF5HaZxF8a3G8bavz8EggQXFMNiPsBnMQz" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "QXpeCNTZvRJi3dWB2TifiRb45LCKzjNHeRV6MuzmbxA", + "reserve": "DjvZuMZ46fxKBoBpGMPcRcos4xdTtHFxwMh69DpA2ZFY", + "mint": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm" + }, + "tokenB": { + "adminFeeAccount": "4FPdCQCjhD7DZUFLuSN92KdyA1tPAgwZ713X9y4TAaf1", + "reserve": "9rvDPJLAng4uVVqnnSoEXgNDFhBZQTny9AoQioYDZbQX", + "mint": "So11111111111111111111111111111111111111112" + }, + "poolTokenMint": "SoCJs5Qw1D3fjGbTqxxovK15FVnYVrwvTbYcBBrZmWj", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "75gshB42wbVJtFyF1PUULR6M4fegdqPLasG3HLGtTwRQ" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms_dev.json b/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms_dev.json new file mode 100644 index 00000000000..f5db28fbaef --- /dev/null +++ b/farms/farm-ctrl/src/metadata/pools/saber/pools_and_farms_dev.json @@ -0,0 +1,431 @@ +{ + "addresses": { + "landlord": "B38L5x5EszUK4iqcNMAZRyaJx8ie8cgGvxxbYmkWkjZe", + "landlordBase": "GHxgjDJgpUkugb2cPvaKQSbkdoHYHCHo2ZZsRFiFt4YL", + "rewarder": "rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk", + "mintWrapper": "EVVDA3ZiAjTizemLGXNUN3gb6cffQFEYkFjFZokPmUPz", + "iouMint": "iouQcQBAiEXe6cKLS85zmZxUqaCqBdeHFpqKoSz615u", + "redeemer": "CL9wkGFT3SZRRNa9dgaovuRV7jrVVigBUZ6DjcgySsCU", + "sbr": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1" + }, + "pools": [ + { + "id": "usdc_usdt", + "name": "USDC-USDT", + "tokens": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "USDT", + "name": "Tether USD", + "logoURI": "/tokens/usdt.svg", + "address": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "tokenIcons": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "USDT", + "name": "Tether USD", + "logoURI": "/tokens/usdt.svg", + "address": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "underlyingIcons": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "USDT", + "name": "Tether USD", + "logoURI": "/tokens/usdt.svg", + "address": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "currency": "USD", + "lpToken": { + "symbol": "SLP", + "name": "USDC-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "YakofBo4X3zMxa823THQJwZ8QeoU8pxPdFdxJs7JW57", + "chainId": 103, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_usdt", + "underlyingTokens": [ + "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS" + ], + "source": "saber" + } + }, + "plotKey": "99CaY6yjPLJzAJU3y2qhuLMFcfoCof4tnbR21FrtiGJd", + "swap": { + "config": { + "swapAccount": "VeNkoB1HvSP6bSeGybQDnx9wTWFsQb2NBCemeCDSuKL", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "72E8LfHqoxQCxnxmBbDG6WSHnDx1rWPUHNKwYvoL5qDm" + }, + "state": { + "adminAccount": "GSmjrpT8zNtp6Ke8y2xS5P1kREEjqZCjwxF8VbxDJAV8", + "tokenA": { + "adminFeeAccount": "6RPzht581g8QLdKaT8CSuCnj9yBhR2u6mxKFFK6Dbhgx", + "reserve": "6aFutFMWR7PbWdBQhdfrcKrAor9WYa2twtSinTMb9tXv", + "mint": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8" + }, + "tokenB": { + "adminFeeAccount": "5Z4M2yHn6LUWaK9Ka8QqByM1NimGGRMdEk7roHoQbDb9", + "reserve": "HXbhpnLTxSDDkTg6deDpsXzJRBf8j7T6Dc3GidwrLWeo", + "mint": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS" + }, + "poolTokenMint": "YakofBo4X3zMxa823THQJwZ8QeoU8pxPdFdxJs7JW57", + "initialAmpFactor": "64", + "targetAmpFactor": "64", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "50.0000000000", + "numerator": "50", + "denominator": "100" + }, + "adminWithdraw": { + "formatted": "50.0000000000", + "numerator": "50", + "denominator": "100" + }, + "trade": { + "formatted": "0.2000000000", + "numerator": "20", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.5000000000", + "numerator": "50", + "denominator": "10000" + } + } + } + }, + "quarry": "8QfbpS8fBNcqee9qHjYG5pgBWTKyM193E7zjwzxeUZ3X" + }, + { + "id": "usdc_pai", + "name": "USDC-PAI", + "tokens": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "PAI", + "name": "PAI", + "logoURI": "/tokens/pai.svg", + "address": "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "tokenIcons": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "PAI", + "name": "PAI", + "logoURI": "/tokens/pai.svg", + "address": "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "underlyingIcons": [ + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + }, + { + "symbol": "PAI", + "name": "PAI", + "logoURI": "/tokens/pai.svg", + "address": "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL", + "decimals": 6, + "chainId": 103, + "tags": ["saber-market-usd"], + "extensions": {} + } + ], + "currency": "USD", + "lpToken": { + "symbol": "SLP", + "name": "USDC-PAI Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "J8fDLz5bfef14jDNC32nJLbVzpS9Rj1LBHwaSGfYn83J", + "chainId": 103, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_pai", + "underlyingTokens": [ + "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL" + ], + "source": "saber" + } + }, + "plotKey": "ByDbnkzqj3QmaTmjAfgDgK4YofpQZ7cNSgkLUm8i26TS", + "swap": { + "config": { + "swapAccount": "DoycojcYVwc42yCpGb4CvkbuKJkQ6KBTugLdJXv3U8ZE", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "8BC6eAF59beKMctxpH7jkx8faR6jdyKc5doHB7Tiffig" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "7uBM28mn7tfussHxAUeAiuUP2K1aSF2P2npzxRXhP8uS", + "reserve": "3hqDsGEp4Zp8PhXhx37ub94bHYfkjjHvZG5YJKyJ17no", + "mint": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8" + }, + "tokenB": { + "adminFeeAccount": "8dzJETPZkwX43cCFBgzWmpdLL9aZ6Ed1fsMDZ3Ujk7rf", + "reserve": "FLX3nBuu77Ld4KG9nTLgNnaqfT8BFnTRGtkwaBYWSXLQ", + "mint": "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL" + }, + "poolTokenMint": "J8fDLz5bfef14jDNC32nJLbVzpS9Rj1LBHwaSGfYn83J", + "initialAmpFactor": "64", + "targetAmpFactor": "64", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "50.0000000000", + "numerator": "50", + "denominator": "100" + }, + "adminWithdraw": { + "formatted": "50.0000000000", + "numerator": "50", + "denominator": "100" + }, + "trade": { + "formatted": "0.2000000000", + "numerator": "20", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.5000000000", + "numerator": "50", + "denominator": "10000" + } + } + } + }, + "quarry": "Cae9hW42nD1G89LCheaSczN6CzngYYWQ6KbZMQXhMwyq" + }, + { + "id": "btc", + "name": "renBTC-WBTC", + "tokens": [ + { + "name": "Test RenBTC", + "address": "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "decimals": 8, + "chainId": 103, + "symbol": "renBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/ethereum/assets/0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D/logo.png", + "tags": ["saber-market-btc"], + "extensions": {} + }, + { + "symbol": "sWBTC-8", + "name": "Saber Test WBTC (8 decimals)", + "address": "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs", + "decimals": 8, + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/bitcoin/info/logo.png", + "chainId": 103, + "tags": ["saber-decimal-wrapped", "saber-market-btc"], + "extensions": { + "assetContract": "Wbt2CgkkD3eVckD5XxWJmT8pTnFTyWrwvGM7bUMLvsM" + } + } + ], + "tokenIcons": [ + { + "name": "Test RenBTC", + "address": "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "decimals": 8, + "chainId": 103, + "symbol": "renBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/ethereum/assets/0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D/logo.png", + "tags": ["saber-market-btc"], + "extensions": {} + }, + { + "symbol": "sWBTC-8", + "name": "Saber Test WBTC (8 decimals)", + "address": "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs", + "decimals": 8, + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/bitcoin/info/logo.png", + "chainId": 103, + "tags": ["saber-decimal-wrapped", "saber-market-btc"], + "extensions": { + "assetContract": "Wbt2CgkkD3eVckD5XxWJmT8pTnFTyWrwvGM7bUMLvsM" + } + } + ], + "underlyingIcons": [ + { + "name": "Test RenBTC", + "address": "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "decimals": 8, + "chainId": 103, + "symbol": "renBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/ethereum/assets/0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D/logo.png", + "tags": ["saber-market-btc"], + "extensions": {} + }, + { + "name": "Test WBTC", + "address": "Wbt2CgkkD3eVckD5XxWJmT8pTnFTyWrwvGM7bUMLvsM", + "decimals": 6, + "chainId": 103, + "symbol": "WBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/bitcoin/info/logo.png", + "tags": ["saber-market-btc"], + "extensions": {} + } + ], + "currency": "BTC", + "lpToken": { + "symbol": "SLP", + "name": "renBTC-WBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "bLpASoWNdsz5DsjCaxpbM2FrkMowTJCydpwiDP4Vdzm", + "chainId": 103, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/btc", + "underlyingTokens": [ + "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs" + ], + "source": "saber" + } + }, + "plotKey": "8c6AgGFMUT6cuU23FJuyfBtug98axAwPNXaHp6pbrqG2", + "swap": { + "config": { + "swapAccount": "AQsYrKkFLuv9Jw7kCcPH7SkeMQ2aZkP1KcBs4RYegHbv", + "swapProgramID": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + "tokenProgramID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "authority": "ChFJZQK4gNpmcrbF71e5Wo2HzF8qePYGnqsgZ6u7anFA" + }, + "state": { + "adminAccount": "H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc", + "tokenA": { + "adminFeeAccount": "HcedJ6dU7sYBkXPqZteZcuroZmnbaKRkjZnzzfbGgba9", + "reserve": "GT9JcsFPaeDJRCeysNHWT76nvN9EWDj7z7psaADSn8QS", + "mint": "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ" + }, + "tokenB": { + "adminFeeAccount": "6ni4cs8vqcTbLDjGDeXSSs7Gs9upCWsYqNoJRMmmFQk7", + "reserve": "6jRUeyuRyaG1BfV5Y7fRCRhUk8P9CJwS1s5qybG6HPti", + "mint": "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs" + }, + "poolTokenMint": "bLpASoWNdsz5DsjCaxpbM2FrkMowTJCydpwiDP4Vdzm", + "initialAmpFactor": "32", + "targetAmpFactor": "32", + "startRampTimestamp": 0, + "stopRampTimestamp": 0, + "fees": { + "adminTrade": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "adminWithdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + }, + "trade": { + "formatted": "0.0400000000", + "numerator": "4", + "denominator": "10000" + }, + "withdraw": { + "formatted": "0.0000000000", + "numerator": "0", + "denominator": "10000" + } + } + } + }, + "quarry": "CeE8rNxCFx2RAgpS7trsmcTz7ydyun33wbNY7r2nEvPi" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/programs/programs.json b/farms/farm-ctrl/src/metadata/programs/programs.json new file mode 100644 index 00000000000..a9a8cba0cd0 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/programs/programs.json @@ -0,0 +1,342 @@ +{ + "name": "Solana Programs List", + "timestamp": "2021-08-22T17:25:00+0000", + "programs": [ + { + "name": "System", + "description": "", + "program_type": "System", + "address": "11111111111111111111111111111111" + }, + { + "name": "BpfLoader2", + "description": "", + "program_type": "System", + "address": "BPFLoader2111111111111111111111111111111111" + }, + { + "name": "BpfLoader", + "description": "", + "program_type": "System", + "address": "BPFLoader1111111111111111111111111111111111" + }, + { + "name": "BpfLoaderUpgradeable", + "description": "", + "program_type": "System", + "address": "BPFLoaderUpgradeab1e11111111111111111111111" + }, + { + "name": "Feature", + "description": "", + "program_type": "System", + "address": "Feature111111111111111111111111111111111111" + }, + { + "name": "Config", + "description": "", + "program_type": "System", + "address": "Config1111111111111111111111111111111111111" + }, + { + "name": "Stake", + "description": "", + "program_type": "System", + "address": "Stake11111111111111111111111111111111111111" + }, + { + "name": "StakeConfig", + "description": "", + "program_type": "System", + "address": "StakeConfig11111111111111111111111111111111" + }, + { + "name": "Vote", + "description": "", + "program_type": "System", + "address": "Vote111111111111111111111111111111111111111" + }, + { + "name": "Secp256k1", + "description": "", + "program_type": "System", + "address": "KeccakSecp256k11111111111111111111111111111" + }, + { + "name": "Sysvar", + "description": "", + "program_type": "System", + "address": "Sysvar1111111111111111111111111111111111111" + }, + { + "name": "SysvarClock", + "description": "", + "program_type": "System", + "address": "SysvarC1ock11111111111111111111111111111111" + }, + { + "name": "SysvarEpochSchedule", + "description": "", + "program_type": "System", + "address": "SysvarEpochSchedu1e111111111111111111111111" + }, + { + "name": "SysvarRent", + "description": "", + "program_type": "System", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "SysvarFees", + "description": "", + "program_type": "System", + "address": "SysvarFees111111111111111111111111111111111" + }, + { + "name": "SysvarRewards", + "description": "", + "program_type": "System", + "address": "SysvarRewards111111111111111111111111111111" + }, + { + "name": "SysvarInstructions", + "description": "", + "program_type": "System", + "address": "Sysvar1nstructions1111111111111111111111111" + }, + { + "name": "SysvarSlotHashes", + "description": "", + "program_type": "System", + "address": "SysvarS1otHashes111111111111111111111111111" + }, + { + "name": "SysvarSlotHistory", + "description": "", + "program_type": "System", + "address": "SysvarS1otHistory11111111111111111111111111" + }, + { + "name": "SysvarStakeHistory", + "description": "", + "program_type": "System", + "address": "SysvarStakeHistory1111111111111111111111111" + }, + { + "name": "SysvarRecentBlockHashes", + "description": "", + "program_type": "System", + "address": "SysvarRecentB1ockHashes11111111111111111111" + }, + { + "name": "SplToken", + "description": "", + "program_type": "System", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "SplTokenMetadata", + "description": "", + "program_type": "System", + "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + }, + { + "name": "SplTokenVault", + "description": "", + "program_type": "System", + "address": "vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn" + }, + { + "name": "SplTokenMint", + "description": "", + "program_type": "System", + "address": "So11111111111111111111111111111111111111112" + }, + { + "name": "AssociatedToken", + "description": "", + "program_type": "System", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "Memo", + "description": "", + "program_type": "System", + "address": "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo" + }, + { + "name": "Programs", + "description": "", + "program_type": "ProgramsRef", + "address": "" + }, + { + "name": "Vaults", + "description": "", + "program_type": "VaultsRef", + "address": "" + }, + { + "name": "Farms", + "description": "", + "program_type": "FarmsRef", + "address": "" + }, + { + "name": "Pools", + "description": "", + "program_type": "PoolsRef", + "address": "" + }, + { + "name": "Tokens", + "description": "", + "program_type": "TokensRef", + "address": "" + }, + { + "name": "MainRouter", + "description": "", + "program_type": "MainRouter", + "address": "" + }, + { + "name": "RaydiumRouter", + "description": "", + "program_type": "Raydium", + "address": "" + }, + { + "name": "SaberRouter", + "description": "", + "program_type": "Saber", + "address": "" + }, + { + "name": "OrcaRouter", + "description": "", + "program_type": "Orca", + "address": "" + }, + { + "name": "FarmGovernance", + "description": "", + "program_type": "System", + "address": "" + }, + { + "name": "SerumV2", + "description": "", + "program_type": "Serum", + "address": "EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o" + }, + { + "name": "SerumV3", + "description": "", + "program_type": "Serum", + "address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" + }, + { + "name": "RaydiumV2", + "description": "", + "program_type": "Raydium", + "address": "RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr" + }, + { + "name": "RaydiumV3", + "description": "", + "program_type": "Raydium", + "address": "27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv" + }, + { + "name": "RaydiumV4", + "description": "", + "program_type": "Raydium", + "address": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" + }, + { + "name": "RaydiumStake", + "description": "", + "program_type": "Raydium", + "address": "EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q" + }, + { + "name": "RaydiumStakeV4", + "description": "", + "program_type": "Raydium", + "address": "CBuCnLe26faBpcBP2fktp4rp8abpcAnTWft6ZrP5Q4T" + }, + { + "name": "RaydiumStakeV5", + "description": "", + "program_type": "Raydium", + "address": "9KEPoZmtHUrBbhWN1v1KWLMkkvwY6WLtAVUCPRtRjP4z" + }, + { + "name": "RaydiumIDO", + "description": "", + "program_type": "Raydium", + "address": "6FJon3QE27qgPVggARueB22hLvoh22VzJpXv4rBEoSLF" + }, + { + "name": "RaydiumIDOV4", + "description": "", + "program_type": "Raydium", + "address": "CC12se5To1CdEuw7fDS27B7Geo5jJyL7t5UK2B44NgiH" + }, + { + "name": "RaydiumIDOV5", + "description": "", + "program_type": "Raydium", + "address": "9HzJyW1qZsEiSfMUf6L2jo3CcTKAyBmSyKdwQeYisHrC" + }, + { + "name": "SaberStableSwap", + "description": "", + "program_type": "Saber", + "address": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ" + }, + { + "name": "SaberQuarryMine", + "description": "", + "program_type": "Saber", + "address": "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB" + }, + { + "name": "SaberRedeemer", + "description": "", + "program_type": "Saber", + "address": "RDM23yr8pr1kEAmhnFpaabPny6C9UVcEcok3Py5v86X" + }, + { + "name": "SaberMintWrapper", + "description": "", + "program_type": "Saber", + "address": "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV" + }, + { + "name": "SaberMintProxy", + "description": "", + "program_type": "Saber", + "address": "UBEBk5idELqykEEaycYtQ7iBVrCg6NmvFSzMpdr22mL" + }, + { + "name": "SaberDecimalWrapper", + "description": "", + "program_type": "Saber", + "address": "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB" + }, + { + "name": "OrcaSwap", + "description": "", + "program_type": "Orca", + "address": "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP" + }, + { + "name": "OrcaStake", + "description": "", + "program_type": "Orca", + "address": "82yxjeMsvaURa4MbZZ7WZZHfobirZYkH1zF8fmeGtyaQ" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/programs/programs_dev.json b/farms/farm-ctrl/src/metadata/programs/programs_dev.json new file mode 100644 index 00000000000..cf52b463e1d --- /dev/null +++ b/farms/farm-ctrl/src/metadata/programs/programs_dev.json @@ -0,0 +1,282 @@ +{ + "name": "Solana Programs List", + "timestamp": "2021-08-22T17:25:00+0000", + "programs": [ + { + "name": "System", + "description": "", + "program_type": "System", + "address": "11111111111111111111111111111111" + }, + { + "name": "BpfLoader2", + "description": "", + "program_type": "System", + "address": "BPFLoader2111111111111111111111111111111111" + }, + { + "name": "BpfLoader", + "description": "", + "program_type": "System", + "address": "BPFLoader1111111111111111111111111111111111" + }, + { + "name": "BpfLoaderUpgradeable", + "description": "", + "program_type": "System", + "address": "BPFLoaderUpgradeab1e11111111111111111111111" + }, + { + "name": "Feature", + "description": "", + "program_type": "System", + "address": "Feature111111111111111111111111111111111111" + }, + { + "name": "Config", + "description": "", + "program_type": "System", + "address": "Config1111111111111111111111111111111111111" + }, + { + "name": "Stake", + "description": "", + "program_type": "System", + "address": "Stake11111111111111111111111111111111111111" + }, + { + "name": "StakeConfig", + "description": "", + "program_type": "System", + "address": "StakeConfig11111111111111111111111111111111" + }, + { + "name": "Vote", + "description": "", + "program_type": "System", + "address": "Vote111111111111111111111111111111111111111" + }, + { + "name": "Secp256k1", + "description": "", + "program_type": "System", + "address": "KeccakSecp256k11111111111111111111111111111" + }, + { + "name": "Sysvar", + "description": "", + "program_type": "System", + "address": "Sysvar1111111111111111111111111111111111111" + }, + { + "name": "SysvarClock", + "description": "", + "program_type": "System", + "address": "SysvarC1ock11111111111111111111111111111111" + }, + { + "name": "SysvarEpochSchedule", + "description": "", + "program_type": "System", + "address": "SysvarEpochSchedu1e111111111111111111111111" + }, + { + "name": "SysvarRent", + "description": "", + "program_type": "System", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "SysvarFees", + "description": "", + "program_type": "System", + "address": "SysvarFees111111111111111111111111111111111" + }, + { + "name": "SysvarRewards", + "description": "", + "program_type": "System", + "address": "SysvarRewards111111111111111111111111111111" + }, + { + "name": "SysvarInstructions", + "description": "", + "program_type": "System", + "address": "Sysvar1nstructions1111111111111111111111111" + }, + { + "name": "SysvarSlotHashes", + "description": "", + "program_type": "System", + "address": "SysvarS1otHashes111111111111111111111111111" + }, + { + "name": "SysvarSlotHistory", + "description": "", + "program_type": "System", + "address": "SysvarS1otHistory11111111111111111111111111" + }, + { + "name": "SysvarStakeHistory", + "description": "", + "program_type": "System", + "address": "SysvarStakeHistory1111111111111111111111111" + }, + { + "name": "SysvarRecentBlockHashes", + "description": "", + "program_type": "System", + "address": "SysvarRecentB1ockHashes11111111111111111111" + }, + { + "name": "SplToken", + "description": "", + "program_type": "System", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "SplTokenMetadata", + "description": "", + "program_type": "System", + "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + }, + { + "name": "SplTokenVault", + "description": "", + "program_type": "System", + "address": "vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn" + }, + { + "name": "SplTokenMint", + "description": "", + "program_type": "System", + "address": "So11111111111111111111111111111111111111112" + }, + { + "name": "AssociatedToken", + "description": "", + "program_type": "System", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "Memo", + "description": "", + "program_type": "System", + "address": "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo" + }, + { + "name": "Programs", + "description": "", + "program_type": "ProgramsRef", + "address": "" + }, + { + "name": "Vaults", + "description": "", + "program_type": "VaultsRef", + "address": "" + }, + { + "name": "Farms", + "description": "", + "program_type": "FarmsRef", + "address": "" + }, + { + "name": "Pools", + "description": "", + "program_type": "PoolsRef", + "address": "" + }, + { + "name": "Tokens", + "description": "", + "program_type": "TokensRef", + "address": "" + }, + { + "name": "MainRouter", + "description": "", + "program_type": "MainRouter", + "address": "" + }, + { + "name": "RaydiumRouter", + "description": "", + "program_type": "Raydium", + "address": "" + }, + { + "name": "SaberRouter", + "description": "", + "program_type": "Saber", + "address": "" + }, + { + "name": "OrcaRouter", + "description": "", + "program_type": "Orca", + "address": "" + }, + { + "name": "FarmGovernance", + "description": "", + "program_type": "System", + "address": "" + }, + { + "name": "SerumV3", + "description": "", + "program_type": "Serum", + "address": "DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY" + }, + { + "name": "RaydiumV4", + "description": "", + "program_type": "Raydium", + "address": "9rpQHSyFVM1dkkHFQ2TtTzPEW7DVmEyPmN8wVniqJtuC" + }, + { + "name": "RaydiumStakeV5", + "description": "", + "program_type": "Raydium", + "address": "EcLzTrNg9V7qhcdyXDe2qjtPkiGzDM2UbdRaeaadU5r2" + }, + { + "name": "SaberStableSwap", + "description": "", + "program_type": "Saber", + "address": "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ" + }, + { + "name": "SaberQuarryMine", + "description": "", + "program_type": "Saber", + "address": "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB" + }, + { + "name": "SaberRedeemer", + "description": "", + "program_type": "Saber", + "address": "RDM23yr8pr1kEAmhnFpaabPny6C9UVcEcok3Py5v86X" + }, + { + "name": "SaberMintWrapper", + "description": "", + "program_type": "Saber", + "address": "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV" + }, + { + "name": "SaberMintProxy", + "description": "", + "program_type": "Saber", + "address": "UBEBk5idELqykEEaycYtQ7iBVrCg6NmvFSzMpdr22mL" + }, + { + "name": "SaberDecimalWrapper", + "description": "", + "program_type": "Saber", + "address": "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB" + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens.json b/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens.json new file mode 100644 index 00000000000..ed335768be7 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens.json @@ -0,0 +1,677 @@ +{ + "name": "Raydium LP Tokens", + "tokens": { + "RAY-WUSDT": { + "symbol": "RAY-WUSDT", + "name": "RAY-WUSDT V2 LP", + "coin": "RAY", + "pc": "WUSDT", + "mintAddress": "CzPDyvotTcxNqtPne32yUiEVQ6jk42HZi1Y3hUu7qf7f", + "decimals": 6 + }, + "RAY-SOL": { + "symbol": "RAY-SOL", + "name": "RAY-SOL LP", + "coin": "RAY", + "pc": "SOL", + "mintAddress": "134Cct3CSdRCbYgq5SkwmHgfwjJ7EM5cG9PzqffWqECx", + "decimals": 6 + }, + "LINK-WUSDT": { + "symbol": "LINK-WUSDT", + "name": "LINK-WUSDT LP", + "coin": "LINK", + "pc": "WUSDT", + "mintAddress": "EVDmwajM5U73PD34bYPugwiA4Eqqbrej4mLXXv15Z5qR", + "decimals": 6 + }, + "ETH-WUSDT": { + "symbol": "ETH-WUSDT", + "name": "ETH-WUSDT LP", + "coin": "ETH", + "pc": "WUSDT", + "mintAddress": "KY4XvwHy7JPzbWYAbk23jQvEb4qWJ8aCqYWREmk1Q7K", + "decimals": 6 + }, + "RAY-USDC": { + "symbol": "RAY-USDC", + "name": "RAY-USDC V2 LP", + "coin": "RAY", + "pc": "USDC", + "mintAddress": "FgmBnsF5Qrnv8X9bomQfEtQTQjNNiBCWRKGpzPnE5BDg", + "decimals": 6 + }, + "RAY-SRM": { + "symbol": "RAY-SRM", + "name": "RAY-SRM V2 LP", + "coin": "RAY", + "pc": "SRM", + "mintAddress": "5QXBMXuCL7zfAk39jEVVEvcrz1AvBGgT9wAhLLHLyyUJ", + "decimals": 6 + }, + "RAY-WUSDT-V3": { + "symbol": "RAY-WUSDT", + "name": "RAY-WUSDT V3 LP", + "coin": "RAY", + "pc": "WUSDT", + "mintAddress": "FdhKXYjCou2jQfgKWcNY7jb8F2DPLU1teTTTRfLBD2v1", + "decimals": 6 + }, + "RAY-USDC-V3": { + "symbol": "RAY-USDC", + "name": "RAY-USDC V3 LP", + "coin": "RAY", + "pc": "USDC", + "mintAddress": "BZFGfXMrjG2sS7QT2eiCDEevPFnkYYF7kzJpWfYxPbcx", + "decimals": 6 + }, + "RAY-SRM-V3": { + "symbol": "RAY-SRM", + "name": "RAY-SRM V3 LP", + "coin": "RAY", + "pc": "SRM", + "mintAddress": "DSX5E21RE9FB9hM8Nh8xcXQfPK6SzRaJiywemHBSsfup", + "decimals": 6 + }, + "RAY-SOL-V3": { + "symbol": "RAY-SOL", + "name": "RAY-SOL V3 LP", + "coin": "RAY", + "pc": "SOL", + "mintAddress": "F5PPQHGcznZ2FxD9JaxJMXaf7XkaFFJ6zzTBcW8osQjw", + "decimals": 6 + }, + "RAY-ETH-V3": { + "symbol": "RAY-ETH", + "name": "RAY-ETH V3 LP", + "coin": "RAY", + "pc": "ETH", + "mintAddress": "8Q6MKy5Yxb9vG1mWzppMtMb2nrhNuCRNUkJTeiE3fuwD", + "decimals": 6 + }, + "FIDA-RAY-V4": { + "symbol": "FIDA-RAY", + "name": "FIDA-RAY LP", + "coin": "FIDA", + "pc": "RAY", + "mintAddress": "DsBuznXRTmzvEdb36Dx3aVLVo1XmH7r1PRZUFugLPTFv", + "decimals": 6 + }, + "OXY-RAY-V4": { + "symbol": "OXY-RAY", + "name": "OXY-RAY LP", + "coin": "OXY", + "pc": "RAY", + "mintAddress": "FwaX9W7iThTZH5MFeasxdLpxTVxRcM7ZHieTCnYog8Yb", + "decimals": 6 + }, + "MAPS-RAY-V4": { + "symbol": "MAPS-RAY", + "name": "MAPS-RAY LP", + "coin": "MAPS", + "pc": "RAY", + "mintAddress": "CcKK8srfVdTSsFGV3VLBb2YDbzF4T4NM2C3UEjC39RLP", + "decimals": 6 + }, + "KIN-RAY-V4": { + "symbol": "KIN-RAY", + "name": "KIN-RAY LP", + "coin": "KIN", + "pc": "RAY", + "mintAddress": "CHT8sft3h3gpLYbCcZ9o27mT5s3Z6VifBVbUiDvprHPW", + "decimals": 6 + }, + "RAY-USDT-V4": { + "symbol": "RAY-USDT", + "name": "RAY-USDT LP", + "coin": "RAY", + "pc": "USDT", + "mintAddress": "C3sT1R3nsw4AVdepvLTLKr5Gvszr7jufyBWUCvy4TUvT", + "decimals": 6 + }, + "SOL-USDC-V4": { + "symbol": "SOL-USDC", + "name": "SOL-USDC LP", + "coin": "SOL", + "pc": "USDC", + "mintAddress": "8HoQnePLqPj4M7PUDzfw8e3Ymdwgc7NLGnaTUapubyvu", + "decimals": 9 + }, + "YFI-USDC-V4": { + "symbol": "YFI-USDC", + "name": "YFI-USDC LP", + "coin": "YFI", + "pc": "USDC", + "mintAddress": "865j7iMmRRycSYUXzJ33ZcvLiX9JHvaLidasCyUyKaRE", + "decimals": 6 + }, + "SRM-USDC-V4": { + "symbol": "SRM-USDC", + "name": "SRM-USDC LP", + "coin": "SRM", + "pc": "USDC", + "mintAddress": "9XnZd82j34KxNLgQfz29jGbYdxsYznTWRpvZE3SRE7JG", + "decimals": 6 + }, + "FTT-USDC-V4": { + "symbol": "FTT-USDC", + "name": "FTT-USDC LP", + "coin": "FTT", + "pc": "USDC", + "mintAddress": "75dCoKfUHLUuZ4qEh46ovsxfgWhB4icc3SintzWRedT9", + "decimals": 6 + }, + "BTC-USDC-V4": { + "symbol": "BTC-USDC", + "name": "BTC-USDC LP", + "coin": "BTC", + "pc": "USDC", + "mintAddress": "2hMdRdVWZqetQsaHG8kQjdZinEMBz75vsoWTCob1ijXu", + "decimals": 6 + }, + "SUSHI-USDC-V4": { + "symbol": "SUSHI-USDC", + "name": "SUSHI-USDC LP", + "coin": "SUSHI", + "pc": "USDC", + "mintAddress": "2QVjeR9d2PbSf8em8NE8zWd8RYHjFtucDUdDgdbDD2h2", + "decimals": 6 + }, + "TOMO-USDC-V4": { + "symbol": "TOMO-USDC", + "name": "TOMO-USDC LP", + "coin": "TOMO", + "pc": "USDC", + "mintAddress": "CHyUpQFeW456zcr5XEh4RZiibH8Dzocs6Wbgz9aWpXnQ", + "decimals": 6 + }, + "LINK-USDC-V4": { + "symbol": "LINK-USDC", + "name": "LINK-USDC LP", + "coin": "LINK", + "pc": "USDC", + "mintAddress": "BqjoYjqKrXtfBKXeaWeAT5sYCy7wsAYf3XjgDWsHSBRs", + "decimals": 6 + }, + "ETH-USDC-V4": { + "symbol": "ETH-USDC", + "name": "ETH-USDC LP", + "coin": "ETH", + "pc": "USDC", + "mintAddress": "13PoKid6cZop4sj2GfoBeujnGfthUbTERdE5tpLCDLEY", + "decimals": 6 + }, + "xCOPE-USDC-V4": { + "symbol": "xCOPE-USDC", + "name": "xCOPE-USDC LP", + "coin": "xCOPE", + "pc": "USDC", + "mintAddress": "2Vyyeuyd15Gp8aH6uKE72c4hxc8TVSLibxDP9vzspQWG", + "decimals": 0 + }, + "SOL-USDT-V4": { + "symbol": "SOL-USDT", + "name": "SOL-USDT LP", + "coin": "SOL", + "pc": "USDT", + "mintAddress": "Epm4KfTj4DMrvqn6Bwg2Tr2N8vhQuNbuK8bESFp4k33K", + "decimals": 9 + }, + "YFI-USDT-V4": { + "symbol": "YFI-USDT", + "name": "YFI-USDT LP", + "coin": "YFI", + "pc": "USDT", + "mintAddress": "FA1i7fej1pAbQbnY8NbyYUsTrWcasTyipKreDgy1Mgku", + "decimals": 6 + }, + "SRM-USDT-V4": { + "symbol": "SRM-USDT", + "name": "SRM-USDT LP", + "coin": "SRM", + "pc": "USDT", + "mintAddress": "HYSAu42BFejBS77jZAZdNAWa3iVcbSRJSzp3wtqCbWwv", + "decimals": 6 + }, + "FTT-USDT-V4": { + "symbol": "FTT-USDT", + "name": "FTT-USDT LP", + "coin": "FTT", + "pc": "USDT", + "mintAddress": "2cTCiUnect5Lap2sk19xLby7aajNDYseFhC9Pigou11z", + "decimals": 6 + }, + "BTC-USDT-V4": { + "symbol": "BTC-USDT", + "name": "BTC-USDT LP", + "coin": "BTC", + "pc": "USDT", + "mintAddress": "DgGuvR9GSHimopo3Gc7gfkbKamLKrdyzWkq5yqA6LqYS", + "decimals": 6 + }, + "SUSHI-USDT-V4": { + "symbol": "SUSHI-USDT", + "name": "SUSHI-USDT LP", + "coin": "SUSHI", + "pc": "USDT", + "mintAddress": "Ba26poEYDy6P2o95AJUsewXgZ8DM9BCsmnU9hmC9i4Ki", + "decimals": 6 + }, + "TOMO-USDT-V4": { + "symbol": "TOMO-USDT", + "name": "TOMO-USDT LP", + "coin": "TOMO", + "pc": "USDT", + "mintAddress": "D3iGro1vn6PWJXo9QAPj3dfta6dKkHHnmiiym2EfsAmi", + "decimals": 6 + }, + "LINK-USDT-V4": { + "symbol": "LINK-USDT", + "name": "LINK-USDT LP", + "coin": "LINK", + "pc": "USDT", + "mintAddress": "Dr12Sgt9gkY8WU5tRkgZf1TkVWJbvjYuPAhR3aDCwiiX", + "decimals": 6 + }, + "ETH-USDT-V4": { + "symbol": "ETH-USDT", + "name": "ETH-USDT LP", + "coin": "ETH", + "pc": "USDT", + "mintAddress": "nPrB78ETY8661fUgohpuVusNCZnedYCgghzRJzxWnVb", + "decimals": 6 + }, + "YFI-SRM-V4": { + "symbol": "YFI-SRM", + "name": "YFI-SRM LP", + "coin": "YFI", + "pc": "SRM", + "mintAddress": "EGJht91R7dKpCj8wzALkjmNdUUUcQgodqWCYweyKcRcV", + "decimals": 6 + }, + "FTT-SRM-V4": { + "symbol": "FTT-SRM", + "name": "FTT-SRM LP", + "coin": "FTT", + "pc": "SRM", + "mintAddress": "AsDuPg9MgPtt3jfoyctUCUgsvwqAN6RZPftqoeiPDefM", + "decimals": 6 + }, + "BTC-SRM-V4": { + "symbol": "BTC-SRM", + "name": "BTC-SRM LP", + "coin": "BTC", + "pc": "SRM", + "mintAddress": "AGHQxXb3GSzeiLTcLtXMS2D5GGDZxsB2fZYZxSB5weqB", + "decimals": 6 + }, + "SUSHI-SRM-V4": { + "symbol": "SUSHI-SRM", + "name": "SUSHI-SRM LP", + "coin": "SUSHI", + "pc": "SRM", + "mintAddress": "3HYhUnUdV67j1vn8fu7ExuVGy5dJozHEyWvqEstDbWwE", + "decimals": 6 + }, + "TOMO-SRM-V4": { + "symbol": "TOMO-SRM", + "name": "TOMO-SRM LP", + "coin": "TOMO", + "pc": "SRM", + "mintAddress": "GgH9RnKrQpaMQeqmdbMvs5oo1A24hERQ9wuY2pSkeG7x", + "decimals": 6 + }, + "LINK-SRM-V4": { + "symbol": "LINK-SRM", + "name": "LINK-SRM LP", + "coin": "LINK", + "pc": "SRM", + "mintAddress": "GXN6yJv12o18skTmJXaeFXZVY1iqR18CHsmCT8VVCmDD", + "decimals": 6 + }, + "ETH-SRM-V4": { + "symbol": "ETH-SRM", + "name": "ETH-SRM LP", + "coin": "ETH", + "pc": "SRM", + "mintAddress": "9VoY3VERETuc2FoadMSYYizF26mJinY514ZpEzkHMtwG", + "decimals": 6 + }, + "SRM-SOL-V4": { + "symbol": "SRM-SOL", + "name": "SRM-SOL LP", + "coin": "SRM", + "pc": "SOL", + "mintAddress": "AKJHspCwDhABucCxNLXUSfEzb7Ny62RqFtC9uNjJi4fq", + "decimals": 6 + }, + "STEP-USDC-V4": { + "symbol": "STEP-USDC", + "name": "STEP-USDC LP", + "coin": "STEP", + "pc": "USDC", + "mintAddress": "3k8BDobgihmk72jVmXYLE168bxxQUhqqyESW4dQVktqC", + "decimals": 9 + }, + "MEDIA-USDC-V4": { + "symbol": "MEDIA-USDC", + "name": "MEDIA-USDC LP", + "coin": "MEDIA", + "pc": "USDC", + "mintAddress": "A5zanvgtioZGiJMdEyaKN4XQmJsp1p7uVxaq2696REvQ", + "decimals": 6 + }, + "ROPE-USDC-V4": { + "symbol": "ROPE-USDC", + "name": "ROPE-USDC LP", + "coin": "ROPE", + "pc": "USDC", + "mintAddress": "Cq4HyW5xia37tKejPF2XfZeXQoPYW6KfbPvxvw5eRoUE", + "decimals": 9 + }, + "MER-USDC-V4": { + "symbol": "MER-USDC", + "name": "MER-USDC LP", + "coin": "MER", + "pc": "USDC", + "mintAddress": "3H9NxvaZoxMZZDZcbBDdWMKbrfNj7PCF5sbRwDr7SdDW", + "decimals": 6 + }, + "COPE-USDC-V4": { + "symbol": "COPE-USDC", + "name": "COPE-USDC LP", + "coin": "COPE", + "pc": "USDC", + "mintAddress": "Cz1kUvHw98imKkrqqu95GQB9h1frY8RikxPojMwWKGXf", + "decimals": 6 + }, + "ALEPH-USDC-V4": { + "symbol": "ALEPH-USDC", + "name": "ALEPH-USDC LP", + "coin": "ALEPH", + "pc": "USDC", + "mintAddress": "iUDasAP2nXm5wvTukAHEKSdSXn8vQkRtaiShs9ceGB7", + "decimals": 6 + }, + "TULIP-USDC-V4": { + "symbol": "TULIP-USDC", + "name": "TULIP-USDC LP", + "coin": "TULIP", + "pc": "USDC", + "mintAddress": "2doeZGLJyACtaG9DCUyqMLtswesfje1hjNA11hMdj6YU", + "decimals": 6 + }, + "WOO-USDC-V4": { + "symbol": "WOO-USDC", + "name": "WOO-USDC LP", + "coin": "WOO", + "pc": "USDC", + "mintAddress": "7cu42ao8Jgrd5A3y3bNQsCxq5poyGZNmTydkGfJYQfzh", + "decimals": 6 + }, + "SNY-USDC-V4": { + "symbol": "SNY-USDC", + "name": "SNY-USDC LP", + "coin": "SNY", + "pc": "USDC", + "mintAddress": "G8qcfeFqxwbCqpxv5LpLWxUCd1PyMB5nWb5e5YyxLMKg", + "decimals": 6 + }, + "BOP-RAY-V4": { + "symbol": "BOP-RAY", + "name": "BOP-RAY LP", + "coin": "BOP", + "pc": "RAY", + "mintAddress": "9nQPYJvysyfnXhQ6nkK5V7sZG26hmDgusfdNQijRk5LD", + "decimals": 8 + }, + "SLRS-USDC-V4": { + "symbol": "SLRS-USDC", + "name": "SLRS-USDC LP", + "coin": "SLRS", + "pc": "USDC", + "mintAddress": "2Xxbm1hdv5wPeen5ponDSMT3VqhGMTQ7mH9stNXm9shU", + "decimals": 6 + }, + "SAMO-RAY-V4": { + "symbol": "SAMO-RAY", + "name": "SAMO-RAY LP", + "coin": "SAMO", + "pc": "RAY", + "mintAddress": "HwzkXyX8B45LsaHXwY8su92NoRBS5GQC32HzjQRDqPnr", + "decimals": 9 + }, + "renBTC-USDC-V4": { + "symbol": "renBTC-USDC", + "name": "renBTC-USDC LP", + "coin": "renBTC", + "pc": "USDC", + "mintAddress": "CTEpsih91ZLo5gunvryLpJ3pzMjmt5jbS6AnSQrzYw7V", + "decimals": 8 + }, + "renDOGE-USDC-V4": { + "symbol": "renDOGE-USDC", + "name": "renDOGE-USDC LP", + "coin": "renDOGE", + "pc": "USDC", + "mintAddress": "Hb8KnZNKvRxu7pgMRWJgoMSMcepfvNiBFFDDrdf9o3wA", + "decimals": 8 + }, + "RAY-USDC-V4": { + "symbol": "RAY-USDC", + "name": "RAY-USDC LP", + "coin": "RAY", + "pc": "USDC", + "mintAddress": "FbC6K13MzHvN42bXrtGaWsvZY9fxrackRSZcBGfjPc7m", + "decimals": 6 + }, + "RAY-SRM-V4": { + "symbol": "RAY-SRM", + "name": "RAY-SRM LP", + "coin": "RAY", + "pc": "SRM", + "mintAddress": "7P5Thr9Egi2rvMmEuQkLn8x8e8Qro7u2U7yLD2tU2Hbe", + "decimals": 6 + }, + "RAY-ETH-V4": { + "symbol": "RAY-ETH", + "name": "RAY-ETH LP", + "coin": "RAY", + "pc": "ETH", + "mintAddress": "mjQH33MqZv5aKAbKHi8dG3g3qXeRQqq1GFcXceZkNSr", + "decimals": 6 + }, + "RAY-SOL-V4": { + "symbol": "RAY-SOL", + "name": "RAY-SOL LP", + "coin": "RAY", + "pc": "SOL", + "mintAddress": "89ZKE4aoyfLBe2RuV6jM3JGNhaV18Nxh8eNtjRcndBip", + "decimals": 6 + }, + "DXL-USDC-V4": { + "symbol": "DXL-USDC", + "name": "DXL-USDC LP", + "coin": "DXL", + "pc": "USDC", + "mintAddress": "4HFaSvfgskipvrzT1exoVKsUZ174JyExEsA8bDfsAdY5", + "decimals": 6 + }, + "LIKE-USDC-V4": { + "symbol": "LIKE-USDC", + "name": "LIKE-USDC LP", + "coin": "LIKE", + "pc": "USDC", + "mintAddress": "cjZmbt8sJgaoyWYUttomAu5LJYU44ZrcKTbzTSEPDVw", + "decimals": 9 + }, + "mSOL-USDC-V4": { + "symbol": "mSOL-USDC", + "name": "mSOL-USDC LP", + "coin": "mSOL", + "pc": "USDC", + "mintAddress": "4xTpJ4p76bAeggXoYywpCCNKfJspbuRzZ79R7pRhbqSf", + "decimals": 9 + }, + "mSOL-SOL-V4": { + "symbol": "mSOL-SOL", + "name": "mSOL-SOL LP", + "coin": "mSOL", + "pc": "SOL", + "mintAddress": "5ijRoAHVgd5T5CNtK5KDRUBZ7Bffb69nktMj5n6ks6m4", + "decimals": 9 + }, + "MER-PAI-V4": { + "symbol": "MER-PAI", + "name": "MER-PAI LP", + "coin": "MER", + "pc": "PAI", + "mintAddress": "DU5RT2D9EviaSmX6Ta8MZwMm85HwSEqGMRdqUiuCGfmD", + "decimals": 6 + }, + "PORT-USDC-V4": { + "symbol": "PORT-USDC", + "name": "PORT-USDC LP", + "coin": "PORT", + "pc": "USDC", + "mintAddress": "9tmNtbUCrLS15qC4tEfr5NNeqcqpZ4uiGgi2vS5CLQBS", + "decimals": 6 + }, + "MNGO-USDC-V4": { + "symbol": "MNGO-USDC", + "name": "MNGO-USDC LP", + "coin": "MNGO", + "pc": "USDC", + "mintAddress": "DkiqCQ792n743xjWQVCbBUaVtkdiuvQeYndM53ReWnCC", + "decimals": 6 + }, + "ATLAS-USDC-V4": { + "symbol": "ATLAS-USDC", + "name": "ATLAS-USDC LP", + "coin": "ATLAS", + "pc": "USDC", + "mintAddress": "9shGU9f1EsxAbiR567MYZ78WUiS6ZNCYbHe53WUULQ7n", + "decimals": 8 + }, + "POLIS-USDC-V4": { + "symbol": "POLIS-USDC", + "name": "POLIS-USDC LP", + "coin": "POLIS", + "pc": "USDC", + "mintAddress": "8MbKSBpyXs8fVneKgt71jfHrn5SWtX8n4wMLpiVfF9So", + "decimals": 8 + }, + "ATLAS-RAY-V4": { + "symbol": "ATLAS-RAY", + "name": "ATLAS-RAY LP", + "coin": "ATLAS", + "pc": "RAY", + "mintAddress": "418MFhkaYQtbn529wmjLLqL6uKxDz7j4eZBaV1cobkyd", + "decimals": 8 + }, + "POLIS-RAY-V4": { + "symbol": "POLIS-RAY", + "name": "POLIS-RAY LP", + "coin": "POLIS", + "pc": "RAY", + "mintAddress": "9ysGKUH6WqzjQEUT4dxqYCUaFNVK9QFEa24pGzjFq8xg", + "decimals": 8 + }, + "ALEPH-RAY-V4": { + "symbol": "ALEPH-RAY", + "name": "ALEPH-RAY LP", + "coin": "ALEPH", + "pc": "RAY", + "mintAddress": "n76skjqv4LirhdLok2zJELXNLdRpYDgVJQuQFbamscy", + "decimals": 6 + }, + "TULIP-RAY-V4": { + "symbol": "TULIP-RAY", + "name": "TULIP-RAY LP", + "coin": "TULIP", + "pc": "RAY", + "mintAddress": "3AZTviji5qduMG2s4FfWGR3SSQmNUCyx8ao6UKCPg3oJ", + "decimals": 6 + }, + "SLRS-RAY-V4": { + "symbol": "SLRS-RAY", + "name": "SLRS-RAY LP", + "coin": "SLRS", + "pc": "RAY", + "mintAddress": "2pk78vsKT3jfJAcN2zbpMUnrR57SZrxHqaZYyFgp92mM", + "decimals": 6 + }, + "MER-RAY-V4": { + "symbol": "MER-RAY", + "name": "MER-RAY LP", + "coin": "MER", + "pc": "RAY", + "mintAddress": "214hxy3AbKoaEKgqcg2aC1cP5R67cGGAyDEg5GDwC7Ub", + "decimals": 6 + }, + "MEDIA-RAY-V4": { + "symbol": "MEDIA-RAY", + "name": "MEDIA-RAY LP", + "coin": "MEDIA", + "pc": "RAY", + "mintAddress": "9Aseg5A1JD1yCiFFdDaNNxCiJ7XzrpZFmcEmLjXFdPaH", + "decimals": 6 + }, + "SNY-RAY-V4": { + "symbol": "SNY-RAY", + "name": "SNY-RAY LP", + "coin": "SNY", + "pc": "RAY", + "mintAddress": "2k4quTuuLUxrSEhFH99qcoZzvgvVEc3b5sz3xz3qstfS", + "decimals": 6 + }, + "LIKE-RAY-V4": { + "symbol": "LIKE-RAY", + "name": "LIKE-RAY LP", + "coin": "LIKE", + "pc": "RAY", + "mintAddress": "7xqDycbFSCpUpzkYapFeyPJWPwEpV7zdWbYf2MVHTNjv", + "decimals": 9 + }, + "COPE-RAY-V4": { + "symbol": "COPE-RAY", + "name": "COPE-RAY LP", + "coin": "COPE", + "pc": "RAY", + "mintAddress": "A7GCVHA8NSsbdFscHdoNU41tL1TRKNmCH4K94CgcLK9F", + "decimals": 6 + }, + "ETH-SOL-V4": { + "symbol": "ETH-SOL", + "name": "ETH-SOL LP", + "coin": "ETH", + "pc": "SOL", + "mintAddress": "GKfgC86iJoMjwAtcyiLu6nWnjggqUXsDQihXkP14fDez", + "decimals": 6 + }, + "stSOL-USDC-V4": { + "symbol": "stSOL-USDC", + "name": "stSOL-USDC LP", + "coin": "stSOL", + "pc": "USDC", + "mintAddress": "HDUJMwYZkjUZre63xUeDhdCi8c6LgUDiBqxmP3QC3VPX", + "decimals": 9 + }, + "GRAPE-USDC-V4": { + "symbol": "GRAPE-USDC", + "name": "GRAPE-USDC LP", + "coin": "GRAPE", + "pc": "USDC", + "mintAddress": "A8ZYmnZ1vwxUa4wpJVUaJgegsuTEz5TKy5CiJXffvmpt", + "decimals": 6 + }, + "SBR-USDC-V4": { + "symbol": "SBR-USDC", + "name": "SBR-USDC LP", + "coin": "SBR", + "pc": "USDC", + "mintAddress": "9FC8xTFRbgTpuZZYAYnZLxgnQ8r7FwfSBM1SWvGwgF7s", + "decimals": 6 + } + } +} diff --git a/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens_dev.json b/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens_dev.json new file mode 100644 index 00000000000..c3d72316d2d --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/raydium/lp_tokens_dev.json @@ -0,0 +1,13 @@ +{ + "name": "Raydium LP Tokens", + "tokens": { + "COIN-PC-V4": { + "symbol": "COIN-PC", + "name": "COIN-PC LP", + "coin": "COIN", + "pc": "PC", + "mintAddress": "14Wp3dxYTQpRMMz3AW7f2XGBTdaBrf1qb2NKjAN3Tb13", + "decimals": 6 + } + } +} diff --git a/farms/farm-ctrl/src/metadata/tokens/saber/tokens.json b/farms/farm-ctrl/src/metadata/tokens/saber/tokens.json new file mode 100644 index 00000000000..2a076e8e2d0 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/saber/tokens.json @@ -0,0 +1,2242 @@ +{ + "name": "Saber Tokens", + "logoURI": "https://registry.saber.so/icon.png", + "tags": { + "stablecoin": { + "name": "stablecoin", + "description": "Tokens that are fixed to an external asset, e.g. the US dollar" + }, + "ethereum": { + "name": "ethereum", + "description": "Asset bridged from ethereum" + }, + "lp-token": { + "name": "lp-token", + "description": "Asset representing liquidity provider token" + }, + "wrapped-sollet": { + "name": "wrapped-sollet", + "description": "Asset wrapped using sollet bridge" + }, + "wrapped": { + "name": "wrapped", + "description": "Asset wrapped using wormhole bridge" + }, + "leveraged": { "name": "leveraged", "description": "Leveraged asset" }, + "bull": { "name": "bull", "description": "Leveraged Bull asset" }, + "bear": { "name": "bear", "description": "Leveraged Bear asset" }, + "nft": { "name": "nft", "description": "Non-fungible token" }, + "security-token": { + "name": "security-token", + "description": "Tokens that are used to gain access to an electronically restricted resource" + }, + "utility-token": { + "name": "utility-token", + "description": "Tokens that are designed to be spent within a certain blockchain ecosystem e.g. most of the SPL-Tokens" + }, + "tokenized-stock": { + "name": "tokenized-stock", + "description": "Tokenized stocks are tokenized derivatives that represent traditional securities, particularly shares in publicly firms traded on regulated exchanges" + }, + "saber-hidden": { + "name": "saber-hidden", + "description": "Hidden from main Saber UI." + }, + "saber-decimal-wrapped": { + "name": "saber-decimal-wrapped", + "description": "Decimal wrapper for a different token. See `assetContract` for the mint of the underlying." + }, + "saber-stableswap-lp": { + "name": "saber-stableswap-lp", + "description": "Saber StableSwap LP token." + }, + "saber-swappable": { + "name": "saber-swappable", + "description": "May be swapped on Saber." + }, + "wormhole-v1": { + "name": "wormhole-v1", + "description": "Wormhole V1 asset." + }, + "wormhole-v2": { + "name": "wormhole-v2", + "description": "Wormhole V2 asset." + }, + "saber-market-usd": { + "name": "saber-market-usd", + "description": "Token which trades against other representations of USD." + }, + "saber-market-btc": { + "name": "saber-market-btc", + "description": "Token which trades against other representations of BTC." + }, + "saber-market-luna": { + "name": "saber-market-luna", + "description": "Token which trades against other representations of LUNA." + }, + "saber-market-ftt": { + "name": "saber-market-ftt", + "description": "Token which trades against other representations of FTT." + }, + "saber-market-srm": { + "name": "saber-market-srm", + "description": "Token which trades against other representations of SRM." + }, + "saber-market-sol": { + "name": "saber-market-sol", + "description": "Token which trades against other representations of SOL." + }, + "saber-market-eth": { + "name": "saber-market-eth", + "description": "Token which trades against other representations of ETH." + } + }, + "timestamp": "2021-10-26T08:41:13.160Z", + "tokens": [ + { + "symbol": "IOU", + "name": "IOU Coin", + "logoURI": "", + "address": "iouQcQBAiEXe6cKLS85zmZxUqaCqBdeHFpqKoSz615u", + "decimals": 6, + "chainId": 101, + "tags": [], + "extensions": {} + }, + { + "chainId": 101, + "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC", + "name": "USD Coin", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "website": "https://www.centre.io/", + "coingeckoId": "usd-coin", + "serumV3Usdt": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "symbol": "USDT", + "name": "USDT", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "website": "https://tether.to/", + "coingeckoId": "tether", + "serumV3Usdc": "77quYg4MGneUdjgXCunt9GgM1usmrxKY31twEy3WHwcS", + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "symbol": "PAI", + "name": "PAI (Parrot USD)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS.svg", + "tags": [ + "utility-token", + "stablecoin", + "saber-market-usd", + "saber-swappable" + ], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "coingeckoId": "parrot-usd", + "currency": "USD" + } + }, + { + "name": "Saber Wrapped USD Coin (9 decimals)", + "address": "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "decimals": 9, + "symbol": "sUSDC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": [ + "stablecoin", + "saber-market-usd", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"], + "currency": "USD" + } + }, + { + "name": "Saber Wrapped USDT (9 decimals)", + "address": "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV", + "decimals": 9, + "symbol": "sUSDT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.svg", + "tags": [ + "stablecoin", + "saber-market-usd", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "coingeckoId": "tether", + "underlyingTokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"], + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm", + "symbol": "wUST_V1", + "name": "Wrapped UST (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-usd", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "website": "https://terra.money", + "address": "0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xa47c8bf37f92aBed4A126BDA807A7b7498661acD", + "coingeckoId": "terrausd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "symbol": "wDAI_V1", + "name": "Dai Stablecoin (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-usd", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F", + "coingeckoId": "dai", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "symbol": "wBUSD_V1", + "name": "Binance USD (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-usd", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "coingeckoId": "binance-usd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "symbol": "wFRAX_V1", + "name": "Frax (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-usd", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x853d955aCEf822Db058eb8505911ED77F175b99e", + "coingeckoId": "frax", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "symbol": "wHUSD_V1", + "name": "HUSD Stablecoin (Wormhole V1)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-usd", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "website": "https://www.stcoins.com/", + "address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1", + "coingeckoId": "husd", + "currency": "USD", + "source": "wormhole-v1" + } + }, + { + "symbol": "wUSDK_V1", + "name": "USDK (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/usdk.png", + "address": "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "decimals": 9, + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v1", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v1", "saber-swappable"] + }, + { + "chainId": 101, + "address": "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "symbol": "wFTT_V1", + "name": "Wrapped FTT (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-ftt", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "wormhole-v1" + } + }, + { + "chainId": 101, + "address": "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "symbol": "wHBTC_V1", + "name": "HBTC (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-btc", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0x0316EB71485b0Ab14103307bf65a021042c6d380", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0x0316EB71485b0Ab14103307bf65a021042c6d380", + "coingeckoId": "huobi-btc", + "currency": "BTC", + "source": "wormhole-v1" + } + }, + { + "symbol": "wibBTC_V1", + "name": "ibBTC (Wormhole V1)", + "logoURI": "https://registry.saber.so/token-icons/ibbtc.svg", + "address": "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "decimals": 9, + "extensions": { + "coingeckoId": "bitcoin", + "source": "wormhole-v1", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v1", "saber-swappable"] + }, + { + "chainId": 101, + "address": "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "symbol": "wLUNA_V1", + "name": "Wrapped LUNA Token (Wormhole V1)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV.png", + "tags": [ + "wrapped", + "wormhole", + "saber-market-luna", + "wormhole-v1", + "saber-swappable" + ], + "extensions": { + "address": "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678", + "assetContract": "https://etherscan.io/address/0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9", + "coingeckoId": "wrapped-terra", + "currency": "LUNA", + "source": "wormhole-v1" + } + }, + { + "symbol": "wSRM_V1", + "name": "Serum (Wormhole V1)", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "address": "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "decimals": 6, + "extensions": { + "coingeckoId": "serum", + "source": "wormhole-v1", + "currency": "SRM" + }, + "chainId": 101, + "tags": ["saber-market-srm", "wormhole-v1", "saber-swappable"] + }, + { + "symbol": "weBUSD", + "name": "BUSD (ETH Wormhole)", + "address": "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX.png", + "extensions": { + "coingeckoId": "binance-usd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "weUSDC", + "name": "USDC (ETH Wormhole)", + "address": "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM.png", + "extensions": { + "coingeckoId": "usd-coin", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "weUSDT", + "name": "USDT (ETH Wormhole)", + "address": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1.png", + "extensions": { + "coingeckoId": "tether", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "wFRAX", + "name": "FRAX (Wormhole)", + "address": "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp.png", + "extensions": { + "coingeckoId": "frax", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "wHBTC", + "name": "HBTC (Wormhole)", + "address": "7dVH61ChzgmN9BwG4PkzwRP8PbYwPJ7ZPNF2vamKT2H8", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7dVH61ChzgmN9BwG4PkzwRP8PbYwPJ7ZPNF2vamKT2H8.png", + "extensions": { + "coingeckoId": "huobi-btc", + "source": "wormhole-v2", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "wormhole-v2"] + }, + { + "symbol": "wHUSD", + "name": "HUSD (Wormhole)", + "address": "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw.png", + "extensions": { + "coingeckoId": "husd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "wUSDK", + "name": "USDK (Wormhole)", + "address": "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F.png", + "extensions": { + "coingeckoId": "usdk", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "wDAI", + "name": "DAI (Wormhole)", + "address": "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o.png", + "extensions": { + "coingeckoId": "dai", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "whETH", + "name": "Ether (Wormhole)", + "address": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "extensions": { + "coingeckoId": "ethereum", + "source": "wormhole-v2", + "currency": "ETH" + }, + "chainId": 101, + "tags": ["saber-market-eth", "wormhole-v2", "saber-swappable"] + }, + { + "name": "Saber Wrapped Ether (Wormhole) (9 decimals)", + "address": "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "decimals": 9, + "symbol": "swhETH-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.png", + "tags": [ + "saber-market-eth", + "wormhole-v2", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "coingeckoId": "ethereum", + "underlyingTokens": ["7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"], + "currency": "ETH" + } + }, + { + "symbol": "wSRM", + "name": "Serum (Wormhole)", + "address": "xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/xnorPhAzWXUczCP3KjU5yDxmKKZi5cSbxytQ1LgE3kG.png", + "extensions": { + "coingeckoId": "serum", + "source": "wormhole-v2", + "currency": "SRM" + }, + "chainId": 101, + "tags": ["saber-market-srm", "wormhole-v2"] + }, + { + "symbol": "wFTT", + "name": "FTT (Wormhole)", + "address": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "extensions": { + "coingeckoId": "ftx-token", + "source": "wormhole-v2", + "currency": "FTT" + }, + "chainId": 101, + "tags": ["saber-market-ftt", "wormhole-v2", "saber-swappable"] + }, + { + "name": "Saber Wrapped FTT (Wormhole) (9 decimals)", + "address": "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx", + "decimals": 9, + "symbol": "swFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv.png", + "tags": [ + "saber-market-ftt", + "wormhole-v2", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", + "coingeckoId": "ftx-token", + "underlyingTokens": ["EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv"], + "currency": "FTT" + } + }, + { + "symbol": "wUST", + "name": "UST (Wormhole)", + "address": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i.png", + "extensions": { + "coingeckoId": "terrausd", + "source": "wormhole-v2", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "wormhole-v2", "saber-swappable"] + }, + { + "symbol": "wLUNA", + "name": "Luna (Wormhole)", + "address": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W.png", + "extensions": { + "coingeckoId": "terra-luna", + "source": "wormhole-v2", + "currency": "LUNA" + }, + "chainId": 101, + "tags": ["saber-market-luna", "wormhole-v2", "saber-swappable"] + }, + { + "chainId": 101, + "address": "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "symbol": "apUSDT", + "name": "Wrapped USDT (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "symbol": "apUSDC", + "name": "Wrapped USDC (Allbridge from Polygon)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "symbol": "abBUSD", + "name": "Wrapped BUSD (Allbridge from BSC)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF.png", + "address": "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "binance-usd", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "symbol": "acUSD", + "name": "Wrapped cUSD (Allbridge from Celo)", + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e.png", + "address": "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "decimals": 9, + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "celo-dollar", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE", + "symbol": "aeWETH", + "name": "Wrapped ETH (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE.png", + "tags": ["stablecoin", "saber-market-eth", "saber-swappable"], + "extensions": { + "coingeckoId": "weth", + "currency": "ETH", + "source": "allbridge" + } + }, + { + "symbol": "MAI", + "name": "MAI (miMATIC)", + "logoURI": "https://registry.saber.so/token-icons/mai.svg", + "address": "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "decimals": 9, + "extensions": { + "coingeckoId": "mimatic", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"] + }, + { + "chainId": 101, + "address": "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "symbol": "aeUSDC", + "name": "Wrapped USDC (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "symbol": "aeUSDT", + "name": "Wrapped USDT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "tether", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "symbol": "aeDAI", + "name": "Wrapped DAI (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "currency": "USD", + "source": "allbridge" + } + }, + { + "chainId": 101, + "address": "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "symbol": "aeFTT", + "name": "Wrapped FTT (Allbridge from Ethereum)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf.png", + "tags": ["saber-market-ftt", "saber-swappable"], + "extensions": { + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "allbridge" + } + }, + { + "address": "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "symbol": "aaUSDC", + "name": "Wrapped USDC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "usd-coin", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "address": "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "symbol": "aaUSDT", + "name": "Wrapped USDT (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj.svg", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "tether", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "address": "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "symbol": "aaWBTC", + "name": "Wrapped BTC (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw.png", + "extensions": { + "coingeckoId": "wrapped-bitcoin", + "source": "allbridge", + "currency": "BTC" + }, + "chainId": 101, + "tags": ["saber-market-btc", "saber-swappable"] + }, + { + "address": "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "symbol": "aaDAI", + "name": "Wrapped DAI (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi.png", + "tags": ["stablecoin", "saber-market-usd", "saber-swappable"], + "extensions": { + "coingeckoId": "multi-collateral-dai", + "source": "allbridge", + "currency": "USD" + }, + "chainId": 101 + }, + { + "address": "AUrMpCDYYcPuHhyNX8gEEqbmDPFUpBpHrNW3vPeCFn5Z", + "symbol": "AVAX", + "name": "AVAX (Allbridge from Avalanche)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AUrMpCDYYcPuHhyNX8gEEqbmDPFUpBpHrNW3vPeCFn5Z.png", + "extensions": { "coingeckoId": "avalanche", "source": "allbridge" }, + "chainId": 101, + "tags": [] + }, + { + "symbol": "pUSDC", + "name": "Port USDC", + "logoURI": "https://registry.saber.so/token-icons/usdc.svg", + "address": "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58", + "decimals": 6, + "extensions": { + "coingeckoId": "usd-coin", + "underlyingTokens": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"] + }, + { + "symbol": "pUSDT", + "name": "Port USDT", + "logoURI": "https://registry.saber.so/token-icons/usdt.svg", + "address": "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "decimals": 6, + "extensions": { + "coingeckoId": "tether", + "underlyingTokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"], + "source": "port", + "currency": "USD" + }, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"] + }, + { + "symbol": "portSOL", + "name": "Port Solana", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/So11111111111111111111111111111111111111112.png", + "address": "8ezDtNNhX91t1NbSLe8xV2PcCEfoQjEm2qDVGjt3rjhg", + "decimals": 9, + "extensions": { + "coingeckoId": "solana", + "underlyingTokens": ["So11111111111111111111111111111111111111112"], + "source": "port", + "currency": "SOL" + }, + "chainId": 101, + "tags": ["saber-market-sol"] + }, + { + "symbol": "pmSOL", + "name": "Port Marinade staked SOL", + "logoURI": "https://cdn.jsdelivr.net/gh/saber-hq/spl-token-icons@master/icons/101/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.png", + "address": "Dt1Cuau5m5CSmun8hZstjEh9RszxAmejnq7ZaHNcuXfA", + "decimals": 9, + "extensions": { + "coingeckoId": "solana", + "underlyingTokens": ["mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"], + "source": "port", + "currency": "SOL" + }, + "chainId": 101, + "tags": ["saber-market-sol"] + }, + { + "chainId": 101, + "address": "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "symbol": "xUSD", + "name": "Synthetic USD", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y.svg", + "tags": ["saber-market-usd", "saber-swappable"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "usd-coin", + "currency": "USD", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "symbol": "xSOL", + "name": "Synthetic SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov.svg", + "tags": ["saber-market-sol", "saber-swappable"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "solana", + "currency": "SOL", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "symbol": "xETH", + "name": "Synthetic ETH", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK.svg", + "tags": ["saber-market-eth", "saber-swappable"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ethereum", + "currency": "ETH", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "symbol": "xBTC", + "name": "Synthetic BTC", + "decimals": 10, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D.svg", + "tags": ["saber-market-btc", "saber-swappable"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "bitcoin", + "currency": "BTC", + "source": "synthetify" + } + }, + { + "chainId": 101, + "address": "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "symbol": "xFTT", + "name": "Synthetic FTT", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9.svg", + "tags": ["saber-market-ftt", "saber-swappable"], + "extensions": { + "website": "https://synthetify.io/", + "twitter": "https://twitter.com/synthetify", + "coingeckoId": "ftx-token", + "currency": "FTT", + "source": "synthetify" + } + }, + { + "name": "Saber Wrapped USD Coin (8 decimals)", + "address": "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy", + "decimals": 8, + "symbol": "sUSDC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.png", + "tags": [ + "stablecoin", + "saber-market-usd", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "coingeckoId": "usd-coin", + "underlyingTokens": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"], + "currency": "USD" + } + }, + { + "chainId": 101, + "address": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "symbol": "FTT", + "name": "Wrapped FTT (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-ftt"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "assetContract": "https://etherscan.io/address/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "serumV3Usdc": "2Pbh1CvRVku1TgewMfycemghf6sU9EyuFDcNXqvRmSxc", + "serumV3Usdt": "Hr3wzG8mZXNHV7TuL6YqtgfVUesCqMxGYCEyP3otywZE", + "coingeckoId": "ftx-token", + "waterfallbot": "https://bit.ly/FTTwaterfall", + "currency": "FTT" + } + }, + { + "name": "Saber Wrapped Wrapped FTT (Sollet) (9 decimals)", + "address": "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt", + "decimals": 9, + "symbol": "sFTT-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-ftt", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", + "coingeckoId": "ftx-token", + "underlyingTokens": ["AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3"], + "currency": "FTT" + } + }, + { + "chainId": 101, + "address": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "symbol": "SRM", + "name": "Serum", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt.png", + "tags": ["saber-market-srm", "saber-swappable"], + "extensions": { + "website": "https://projectserum.com/", + "serumV3Usdc": "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA", + "serumV3Usdt": "AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb", + "coingeckoId": "serum", + "waterfallbot": "https://bit.ly/SRMwaterfall", + "currency": "SRM" + } + }, + { + "chainId": 101, + "address": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "symbol": "ETH", + "name": "Wrapped Ethereum (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-eth"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "serumV3Usdc": "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX", + "serumV3Usdt": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF", + "coingeckoId": "ethereum", + "currency": "ETH" + } + }, + { + "name": "Saber Wrapped Wrapped Ethereum (Sollet) (8 decimals)", + "address": "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj", + "decimals": 8, + "symbol": "sETH-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-eth", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", + "coingeckoId": "ethereum", + "underlyingTokens": ["2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk"], + "currency": "ETH" + } + }, + { + "chainId": 101, + "address": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "symbol": "renBTC", + "name": "renBTC", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-swappable"], + "extensions": { + "coingeckoId": "renbtc", + "website": "https://renproject.io/", + "serumV3Usdc": "74Ciu5yRzhe8TFTHvQuEVbFZJrbnCMRoohBK33NNiPtv", + "currency": "BTC" + } + }, + { + "name": "Saber Wrapped renBTC (9 decimals)", + "address": "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5", + "decimals": 9, + "symbol": "srenBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped", "saber-swappable"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": ["CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5"], + "currency": "BTC" + } + }, + { + "name": "Saber Wrapped renBTC (10 decimals)", + "address": "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi", + "decimals": 10, + "symbol": "srenBTC-10", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5.png", + "tags": ["saber-market-btc", "saber-decimal-wrapped", "saber-swappable"], + "extensions": { + "assetContract": "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "coingeckoId": "renbtc", + "underlyingTokens": ["CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5"], + "currency": "BTC" + } + }, + { + "chainId": 101, + "address": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "symbol": "BTC", + "name": "Wrapped Bitcoin (Sollet)", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": ["wrapped-sollet", "ethereum", "saber-market-btc"], + "extensions": { + "bridgeContract": "https://etherscan.io/address/0xeae57ce9cc1984f202e15e038b964bb8bdf7229a", + "serumV3Usdc": "A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw", + "serumV3Usdt": "C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4", + "coingeckoId": "bitcoin", + "currency": "BTC" + } + }, + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (8 decimals)", + "address": "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv", + "decimals": 8, + "symbol": "sBTC-8", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": ["9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E"], + "currency": "BTC" + } + }, + { + "name": "Saber Wrapped Wrapped Bitcoin (Sollet) (9 decimals)", + "address": "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H", + "decimals": 9, + "symbol": "sBTC-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E.png", + "tags": [ + "wrapped-sollet", + "ethereum", + "saber-market-btc", + "saber-decimal-wrapped", + "saber-swappable" + ], + "extensions": { + "assetContract": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", + "coingeckoId": "bitcoin", + "underlyingTokens": ["9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E"], + "currency": "BTC" + } + }, + { + "address": "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun", + "symbol": "pBTC", + "name": "pBTC (Parrot BTC)", + "decimals": 8, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun.svg", + "tags": [ + "stablecoin", + "saber-market-btc", + "saber-swappable", + "saber-market-btc", + "saber-swappable" + ], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "BTC" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "symbol": "renLUNA", + "name": "renLUNA", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna", "saber-swappable"], + "extensions": { + "website": "https://renproject.io/", + "serumV3Usdc": "CxDhLbbM9uAA2AXfSPar5qmyfmC69NLj3vgJXYAsSVBT", + "currency": "LUNA" + } + }, + { + "name": "Saber Wrapped renLUNA (9 decimals)", + "address": "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT", + "decimals": 9, + "symbol": "srenLUNA-9", + "chainId": 101, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE.png", + "tags": ["saber-market-luna", "saber-decimal-wrapped", "saber-swappable"], + "extensions": { + "assetContract": "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE", + "underlyingTokens": ["8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE"], + "currency": "LUNA" + } + }, + { + "chainId": 101, + "address": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "symbol": "mSOL", + "name": "Marinade staked SOL (mSOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.png", + "tags": ["saber-market-sol", "saber-swappable"], + "extensions": { + "coingeckoId": "msol", + "website": "https://marinade.finance", + "twitter": "https://twitter.com/MarinadeFinance", + "discord": "https://discord.gg/mGqZA5pjRN", + "medium": "https://medium.com/marinade-finance", + "github": "https://github.com/marinade-finance", + "serumV3Usdc": "6oGsL2puUgySccKzn9XA9afqF217LfxP5ocq4B3LWsjy", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/So11111111111111111111111111111111111111112.png", + "tags": ["saber-market-sol", "saber-swappable"], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana", + "currency": "SOL" + } + }, + { + "symbol": "prtSOL", + "name": "Parrot Stake Pool SOL", + "logoURI": "https://registry.saber.so/token-icons/prtsol.svg", + "address": "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "decimals": 9, + "extensions": { "coingeckoId": "solana", "currency": "SOL" }, + "chainId": 101, + "tags": ["saber-market-sol", "saber-swappable"] + }, + { + "address": "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "symbol": "pSOL", + "name": "pSOL (Parrot SOL)", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX.svg", + "tags": ["stablecoin", "saber-market-sol", "saber-swappable"], + "extensions": { + "website": "https://parrot.fi", + "twitter": "https://twitter.com/gopartyparrot", + "telegram": "https://t.me/gopartyparrot", + "medium": "https://gopartyparrot.medium.com/", + "discord": "https://discord.gg/gopartyparrot", + "currency": "SOL" + }, + "chainId": 101 + }, + { + "chainId": 101, + "address": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "symbol": "stSOL", + "name": "Lido Staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj.png", + "tags": ["saber-market-sol", "saber-swappable"], + "extensions": { + "website": "https://solana.lido.fi/", + "twitter": "https://twitter.com/LidoFinance/", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "symbol": "scnSOL", + "name": "Socean staked SOL", + "decimals": 9, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm.png", + "tags": ["stake-pool", "saber-market-sol", "saber-swappable"], + "extensions": { + "discord": "https://discord.gg/k8ZcW27bq9/", + "medium": "https://medium.com/@soceanfinance/", + "twitter": "https://twitter.com/soceanfinance/", + "website": "https://socean.fi/", + "coingeckoId": "socean-staked-sol", + "currency": "SOL" + } + }, + { + "chainId": 101, + "address": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1", + "symbol": "SBR", + "name": "Saber Protocol Token", + "decimals": 6, + "logoURI": "https://spl-token-icons.static-assets.ship.capital/icons/101/Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1.svg", + "waterfallbot": "https://bit.ly/SBRwaterfall", + "tags": ["saber-hidden"], + "extensions": { + "website": "https://saber.so", + "twitter": "https://twitter.com/saber_hq", + "github": "https://github.com/saber-hq", + "medium": "https://blog.saber.so", + "discord": "https://chat.saber.so", + "serumV3Usdc": "HXBi8YBwbh4TXF6PjVw81m8Z3Cc4WBofvauj5SBFdgUs", + "coingeckoId": "saber" + } + }, + { + "symbol": "sbrUSDCUSDT", + "name": "USDT-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "2poo1w1DL6yd2WNTCnNTzDqkC6MBXq7axo77P16yrBuf", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_usdt", + "underlyingTokens": [ + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + { + "symbol": "sbrPAIUSDC", + "name": "PAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PaiYwHYxr4SsEWox9YmyBNJmxVG7GdauirbBcYGB7cJ", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_pai", + "underlyingTokens": [ + "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + { + "symbol": "sbrrenBTCsBTC-8", + "name": "BTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "SLPbsNrLHv8xG4cTc4R5Ci8kB9wUPs6yn6f7cKosoxs", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/btc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "SBTCB6pWqeDo6zGi9WVRMLCsKsN6JiR1RMUqvLtgSRv" + ], + "source": "saber" + } + }, + { + "symbol": "sbrrenBTCpBTC", + "name": "pBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "pBTCmyG7FaZx4uk3Q2pT5jHKWmWDn84npdc7gZXpQ1x", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/pbtc", + "underlyingTokens": [ + "CDJWUqTcYTVAKXAVXoQZFes5JUFc7owSeq7eMQcDSbo5", + "DYDWu4hE4MN3aH897xQ3sRTs5EAjJDmQsKLNhbpUiKun" + ], + "source": "saber" + } + }, + { + "symbol": "sbrxUSDUSDC", + "name": "xUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "XUSDfnsgc2QYXRdbPAbMWoXCbBCCspRSvoGJ8o7RV9n", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xusd", + "underlyingTokens": [ + "83LGLCm7QKpYZbX8q4W2kYWbtt8NJBwbVwEepzkVnJ9y", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + { + "symbol": "sbrxSOLSOL", + "name": "xSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "xSoLVBNztDTUW8Kou2GJinHoe54Siu9Sk3e2uoU9aUi", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xsol", + "underlyingTokens": [ + "BdUJucPJyjkHxLMv6ipKNUhSeY3DWrVtgxAES1iSBAov", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + { + "symbol": "sbrxFTTwFTT", + "name": "xFTT-wFTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "xFTTLsMdN28XHtYTTTVWYz5zwXWBm5r1WTuZ7Cc7SyA", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xftt", + "underlyingTokens": [ + "Fr3W7NPVvdVbwMcHgA7Gx2wUxP43txdsn3iULJGFbKz9", + "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv" + ], + "source": "saber" + } + }, + { + "symbol": "sbrxETHswhETH-9", + "name": "xETH-whETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "xETH89889mVRwsw9tSUnULsdLUPryTpijagy2YXxWyY", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xeth", + "underlyingTokens": [ + "8bqjz8DeSuim1sEAsQatjJN4zseyxSPdhHQcuuhL8PCK", + "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma" + ], + "source": "saber" + } + }, + { + "symbol": "sbrxBTCsrenBTC-10", + "name": "xBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 10, + "address": "xBTCPvRuEuRgz5DuuUd3ju3VP5XtR2Dsu1AxyW9JpXK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-synthetify"], + "extensions": { + "website": "https://app.saber.so/#/pools/xbtc", + "underlyingTokens": [ + "HWxpSV3QAGzLQzGAtvhSYAEr7sTQugQygnni1gnUGh1D", + "BtX7AfzEJLnU8KQR1AgHrhGH5s2AHUTbfjhUQP8BhPvi" + ], + "source": "saber" + } + }, + { + "symbol": "sbrsUSDC-9wUST_V1", + "name": "wUST_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "UST32f2JtPGocLzsL41B3VBBoJzTm1mK1j3rwyM3Wgc", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ust", + "underlyingTokens": [ + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1", + "CXLBjMMcwkc17GfJtBos6rQCo1ypeH6eDbB82Kby4MRm" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwDAI_V1sUSDC-9", + "name": "wDAI_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "Daimhb91DY4e3aVaa7YCW5GgwaMT9j1ALSi2GriBvDNh", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/dai", + "underlyingTokens": [ + "FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwBUSD_V1sUSDC-9", + "name": "wBUSD_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BUSDaZjarCrQJLeHpWi7aLaKptdR1S8DFpwdDuuZu9p3", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/busd", + "underlyingTokens": [ + "AJ1W9A9N9dEMdVyoDiam2rV44gnBm2csrPDP7xqcapgX", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwLUNA_V1srenLUNA-9", + "name": "wLUNA_V1-renLUNA Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "LUNkiLcb2wxcqULmJvMjuM6YQhpFBadG5KZBe7qBpSE", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/luna", + "underlyingTokens": [ + "2Xf2yAXJfg82sWwdLUo2x9mZXy6JCdszdMZkcF1Hf4KV", + "KUANeD8EQvwpT1W7QZDtDqctLEh2FfSTy5pThE9CogT" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwFRAX_V1sUSDC-9", + "name": "wFRAX_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FRAXXvt2ucEsxYPK4nufDy5zKhb2xysieqRBE1dQTqnK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/frax", + "underlyingTokens": [ + "8L8pDf3jutdpdr4m3np68CL9ZroLActrqwxi6s9Ah5xU", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwHBTC_V1srenBTC-9", + "name": "wHBTC_V1-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "HBTCNvkwjMsEtwe2PeXUuMcu8C4Hobw6HDP2m6vpWHGo", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/hbtc", + "underlyingTokens": [ + "8pBc4v9GAwCBNWPB5XKA93APexMGAS4qMr37vNke9Ref", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwHUSD_V1sUSDC-8", + "name": "wHUSD_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "HUSDgP5YieANhAAHD42yivX9aFS1zbodTut2Dvvkj8QS", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/husd", + "underlyingTokens": [ + "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwUSDK_V1sUSDC-9", + "name": "wUSDK_V1-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "uSdKg2Cs5bCtFSeNXs7aRVNzZJauX58eCkdsfssxTdW", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdk", + "underlyingTokens": [ + "2kycGCD8tJbrjJJqWN2Qz5ysN9iB4Bth3Uic4mSB7uak", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwFTT_V1sFTT-9", + "name": "wFTT_V1-FTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FTXdV5wFFhceKjcd1JRrRQTT2uB7ruMerAqbj2rj1Mz7", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ftt", + "underlyingTokens": [ + "GbBWwtYTMPis4VHb8MrBbdibPhn28TSrLB53KvUmb7Gi", + "FTT9rBBrYwcHam4qLvkzzzhrsihYMbZ3k6wJbdoahxAt" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwSRM_V1SRM", + "name": "wSRM_V1-SRM Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "SRMKjSJpBHJ5gSVTrimci49SnXc1LVkBi9TGF9RNYdp", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/srm", + "underlyingTokens": [ + "2jXy799YnEcRXneFo2GEAB6SDRsAa767HpWmktRr1DaP", + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwibBTC_V1sBTC-9", + "name": "wibBTC_V1-BTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BADGsQo6rTxKZuqkY1kSoqhriQwZW3ZVgyPjgDk9mvyo", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ibbtc", + "underlyingTokens": [ + "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "9999j2A8sXUtHtDoQdk528oVzhaKBsXyRGZ67FKGoi7H" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwibBTC_V1srenBTC-9", + "name": "wibBTC_V1-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BRENm9SgYJZuCxM4ZJiH6CmZqEBn4MLpD9cnBZDnJgeT", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v1"], + "extensions": { + "website": "https://app.saber.so/#/pools/ibbtc_ren", + "underlyingTokens": [ + "66CgfJQoZkpkrEgC1z4vFJcSFc4V6T5HqbjSSNuqcNJz", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + { + "symbol": "sbrweBUSDsUSDC-8", + "name": "weBUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "BSCNZ4GLnpZYv4BLk5edymk4qty8a6ZpiMbfvtv9gAzL", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/webusd", + "underlyingTokens": [ + "33fsBLA8djQm82RpHmE3SuVrPGtZBWNYExsEUeKX1HXX", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrweUSDCUSDC", + "name": "weUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USDCgfM1psLGhAbx99iPA72mTySvUcVq33qhCJpm65c", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/weusdc", + "underlyingTokens": [ + "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + { + "symbol": "sbrweUSDTUSDT", + "name": "weUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USDTJZL2vH92K5QeCvQTTzvMXUYAdvk3v46CwZyfsue", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/weusdt", + "underlyingTokens": [ + "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwUSDKsUSDC-8", + "name": "wUSDK-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "USDKKmk1anWU1aEn6GJ6skL3ZvcB9CBAWVkmPGQEHtz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wusdk", + "underlyingTokens": [ + "43m2ewFV5nDepieFjT9EmAQnc1HRtAF247RBpLGFem5F", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwHUSDsUSDC-8", + "name": "wHUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "HUSzWddUQbavKn24cjozm65eps8rq9yhNn5edtTLWfdz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/whusd", + "underlyingTokens": [ + "7VQo3HFLNH5QqGtM8eC3XQbPkJUu7nS9LeGWjerRh5Sw", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwDAIsUSDC-8", + "name": "wDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "DAihWEjhBc8LEmV1rEekTaiC2zqE5ex7nEFkmoe1Ppp3", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wdai", + "underlyingTokens": [ + "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwFRAXsUSDC-8", + "name": "wFRAX-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "FRXsjEv4jF3r72FgbCXu8uLbPoZGLmCmg3EN1S3cfC4x", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wfrax", + "underlyingTokens": [ + "FR87nWEUxVgerFGhZM8Y4AggKGLnaXswr1Pd8wZ4kZcp", + "88881Hu2jGMfCs9tMu5Rr7Ah7WBNBuXqde4nR5ZmKYYy" + ], + "source": "saber" + } + }, + { + "symbol": "sbrsETH-8whETH", + "name": "ETH-whETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "WTHPuMavN9HBvgUafjrL65WqQytQHDwnTAmdFB9whXA", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wheth", + "underlyingTokens": [ + "SL819j8K9FuFPL84UepVcFkEZqDUUvVzwDmJjCHySYj", + "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" + ], + "source": "saber" + } + }, + { + "symbol": "sbraeFTTswFTT-9", + "name": "aeFTT-wFTT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "FTXjwjwWqituSXEHnL5VF1mjDhZoAyJqvHiRPsRq3KWK", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wftt", + "underlyingTokens": [ + "BFsCwfk8VsEbSfLkkgmoKsAPk2N6FMJjeTsuxfGa9VEf", + "FTT9GrHBVHvDeUTgLU8FxVJouGqg9uiWGmmjETdm32Sx" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwUSTUSDC", + "name": "wUST-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "USTCmQpbUGj5iTsXdnTYHZupY1QpftDZhLokSVk6UWi", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wust", + "underlyingTokens": [ + "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + { + "symbol": "sbrwLUNArenLUNA", + "name": "wLUNA-renLUNA Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "LUN1p1dZwSBgTv1JSdn2apdUuLanHKtgNcnpDydVFTU", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-wormhole-v2"], + "extensions": { + "website": "https://app.saber.so/#/pools/wluna", + "underlyingTokens": [ + "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", + "8wv2KAykQstNAj2oW6AHANGBiFKVFhvMiyyzzjhkmGvE" + ], + "source": "saber" + } + }, + { + "symbol": "sbracUSDsUSDC-9", + "name": "acUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "cUSDDDBZRhpDW7eyUUPMuw6u1SiMnzu6i7movwf5jxk", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/acusd", + "underlyingTokens": [ + "EwxNF8g9UfmsJVcZFTpL9Hx5MCkoQFoJi6XNWzKf1j8e", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrapUSDTUSDT", + "name": "apUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PLYJZgSkcV8UXTWhTyf2WLCMeBoZum1Y4rXgXkoYiNj", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/apusdt", + "underlyingTokens": [ + "DNhZkUaxHXYvpxZ7LNnHtss8sQgdAfd1ZYS1fB7LKWUZ", + "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" + ], + "source": "saber" + } + }, + { + "symbol": "sbrapUSDCUSDC", + "name": "apUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "APUVVYA8Xf7T1PqLyDvNxLtwQ9rRDf3RUxfMttreVzHP", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/apusdc", + "underlyingTokens": [ + "eqKJTf1Do4MDPyKisMYqVaUFpkEFAs3riGF3ceDH2Ca", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ], + "source": "saber" + } + }, + { + "symbol": "sbrMAIsUSDC-9", + "name": "MAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "MAiP3Zmjhc6NYiCb2xK2893ifvTTDHciCS57Kga39pC", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/mai", + "underlyingTokens": [ + "9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrabBUSDsUSDC-9", + "name": "abBUSD-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "BUSDjE9NEQ15aRFTxKFAjUf5vzqBhEgTNbYevWcSB5qp", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/abbusd", + "underlyingTokens": [ + "6nuaX3ogrr2CaoAPjtaKHAoBNWok32BMcRozuf32s2QF", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbrswhETH-9aeWETH", + "name": "whETH-aeWETH Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AET3m1Mp2SLi7QX3tSypcZWyEtk1d8dUGcwhweDiZdaR", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeeth", + "underlyingTokens": [ + "KNVfdSJyq1pRQk9AKKv1g5uyGuk6wpm4WG16Bjuwdma", + "AaAEw2VCw1XzgvKB8Rj2DyK2ZVau9fbt2bE8hZFWsMyE" + ], + "source": "saber" + } + }, + { + "symbol": "sbraeUSDCsUSDC-9", + "name": "aeUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AECpyKJWfXVyWnk2d9md5dUj3RuzHRKfQra8MakjuVRz", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeusdc", + "underlyingTokens": [ + "DdFPRnccQqLD4zCHrBqdY95D6hvw6PLWp9DEXj1fLCL9", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbraeUSDTsUSDT-9", + "name": "aeUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "aeTwxcJhujVCq6rwbJri3s6ViYifsJUCFirMjLHgHZ7", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aeusdt", + "underlyingTokens": [ + "Bn113WT6rbdgwrm12UJtnmNqGqZjY4it2WoUQuQopFVn", + "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + ], + "source": "saber" + } + }, + { + "symbol": "sbraeDAIsUSDC-9", + "name": "aeDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "aeDebgky5BssqgLo426rXoQTmGrAn1JjEXp6aXFNLic", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aedai", + "underlyingTokens": [ + "9w6LpS7RU1DKftiwH3NgShtXbkMM1ke9iNU4g3MBXSUs", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbraaUSDCsUSDC-9", + "name": "aaUSDC-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVC7uVb6R9B34T8zWxQMEK8twvYk26U71gworsujxFNv", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aausdc", + "underlyingTokens": [ + "8Yv9Jz4z7BUHP68dz8E8m3tMe6NKgpMUKn8KVqrPA6Fr", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbraaUSDTsUSDT-9", + "name": "aaUSDT-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVTrxHq5P57fYZTYjMuCRWFqsrLmom2gGThNtgEgK1ip", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aausdt", + "underlyingTokens": [ + "FwEHs3kJEdMa2qZHv7SgzCiFXUQPEycEXksfBkwmS8gj", + "AEUT5uFm1D575FVCoQd5Yq891FJEqkncZUbBFoFcAhTV" + ], + "source": "saber" + } + }, + { + "symbol": "sbraaDAIsUSDC-9", + "name": "aaDAI-USDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVDuGckLavyLr5YifViaxnoveY6rwqDezHw5kiKiRQEC", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aadai", + "underlyingTokens": [ + "EgQ3yNtVhdHz7g1ZhjfGbxhFKMPPaFkz8QHXM5RBZBgi", + "JEFFSQ3s8T3wKsvp4tnRAsUBW7Cqgnf8ukBZC4C8XBm1" + ], + "source": "saber" + } + }, + { + "symbol": "sbraaWBTCsrenBTC-9", + "name": "aaWBTC-renBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "AVBDpg1UYpDYQLbzEnRY76R3u82PYHtDuc3NBdFS2k39", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-allbridge"], + "extensions": { + "website": "https://app.saber.so/#/pools/aawbtc", + "underlyingTokens": [ + "Fd8xyHHRjTvxfZrBirb6MaxSmrZYw99gRSqFUKdFwFvw", + "FACTQhZBfRzC7A76antnpAoZtiwYmUfdAN8wz7e8rxC5" + ], + "source": "saber" + } + }, + { + "symbol": "sbrpUSDTpUSDC", + "name": "pUSDT-pUSDC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "PortuzxBGYMQXeNmM9Kc6AtHLBwqSrb6xWwZ4trQ1en", + "chainId": 101, + "tags": ["saber-stableswap-lp", "saber-lp-port"], + "extensions": { + "website": "https://app.saber.so/#/pools/port_2pool", + "underlyingTokens": [ + "3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ", + "FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58" + ], + "source": "saber" + } + }, + { + "symbol": "sbrmSOLSOL", + "name": "mSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "SoLEao8wTzSfqhuou8rcYsVoLjthVmiXuEjzdNPMnCz", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/msol_sol", + "underlyingTokens": [ + "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + { + "symbol": "sbrpSOLprtSOL", + "name": "pSOL-prtSOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "PSopTFPXzTRysj2H6W8oTvYBZmJHtRcVaQaDkckifAy", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/psol", + "underlyingTokens": [ + "9EaLkQrbjmbbuZG9Wdpo8qfNUEjHATJFSycEmw6f1rGX", + "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3" + ], + "source": "saber" + } + }, + { + "symbol": "sbrprtSOLSOL", + "name": "prtSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "PrsVdKtXDDf6kJQu5Ff6YqmjfE4TZXtBgHM4bjuvRnR", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/prtsol", + "underlyingTokens": [ + "BdZPG9xWrG3uFrx2KrUW1jT4tZ9VKPDWknYihzoPRJS3", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + { + "symbol": "sbrstSOLSOL", + "name": "stSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "stSjCmjQ96BiGhTk8gkU22j1739R8YBQVMq7KXWTqUV", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/stsol", + "underlyingTokens": [ + "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + }, + { + "symbol": "sbrscnSOLSOL", + "name": "scnSOL-SOL Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 9, + "address": "SoCJs5Qw1D3fjGbTqxxovK15FVnYVrwvTbYcBBrZmWj", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/socean", + "underlyingTokens": [ + "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", + "So11111111111111111111111111111111111111112" + ], + "source": "saber" + } + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/tokens/saber/tokens_dev.json b/farms/farm-ctrl/src/metadata/tokens/saber/tokens_dev.json new file mode 100644 index 00000000000..fc42dadf254 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/saber/tokens_dev.json @@ -0,0 +1,230 @@ +{ + "name": "Saber Tokens", + "logoURI": "https://registry.saber.so/icon.png", + "tags": { + "stablecoin": { + "name": "stablecoin", + "description": "Tokens that are fixed to an external asset, e.g. the US dollar" + }, + "ethereum": { + "name": "ethereum", + "description": "Asset bridged from ethereum" + }, + "lp-token": { + "name": "lp-token", + "description": "Asset representing liquidity provider token" + }, + "wrapped-sollet": { + "name": "wrapped-sollet", + "description": "Asset wrapped using sollet bridge" + }, + "wrapped": { + "name": "wrapped", + "description": "Asset wrapped using wormhole bridge" + }, + "leveraged": { "name": "leveraged", "description": "Leveraged asset" }, + "bull": { "name": "bull", "description": "Leveraged Bull asset" }, + "bear": { "name": "bear", "description": "Leveraged Bear asset" }, + "nft": { "name": "nft", "description": "Non-fungible token" }, + "security-token": { + "name": "security-token", + "description": "Tokens that are used to gain access to an electronically restricted resource" + }, + "utility-token": { + "name": "utility-token", + "description": "Tokens that are designed to be spent within a certain blockchain ecosystem e.g. most of the SPL-Tokens" + }, + "tokenized-stock": { + "name": "tokenized-stock", + "description": "Tokenized stocks are tokenized derivatives that represent traditional securities, particularly shares in publicly firms traded on regulated exchanges" + }, + "saber-hidden": { + "name": "saber-hidden", + "description": "Hidden from main Saber UI." + }, + "saber-decimal-wrapped": { + "name": "saber-decimal-wrapped", + "description": "Decimal wrapper for a different token. See `assetContract` for the mint of the underlying." + }, + "saber-stableswap-lp": { + "name": "saber-stableswap-lp", + "description": "Saber StableSwap LP token." + }, + "saber-swappable": { + "name": "saber-swappable", + "description": "May be swapped on Saber." + }, + "wormhole-v1": { + "name": "wormhole-v1", + "description": "Wormhole V1 asset." + }, + "wormhole-v2": { + "name": "wormhole-v2", + "description": "Wormhole V2 asset." + }, + "saber-market-usd": { + "name": "saber-market-usd", + "description": "Token which trades against other representations of USD." + }, + "saber-market-btc": { + "name": "saber-market-btc", + "description": "Token which trades against other representations of BTC." + }, + "saber-market-luna": { + "name": "saber-market-luna", + "description": "Token which trades against other representations of LUNA." + }, + "saber-market-ftt": { + "name": "saber-market-ftt", + "description": "Token which trades against other representations of FTT." + }, + "saber-market-srm": { + "name": "saber-market-srm", + "description": "Token which trades against other representations of SRM." + }, + "saber-market-sol": { + "name": "saber-market-sol", + "description": "Token which trades against other representations of SOL." + }, + "saber-market-eth": { + "name": "saber-market-eth", + "description": "Token which trades against other representations of ETH." + } + }, + "timestamp": "2021-10-08T22:59:09.384Z", + "tokens": [ + { + "symbol": "IOU", + "name": "IOU Coin", + "logoURI": "", + "address": "iouQcQBAiEXe6cKLS85zmZxUqaCqBdeHFpqKoSz615u", + "decimals": 6, + "chainId": 101, + "tags": [], + "extensions": {} + }, + { + "symbol": "USDC", + "name": "USD Coin", + "logoURI": "/tokens/usdc.svg", + "address": "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "decimals": 6, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"], + "extensions": {} + }, + { + "symbol": "USDT", + "name": "Tether USD", + "logoURI": "/tokens/usdt.svg", + "address": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS", + "decimals": 6, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"], + "extensions": {} + }, + { + "symbol": "PAI", + "name": "PAI", + "logoURI": "/tokens/pai.svg", + "address": "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL", + "decimals": 6, + "chainId": 101, + "tags": ["saber-market-usd", "saber-swappable"], + "extensions": {} + }, + { + "name": "Test WBTC", + "address": "Wbt2CgkkD3eVckD5XxWJmT8pTnFTyWrwvGM7bUMLvsM", + "decimals": 6, + "chainId": 101, + "symbol": "WBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/bitcoin/info/logo.png", + "tags": ["saber-market-btc"], + "extensions": {} + }, + { + "name": "Test RenBTC", + "address": "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "decimals": 8, + "chainId": 101, + "symbol": "renBTC", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/ethereum/assets/0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D/logo.png", + "tags": ["saber-market-btc", "saber-swappable"], + "extensions": {} + }, + { + "symbol": "sWBTC-8", + "name": "Saber Test WBTC (8 decimals)", + "address": "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs", + "decimals": 8, + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/bitcoin/info/logo.png", + "chainId": 101, + "tags": ["saber-decimal-wrapped", "saber-market-btc", "saber-swappable"], + "extensions": { + "assetContract": "Wbt2CgkkD3eVckD5XxWJmT8pTnFTyWrwvGM7bUMLvsM" + } + }, + { + "name": "Saber Protocol Token", + "symbol": "SBR", + "address": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1", + "logoURI": "/tokens/sbr.svg", + "decimals": 6, + "chainId": 101, + "tags": [], + "extensions": {} + }, + { + "symbol": "SLP", + "name": "USDC-USDT Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "YakofBo4X3zMxa823THQJwZ8QeoU8pxPdFdxJs7JW57", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_usdt", + "underlyingTokens": [ + "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS" + ], + "source": "saber" + } + }, + { + "symbol": "SLP", + "name": "USDC-PAI Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 6, + "address": "J8fDLz5bfef14jDNC32nJLbVzpS9Rj1LBHwaSGfYn83J", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/usdc_pai", + "underlyingTokens": [ + "2tWC4JAdL4AxEFJySziYJfsAnW2MHKRo98vbAPiRDSk8", + "4ry1pMstKzMJvMZSms62HduTyCbbqkUyrz17x1dajBmL" + ], + "source": "saber" + } + }, + { + "symbol": "SLP", + "name": "renBTC-WBTC Saber LP", + "logoURI": "https://registry.saber.so/token-icons/slp.png", + "decimals": 8, + "address": "bLpASoWNdsz5DsjCaxpbM2FrkMowTJCydpwiDP4Vdzm", + "chainId": 101, + "tags": ["saber-stableswap-lp"], + "extensions": { + "website": "https://app.saber.so/#/pools/btc", + "underlyingTokens": [ + "Ren3RLPCG6hpKay86d2fQccQLuGG331UNxwn2VTw3GJ", + "BtceyXMo5kwg8u6es4NoukBWQuMwtcBCZpFWUfZgVuZs" + ], + "source": "saber" + } + } + ] +} diff --git a/farms/farm-ctrl/src/metadata/tokens/solana_token_list/get_solana_tokens.sh b/farms/farm-ctrl/src/metadata/tokens/solana_token_list/get_solana_tokens.sh new file mode 100755 index 00000000000..00817d7c589 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/solana_token_list/get_solana_tokens.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Solana tokens loader + +url="https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" + +if hash wget 2>/dev/null; then + wget_or_curl="wget -O tokens.json $url" +elif hash curl 2>/dev/null; then + wget_or_curl="curl -o tokens.json -L $url" +else + echo "Error: Neither curl nor wget were found" >&2 + return 1 +fi + +exec $wget_or_curl diff --git a/farms/farm-ctrl/src/metadata/tokens/solana_token_list/tokens_dev.json b/farms/farm-ctrl/src/metadata/tokens/solana_token_list/tokens_dev.json new file mode 100644 index 00000000000..00a28bf48c9 --- /dev/null +++ b/farms/farm-ctrl/src/metadata/tokens/solana_token_list/tokens_dev.json @@ -0,0 +1,102 @@ +{ + "name": "Solana Token List", + "logoURI": "https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/solana/info/logo.png", + "keywords": ["solana", "spl"], + "tags": { + "stablecoin": { + "name": "stablecoin", + "description": "Tokens that are fixed to an external asset, e.g. the US dollar" + }, + "ethereum": { + "name": "ethereum", + "description": "Asset bridged from ethereum" + }, + "lp-token": { + "name": "lp-token", + "description": "Asset representing liquidity provider token" + }, + "wrapped-sollet": { + "name": "wrapped-sollet", + "description": "Asset wrapped using sollet bridge" + }, + "wrapped": { + "name": "wrapped", + "description": "Asset wrapped using wormhole bridge" + }, + "leveraged": { + "name": "leveraged", + "description": "Leveraged asset" + }, + "bull": { + "name": "bull", + "description": "Leveraged Bull asset" + }, + "bear": { + "name": "bear", + "description": "Leveraged Bear asset" + }, + "nft": { + "name": "nft", + "description": "Non-fungible token" + }, + "security-token": { + "name": "security-token", + "description": "Tokens that are used to gain access to an electronically restricted resource" + }, + "utility-token": { + "name": "utility-token", + "description": "Tokens that are designed to be spent within a certain blockchain ecosystem e.g. most of the SPL-Tokens" + }, + "tokenized-stock": { + "name": "tokenized-stock", + "description": "Tokenized stocks are tokenized derivatives that represent traditional securities, particularly shares in publicly firms traded on regulated exchanges" + } + }, + "timestamp": "2021-03-03T19:57:21+0000", + "tokens": [ + { + "chainId": 101, + "address": "So11111111111111111111111111111111111111112", + "symbol": "SOL", + "name": "Wrapped SOL", + "decimals": 9, + "logoURI": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png", + "tags": [], + "extensions": { + "website": "https://solana.com/", + "serumV3Usdc": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT", + "serumV3Usdt": "HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1", + "coingeckoId": "solana" + } + }, + { + "chainId": 101, + "address": "BEcGFQK1T1tSu3kvHC17cyCkQ5dvXqAJ7ExB2bb5Do7a", + "symbol": "COIN", + "name": "Wrapped COIN", + "decimals": 6, + "logoURI": "", + "tags": [], + "extensions": { + "website": "https://solana.com/" + } + }, + { + "chainId": 101, + "address": "FSRvxBNrQWX2Fy2qvKMLL3ryEdRtE3PUTZBcdKwASZTU", + "symbol": "PC", + "name": "Wrapped PC", + "decimals": 6, + "logoURI": "", + "tags": [], + "extensions": { + "website": "https://solana.com/" + } + } + ], + "version": { + "major": 0, + "minor": 2, + "patch": 2 + } +} diff --git a/farms/farm-ctrl/src/print.rs b/farms/farm-ctrl/src/print.rs new file mode 100644 index 00000000000..9757ef856c7 --- /dev/null +++ b/farms/farm-ctrl/src/print.rs @@ -0,0 +1,80 @@ +//! Handlers for print_pda and print_size commands + +use { + crate::config::Config, + log::info, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{ + farm::Farm, pool::Pool, program::pda, refdb::ReferenceType, refdb::StorageType, + token::Token, vault::Vault, + }, +}; + +pub fn print_pda(_client: &FarmClient, _config: &Config, target: StorageType) { + info!( + "{} RefDB address: {}", + target, + pda::find_refdb_pda(&target.to_string()).0 + ); +} + +pub fn print_pda_all(client: &FarmClient, config: &Config) { + print_pda(client, config, StorageType::Program); + print_pda(client, config, StorageType::Token); + print_pda(client, config, StorageType::Pool); + print_pda(client, config, StorageType::Farm); + print_pda(client, config, StorageType::Vault); +} + +pub fn print_size(client: &FarmClient, _config: &Config, target: StorageType) { + let refdb_size = StorageType::get_storage_size_for_max_records(target, ReferenceType::Pubkey); + let target_size = match target { + StorageType::Program => 0, + StorageType::Token => Token::LEN, + StorageType::Pool => Pool::MAX_LEN, + StorageType::Farm => Farm::MAX_LEN, + StorageType::Vault => Vault::MAX_LEN, + _ => 0, + }; + let target_max_recs = StorageType::get_default_max_records(target, ReferenceType::Pubkey); + let refdb_cost = client + .rpc_client + .get_minimum_balance_for_rent_exemption(refdb_size) + .unwrap(); + let target_cost = client + .rpc_client + .get_minimum_balance_for_rent_exemption(target_size) + .unwrap(); + + info!("{} recs / size / cost:", target.to_string()); + info!( + "RefDB: {} / {} / {}", + target_max_recs, + refdb_size, + lam_to_sol(refdb_cost) + ); + info!( + "Target: {} / {} / {}", + 1, + target_size, + lam_to_sol(target_cost) + ); + info!( + "Target Max: {} / {} / {}\n", + target_max_recs, + target_size * target_max_recs, + lam_to_sol(target_cost * (target_max_recs as u64)) + ); +} + +pub fn print_size_all(client: &FarmClient, config: &Config) { + print_size(client, config, StorageType::Program); + print_size(client, config, StorageType::Token); + print_size(client, config, StorageType::Pool); + print_size(client, config, StorageType::Farm); + print_size(client, config, StorageType::Vault); +} + +fn lam_to_sol(amount: u64) -> f64 { + (amount as f64) / 10f64.powi(9) +} diff --git a/farms/farm-ctrl/src/refdb.rs b/farms/farm-ctrl/src/refdb.rs new file mode 100644 index 00000000000..2c9a3a79e7b --- /dev/null +++ b/farms/farm-ctrl/src/refdb.rs @@ -0,0 +1,58 @@ +//! Handlers for refdb_init and refdb_drop commands + +use { + crate::config::Config, + log::info, + solana_farm_client::client::FarmClient, + solana_farm_sdk::{refdb::ReferenceType, refdb::StorageType}, +}; + +pub fn init(client: &FarmClient, config: &Config, target: StorageType) { + if client.is_refdb_initialized(&target.to_string()).unwrap() { + info!("Already initialized RefDB found for {} objects", target); + return; + } + info!("Initializing RefDB for {} objects", target); + + client + .initialize_refdb( + config.keypair.as_ref(), + &target.to_string(), + ReferenceType::Pubkey, + StorageType::get_default_max_records(target, ReferenceType::Pubkey), + true, + ) + .unwrap(); + + info!("Done.") +} + +pub fn init_all(client: &FarmClient, config: &Config) { + init(client, config, StorageType::Program); + init(client, config, StorageType::Token); + init(client, config, StorageType::Pool); + init(client, config, StorageType::Farm); + init(client, config, StorageType::Vault); +} + +pub fn drop(client: &FarmClient, config: &Config, target: StorageType) { + if !client.is_refdb_initialized(&target.to_string()).unwrap() { + info!("No initialized RefDB found for {} objects", target); + return; + } + info!("Removing RefDB for {} objects", target); + + client + .drop_refdb(config.keypair.as_ref(), &target.to_string(), true) + .unwrap(); + + info!("Done.") +} + +pub fn drop_all(client: &FarmClient, config: &Config) { + drop(client, config, StorageType::Vault); + drop(client, config, StorageType::Farm); + drop(client, config, StorageType::Pool); + drop(client, config, StorageType::Token); + drop(client, config, StorageType::Program); +} diff --git a/farms/farm-ctrl/src/remove.rs b/farms/farm-ctrl/src/remove.rs new file mode 100644 index 00000000000..cbed96be386 --- /dev/null +++ b/farms/farm-ctrl/src/remove.rs @@ -0,0 +1,83 @@ +//! Handlers for remove and remove_all commands + +use { + crate::config::Config, log::info, solana_farm_client::client::FarmClient, + solana_farm_sdk::refdb::StorageType, +}; + +pub fn remove(client: &FarmClient, config: &Config, target: StorageType, object: &str) { + info!("Removing {} object {}...", target, object); + + match target { + StorageType::Program => { + client + .remove_program_id(config.keypair.as_ref(), object) + .unwrap(); + } + StorageType::Vault => { + client + .remove_vault(config.keypair.as_ref(), object) + .unwrap(); + } + StorageType::Farm => { + client.remove_farm(config.keypair.as_ref(), object).unwrap(); + } + StorageType::Pool => { + client.remove_pool(config.keypair.as_ref(), object).unwrap(); + } + StorageType::Token => { + client + .remove_token(config.keypair.as_ref(), object) + .unwrap(); + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} + +pub fn remove_all(client: &FarmClient, config: &Config, target: StorageType) { + info!("Removing all {} objects...", target); + + match target { + StorageType::Program => { + let storage = client.get_program_ids().unwrap(); + for (name, _) in storage.iter() { + client + .remove_program_id(config.keypair.as_ref(), name) + .unwrap(); + } + } + StorageType::Vault => { + let storage = client.get_vaults().unwrap(); + for (name, _) in storage.iter() { + client.remove_vault(config.keypair.as_ref(), name).unwrap(); + } + } + StorageType::Farm => { + let storage = client.get_farms().unwrap(); + for (name, _) in storage.iter() { + client.remove_farm(config.keypair.as_ref(), name).unwrap(); + } + } + StorageType::Pool => { + let storage = client.get_pools().unwrap(); + for (name, _) in storage.iter() { + client.remove_pool(config.keypair.as_ref(), name).unwrap(); + } + } + StorageType::Token => { + let storage = client.get_tokens().unwrap(); + for (name, _) in storage.iter() { + client.remove_token(config.keypair.as_ref(), name).unwrap(); + } + } + _ => { + unreachable!(); + } + } + + info!("Done.") +} diff --git a/farms/farm-ctrl/src/vault.rs b/farms/farm-ctrl/src/vault.rs new file mode 100644 index 00000000000..90f88034b18 --- /dev/null +++ b/farms/farm-ctrl/src/vault.rs @@ -0,0 +1,192 @@ +//! Handlers for feature toggling commands + +use { + crate::config::Config, log::info, solana_farm_client::client::FarmClient, + solana_farm_sdk::string::to_pretty_json, +}; + +pub fn init(client: &FarmClient, config: &Config, vault_names: &str, step: u64) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Initializing Vault {}...", vault); + info!( + "Signature: {}", + client + .init_vault(config.keypair.as_ref(), vault, step) + .unwrap() + ); + } + info!("Done.") +} + +pub fn shutdown(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Shutting down Vault {}...", vault); + info!( + "Signature: {}", + client + .shutdown_vault(config.keypair.as_ref(), vault) + .unwrap() + ); + } + info!("Done.") +} + +pub fn crank(client: &FarmClient, config: &Config, vault_names: &str, step: u64) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Cranking step {} for Vault {}...", step, vault); + info!( + "Signature: {}", + client + .crank_vault(config.keypair.as_ref(), vault, step) + .unwrap() + ); + } + info!("Done.") +} + +pub fn crank_all(client: &FarmClient, config: &Config, step: u64) { + let vaults = client.get_vaults().unwrap(); + for (vault_name, _) in vaults.iter() { + info!("Cranking step {} for Vault {}...", step, vault_name); + info!( + "Signature: {}", + client + .crank_vault(config.keypair.as_ref(), vault_name, step) + .unwrap() + ); + } + info!("Done.") +} + +pub fn set_fee(client: &FarmClient, config: &Config, vault_names: &str, fee_percent: f32) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Setting fee to {} for Vault {}...", fee_percent, vault); + info!( + "Signature: {}", + client + .set_fee_vault(config.keypair.as_ref(), vault, fee_percent) + .unwrap() + ); + } + info!("Done.") +} + +pub fn set_external_fee( + client: &FarmClient, + config: &Config, + vault_names: &str, + external_fee_percent: f32, +) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!( + "Setting external fee to {} for Vault {}...", + external_fee_percent, vault + ); + info!( + "Signature: {}", + client + .set_external_fee_vault(config.keypair.as_ref(), vault, external_fee_percent) + .unwrap() + ); + } + info!("Done.") +} + +pub fn set_min_crank_interval( + client: &FarmClient, + config: &Config, + vault_names: &str, + min_crank_interval: u32, +) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!( + "Setting min crank interval to {} for Vault {}...", + min_crank_interval, vault + ); + info!( + "Signature: {}", + client + .set_min_crank_interval_vault(config.keypair.as_ref(), vault, min_crank_interval) + .unwrap() + ); + } + info!("Done.") +} + +pub fn disable_deposit(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Disabling deposits for Vault {}...", vault); + info!( + "Signature: {}", + client + .disable_deposit_vault(config.keypair.as_ref(), vault) + .unwrap() + ); + } + info!("Done.") +} + +pub fn enable_deposit(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Enabling deposits for Vault {}...", vault); + info!( + "Signature: {}", + client + .enable_deposit_vault(config.keypair.as_ref(), vault) + .unwrap() + ); + } + info!("Done.") +} + +pub fn disable_withdrawal(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Disabling withdrawals for Vault {}...", vault); + info!( + "Signature: {}", + client + .disable_withdrawal_vault(config.keypair.as_ref(), vault) + .unwrap() + ); + } + info!("Done.") +} + +pub fn enable_withdrawal(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Enabling withdrawals for Vault {}...", vault); + info!( + "Signature: {}", + client + .enable_withdrawal_vault(config.keypair.as_ref(), vault) + .unwrap() + ); + } + info!("Done.") +} + +pub fn get_info(client: &FarmClient, config: &Config, vault_names: &str) { + let vaults = vault_names.split(',').collect::>(); + for vault in vaults { + info!("Retreiving stats for Vault {}...", vault); + + let info = client.get_vault_info(vault).unwrap(); + + if config.no_pretty_print { + println!("{}", info); + } else { + println!("{}", to_pretty_json(&info).unwrap()); + } + } + info!("Done.") +} diff --git a/farms/farm-rpc/.gitignore b/farms/farm-rpc/.gitignore new file mode 100644 index 00000000000..ea8c4bf7f35 --- /dev/null +++ b/farms/farm-rpc/.gitignore @@ -0,0 +1 @@ +/target diff --git a/farms/farm-rpc/Cargo.lock b/farms/farm-rpc/Cargo.lock new file mode 100644 index 00000000000..32682dcff08 --- /dev/null +++ b/farms/farm-rpc/Cargo.lock @@ -0,0 +1,4564 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "anyhow" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arraystring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185" +dependencies = [ + "typenum", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "async-trait" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "atomic" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" +dependencies = [ + "autocfg", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "serde", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake3" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 0.1.10", + "constant_time_eq", + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dda7dc709193c0d86a1a51050a926dc3df1cf262ec46a23a25dba421ea1924" +dependencies = [ + "borsh-derive", + "hashbrown 0.9.1", +] + +[[package]] +name = "borsh-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684155372435f578c0fa1acd13ebbb182cc19d6b38b64ae7901da4393217d264" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.27", + "syn 1.0.73", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2102f62f8b6d3edeab871830782285b64cc1830168094db05c8e458f209bc5c3" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196c978c4c9b0b142d446ef3240690bf5a8a33497074a113ff9a337ccb750483" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "either", + "iovec", +] + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "bzip2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time 0.1.43", + "winapi 0.3.9", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "console" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "termios", + "unicode-width", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi 0.3.9", +] + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" +dependencies = [ + "percent-encoding", + "time 0.2.27", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch 0.8.2", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch 0.9.5", + "crossbeam-utils 0.8.5", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset 0.5.6", + "scopeguard", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.5", + "lazy_static", + "memoffset 0.6.4", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", + "rayon", +] + +[[package]] +name = "derivation-path" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193388a8c8c75a490b604ff61775e236541b8975e98e5ca1f6ea97d122b7e2db" +dependencies = [ + "failure", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "derive_more" +version = "0.99.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +dependencies = [ + "convert_case", + "proc-macro2 1.0.27", + "quote 1.0.9", + "rustc_version 0.3.3", + "syn 1.0.73", +] + +[[package]] +name = "devise" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411cf45ac38f00df3679689616649dc12607b846db171780bb790b514a042832" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf7081f06822f1787e29359354426132cf832cc977d7a8ff747848631462ad1" +dependencies = [ + "devise_core", + "quote 1.0.9", +] + +[[package]] +name = "devise_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c23631758736875d7ce08f847f296b4001b72cf90878e85b47df7ac5442147" +dependencies = [ + "bitflags", + "proc-macro2 1.0.27", + "proc-macro2-diagnostics", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "dialoguer" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aa86af7b19b40ef9cbef761ed411a49f0afa06b7b6dcd3dfe2f96a3c546138" +dependencies = [ + "console 0.11.3", + "lazy_static", + "tempfile", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "dir-diff" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" +dependencies = [ + "walkdir", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "ed25519" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0860415b12243916284c67a9be413e044ee6668247b99ba26d94b2bc06c8f6" +dependencies = [ + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.1.0", + "ed25519", + "rand 0.7.3", + "serde", + "serde_bytes", + "sha2", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057f328f31294b5ab432e6c39642f54afd1531677d6d4ba2905932844cc242f3" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "failure", + "hmac 0.9.0", + "sha2", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.9", + "winapi 0.3.9", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-executor" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi 0.3.9", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" +dependencies = [ + "bytes 1.0.1", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 1.8.1", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hidapi" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e07da7e8614133e88b3a93b7352eb3729e3ccd82d5ab661adf23bef1761bf8" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac 0.9.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac 0.10.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.4", + "hmac 0.8.1", +] + +[[package]] +name = "http" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +dependencies = [ + "bytes 1.0.1", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +dependencies = [ + "bytes 1.0.1", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03" +dependencies = [ + "bytes 1.0.1", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.0", + "tokio 1.8.1", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "futures-util", + "hyper", + "log", + "rustls", + "tokio 1.8.1", + "tokio-rustls", + "webpki", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.0.1", + "hyper", + "native-tls", + "tokio 1.8.1", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown 0.11.2", + "serde", +] + +[[package]] +name = "indicatif" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" +dependencies = [ + "console 0.14.1", + "lazy_static", + "number_prefix", + "regex", +] + +[[package]] +name = "inlinable_string" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.6", +] + +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "jobserver" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4467ab6dfa369b69e52bd0692e480c4d117410538526a57a304a0f2250fd95e" +dependencies = [ + "futures 0.3.15", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" + +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + +[[package]] +name = "libsecp256k1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee11012b293ea30093c129173cac4335513064094619f4639a25b310fd33c11" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32239626ffbb6a095b83b37a02ceb3672b2443a87a000a884fc3c4d16925c9c0" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76acb433e21d10f5f9892b1962c2856c58c7f39a9e4bd68ac82b9436a0ffd5b9" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "loom" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111607c723d7857e0d8299d5ce7a0bf4b844d3e44f8de136b13da513eaf8fc4" +dependencies = [ + "cfg-if 1.0.0", + "generator", + "scoped-tls", + "serde", + "serde_json", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "memmap2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "multer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdd568fea4758b30d6423f013f7171e193c34aa97828d1bd9f924fb3af30a8c" +dependencies = [ + "bytes 1.0.1", + "derive_more", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "mime", + "spin 0.9.2", + "tokio 1.8.1", + "tokio-util", + "twoway", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5adf0198d427ee515335639f275e806ca01acf9f07d7cf14bb36a10532a6169" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1def5a3f69d4707d8a040b12785b98029a39e8c610ae685c7f6265669767482" +dependencies = [ + "proc-macro-crate 1.0.0", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "number_prefix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" + +[[package]] +name = "object" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +dependencies = [ + "parking_lot 0.11.1", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ouroboros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84236d64f1718c387232287cf036eb6632a5ecff226f4ff9dccb8c2b79ba0bde" +dependencies = [ + "aliasable", + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.6.2", + "rustc_version 0.2.3", +] + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.7.2", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api 0.4.4", + "parking_lot_core 0.8.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "rustc_version 0.2.3", + "smallvec 0.6.14", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "smallvec 1.6.1", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.9", + "smallvec 1.6.1", + "winapi 0.3.9", +] + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b8c0d71734018084da0c0354193a5edfb81b20d2d57a92c5b154aefc554a4a" +dependencies = [ + "crypto-mac 0.10.0", +] + +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2 1.0.27", + "proc-macro2-diagnostics", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid 0.2.2", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "version_check", + "yansi", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2 1.0.27", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque 0.8.0", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel 0.5.1", + "crossbeam-deque 0.8.0", + "crossbeam-utils 0.8.5", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.3", + "redox_syscall 0.2.9", +] + +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +dependencies = [ + "base64 0.13.0", + "bytes 1.0.1", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio 1.8.1", + "tokio-native-tls", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "atty", + "binascii", + "bytes 1.0.1", + "either", + "figment", + "futures 0.3.15", + "indexmap", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot 0.11.1", + "pin-project-lite", + "rand 0.8.4", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time 0.2.27", + "tokio 1.8.1", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2 1.0.27", + "quote 1.0.9", + "rocket_http", + "syn 1.0.73", + "unicode-xid 0.2.2", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +dependencies = [ + "cookie", + "either", + "http", + "hyper", + "indexmap", + "log", + "memchr", + "mime", + "parking_lot 0.11.1", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec 1.6.1", + "stable-pattern", + "state", + "time 0.2.27", + "tokio 1.8.1", + "uncased", +] + +[[package]] +name = "rpassword" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "socket2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "solana-account-decoder" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4035d75479ba479cc36a15100c9c2786f8c12cb567a81444a3d8b6e4aba75712" +dependencies = [ + "Inflector", + "base64 0.12.3", + "bincode", + "bs58", + "bv", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-config-program", + "solana-sdk", + "solana-vote-program", + "spl-token", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-clap-utils" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511ec69f7913a01987dc3421724c847fa4f7bcbc30c281ea7246f0f0e7495768" +dependencies = [ + "chrono", + "clap", + "rpassword", + "solana-remote-wallet", + "solana-sdk", + "thiserror", + "tiny-bip39", + "uriparse", + "url", +] + +[[package]] +name = "solana-cli-config" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd0a63bd98f787d09c54f60d09d5814a821bc566dc7296571c8eb33d1c674aa" +dependencies = [ + "dirs-next", + "lazy_static", + "serde", + "serde_derive", + "serde_yaml", + "url", +] + +[[package]] +name = "solana-client" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ebde9efaa90db1b5653a367d7d2789fcaf88a9299650cd901f9939c510babd3" +dependencies = [ + "base64 0.13.0", + "bincode", + "bs58", + "clap", + "indicatif", + "jsonrpc-core", + "log", + "net2", + "rayon", + "reqwest", + "semver 0.11.0", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-clap-utils", + "solana-faucet", + "solana-net-utils", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "thiserror", + "tokio 1.8.1", + "tungstenite", + "url", +] + +[[package]] +name = "solana-config-program" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbfc87161a8231c009bcb15573cba8c1a84ea012caed3b5192989150398e66e" +dependencies = [ + "bincode", + "chrono", + "log", + "rand_core 0.6.3", + "serde", + "serde_derive", + "solana-sdk", +] + +[[package]] +name = "solana-crate-features" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993fdca281d4ff75c10dab9725a9d9f3e4e5d293cc4a3a89b50a5a1430503d95" +dependencies = [ + "backtrace", + "bytes 0.4.12", + "cc", + "curve25519-dalek 2.1.3", + "ed25519-dalek", + "either", + "lazy_static", + "libc", + "rand_chacha 0.2.2", + "regex-syntax", + "reqwest", + "ring", + "serde", + "syn 0.15.44", + "syn 1.0.73", + "tokio 0.1.22", + "winapi 0.3.9", +] + +[[package]] +name = "solana-farm-client" +version = "0.0.1" +dependencies = [ + "arrayvec", + "bs58", + "solana-account-decoder", + "solana-client", + "solana-farm-sdk", + "solana-sdk", + "spl-associated-token-account", + "spl-token", + "thiserror", +] + +[[package]] +name = "solana-farm-rpc" +version = "0.0.1" +dependencies = [ + "bs58", + "clap", + "dirs-next", + "lazy_static", + "log", + "reqwest", + "rocket", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "solana-clap-utils", + "solana-client", + "solana-farm-client", + "solana-farm-sdk", + "solana-logger", + "solana-sdk", + "solana-version", + "url", +] + +[[package]] +name = "solana-farm-sdk" +version = "0.0.1" +dependencies = [ + "arrayref", + "arraystring", + "bs58", + "num_enum", + "serde", + "serde_derive", + "serde_json", + "solana-program", +] + +[[package]] +name = "solana-faucet" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8cb0f4c332566ee8bcebca00caeb65b70a85f06b2d192f9fa4ba466fa75e3e" +dependencies = [ + "bincode", + "byteorder", + "clap", + "log", + "serde", + "serde_derive", + "solana-clap-utils", + "solana-cli-config", + "solana-logger", + "solana-metrics", + "solana-sdk", + "solana-version", + "spl-memo", + "thiserror", + "tokio 1.8.1", +] + +[[package]] +name = "solana-frozen-abi" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbee6969ad41585a93c1669c9b4417cefff4ff52957a45f6c579a1f66f84bb03" +dependencies = [ + "bs58", + "bv", + "generic-array 0.14.4", + "log", + "memmap2", + "rustc_version 0.2.3", + "serde", + "serde_derive", + "sha2", + "solana-frozen-abi-macro", + "solana-logger", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fefced09f5cf4a8bb6b3d475a08992f28215c1bc5c05a2657a85c0e80e8cfd" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "rustc_version 0.2.3", + "syn 1.0.73", +] + +[[package]] +name = "solana-logger" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46823a4af30167a864e38258c2ae803cebac0b19bde9575dc1bdd0e3453c8ccd" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-measure" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d4738c2c946fcfa1563e7dc388c5e70c4d49ae9bded62b586b826ced9f0480" +dependencies = [ + "log", + "solana-metrics", + "solana-sdk", +] + +[[package]] +name = "solana-metrics" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c69913f6b2848ca3383a64f660c66657ff22728969829b35bd0fcb538851a2" +dependencies = [ + "env_logger", + "gethostname", + "lazy_static", + "log", + "reqwest", + "solana-sdk", +] + +[[package]] +name = "solana-net-utils" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017812555b0ff4eba97848e845658317cdc9b70e0ea352f30f1db59d3a923c64" +dependencies = [ + "bincode", + "clap", + "log", + "nix", + "rand 0.7.3", + "serde", + "serde_derive", + "socket2 0.3.19", + "solana-clap-utils", + "solana-logger", + "solana-sdk", + "solana-version", + "tokio 1.8.1", + "url", +] + +[[package]] +name = "solana-program" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4d4caae58d145ddf277a5e6b1316d9aa727269e3a5e443f5faa90532e9db22" +dependencies = [ + "bincode", + "blake3", + "borsh", + "borsh-derive", + "bs58", + "bv", + "curve25519-dalek 2.1.3", + "hex", + "itertools", + "lazy_static", + "libsecp256k1", + "log", + "num-derive", + "num-traits", + "rand 0.7.3", + "rustc_version 0.2.3", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "sha2", + "sha3", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-sdk-macro", + "thiserror", +] + +[[package]] +name = "solana-rayon-threadlimit" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02da652f0b386a1948b263336b1f5f78d209dc845ad9418a8d21b09c62baf597" +dependencies = [ + "lazy_static", + "num_cpus", +] + +[[package]] +name = "solana-remote-wallet" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ce4eb2da88c2a830fa09a33392bedcfdc74a1872ec1086bdbb43a52b146de9" +dependencies = [ + "base32", + "console 0.14.1", + "dialoguer", + "hidapi", + "log", + "num-derive", + "num-traits", + "parking_lot 0.10.2", + "qstring", + "semver 0.9.0", + "solana-sdk", + "thiserror", + "uriparse", +] + +[[package]] +name = "solana-runtime" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ec9351f7406a9bfe220d87edd44e37dc8817a29b8d9ff46d4a0533f29550368" +dependencies = [ + "arrayref", + "bincode", + "blake3", + "bv", + "byteorder", + "bzip2", + "crossbeam-channel 0.4.4", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "itertools", + "lazy_static", + "libc", + "libloading", + "log", + "memmap2", + "num-derive", + "num-traits", + "num_cpus", + "ouroboros", + "rand 0.7.3", + "rayon", + "regex", + "rustc_version 0.2.3", + "serde", + "serde_derive", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-measure", + "solana-metrics", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-secp256k1-program", + "solana-stake-program", + "solana-vote-program", + "symlink", + "tar", + "tempfile", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-sdk" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258e8fa995b28bfe53a90dea7cb3d17c80f022b352349b05b81ef92fa760380c" +dependencies = [ + "assert_matches", + "bincode", + "bs58", + "bv", + "byteorder", + "chrono", + "derivation-path", + "digest 0.9.0", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array 0.14.4", + "hex", + "hmac 0.10.1", + "itertools", + "lazy_static", + "libsecp256k1", + "log", + "memmap2", + "num-derive", + "num-traits", + "pbkdf2 0.6.0", + "qstring", + "rand 0.7.3", + "rand_chacha 0.2.2", + "rand_core 0.6.3", + "rustc_version 0.2.3", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2", + "sha3", + "solana-crate-features", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e988d7938720ca3090cc02caafa7952a98e28d153c5ecc7661208d5adfd520" +dependencies = [ + "bs58", + "proc-macro2 1.0.27", + "quote 1.0.9", + "rustversion", + "syn 1.0.73", +] + +[[package]] +name = "solana-secp256k1-program" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba307591b7e8e7fada2f4f7f2f7024a9ca3ab2894a09ce05e7ce4fa1387a46" +dependencies = [ + "solana-sdk", +] + +[[package]] +name = "solana-stake-program" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e26bfb4c52fed2c2c2dc3f140a1ac4f871d0c969a2a0526394729c076be1199" +dependencies = [ + "bincode", + "log", + "num-derive", + "num-traits", + "rustc_version 0.2.3", + "serde", + "serde_derive", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-metrics", + "solana-sdk", + "solana-vote-program", + "thiserror", +] + +[[package]] +name = "solana-transaction-status" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d655a6382393c0e80ccd433cf9ddea73f1364a02c5e39db050751dd20a74421" +dependencies = [ + "Inflector", + "base64 0.12.3", + "bincode", + "bs58", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-runtime", + "solana-sdk", + "solana-vote-program", + "spl-associated-token-account", + "spl-memo", + "spl-token", + "thiserror", +] + +[[package]] +name = "solana-version" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852243e0a70048295b838fd36ff9c9123149dc1b8ae1373e24f0e788108c38a1" +dependencies = [ + "log", + "rustc_version 0.2.3", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-sdk", +] + +[[package]] +name = "solana-vote-program" +version = "1.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80cbea1e2874bc829356d423cd546da1c341b2c1e69b20b6f7096aad0825bcd0" +dependencies = [ + "bincode", + "log", + "num-derive", + "num-traits", + "rustc_version 0.2.3", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-metrics", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" + +[[package]] +name = "spl-associated-token-account" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393e2240d521c3dd770806bff25c2c00d761ac962be106e14e22dd912007f428" +dependencies = [ + "solana-program", + "spl-token", +] + +[[package]] +name = "spl-memo" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-token" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93bfdd5bd7c869cb565c7d7635c4fafe189b988a0bdef81063cd9585c6b8dc01" +dependencies = [ + "arrayref", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror", +] + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "state" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +dependencies = [ + "loom", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "serde", + "serde_derive", + "syn 1.0.73", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.27", + "quote 1.0.9", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.73", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "unicode-xid 0.2.2", +] + +[[package]] +name = "synstructure" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "unicode-xid 0.2.2", +] + +[[package]] +name = "tar" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d779dc6aeff029314570f666ec83f19df7280bb36ef338442cfa8c604021b80" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.4", + "redox_syscall 0.2.9", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.27", + "quote 1.0.9", + "standback", + "syn 1.0.73", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e44c4759bae7f1032e286a7ef990bd9ed23fe831b7eeba0beb97484c2e59b8" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2", + "thiserror", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "mio 0.6.23", + "num_cpus", + "tokio-codec", + "tokio-current-thread", + "tokio-executor", + "tokio-fs", + "tokio-io", + "tokio-reactor", + "tokio-sync", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer", + "tokio-udp", + "tokio-uds", +] + +[[package]] +name = "tokio" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" +dependencies = [ + "autocfg", + "bytes 1.0.1", + "libc", + "memchr", + "mio 0.7.13", + "num_cpus", + "once_cell", + "parking_lot 0.11.1", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-codec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "tokio-io", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" +dependencies = [ + "futures 0.1.31", + "tokio-executor", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", +] + +[[package]] +name = "tokio-fs" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" +dependencies = [ + "futures 0.1.31", + "tokio-io", + "tokio-threadpool", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", +] + +[[package]] +name = "tokio-macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.8.1", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "lazy_static", + "log", + "mio 0.6.23", + "num_cpus", + "parking_lot 0.9.0", + "slab", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio 1.8.1", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio 1.8.1", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures 0.1.31", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "mio 0.6.23", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" +dependencies = [ + "crossbeam-deque 0.7.3", + "crossbeam-queue", + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "lazy_static", + "log", + "num_cpus", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-timer" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-udp" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", + "mio 0.6.23", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-uds" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "libc", + "log", + "mio 0.6.23", + "mio-uds", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-util" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +dependencies = [ + "bytes 1.0.1", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio 1.8.1", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +dependencies = [ + "base64 0.11.0", + "byteorder", + "bytes 0.5.6", + "http", + "httparse", + "input_buffer", + "log", + "native-tls", + "rand 0.7.3", + "sha-1", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "ubyte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +dependencies = [ + "serde", +] + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "uriparse" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote 1.0.9", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" + +[[package]] +name = "zeroize" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeafe61337cb2c879d328b74aa6cd9d794592c82da6be559fdf11493f02a2d18" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" +dependencies = [ + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", + "synstructure", +] + +[[package]] +name = "zstd" +version = "0.5.4+zstd.1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "2.0.6+zstd.1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.4.18+zstd.1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81" +dependencies = [ + "cc", + "glob", + "itertools", + "libc", +] diff --git a/farms/farm-rpc/Cargo.toml b/farms/farm-rpc/Cargo.toml new file mode 100644 index 00000000000..f9ce9b2d877 --- /dev/null +++ b/farms/farm-rpc/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "solana-farm-rpc" +version = "0.0.1" +description = "Solana Farm RPC" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +debug = [] + +[dependencies] +log = "0.4.14" +bs58 = "0.4.0" +clap = "2.33.3" +dirs-next = "2.0.0" +lazy_static = "1.4.0" +reqwest = "0.11.6" +rocket = { version = "0.5.0-rc.1", features = ["json"] } +serde = "1.0.130" +serde_derive = "1.0.130" +serde_json = "1.0.69" +serde_yaml = "0.8.21" +solana-client = "1.8.1" +solana-logger = "1.8.1" +solana-version = "1.8.1" +solana-clap-utils = "1.8.1" +solana-sdk = "1.8.1" +solana-farm-sdk = { path = "../farm-sdk" } +solana-farm-client = { path = "../farm-client" } +solana-account-decoder = "1.8.1" +url = "2.2.2" diff --git a/farms/farm-rpc/src/config.rs b/farms/farm-rpc/src/config.rs new file mode 100644 index 00000000000..69b2f57f06b --- /dev/null +++ b/farms/farm-rpc/src/config.rs @@ -0,0 +1,101 @@ +//! Configuration management + +use { + serde_derive::{Deserialize, Serialize}, + std::{ + fs::{create_dir_all, File}, + io::{self, Write}, + path::Path, + }, +}; + +lazy_static! { + pub static ref CONFIG_FILE: Option = { + dirs_next::home_dir().map(|mut path| { + path.extend(&[".config", "solana", "farm", "rpc_config.yml"]); + path.to_str().unwrap().to_string() + }) + }; +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Config { + pub json_rpc_url: String, + pub websocket_url: String, + pub max_threads: u32, + pub token_list_url: String, + pub farm_client_url: String, +} + +impl Default for Config { + fn default() -> Self { + let json_rpc_url = "http://127.0.0.1:9000".to_string(); + let websocket_url = "wss://127.0.0.1:9001".to_string(); + let token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json".to_string(); + let farm_client_url = "http://127.0.0.1:8899".to_string(); + let max_threads = 4; + + Self { + json_rpc_url, + websocket_url, + max_threads, + token_list_url, + farm_client_url, + } + } +} + +impl Config { + pub fn load(&mut self, config_file: &str) -> Result<(), io::Error> { + let file = File::open(config_file)?; + *self = serde_yaml::from_reader(file) + .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; + Ok(()) + } + + pub fn save(&self, config_file: &str) -> Result<(), io::Error> { + let serialized = serde_yaml::to_string(self) + .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; + + if let Some(outdir) = Path::new(config_file).parent() { + create_dir_all(outdir)?; + } + let mut file = File::create(config_file)?; + file.write_all(&serialized.into_bytes())?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_default() { + let config: Config = Default::default(); + assert_eq!(config.json_rpc_url, "http://127.0.0.1:9000"); + assert_eq!(config.websocket_url, "wss://127.0.0.1:9001"); + assert_eq!(config.farm_client_url, "http://127.0.0.1:8899"); + assert_eq!(config.max_threads, 4); + } + + #[test] + fn test_load_save() { + let config = Config { + json_rpc_url: "http://test:9000".to_string(), + websocket_url: "wss://test:9001".to_string(), + max_threads: 99, + token_list_url: "none".to_string(), + farm_client_url: "http://test_farm:8899".to_string(), + }; + let _ = config.save("_test_config.yml"); + + let mut config2: Config = Default::default(); + let _ = config2.load("_test_config.yml"); + + assert_eq!(config.json_rpc_url, config2.json_rpc_url); + assert_eq!(config.websocket_url, config2.websocket_url); + assert_eq!(config.max_threads, config2.max_threads); + assert_eq!(config.farm_client_url, config2.farm_client_url); + } +} diff --git a/farms/farm-rpc/src/git_token.rs b/farms/farm-rpc/src/git_token.rs new file mode 100644 index 00000000000..568dbe8efaa --- /dev/null +++ b/farms/farm-rpc/src/git_token.rs @@ -0,0 +1,21 @@ +use { + serde::{Deserialize, Serialize}, + serde_json::Value, + std::collections::HashMap, +}; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GitToken { + #[serde(rename = "chainId")] + pub chain_id: i32, + pub address: String, + pub symbol: String, + pub name: String, + pub decimals: i32, + #[serde(rename = "logoURI", default)] + pub logo_uri: String, + #[serde(default)] + pub tags: Vec, + #[serde(flatten)] + pub extra: HashMap, +} diff --git a/farms/farm-rpc/src/json_rpc.rs b/farms/farm-rpc/src/json_rpc.rs new file mode 100644 index 00000000000..3b350701ffc --- /dev/null +++ b/farms/farm-rpc/src/json_rpc.rs @@ -0,0 +1,2022 @@ +//! JSON RPC service + +use { + crate::config::Config, + rocket::{ + fairing::{AdHoc, Fairing, Info, Kind}, + form::{ + error::{Error, Errors}, + FromFormField, ValueField, + }, + fs::{relative, FileServer}, + http::{ContentType, Header}, + request::{FromParam, Request}, + response, + response::{status::NotFound, Responder, Response}, + serde::json::Json, + Build, Rocket, State, + }, + serde_json::{from_str, from_value, json, Value}, + solana_account_decoder::parse_token::UiTokenAccount, + solana_farm_client::client::{FarmClient, FarmMap, PoolMap, PubkeyMap, TokenMap, VaultMap}, + solana_farm_sdk::{ + farm::Farm, + git_token::GitToken, + pool::Pool, + string::{instruction_to_string, pubkey_map_to_string}, + token::Token, + vault::{UserInfo, Vault, VaultInfo}, + }, + solana_sdk::{ + commitment_config::CommitmentConfig, instruction::Instruction, pubkey::Pubkey, + signature::Keypair, + }, + std::{ + collections::HashMap, + convert::Into, + str::FromStr, + sync::{Arc, Mutex}, + }, +}; + +type Result = std::result::Result; +type GitTokens = HashMap; +type FarmClientArc = Arc>; + +pub struct Cors; + +#[rocket::async_trait] +impl Fairing for Cors { + fn info(&self) -> Info { + Info { + name: "Add CORS headers to responses", + kind: Kind::Response, + } + } + + async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { + response.set_header(Header::new("Access-Control-Allow-Origin", "*")); + response.set_header(Header::new( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS", + )); + response.set_header(Header::new("Access-Control-Allow-Headers", "*")); + response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); + } +} + +// Pubkey parameters handling +struct PubkeyParam { + key: Pubkey, +} + +impl<'r> FromParam<'r> for PubkeyParam { + type Error = &'r str; + fn from_param(param: &'r str) -> Result { + Pubkey::from_str(param) + .map(|value| PubkeyParam { key: value }) + .map_err(|_| "Failed to convert string parameter to Pubkey") + } +} + +impl<'r> FromFormField<'r> for PubkeyParam { + fn from_value(field: ValueField<'r>) -> rocket::form::Result<'r, Self> { + Pubkey::from_str(field.value) + .map(|value| PubkeyParam { key: value }) + .map_err(|_| { + Errors::from(Error::validation( + "Failed to convert string argument to Pubkey", + )) + }) + } +} + +// Keypair parameters handling +struct KeypairParam { + key: Keypair, +} + +impl<'r> FromParam<'r> for KeypairParam { + type Error = &'r str; + fn from_param(param: &'r str) -> Result { + let v = &bs58::decode(param) + .into_vec() + .map_err(|_| "Failed to convert parameter to Keypair")?; + Keypair::from_bytes(v) + .map(|value| KeypairParam { key: value }) + .map_err(|_| "Failed to convert parameter to Keypair") + } +} + +impl<'r> FromFormField<'r> for KeypairParam { + fn from_value(field: ValueField<'r>) -> rocket::form::Result<'r, Self> { + let v = &bs58::decode(field.value).into_vec().map_err(|_| { + Errors::from(Error::validation( + "Failed to convert string argument to Pubkey", + )) + })?; + Keypair::from_bytes(v) + .map(|value| KeypairParam { key: value }) + .map_err(|_| { + Errors::from(Error::validation( + "Failed to convert string argument to Pubkey", + )) + }) + } +} + +fn check_unwrap_pubkey( + pubkey_param: Option, + param_name: &str, +) -> Result> { + if let Some(pubkey) = pubkey_param { + Ok(pubkey.key) + } else { + Err(NotFound(format!("Invalid {} argument", param_name))) + } +} + +fn check_unwrap_keypair( + keypair_param: Option, + param_name: &str, +) -> Result> { + if let Some(keypair) = keypair_param { + Ok(keypair.key) + } else { + Err(NotFound(format!("Invalid {} argument", param_name))) + } +} + +// Custom Json responders +#[derive(Debug)] +struct JsonWithPubkeyMap { + data: String, +} + +impl JsonWithPubkeyMap { + pub fn new(data: &PubkeyMap) -> Self { + Self { + data: pubkey_map_to_string(data), + } + } +} + +impl<'r> Responder<'r, 'static> for JsonWithPubkeyMap { + fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> { + Response::build() + .merge(self.data.respond_to(request)?) + .header(ContentType::JSON) + .ok() + } +} + +#[derive(Debug)] +struct JsonWithInstruction { + data: String, +} + +impl JsonWithInstruction { + pub fn new(data: &Instruction) -> Self { + Self { + data: instruction_to_string(data), + } + } +} + +impl<'r> Responder<'r, 'static> for JsonWithInstruction { + fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> { + Response::build() + .merge(self.data.respond_to(request)?) + .header(ContentType::JSON) + .ok() + } +} + +// Routes + +/// Returns Token metadata from Github +#[get("/git_token?")] +async fn get_git_token( + name: &str, + git_tokens: &State, +) -> Result, NotFound> { + if !git_tokens.inner().contains_key(name) { + return Err(NotFound(format!("Record not found: Token {}", name))); + } + Ok(Json(git_tokens.inner()[name].clone())) +} + +/// Returns all Tokens from Github +#[get("/git_tokens")] +async fn get_git_tokens(git_tokens: &State) -> Result> { + Ok(Json(git_tokens.inner().clone())) +} + +/// Returns the Vault struct for the given name +#[get("/vault?")] +async fn get_vault( + name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault = farm_client + .get_vault(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(vault)) +} + +/// Returns all Vaults available +#[get("/vaults")] +async fn get_vaults( + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vaults = farm_client + .get_vaults() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(vaults)) +} + +/// Returns the Vault metadata address for the given name +#[get("/vault_ref?")] +async fn get_vault_ref( + name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault_ref = farm_client + .get_vault_ref(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(vault_ref.to_string()) +} + +/// Returns Vault refs: a map of Vault name to account address with metadata +#[get("/vault_refs")] +async fn get_vault_refs( + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault_refs = farm_client + .get_vault_refs() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithPubkeyMap::new(&vault_refs)) +} + +/// Returns the Vault metadata at the specified address +#[get("/vault_by_ref?")] +async fn get_vault_by_ref( + vault_ref: Option, + farm_client: &State, +) -> Result, NotFound> { + let vault_ref = check_unwrap_pubkey(vault_ref, "vault_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault = farm_client + .get_vault_by_ref(&vault_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(vault)) +} + +/// Returns the Vault name for the given metadata address +#[get("/vault_name?")] +async fn get_vault_name( + vault_ref: Option, + farm_client: &State, +) -> Result> { + let vault_ref = check_unwrap_pubkey(vault_ref, "vault_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault_name = farm_client + .get_vault_name(&vault_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(vault_name) +} + +/// Returns all Vaults with tokens A and B sorted by version +#[get("/find_vaults?&")] +async fn find_vaults( + token_a: &str, + token_b: &str, + farm_client: &State, +) -> Result>, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vaults = farm_client + .find_vaults(token_a, token_b) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(vaults)) +} + +/// Returns the Pool struct for the given name +#[get("/pool?")] +async fn get_pool( + name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool = farm_client + .get_pool(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pool)) +} + +/// Returns all Pools available +#[get("/pools")] +async fn get_pools(farm_client: &State) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool_map = farm_client + .get_pools() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pool_map)) +} + +/// Returns the Pool metadata address for the given name +#[get("/pool_ref?")] +async fn get_pool_ref( + name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool_ref = farm_client + .get_pool_ref(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(pool_ref.to_string()) +} + +/// Returns Pool refs: a map of Pool name to account address with metadata +#[get("/pool_refs")] +async fn get_pool_refs( + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool_refs = farm_client + .get_pool_refs() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithPubkeyMap::new(&pool_refs)) +} + +/// Returns the Pool metadata at the specified address +#[get("/pool_by_ref?")] +async fn get_pool_by_ref( + pool_ref: Option, + farm_client: &State, +) -> Result, NotFound> { + let pool_ref = check_unwrap_pubkey(pool_ref, "pool_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool = farm_client + .get_pool_by_ref(&pool_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pool)) +} + +/// Returns the Pool name for the given metadata address +#[get("/pool_name?")] +async fn get_pool_name( + pool_ref: Option, + farm_client: &State, +) -> Result> { + let pool_ref = check_unwrap_pubkey(pool_ref, "pool_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool_name = farm_client + .get_pool_name(&pool_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(pool_name) +} + +/// Returns all Pools with tokens A and B sorted by version for the given protocol +#[get("/find_pools?&&")] +async fn find_pools( + protocol: &str, + token_a: &str, + token_b: &str, + farm_client: &State, +) -> Result>, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pools = farm_client + .find_pools(protocol, token_a, token_b) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pools)) +} + +/// Returns all Pools sorted by version for the given LP token +#[get("/find_pools_with_lp?")] +async fn find_pools_with_lp( + lp_token: &str, + farm_client: &State, +) -> Result>, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pools = farm_client + .find_pools_with_lp(lp_token) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pools)) +} + +/// Returns pair's price based on the ratio of tokens in the pool +#[get("/pool_price?")] +async fn get_pool_price( + name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let pool_price = farm_client + .get_pool_price(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(pool_price)) +} + +/// Returns the Farm struct for the given name +#[get("/farm?")] +async fn get_farm( + name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farm = farm_client + .get_farm(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(farm)) +} + +/// Returns all Farms available +#[get("/farms")] +async fn get_farms(farm_client: &State) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farms = farm_client + .get_farms() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(farms)) +} + +/// Returns the Farm metadata address for the given name +#[get("/farm_ref?")] +async fn get_farm_ref( + name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farm_ref = farm_client + .get_farm_ref(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(farm_ref.to_string()) +} + +/// Returns Farm refs: a map of Farm name to account address with metadata +#[get("/farm_refs")] +async fn get_farm_refs( + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farm_refs = farm_client + .get_farm_refs() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithPubkeyMap::new(&farm_refs)) +} + +/// Returns the Farm metadata at the specified address +#[get("/farm_by_ref?")] +async fn get_farm_by_ref( + farm_ref: Option, + farm_client: &State, +) -> Result, NotFound> { + let farm_ref = check_unwrap_pubkey(farm_ref, "farm_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farm = farm_client + .get_farm_by_ref(&farm_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(farm)) +} + +/// Returns the Farm name for the given metadata address +#[get("/farm_name?")] +async fn get_farm_name( + farm_ref: Option, + farm_client: &State, +) -> Result> { + let farm_ref = check_unwrap_pubkey(farm_ref, "farm_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farm_name = farm_client + .get_farm_name(&farm_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(farm_name) +} + +/// Returns all Farms for the given LP token +#[get("/find_farms_with_lp?")] +async fn find_farms_with_lp( + lp_token: &str, + farm_client: &State, +) -> Result>, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let farms = farm_client + .find_farms_with_lp(lp_token) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(farms)) +} + +/// Returns the Token struct for the given name +#[get("/token?")] +async fn get_token( + name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token = farm_client + .get_token(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(token)) +} + +/// Returns all Tokens available +#[get("/tokens")] +async fn get_tokens( + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token = farm_client + .get_tokens() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(token)) +} + +/// Returns the Token metadata address for the given name +#[get("/token_ref?")] +async fn get_token_ref( + name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_ref = farm_client + .get_token_ref(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(token_ref.to_string()) +} + +/// Returns Token refs: a map of Token name to account address with metadata +#[get("/token_refs")] +async fn get_token_refs( + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_refs = farm_client + .get_token_refs() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithPubkeyMap::new(&token_refs)) +} + +/// Returns the Token metadata at the specified address +#[get("/token_by_ref?")] +async fn get_token_by_ref( + token_ref: Option, + farm_client: &State, +) -> Result, NotFound> { + let token_ref = check_unwrap_pubkey(token_ref, "token_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token = farm_client + .get_token_by_ref(&token_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(token)) +} + +/// Returns the Token name for the given metadata address +#[get("/token_name?")] +async fn get_token_name( + token_ref: Option, + farm_client: &State, +) -> Result> { + let token_ref = check_unwrap_pubkey(token_ref, "token_ref")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_name = farm_client + .get_token_name(&token_ref) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(token_name) +} + +/// Returns the Token metadata for the specified mint +#[get("/get_token_with_mint?")] +async fn get_token_with_mint( + token_mint: Option, + farm_client: &State, +) -> Result, NotFound> { + let token_mint = check_unwrap_pubkey(token_mint, "token_mint")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token = farm_client + .get_token_with_mint(&token_mint) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(token)) +} + +/// Returns the official Program ID for the given name +#[get("/program_id?")] +async fn get_program_id( + name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let program_id = farm_client + .get_program_id(name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(program_id.to_string()) +} + +/// Returns all official Program IDs available +#[get("/program_ids")] +async fn get_program_ids( + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let program_ids = farm_client + .get_program_ids() + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithPubkeyMap::new(&program_ids)) +} + +/// Returns the official program name for the given Program ID +#[get("/program_name?")] +async fn get_program_name( + prog_id: Option, + farm_client: &State, +) -> Result> { + let prog_id = check_unwrap_pubkey(prog_id, "prog_id")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let program_name = farm_client + .get_program_name(&prog_id) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(program_name) +} + +/// Checks if the given address is the official Program ID +#[get("/is_official_id?")] +async fn is_official_id( + prog_id: Option, + farm_client: &State, +) -> Result, NotFound> { + let prog_id = check_unwrap_pubkey(prog_id, "prog_id")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let is_official = farm_client + .is_official_id(&prog_id) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(is_official)) +} + +/// Creates a new system account +#[post("/create_system_account?&&&&")] +async fn create_system_account( + wallet_keypair: Option, + new_account_keypair: Option, + lamports: u64, + space: usize, + owner: Option, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let new_account_keypair = check_unwrap_keypair(new_account_keypair, "new_account_keypair")?; + let owner = check_unwrap_pubkey(owner, "owner")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .create_system_account( + &wallet_keypair, + &new_account_keypair, + lamports, + space, + &owner, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Closes existing system account +#[post("/close_system_account?&")] +async fn close_system_account( + wallet_keypair: Option, + target_account_keypair: Option, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let target_account_keypair = + check_unwrap_keypair(target_account_keypair, "target_account_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .close_system_account(&wallet_keypair, &target_account_keypair) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Transfers native SOL from the wallet to the destination +#[post("/transfer?&&")] +async fn transfer( + wallet_keypair: Option, + destination_wallet: Option, + sol_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let destination_wallet = check_unwrap_pubkey(destination_wallet, "destination_wallet")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .transfer(&wallet_keypair, &destination_wallet, sol_ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Transfers native SOL from the wallet to the associated Wrapped SOL account. +#[post("/transfer_sol_to_wsol?&")] +async fn transfer_sol_to_wsol( + wallet_keypair: Option, + sol_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .transfer_sol_to_wsol(&wallet_keypair, sol_ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Transfers tokens from the wallet to the destination +#[post("/token_transfer?&&&")] +async fn token_transfer( + wallet_keypair: Option, + token_name: &str, + destination_wallet: Option, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let destination_wallet = check_unwrap_pubkey(destination_wallet, "destination_wallet")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .token_transfer(&wallet_keypair, token_name, &destination_wallet, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Updates token balance of the account, usefull after transfer SOL to WSOL account +#[post("/sync_token_balance?&")] +async fn sync_token_balance( + wallet_keypair: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .sync_token_balance(&wallet_keypair, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Returns the associated token account for the given user's main account or creates one +/// if it doesn't exist +#[post("/create_token_account?&")] +async fn get_or_create_token_account( + wallet_keypair: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .get_or_create_token_account(&wallet_keypair, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Closes existing token account associated with the given user's main account +#[post("/close_token_account?&")] +async fn close_token_account( + wallet_keypair: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .close_token_account(&wallet_keypair, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Returns the associated token account address for the given token name +#[get("/associated_token_address?&")] +async fn get_associated_token_address( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_address = farm_client + .get_associated_token_address(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(token_address.to_string()) +} + +/// Returns all tokens with active account in the wallet +#[get("/wallet_tokens?")] +async fn get_wallet_tokens( + wallet_address: Option, + farm_client: &State, +) -> Result>, NotFound> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let tokens = farm_client + .get_wallet_tokens(&wallet_address) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(tokens)) +} + +/// Returns UiTokenAccount struct data for the associated token account address +#[get("/token_account_data?&")] +async fn get_token_account_data( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result, NotFound> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_data = farm_client + .get_token_account_data(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(token_data)) +} + +/// Returns native SOL balance +#[get("/account_balance?")] +async fn get_account_balance( + wallet_address: Option, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let balance = farm_client + .get_account_balance(&wallet_address) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(balance.to_string()) +} + +/// Returns token balance for the associated token account address +#[get("/token_account_balance?&")] +async fn get_token_account_balance( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let token_balance = farm_client + .get_token_account_balance(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(token_balance.to_string()) +} + +/// Returns true if the associated token account exists and is initialized +#[get("/has_active_token_account?&")] +async fn has_active_token_account( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result, NotFound> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let has_active_account = farm_client.has_active_token_account(&wallet_address, token_name); + + Ok(Json(has_active_account)) +} + +/// Returns User's stacked balance +#[get("/user_stake_balance?&")] +async fn get_user_stake_balance( + wallet_address: Option, + farm_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let balance = farm_client + .get_user_stake_balance(&wallet_address, farm_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(balance.to_string()) +} + +/// Returns Vault's stacked balance +#[get("/vault_stake_balance?")] +async fn get_vault_stake_balance( + vault_name: &str, + farm_client: &State, +) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let balance = farm_client + .get_vault_stake_balance(vault_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(balance.to_string()) +} + +/// Returns user stats for specific Vault +#[get("/vault_user_info?&")] +async fn get_vault_user_info( + wallet_address: Option, + vault_name: &str, + farm_client: &State, +) -> Result, NotFound> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let user_info = farm_client + .get_vault_user_info(&wallet_address, vault_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(user_info)) +} + +/// Returns Vault stats +#[get("/vault_info?")] +async fn get_vault_info( + vault_name: &str, + farm_client: &State, +) -> Result, NotFound> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let vault_info = farm_client + .get_vault_info(vault_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(vault_info)) +} + +/// Initializes a new User for the Vault +#[post("/user_init_vault?&")] +async fn user_init_vault( + wallet_keypair: Option, + vault_name: &str, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .user_init_vault(&wallet_keypair, vault_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Adds liquidity to the Vault +#[post("/add_liquidity_vault?&&&")] +async fn add_liquidity_vault( + wallet_keypair: Option, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .add_liquidity_vault( + &wallet_keypair, + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Adds locked liquidity to the Vault +#[post("/add_locked_liquidity_vault?&&")] +async fn add_locked_liquidity_vault( + wallet_keypair: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .add_locked_liquidity_vault(&wallet_keypair, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Removes liquidity from the Vault +#[post("/remove_liquidity_vault?&&")] +async fn remove_liquidity_vault( + wallet_keypair: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .remove_liquidity_vault(&wallet_keypair, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Removes unlocked liquidity from the Vault +#[post("/remove_unlocked_liquidity_vault?&&")] +async fn remove_unlocked_liquidity_vault( + wallet_keypair: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .remove_unlocked_liquidity_vault(&wallet_keypair, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Adds liquidity to the Pool +#[post("/add_liquidity_pool?&&&")] +async fn add_liquidity_pool( + wallet_keypair: Option, + pool_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .add_liquidity_pool( + &wallet_keypair, + pool_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Removes liquidity from the Pool +#[post("/remove_liquidity_pool?&&")] +async fn remove_liquidity_pool( + wallet_keypair: Option, + pool_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .remove_liquidity_pool(&wallet_keypair, pool_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Swaps tokens +#[post( + "/swap?&&&&&" +)] +async fn swap( + wallet_keypair: Option, + protocol: &str, + from_token: &str, + to_token: &str, + ui_amount_in: f64, + min_ui_amount_out: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .swap( + &wallet_keypair, + protocol, + from_token, + to_token, + ui_amount_in, + min_ui_amount_out, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Stakes tokens to the Farm +#[post("/stake?&&")] +async fn stake( + wallet_keypair: Option, + farm_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .stake(&wallet_keypair, farm_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Unstakes tokens from the Farm +#[post("/unstake?&&")] +async fn unstake( + wallet_keypair: Option, + farm_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .unstake(&wallet_keypair, farm_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Harvests rewards from the Pool +#[post("/harvest?&")] +async fn harvest( + wallet_keypair: Option, + farm_name: &str, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .harvest(&wallet_keypair, farm_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Cranks single Vault +#[post("/crank_vault?&&")] +async fn crank_vault( + wallet_keypair: Option, + vault_name: &str, + step: u64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let signature = farm_client + .crank_vault(&wallet_keypair, vault_name, step) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(signature.to_string()) +} + +/// Cranks all Vaults +#[post("/crank_vaults?&")] +async fn crank_vaults( + wallet_keypair: Option, + step: u64, + farm_client: &State, +) -> Result> { + let wallet_keypair = check_unwrap_keypair(wallet_keypair, "wallet_keypair")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let cranked = farm_client + .crank_vaults(&wallet_keypair, step) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(cranked.to_string()) +} + +/// Clears cache records to force re-pull from blockchain +#[post("/reset_cache")] +async fn reset_cache(farm_client: &State) -> Result> { + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + farm_client.reset_cache(); + + Ok("OK".to_string()) +} + +/// Returns a new Instruction for creating system account +#[get("/new_instruction_create_system_account?&&&&")] +async fn new_instruction_create_system_account( + wallet_address: Option, + new_address: Option, + lamports: u64, + space: usize, + owner: Option, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let new_address = check_unwrap_pubkey(new_address, "new_address")?; + let owner = check_unwrap_pubkey(owner, "owner")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_create_system_account( + &wallet_address, + &new_address, + lamports, + space, + &owner, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Returns a new Instruction for closing system account +#[get("/new_instruction_close_system_account?&")] +async fn new_instruction_close_system_account( + wallet_address: Option, + target_address: Option, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let target_address = check_unwrap_pubkey(target_address, "target_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_close_system_account(&wallet_address, &target_address) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates the native SOL transfer instruction +#[get("/new_instruction_transfer?&&")] +async fn new_instruction_transfer( + wallet_address: Option, + destination_wallet: Option, + sol_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let destination_wallet = check_unwrap_pubkey(destination_wallet, "destination_wallet")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_transfer(&wallet_address, &destination_wallet, sol_ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a tokens transfer instruction +#[get("/new_instruction_token_transfer?&&&")] +async fn new_instruction_token_transfer( + wallet_address: Option, + token_name: &str, + destination_wallet: Option, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let destination_wallet = check_unwrap_pubkey(destination_wallet, "destination_wallet")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_token_transfer(&wallet_address, token_name, &destination_wallet, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for syncing token balance for the specified account +#[get("/new_instruction_sync_token_balance?&")] +async fn new_instruction_sync_token_balance( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_sync_token_balance(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Returns a new Instruction for creating associated token account +#[get("/new_instruction_create_token_account?&")] +async fn new_instruction_create_token_account( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_create_token_account(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Returns a new Instruction for closing associated token account +#[get("/new_instruction_close_token_account?&")] +async fn new_instruction_close_token_account( + wallet_address: Option, + token_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_close_token_account(&wallet_address, token_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for initializing a new User for the Vault +#[get("/new_instruction_user_init_vault?&")] +async fn new_instruction_user_init_vault( + wallet_address: Option, + vault_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_user_init_vault(&wallet_address, vault_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for adding liquidity to the Vault +#[get("/new_instruction_add_liquidity_vault?&&&")] +async fn new_instruction_add_liquidity_vault( + wallet_address: Option, + vault_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_add_liquidity_vault( + &wallet_address, + vault_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for locking liquidity in the Vault +#[get("/new_instruction_lock_liquidity_vault?&&")] +async fn new_instruction_lock_liquidity_vault( + wallet_address: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_lock_liquidity_vault(&wallet_address, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for unlocking liquidity from the Vault +#[get("/new_instruction_unlock_liquidity_vault?&&")] +async fn new_instruction_unlock_liquidity_vault( + wallet_address: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_unlock_liquidity_vault(&wallet_address, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for removing liquidity from the Vault +#[get("/new_instruction_remove_liquidity_vault?&&")] +async fn new_instruction_remove_liquidity_vault( + wallet_address: Option, + vault_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_remove_liquidity_vault(&wallet_address, vault_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for adding liquidity to the Pool +#[get("/new_instruction_add_liquidity_pool?&&&")] +async fn new_instruction_add_liquidity_pool( + wallet_address: Option, + pool_name: &str, + max_token_a_ui_amount: f64, + max_token_b_ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_add_liquidity_pool( + &wallet_address, + pool_name, + max_token_a_ui_amount, + max_token_b_ui_amount, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for removing liquidity from the Pool +#[get("/new_instruction_remove_liquidity_pool?&&")] +async fn new_instruction_remove_liquidity_pool( + wallet_address: Option, + pool_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_remove_liquidity_pool(&wallet_address, pool_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for tokens swap +#[get("/new_instruction_swap?&&&&&")] +async fn new_instruction_swap( + wallet_address: Option, + protocol: &str, + from_token: &str, + to_token: &str, + ui_amount_in: f64, + min_ui_amount_out: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_swap( + &wallet_address, + protocol, + from_token, + to_token, + ui_amount_in, + min_ui_amount_out, + ) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for tokens staking +#[get("/new_instruction_stake?&&")] +async fn new_instruction_stake( + wallet_address: Option, + farm_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_stake(&wallet_address, farm_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for tokens unstaking +#[get("/new_instruction_unstake?&&")] +async fn new_instruction_unstake( + wallet_address: Option, + farm_name: &str, + ui_amount: f64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_unstake(&wallet_address, farm_name, ui_amount) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Instruction for rewards harvesting +#[get("/new_instruction_harvest?&")] +async fn new_instruction_harvest( + wallet_address: Option, + farm_name: &str, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_harvest(&wallet_address, farm_name) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Creates a new Vault Crank Instruction +#[get("/new_instruction_crank_vault?&&")] +async fn new_instruction_crank_vault( + wallet_address: Option, + vault_name: &str, + step: u64, + farm_client: &State, +) -> Result> { + let wallet_address = check_unwrap_pubkey(wallet_address, "wallet_address")?; + let farm_client = farm_client + .inner() + .lock() + .map_err(|e| NotFound(e.to_string()))?; + let instruction = farm_client + .new_instruction_crank_vault(&wallet_address, vault_name, step) + .map_err(|e| NotFound(e.to_string()))?; + + Ok(JsonWithInstruction::new(&instruction)) +} + +/// Retrieves data from URL as JSON +async fn get_url_data_as_json(url: &str) -> Result { + let response = reqwest::get(url).await.map_err(|err| err.to_string())?; + let text = response.text().await.map_err(|err| err.to_string())?; + let value = from_str(text.as_str()).map_err(|err| err.to_string())?; + Ok(value) +} + +/// Initializes network service +async fn init_rpc(rocket: Rocket) -> Rocket { + rocket +} + +/// Initilizes data to be served +async fn init_db( + config: &Config, + farm_client: &FarmClientArc, + git_tokens: &mut GitTokens, +) -> Result<()> { + // load tokens from GitHub + info!("Loading tokens from {}", config.token_list_url); + let dict: Value = get_url_data_as_json(&config.token_list_url).await.unwrap(); + assert!(dict.is_object()); + assert_ne!(dict["tokens"], json!(null)); + + let loaded_tokens = dict["tokens"].as_array().unwrap(); + for val in loaded_tokens { + let token: GitToken = from_value(val.clone()).unwrap(); + git_tokens.insert(token.symbol.clone(), token.clone()); + } + + info!("Loading data from the blockchain, this may take a few mins..."); + let farm_client = farm_client.lock().map_err(|e| e.to_string())?; + info!("Loading pools..."); + let _ = farm_client.get_pools().unwrap(); + info!("Loading farms..."); + let _ = farm_client.get_farms().unwrap(); + info!("Loading vaults..."); + let _ = farm_client.get_vaults().unwrap(); + info!("Loading programs..."); + let _ = farm_client.get_program_ids().unwrap(); + info!("Loading tokens..."); + let _ = farm_client.get_tokens().unwrap(); + info!("Done!"); + + Ok(()) +} + +/// Entry point for JSON RPC, called from main +pub async fn stage(config: &Config) -> AdHoc { + info!("Connecting Farm Client to {}", config.farm_client_url); + let client_mutex = Arc::new(Mutex::new(FarmClient::new_with_commitment( + &config.farm_client_url, + CommitmentConfig::confirmed(), + ))); + // check Cluster connectivity and version + { + let farm_client = client_mutex + .lock() + .expect("Failed to get lock on Farm Client"); + let version = farm_client + .rpc_client + .get_version() + .expect("Failed to get Cluster version; Check Farm Client URL"); + info!("Cluster version: {}", version); + } + + let mut git_tokens: GitTokens = GitTokens::new(); + init_db(config, &client_mutex, &mut git_tokens) + .await + .unwrap(); + + AdHoc::on_ignite("JSON RPC Stage", |rocket| async { + rocket + .manage(git_tokens) + .manage(client_mutex) + .attach(Cors) + .attach(AdHoc::on_ignite("JSON RPC Init", init_rpc)) + .mount("/", FileServer::from(relative!("static"))) + .mount( + "/api/v1", + routes![ + get_git_token, + get_git_tokens, + get_vault, + get_vaults, + get_vault_refs, + get_vault_by_ref, + get_vault_name, + find_vaults, + get_pool, + get_pools, + get_pool_refs, + get_pool_by_ref, + get_pool_name, + find_pools, + find_pools_with_lp, + get_farm, + get_farms, + get_farm_refs, + get_farm_by_ref, + get_farm_name, + find_farms_with_lp, + get_token, + get_tokens, + get_token_refs, + get_token_by_ref, + get_token_name, + get_token_with_mint, + get_program_id, + get_program_ids, + get_program_name, + get_vault_ref, + get_pool_price, + get_pool_ref, + get_farm_ref, + get_token_ref, + get_vault_user_info, + get_vault_info, + get_account_balance, + is_official_id, + create_system_account, + close_system_account, + transfer, + transfer_sol_to_wsol, + token_transfer, + sync_token_balance, + get_or_create_token_account, + close_token_account, + get_associated_token_address, + get_wallet_tokens, + get_token_account_data, + get_token_account_balance, + has_active_token_account, + get_user_stake_balance, + get_vault_stake_balance, + user_init_vault, + add_liquidity_vault, + add_locked_liquidity_vault, + remove_liquidity_vault, + remove_unlocked_liquidity_vault, + add_liquidity_pool, + remove_liquidity_pool, + swap, + stake, + unstake, + harvest, + crank_vault, + crank_vaults, + reset_cache, + new_instruction_create_system_account, + new_instruction_close_system_account, + new_instruction_transfer, + new_instruction_token_transfer, + new_instruction_sync_token_balance, + new_instruction_create_token_account, + new_instruction_close_token_account, + new_instruction_user_init_vault, + new_instruction_add_liquidity_vault, + new_instruction_lock_liquidity_vault, + new_instruction_unlock_liquidity_vault, + new_instruction_remove_liquidity_vault, + new_instruction_add_liquidity_pool, + new_instruction_remove_liquidity_pool, + new_instruction_swap, + new_instruction_stake, + new_instruction_unstake, + new_instruction_harvest, + new_instruction_crank_vault, + ], + ) + }) +} diff --git a/farms/farm-rpc/src/main.rs b/farms/farm-rpc/src/main.rs new file mode 100644 index 00000000000..e41ba953a0c --- /dev/null +++ b/farms/farm-rpc/src/main.rs @@ -0,0 +1,165 @@ +//! Solana Farms RPC Backend. + +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate rocket; + +mod config; +mod git_token; +mod json_rpc; + +use { + clap::{crate_description, crate_name, App, Arg}, + log::{debug, info}, + solana_clap_utils::input_validators::is_url, + url::Url, +}; + +#[rocket::main] +async fn main() { + let matches = App::new(crate_name!()) + .about(crate_description!()) + .version(solana_version::version!()) + .arg( + Arg::with_name("config_file") + .short("C") + .long("config-file") + .value_name("PATH") + .takes_value(true) + .help("Configuration file to use"), + ) + .arg( + Arg::with_name("save_config") + .short("S") + .long("save-config") + .value_name("PATH") + .takes_value(true) + .help("Write current config to a file"), + ) + .arg( + Arg::with_name("log_level") + .short("L") + .long("log-level") + .takes_value(true) + .help("Log verbosity level (debug, info, warning, error)") + .validator(|p| { + let allowed = ["debug", "info", "warning", "error"]; + if allowed.contains(&p.as_str()) { + Ok(()) + } else { + Err(String::from("Must be one of: debug, info, warning, error")) + } + }), + ) + .arg( + Arg::with_name("json_rpc_url") + .short("u") + .long("json-rpc-url") + .value_name("STR") + .takes_value(true) + .validator(is_url) + .help("URL for JSON RPC service"), + ) + .arg( + Arg::with_name("websocket_url") + .short("w") + .long("websocket-url") + .value_name("STR") + .takes_value(true) + .validator(is_url) + .help("URL for Websocket service"), + ) + .arg( + Arg::with_name("max_threads") + .short("m") + .long("max-threads") + .value_name("NUM") + .takes_value(true) + .validator(|p| match p.parse::() { + Err(_) => Err(String::from("Must be unsigned integer")), + Ok(_) => Ok(()), + }) + .help("Max threads for incoming connections"), + ) + .arg( + Arg::with_name("token_list_url") + .short("t") + .long("token-list-url") + .value_name("STR") + .takes_value(true) + .validator(is_url) + .help("URL for Solana's tokens list"), + ) + .arg( + Arg::with_name("farm_client_url") + .short("f") + .long("farm-client-url") + .value_name("STR") + .takes_value(true) + .validator(is_url) + .help("RPC URL to use with Farm Client"), + ) + .get_matches(); + + // set log verbosity level + let mut log_level = String::from("solana=info"); + if let Some(level) = matches.value_of("log_level") { + log_level = "solana=".to_string() + level; + } + solana_logger::setup_with_default(log_level.as_str()); + + info!("Loading configuration..."); + + // start with default config settings + let mut config: config::Config = Default::default(); + // if config path is explicitly specified, load config from there and stop + // on error. Otherwise try to load from default path and allow to proceed + // with default config if file not found. + if let Some(config_file) = matches.value_of("config_file") { + config.load(config_file).unwrap(); + } else if let Some(ref config_file) = *config::CONFIG_FILE { + let _ = config.load(config_file); + } + // override loaded or default params with explicit cmd line arguments + if let Some(json_rpc_url) = matches.value_of("json_rpc_url") { + config.json_rpc_url = json_rpc_url.to_string(); + } + if let Some(websocket_url) = matches.value_of("websocket_url") { + config.websocket_url = websocket_url.to_string(); + } + if let Some(max_threads) = matches.value_of("max_threads") { + config.max_threads = max_threads.parse().unwrap(); + } + if let Some(token_list_url) = matches.value_of("token_list_url") { + config.token_list_url = token_list_url.to_string(); + } + if let Some(farm_client_url) = matches.value_of("farm_client_url") { + config.farm_client_url = farm_client_url.to_string(); + } + // save config to a file + if let Some(config_file) = matches.value_of("save_config") { + config.save(config_file).unwrap(); + info!("Configuration saved to: {}", config_file); + } + + debug!("json_rpc_url: {}", config.json_rpc_url); + debug!("websocket_url: {}", config.websocket_url); + debug!("farm_client_url: {}", config.farm_client_url); + debug!("max_threads: {}", config.max_threads); + + info!("Starting JSON RPC on {}", config.json_rpc_url); + let parsed_url: Url = config.json_rpc_url.parse().unwrap(); + let figment = rocket::Config::figment() + .merge(("port", parsed_url.port().unwrap())) + .merge(("address", parsed_url.host_str().unwrap())) + .merge(("workers", config.max_threads)) + .merge(("ident", "Farms JSON RPC")); + + let json_rpc = rocket::custom(figment) + .attach(json_rpc::stage(&config).await) + .launch(); + json_rpc.await.unwrap(); + + info!("Shutting down..."); +} diff --git a/farms/farm-rpc/static/favicon.ico b/farms/farm-rpc/static/favicon.ico new file mode 100644 index 00000000000..da4839396bd Binary files /dev/null and b/farms/farm-rpc/static/favicon.ico differ diff --git a/farms/farm-rpc/static/index.html b/farms/farm-rpc/static/index.html new file mode 100644 index 00000000000..076d1036838 --- /dev/null +++ b/farms/farm-rpc/static/index.html @@ -0,0 +1,261 @@ + + + + + + + + + + + + + Solana Farms RPC Service + + + +
+

Solana Farms RPC Service

+

Get:

+

/// Returns Token metadata from Github

+ /api/v1/git_token?name=[name] +

/// Returns all Tokens from Github

+ /api/v1/git_tokens +

/// Returns the Vault struct for the given name

+ /api/v1/vault?name=[name] +

/// Returns all Vaults available

+ /api/v1/vaults +

/// Returns the Vault metadata address for the given name

+ /api/v1/vault_ref?name=[name] +

/// Returns Vault refs: a map of Vault name to account address with metadata

+ /api/v1/vault_refs +

/// Loads the Vault struct data from the specified metadata address

+ /api/v1/vault_by_ref?vault_ref=[vault_ref] +

/// Returns the Vault name for the given metadata address

+ /api/v1/vault_name?vault_ref=[vault_ref] +

/// Returns all Vaults with tokens A and B sorted by version

+ /api/v1/find_vaults?token_a=[token_a]&token_b=[token_b] +

/// Returns the Pool struct for the given name

+ /api/v1/pool?name=[name] +

/// Returns all Pools available

+ /api/v1/pools +

/// Returns the Pool metadata address for the given name

+ /api/v1/pool_ref?name=[name] +

/// Returns Pool refs: a map of Pool name to account address with metadata

+ /api/v1/pool_refs +

/// Loads the Pool struct data from the specified metadata address

+ /api/v1/pool_by_ref?pool_ref=[pool_ref] +

/// Returns the Pool name for the given metadata address

+ /api/v1/pool_name?pool_ref=[pool_ref] +

/// Returns all Pools with tokens A and B sorted by version for the given protocol

+ /api/v1/find_pools?protocol=[protocol]&token_a=[token_a]&token_b=[token_b] +

/// Returns all Pools sorted by version for the given LP token

+ /api/v1/find_pools_with_lp?lp_token=[lp_token] +

/// Returns pair's price based on the ratio of tokens in the pool

+ /api/v1/pool_price?name=[name] +

/// Returns the Farm struct for the given name

+ /api/v1/farm?name=[name] +

/// Returns all Farms available

+ /api/v1/farms +

/// Returns the Farm metadata address for the given name

+ /api/v1/farm_ref?name=[name] +

/// Returns Farm refs: a map of Farm name to account address with metadata

+ /api/v1/farm_refs +

/// Loads the Farm struct data from the specified metadata address

+ /api/v1/farm_by_ref?farm_ref=[farm_ref] +

/// Returns the Farm name for the given metadata address

+ /api/v1/farm_name?farm_ref=[farm_ref] +

/// Returns all Farms sorted by version for the given LP token

+ /api/v1/find_farms_with_lp?lp_token=[lp_token] +

/// Returns the Token struct for the given name

+ /api/v1/token?name=[name] +

/// Returns all Tokens available

+ /api/v1/tokens +

/// Returns the Token metadata address for the given name

+ /api/v1/token_ref?name=[name] +

/// Returns Token refs: a map of Token name to account address with metadata

+ /api/v1/token_refs +

/// Loads the Token struct data from the specified metadata address

+ /api/v1/token_by_ref?token_ref=[token_ref] +

/// Returns the Token name for the given metadata address

+ /api/v1/token_name?token_ref=[token_ref] +

/// Returns the Token metadata for the specified mint

+ /api/v1/token_with_mint?token_mint=[token_mint] +

/// Returns the official Program ID for the given name

+ /api/v1/program_id?name=[name] +

/// Returns all official Program IDs available

+ /api/v1/program_ids +

/// Returns the official program name for the given Program ID

+ /api/v1/program_name?prog_id=[prog_id] +

/// Checks if the given address is the official Program ID

+ /api/v1/is_official_id?prog_id=[prog_id] +

/// Closes existing system account

+ /api/v1/close_system_account?wallet_keypair=[wallet_keypair]&target_account_keypair=[target_account_keypair] +

/// Returns the associated token account address for the given token name

+ /api/v1/associated_token_address?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns all tokens with active account in the wallet

+ /api/v1/wallet_tokens?wallet_address=[wallet_address] +

/// Returns UiTokenAccount struct data for the associated token account address

+ /api/v1/token_account_data?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns native SOL balance

+ /api/v1/account_balance?wallet_address=[wallet_address] +

/// Returns token balance for the associated token account address

+ /api/v1/token_account_balance?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns true if the associated token account exists and is initialized

+ /api/v1/has_active_token_account?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns User's stacked balance

+ /api/v1/user_stake_balance?wallet_address=[wallet_address]&farm_name=[farm_name] +

/// Returns Vault's stacked balance

+ /api/v1/vault_stake_balance?vault_name=[vault_name] +

/// Returns user stats for specific Vault

+ /api/v1/vault_user_info?wallet_address=[wallet_address]&vault_name=[vault_name] +

/// Returns Vault stats

+ /api/v1/vault_info?vault_name=[vault_name] +

/// Returns a new Instruction for creating system account

+ /api/v1/new_instruction_create_system_account?wallet_address=[wallet_address]&new_address=[new_address]&lamports=[lamports]&space=[space]&owner=[owner] +

/// Returns a new Instruction for closing system account

+ /api/v1/new_instruction_close_system_account?wallet_address=[wallet_address]&target_address=[target_address] +

/// Creates the native SOL transfer instruction

+ /api/v1/new_instruction_transfer?wallet_address=[wallet_address]&destination_wallet=[destination_wallet]&sol_ui_amount=[sol_ui_amount] +

/// Creates a tokens transfer instruction

+ /api/v1/new_instruction_token_transfer?wallet_address=[wallet_address]&token_name=[token_name]&destination_wallet=[destination_wallet]&ui_amount=[ui_amount] +

/// Creates a new Instruction for syncing token balance for the specified account

+ /api/v1/new_instruction_sync_token_balance?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns a new Instruction for creating associated token account

+ /api/v1/new_instruction_create_token_account?wallet_address=[wallet_address]&token_name=[token_name] +

/// Returns a new Instruction for closing associated token account

+ /api/v1/new_instruction_close_token_account?wallet_address=[wallet_address]&token_name=[token_name] +

/// Creates a new Instruction for initializing a new User for the Vault

+ /api/v1/new_instruction_add_liquidity_vault?wallet_address=[wallet_address]&vault_name=[vault_name] +

/// Creates a new Instruction for adding liquidity to the Vault

+ /api/v1/new_instruction_add_liquidity_vault?wallet_address=[wallet_address]&vault_name=[vault_name]&max_token_a_ui_amount=[max_token_a_ui_amount]&max_token_b_ui_amount=[max_token_b_ui_amount] +

/// Creates a new Instruction for unlocking liquidity from the Vault

+ /api/v1/new_instruction_unlock_liquidity_vault?wallet_address=[wallet_address]&vault_name=[vault_name]&ui_amount=[ui_amount] +

/// Creates a new Instruction for removing liquidity from the Vault

+ /api/v1/new_instruction_remove_liquidity_vault?wallet_address=[wallet_address]&vault_name=[vault_name]&ui_amount=[ui_amount] +

/// Creates a new Instruction for adding liquidity to the Pool

+ /api/v1/new_instruction_add_liquidity_pool?wallet_address=[wallet_address]&pool_name=[pool_name]&max_token_a_ui_amount=[max_token_a_ui_amount]&max_token_b_ui_amount=[max_token_b_ui_amount] +

/// Creates a new Instruction for removing liquidity from the Pool

+ /api/v1/new_instruction_remove_liquidity_pool?wallet_address=[wallet_address]&pool_name=[pool_name]&ui_amount=[ui_amount] +

/// Creates a new Instruction for tokens swap

+ /api/v1/new_instruction_swap?wallet_address=[wallet_address]&protocol=[protocol]&from_token=[from_token]&to_token=[to_token]&ui_amount_in=[ui_amount_in]&min_ui_amount_out=[min_ui_amount_out] +

/// Creates a new Instruction for tokens staking

+ /api/v1/new_instruction_stake?wallet_address=[wallet_address]&farm_name=[farm_name]&ui_amount=[ui_amount] +

/// Creates a new Instruction for tokens unstaking

+ /api/v1/new_instruction_unstake?wallet_address=[wallet_address]&farm_name=[farm_name]&ui_amount=[ui_amount] +

/// Creates a new Instruction for rewards harvesting

+ /api/v1/new_instruction_harvest?wallet_address=[wallet_address]&farm_name=[farm_name] +

/// Creates a new Vault Crank Instruction

+ /api/v1/new_instruction_crank_vault?wallet_address=[wallet_address]&vault_name=[vault_name]&step=[step] +

Post:

+

/// Creates a new system account

+

/// Transfers native SOL from the wallet to the destination

+

/// Transfers native SOL from the wallet to the associated Wrapped SOL account.

+

/// Transfers tokens from the wallet to the destination

+

/// Updates token balance of the account, usefull after transfer SOL to WSOL account

+

/// Returns the associated token account or creates one if it doesn't exist

+

/// Closes existing token account associated with the given user's main account

+

/// Initializes a new User for the Vault

+

/// Adds liquidity to the Vault

+

/// Removes liquidity from the Vault

+

/// Removes unlocked liquidity from the Vault

+

/// Adds liquidity to the Pool

+

/// Removes liquidity from the Pool

+

/// Swaps tokens

+

/// Stakes tokens to the Farm

+

/// Unstakes tokens from the Farm

+

/// Harvests rewards from the Pool

+

/// Cranks single Vault

+

/// Cranks all Vaults

+
+ + \ No newline at end of file diff --git a/farms/farm-rpc/static/manifest.json b/farms/farm-rpc/static/manifest.json new file mode 100644 index 00000000000..fd882ef77e5 --- /dev/null +++ b/farms/farm-rpc/static/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "Solana Farms", + "name": "Solana Farms RPC Service", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "rainbow192.png", + "type": "image/png", + "sizes": "192x192" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#75FBB4", + "background_color": "#ffffff" +} diff --git a/farms/farm-rpc/static/rainbow192.png b/farms/farm-rpc/static/rainbow192.png new file mode 100644 index 00000000000..bb7d3cea7ed Binary files /dev/null and b/farms/farm-rpc/static/rainbow192.png differ diff --git a/farms/farm-rpc/swagger.yaml b/farms/farm-rpc/swagger.yaml new file mode 100644 index 00000000000..daff3faaa53 --- /dev/null +++ b/farms/farm-rpc/swagger.yaml @@ -0,0 +1,1269 @@ +openapi: 3.0.1 +info: + title: Solana Farms RPC Service + description: RPC service for interaction with pools, farms and vaults built on Solana + version: "0.1" +servers: + - url: "http://127.0.0.1:9090" + - url: "http://localhost:9090" +paths: + /api/v1/git_token: + get: + description: Returns Token metadata from Github + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/git_tokens: + get: + description: Returns all Tokens from Github + responses: + default: + description: Json object or error string + /api/v1/vault: + get: + description: Returns the Vault struct for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vaults: + get: + description: Returns all Vaults available + responses: + default: + description: Json object or error string + /api/v1/vault_ref: + get: + description: Returns the Vault metadata address for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vault_refs: + get: + description: Returns Vault refs - a map of Vault name to account address with metadata + responses: + default: + description: Json object or error string + /api/v1/vault_by_ref: + get: + description: Loads the Vault struct data from the specified metadata address + parameters: + - name: vault_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vault_name: + get: + description: Returns the Vault name for the given metadata address + parameters: + - name: vault_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/find_vaults: + get: + description: Returns all Vaults with tokens A and B sorted by version + parameters: + - name: token_a + in: query + schema: + type: string + - name: token_b + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/pool: + get: + description: Returns the Pool struct for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/pools: + get: + description: Returns all Pools available + responses: + default: + description: Json object or error string + /api/v1/pool_ref: + get: + description: Returns the Pool metadata address for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/pool_refs: + get: + description: Returns Pool refs - a map of Pool name to account address with metadata + responses: + default: + description: Json object or error string + /api/v1/pool_by_ref: + get: + description: Loads the Pool struct data from the specified metadata address + parameters: + - name: pool_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/pool_name: + get: + description: Returns the Pool name for the given metadata address + parameters: + - name: pool_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/find_pools: + get: + description: Returns all Pools with tokens A and B sorted by version for the given protocol + parameters: + - name: protocol + in: query + schema: + type: string + - name: token_a + in: query + schema: + type: string + - name: token_b + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/find_pools_with_lp: + get: + description: Returns all Pools sorted by version for the given LP token + parameters: + - name: lp_token + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/pool_price: + get: + description: Returns pair's price based on the ratio of tokens in the pool + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/farm: + get: + description: Returns the Farm struct for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/farms: + get: + description: Returns all Farms available + responses: + default: + description: Json object or error string + /api/v1/farm_ref: + get: + description: Returns the Farm metadata address for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/farm_refs: + get: + description: Returns Farm refs - a map of Farm name to account address with metadata + responses: + default: + description: Json object or error string + /api/v1/farm_by_ref: + get: + description: Loads the Farm struct data from the specified metadata address + parameters: + - name: farm_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/farm_name: + get: + description: Returns the Farm name for the given metadata address + parameters: + - name: farm_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/find_farms_with_lp: + get: + description: Returns all Farms for the given LP token + parameters: + - name: lp_token + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token: + get: + description: Returns the Token struct for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/tokens: + get: + description: Returns all Tokens available + responses: + default: + description: Json object or error string + /api/v1/token_ref: + get: + description: Returns the Token metadata address for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token_refs: + get: + description: Returns Token refs - a map of Token name to account address with metadata + responses: + default: + description: Json object or error string + /api/v1/token_by_ref: + get: + description: Loads the Token struct data from the specified metadata address + parameters: + - name: token_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token_name: + get: + description: Returns the Token name for the given metadata address + parameters: + - name: token_ref + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token_with_mint: + get: + description: Loads the Token struct data from the specified mint + parameters: + - name: token_mint + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/program_id: + get: + description: Returns the official Program ID for the given name + parameters: + - name: name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/program_ids: + get: + description: Returns all official Program IDs available + responses: + default: + description: Json object or error string + /api/v1/program_name: + get: + description: Returns the official program name for the given Program ID + parameters: + - name: prog_id + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/is_official_id: + get: + description: Checks if the given address is the official Program ID + parameters: + - name: prog_id + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/associated_token_address: + get: + description: Returns the associated token account address for the given token name + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/wallet_tokens: + get: + description: Returns all tokens with active account in the wallet + parameters: + - name: wallet_address + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token_account_data: + get: + description: Returns UiTokenAccount struct data for the associated token account address + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/account_balance: + get: + description: Returns native SOL balance + parameters: + - name: wallet_address + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/token_account_balance: + get: + description: Returns token balance for the associated token account address + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/has_active_token_account: + get: + description: Returns true if the associated token account exists and is initialized + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/user_stake_balance: + get: + description: Returns User's stacked balance + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vault_stake_balance: + get: + description: Returns Vault's stacked balance + parameters: + - name: vault_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vault_user_info: + get: + description: Returns user stats for specific Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/vault_info: + get: + description: Returns Vault stats + parameters: + - name: vault_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_create_system_account: + get: + description: Returns a new Instruction for creating system account + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: new_address + in: query + schema: + type: string + - name: lamports + in: query + schema: + type: string + - name: space + in: query + schema: + type: string + - name: owner + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_close_system_account: + get: + description: Returns a new Instruction for closing system account + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: target_address + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_transfer: + get: + description: Creates the native SOL transfer instruction + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: destination_wallet + in: query + schema: + type: string + - name: sol_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_token_transfer: + get: + description: Creates a tokens transfer instruction + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + - name: destination_wallet + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_sync_token_balance: + get: + description: Creates a new Instruction for syncing token balance for the specified account + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_create_token_account: + get: + description: Returns a new Instruction for creating associated token account + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_close_token_account: + get: + description: Returns a new Instruction for closing associated token account + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_user_init_vault: + get: + description: Creates a new Instruction for initializing a new User for the Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_add_liquidity_vault: + get: + description: Creates a new Instruction for adding liquidity to the Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: max_token_a_ui_amount + in: query + schema: + type: number + - name: max_token_b_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_lock_liquidity_vault: + get: + description: Creates a new Instruction for locking liquidity in the Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_unlock_liquidity_vault: + get: + description: Creates a new Instruction for unlocking liquidity from the Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_remove_liquidity_vault: + get: + description: Creates a new Instruction for removing liquidity from the Vault + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_add_liquidity_pool: + get: + description: Creates a new Instruction for adding liquidity to the Pool + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: pool_name + in: query + schema: + type: string + - name: max_token_a_ui_amount + in: query + schema: + type: number + - name: max_token_b_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_remove_liquidity_pool: + get: + description: Creates a new Instruction for removing liquidity from the Pool + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: pool_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_swap: + get: + description: Creates a new Instruction for tokens swap + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: protocol + in: query + schema: + type: string + - name: from_token + in: query + schema: + type: string + - name: to_token + in: query + schema: + type: string + - name: ui_amount_in + in: query + schema: + type: number + - name: min_ui_amount_out + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_stake: + get: + description: Creates a new Instruction for tokens staking + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_unstake: + get: + description: Creates a new Instruction for tokens unstaking + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/new_instruction_harvest: + get: + description: Creates a new Instruction for rewards harvesting + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/new_instruction_crank_vault: + get: + description: Creates a new Vault Crank Instruction + parameters: + - name: wallet_address + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: step + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/create_system_account: + post: + description: Creates a new system account + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: new_account_keypair + in: query + schema: + type: string + - name: lamports + in: query + schema: + type: string + - name: space + in: query + schema: + type: string + - name: owner + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/close_system_account: + post: + description: Closes existing system account + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: target_account_keypair + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/transfer: + post: + description: Transfers native SOL from the wallet to the destination + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: destination_wallet + in: query + schema: + type: string + - name: sol_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/transfer_sol_to_wsol: + post: + description: Transfers native SOL from the wallet to the associated Wrapped SOL account. + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: sol_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/token_transfer: + post: + description: Transfers tokens from the wallet to the destination + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + - name: destination_wallet + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/sync_token_balance: + post: + description: Updates token balance of the account, usefull after transfer SOL to WSOL account + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/create_token_account: + post: + description: Returns the associated token account or creates one if it doesn't exist + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/close_token_account: + post: + description: Closes existing token account associated with the given user's main account + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: token_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/user_init_vault: + post: + description: Initializes a new User for the Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/add_liquidity_vault: + post: + description: Adds liquidity to the Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: max_token_a_ui_amount + in: query + schema: + type: number + - name: max_token_b_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/add_locked_liquidity_vault: + post: + description: Adds locked liquidity to the Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/remove_liquidity_vault: + post: + description: Removes liquidity from the Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/remove_unlocked_liquidity_vault: + post: + description: Removes unlocked liquidity from the Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/add_liquidity_pool: + post: + description: Adds liquidity to the Pool + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: pool_name + in: query + schema: + type: string + - name: max_token_a_ui_amount + in: query + schema: + type: number + - name: max_token_b_ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/remove_liquidity_pool: + post: + description: Removes liquidity from the Pool + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: pool_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/swap: + post: + description: Swaps tokens + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: protocol + in: query + schema: + type: string + - name: from_token + in: query + schema: + type: string + - name: to_token + in: query + schema: + type: string + - name: ui_amount_in + in: query + schema: + type: number + - name: min_ui_amount_out + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/stake: + post: + description: Stakes tokens to the Farm + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/unstake: + post: + description: Unstakes tokens from the Farm + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + - name: ui_amount + in: query + schema: + type: number + responses: + default: + description: Json object or error string + /api/v1/harvest: + post: + description: Harvests rewards from the Pool + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: farm_name + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/crank_vault: + post: + description: Cranks single Vault + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: vault_name + in: query + schema: + type: string + - name: step + in: query + schema: + type: string + responses: + default: + description: Json object or error string + /api/v1/crank_vaults: + post: + description: Cranks all Vaults + parameters: + - name: wallet_keypair + in: query + schema: + type: string + - name: step + in: query + schema: + type: string + responses: + default: + description: Json object or error string diff --git a/farms/farm-sdk/.gitignore b/farms/farm-sdk/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/farms/farm-sdk/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/farms/farm-sdk/Cargo.toml b/farms/farm-sdk/Cargo.toml new file mode 100644 index 00000000000..747b41d0427 --- /dev/null +++ b/farms/farm-sdk/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "solana-farm-sdk" +version = "0.0.1" +description = "Solana Farm SDK" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +debug = [] + +[dependencies] +solana-program = "1.8.1" +arrayref = "0.3.6" +arraystring = "0.3.0" +serde = "1.0.130" +serde_derive = "1.0.130" +serde_json = "1.0.69" +num_enum = "0.5.4" +num-traits = "0.2.14" +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = "1.0.3", features = ["no-entrypoint"] } +quarry-mine = { version = "1.10.0", features = ["no-entrypoint"] } +quarry-mint-wrapper = { version = "1.10.0", features = ["no-entrypoint"] } +quarry-redeemer = { version = "1.10.0", features = ["no-entrypoint"] } +stable-swap-client = "1.5.2" + +[dev-dependencies] +solana-program-test = "1.8.1" + diff --git a/farms/farm-sdk/src/farm.rs b/farms/farm-sdk/src/farm.rs new file mode 100644 index 00000000000..feace12fc5e --- /dev/null +++ b/farms/farm-sdk/src/farm.rs @@ -0,0 +1,631 @@ +//! Stake Farms + +use { + crate::{pack::*, string::ArrayString64, traits::*}, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + serde_json::to_string, + solana_program::{program_error::ProgramError, pubkey::Pubkey}, +}; + +#[allow(clippy::large_enum_variant)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum FarmRoute { + Raydium { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_lp_token_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_reward_token_a_account: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + farm_reward_token_b_account: Option, + }, + Saber { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + quarry: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + rewarder: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + redeemer: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + redeemer_program: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + minter: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + mint_wrapper: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + mint_wrapper_program: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + iou_fees_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + sbr_vault: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + mint_proxy_program: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + mint_proxy_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + mint_proxy_state: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + minter_info: Pubkey, + }, + Orca { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_token_ref: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + base_token_vault: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + reward_token_vault: Pubkey, + }, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum FarmRouteType { + Raydium, + Saber, + Orca, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum FarmType { + SingleReward, + DualReward, + ProtocolTokenStake, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct Farm { + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, + pub version: u16, + pub farm_type: FarmType, + pub official: bool, + pub refdb_index: Option, + pub refdb_counter: u16, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub lp_token_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub reward_token_a_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub reward_token_b_ref: Option, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub router_program_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub farm_program_id: Pubkey, + pub route: FarmRoute, +} + +impl Named for Farm { + fn name(&self) -> ArrayString64 { + self.name + } +} + +impl Versioned for Farm { + fn version(&self) -> u16 { + self.version + } +} + +impl Farm { + pub const MAX_LEN: usize = 655; + pub const RAYDIUM_FARM_LEN: usize = 400; + pub const SABER_FARM_LEN: usize = 655; + pub const ORCA_FARM_LEN: usize = 399; + + pub fn get_size(&self) -> usize { + match self.route { + FarmRoute::Raydium { .. } => Farm::RAYDIUM_FARM_LEN, + FarmRoute::Saber { .. } => Farm::SABER_FARM_LEN, + FarmRoute::Orca { .. } => Farm::ORCA_FARM_LEN, + } + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self.route { + FarmRoute::Raydium { .. } => self.pack_raydium(output), + FarmRoute::Saber { .. } => self.pack_saber(output), + FarmRoute::Orca { .. } => self.pack_orca(output), + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Farm::MAX_LEN] = [0; Farm::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let farm_route_type = FarmRouteType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidAccountData))?; + match farm_route_type { + FarmRouteType::Raydium => Farm::unpack_raydium(input), + FarmRouteType::Saber => Farm::unpack_saber(input), + FarmRouteType::Orca => Farm::unpack_orca(input), + } + } + + fn pack_raydium(&self, output: &mut [u8]) -> Result { + check_data_len(output, Farm::RAYDIUM_FARM_LEN)?; + + if let FarmRoute::Raydium { + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + } = self.route + { + let output = array_mut_ref![output, 0, Farm::RAYDIUM_FARM_LEN]; + + let ( + farm_route_type_out, + name_out, + version_out, + farm_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + lp_token_ref_out, + reward_token_a_ref_out, + reward_token_b_ref_out, + router_program_id_out, + farm_program_id_out, + farm_id_out, + farm_authority_out, + farm_lp_token_account_out, + farm_reward_token_a_account_out, + farm_reward_token_b_account_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 33 + ]; + + farm_route_type_out[0] = FarmRouteType::Raydium as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + farm_type_out[0] = self.farm_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.reward_token_a_ref, reward_token_a_ref_out); + pack_option_key(&self.reward_token_b_ref, reward_token_b_ref_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + farm_program_id_out.copy_from_slice(self.farm_program_id.as_ref()); + farm_id_out.copy_from_slice(farm_id.as_ref()); + farm_authority_out.copy_from_slice(farm_authority.as_ref()); + farm_lp_token_account_out.copy_from_slice(farm_lp_token_account.as_ref()); + farm_reward_token_a_account_out.copy_from_slice(farm_reward_token_a_account.as_ref()); + pack_option_key( + &farm_reward_token_b_account, + farm_reward_token_b_account_out, + ); + + Ok(Farm::RAYDIUM_FARM_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn pack_saber(&self, output: &mut [u8]) -> Result { + check_data_len(output, Farm::SABER_FARM_LEN)?; + + if let FarmRoute::Saber { + quarry, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + iou_fees_account, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info, + } = self.route + { + let output = array_mut_ref![output, 0, Farm::SABER_FARM_LEN]; + + let ( + farm_route_type_out, + name_out, + version_out, + farm_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + lp_token_ref_out, + reward_token_a_ref_out, + reward_token_b_ref_out, + router_program_id_out, + farm_program_id_out, + quarry_out, + rewarder_out, + redeemer_out, + redeemer_program_out, + minter_out, + mint_wrapper_out, + mint_wrapper_program_out, + iou_fees_account_out, + sbr_vault_out, + mint_proxy_program_out, + mint_proxy_authority_out, + mint_proxy_state_out, + minter_info_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32 + ]; + + farm_route_type_out[0] = FarmRouteType::Saber as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + farm_type_out[0] = self.farm_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.reward_token_a_ref, reward_token_a_ref_out); + pack_option_key(&self.reward_token_b_ref, reward_token_b_ref_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + farm_program_id_out.copy_from_slice(self.farm_program_id.as_ref()); + quarry_out.copy_from_slice(quarry.as_ref()); + rewarder_out.copy_from_slice(rewarder.as_ref()); + redeemer_out.copy_from_slice(redeemer.as_ref()); + redeemer_program_out.copy_from_slice(redeemer_program.as_ref()); + minter_out.copy_from_slice(minter.as_ref()); + mint_wrapper_out.copy_from_slice(mint_wrapper.as_ref()); + mint_wrapper_program_out.copy_from_slice(mint_wrapper_program.as_ref()); + iou_fees_account_out.copy_from_slice(iou_fees_account.as_ref()); + sbr_vault_out.copy_from_slice(sbr_vault.as_ref()); + mint_proxy_program_out.copy_from_slice(mint_proxy_program.as_ref()); + mint_proxy_authority_out.copy_from_slice(mint_proxy_authority.as_ref()); + mint_proxy_state_out.copy_from_slice(mint_proxy_state.as_ref()); + minter_info_out.copy_from_slice(minter_info.as_ref()); + + Ok(Farm::SABER_FARM_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn pack_orca(&self, output: &mut [u8]) -> Result { + check_data_len(output, Farm::ORCA_FARM_LEN)?; + + if let FarmRoute::Orca { + farm_id, + farm_authority, + farm_token_ref, + base_token_vault, + reward_token_vault, + } = self.route + { + let output = array_mut_ref![output, 0, Farm::ORCA_FARM_LEN]; + + let ( + farm_route_type_out, + name_out, + version_out, + farm_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + lp_token_ref_out, + reward_token_a_ref_out, + reward_token_b_ref_out, + router_program_id_out, + farm_program_id_out, + farm_id_out, + farm_authority_out, + farm_token_ref_out, + base_token_vault_out, + reward_token_vault_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32 + ]; + + farm_route_type_out[0] = FarmRouteType::Orca as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + farm_type_out[0] = self.farm_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.reward_token_a_ref, reward_token_a_ref_out); + pack_option_key(&self.reward_token_b_ref, reward_token_b_ref_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + farm_program_id_out.copy_from_slice(self.farm_program_id.as_ref()); + farm_id_out.copy_from_slice(farm_id.as_ref()); + farm_authority_out.copy_from_slice(farm_authority.as_ref()); + farm_token_ref_out.copy_from_slice(farm_token_ref.as_ref()); + base_token_vault_out.copy_from_slice(base_token_vault.as_ref()); + reward_token_vault_out.copy_from_slice(reward_token_vault.as_ref()); + + Ok(Farm::ORCA_FARM_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn unpack_raydium(input: &[u8]) -> Result { + check_data_len(input, Farm::RAYDIUM_FARM_LEN)?; + + let input = array_ref![input, 1, Farm::RAYDIUM_FARM_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + farm_type, + official, + refdb_index, + refdb_counter, + lp_token_ref, + reward_token_a_ref, + reward_token_b_ref, + router_program_id, + farm_program_id, + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + ) = array_refs![input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 33]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + farm_type: FarmType::try_from_primitive(farm_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + lp_token_ref: unpack_option_key(lp_token_ref)?, + reward_token_a_ref: unpack_option_key(reward_token_a_ref)?, + reward_token_b_ref: unpack_option_key(reward_token_b_ref)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + farm_program_id: Pubkey::new_from_array(*farm_program_id), + route: FarmRoute::Raydium { + farm_id: Pubkey::new_from_array(*farm_id), + farm_authority: Pubkey::new_from_array(*farm_authority), + farm_lp_token_account: Pubkey::new_from_array(*farm_lp_token_account), + farm_reward_token_a_account: Pubkey::new_from_array(*farm_reward_token_a_account), + farm_reward_token_b_account: unpack_option_key(farm_reward_token_b_account)?, + }, + }) + } + + fn unpack_saber(input: &[u8]) -> Result { + check_data_len(input, Farm::SABER_FARM_LEN)?; + + let input = array_ref![input, 1, Farm::SABER_FARM_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + farm_type, + official, + refdb_index, + refdb_counter, + lp_token_ref, + reward_token_a_ref, + reward_token_b_ref, + router_program_id, + farm_program_id, + quarry, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + iou_fees_account, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info, + ) = array_refs![ + input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32 + ]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + farm_type: FarmType::try_from_primitive(farm_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + lp_token_ref: unpack_option_key(lp_token_ref)?, + reward_token_a_ref: unpack_option_key(reward_token_a_ref)?, + reward_token_b_ref: unpack_option_key(reward_token_b_ref)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + farm_program_id: Pubkey::new_from_array(*farm_program_id), + route: FarmRoute::Saber { + quarry: Pubkey::new_from_array(*quarry), + rewarder: Pubkey::new_from_array(*rewarder), + redeemer: Pubkey::new_from_array(*redeemer), + redeemer_program: Pubkey::new_from_array(*redeemer_program), + minter: Pubkey::new_from_array(*minter), + mint_wrapper: Pubkey::new_from_array(*mint_wrapper), + mint_wrapper_program: Pubkey::new_from_array(*mint_wrapper_program), + iou_fees_account: Pubkey::new_from_array(*iou_fees_account), + sbr_vault: Pubkey::new_from_array(*sbr_vault), + mint_proxy_program: Pubkey::new_from_array(*mint_proxy_program), + mint_proxy_authority: Pubkey::new_from_array(*mint_proxy_authority), + mint_proxy_state: Pubkey::new_from_array(*mint_proxy_state), + minter_info: Pubkey::new_from_array(*minter_info), + }, + }) + } + + fn unpack_orca(input: &[u8]) -> Result { + check_data_len(input, Farm::ORCA_FARM_LEN)?; + + let input = array_ref![input, 1, Farm::ORCA_FARM_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + farm_type, + official, + refdb_index, + refdb_counter, + lp_token_ref, + reward_token_a_ref, + reward_token_b_ref, + router_program_id, + farm_program_id, + farm_id, + farm_authority, + farm_token_ref, + base_token_vault, + reward_token_vault, + ) = array_refs![input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + farm_type: FarmType::try_from_primitive(farm_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + lp_token_ref: unpack_option_key(lp_token_ref)?, + reward_token_a_ref: unpack_option_key(reward_token_a_ref)?, + reward_token_b_ref: unpack_option_key(reward_token_b_ref)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + farm_program_id: Pubkey::new_from_array(*farm_program_id), + route: FarmRoute::Orca { + farm_id: Pubkey::new_from_array(*farm_id), + farm_authority: Pubkey::new_from_array(*farm_authority), + farm_token_ref: Pubkey::new_from_array(*farm_token_ref), + base_token_vault: Pubkey::new_from_array(*base_token_vault), + reward_token_vault: Pubkey::new_from_array(*reward_token_vault), + }, + }) + } +} + +impl std::fmt::Display for FarmType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + FarmType::SingleReward => write!(f, "SingleReward"), + FarmType::DualReward => write!(f, "DualReward"), + FarmType::ProtocolTokenStake => write!(f, "ProtocolTokenStake"), + } + } +} + +impl std::fmt::Display for Farm { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} diff --git a/farms/farm-sdk/src/git_token.rs b/farms/farm-sdk/src/git_token.rs new file mode 100644 index 00000000000..568dbe8efaa --- /dev/null +++ b/farms/farm-sdk/src/git_token.rs @@ -0,0 +1,21 @@ +use { + serde::{Deserialize, Serialize}, + serde_json::Value, + std::collections::HashMap, +}; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GitToken { + #[serde(rename = "chainId")] + pub chain_id: i32, + pub address: String, + pub symbol: String, + pub name: String, + pub decimals: i32, + #[serde(rename = "logoURI", default)] + pub logo_uri: String, + #[serde(default)] + pub tags: Vec, + #[serde(flatten)] + pub extra: HashMap, +} diff --git a/farms/farm-sdk/src/id.rs b/farms/farm-sdk/src/id.rs new file mode 100644 index 00000000000..5d1efb24d66 --- /dev/null +++ b/farms/farm-sdk/src/id.rs @@ -0,0 +1,41 @@ +//! Official accounts and program ids + +use serde::{Deserialize, Serialize}; + +pub mod main_router { + solana_program::declare_id!("RepLaceThisWithVaLidMainRouterProgramPubkey"); +} + +pub mod main_router_admin { + solana_program::declare_id!("RepLaceThisWithCorrectMainRouterAdminPubkey"); +} + +pub mod zero { + // ID that represents the unset Pubkey. This is to avoid passing Pubkey::default() which + // is equal to system_program::id(). + // [14, 196, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + solana_program::declare_id!("zeRosMEYuuABXv5y2LNUbgmPp62yFD5CULW5soHS9HR"); +} + +pub const DAO_TOKEN_NAME: &str = "FARM_DAO"; +pub const DAO_PROGRAM_NAME: &str = "FarmGovernance"; +pub const DAO_MINT_NAME: &str = "FarmGovernanceMint"; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProgramIDType { + System, + ProgramsRef, + VaultsRef, + Vault, + FarmsRef, + Farm, + PoolsRef, + Pool, + TokensRef, + Token, + MainRouter, + Serum, + Raydium, + Saber, + Orca, +} diff --git a/farms/farm-sdk/src/instruction/amm.rs b/farms/farm-sdk/src/instruction/amm.rs new file mode 100644 index 00000000000..bfe014e8a13 --- /dev/null +++ b/farms/farm-sdk/src/instruction/amm.rs @@ -0,0 +1,417 @@ +//! Raydium router instructions. + +use { + crate::pack::check_data_len, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AmmInstruction { + /// Initialize on-chain records for a new user + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + UserInit, + + /// Add liquidity to the AMM Pool + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + AddLiquidity { + max_token_a_amount: u64, + max_token_b_amount: u64, + }, + + /// Remove liquidity from the AMM Pool + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + RemoveLiquidity { amount: u64 }, + + /// Swap tokens in the AMM Pool + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Swap { + token_a_amount_in: u64, + token_b_amount_in: u64, + min_token_amount_out: u64, + }, + + /// Stake LP tokens to the Farm + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Stake { amount: u64 }, + + /// Unstake LP tokens from the Farm + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Unstake { amount: u64 }, + + /// Claim pending rewards from the Farm + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Harvest, + + /// Wrap the token to protocol specific token + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + WrapToken { amount: u64 }, + + /// Unwrap the token from protocol specific token + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + UnwrapToken { amount: u64 }, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum AmmInstructionType { + UserInit, + AddLiquidity, + RemoveLiquidity, + Swap, + Stake, + Unstake, + Harvest, + WrapToken, + UnwrapToken, +} + +impl AmmInstruction { + pub const MAX_LEN: usize = 25; + pub const USER_INIT_LEN: usize = 1; + pub const ADD_LIQUIDITY_LEN: usize = 17; + pub const REMOVE_LIQUIDITY_LEN: usize = 9; + pub const SWAP_LEN: usize = 25; + pub const STAKE_LEN: usize = 9; + pub const UNSTAKE_LEN: usize = 9; + pub const HARVEST_LEN: usize = 1; + pub const WRAP_TOKEN_LEN: usize = 9; + pub const UNWRAP_TOKEN_LEN: usize = 9; + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self { + Self::UserInit { .. } => self.pack_user_init(output), + Self::AddLiquidity { .. } => self.pack_add_liquidity(output), + Self::RemoveLiquidity { .. } => self.pack_remove_liquidity(output), + Self::Swap { .. } => self.pack_swap(output), + Self::Stake { .. } => self.pack_stake(output), + Self::Unstake { .. } => self.pack_unstake(output), + Self::Harvest { .. } => self.pack_harvest(output), + Self::WrapToken { .. } => self.pack_wrap_token(output), + Self::UnwrapToken { .. } => self.pack_unwrap_token(output), + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; AmmInstruction::MAX_LEN] = [0; AmmInstruction::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let instruction_type = AmmInstructionType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidInstructionData))?; + match instruction_type { + AmmInstructionType::UserInit => AmmInstruction::unpack_user_init(input), + AmmInstructionType::AddLiquidity => AmmInstruction::unpack_add_liquidity(input), + AmmInstructionType::RemoveLiquidity => AmmInstruction::unpack_remove_liquidity(input), + AmmInstructionType::Swap => AmmInstruction::unpack_swap(input), + AmmInstructionType::Stake => AmmInstruction::unpack_stake(input), + AmmInstructionType::Unstake => AmmInstruction::unpack_unstake(input), + AmmInstructionType::Harvest => AmmInstruction::unpack_harvest(input), + AmmInstructionType::WrapToken => AmmInstruction::unpack_wrap_token(input), + AmmInstructionType::UnwrapToken => AmmInstruction::unpack_unwrap_token(input), + } + } + + fn pack_user_init(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::USER_INIT_LEN)?; + + if let AmmInstruction::UserInit = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = AmmInstructionType::UserInit as u8; + + Ok(AmmInstruction::USER_INIT_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_add_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::ADD_LIQUIDITY_LEN)?; + + if let AmmInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } = self + { + let output = array_mut_ref![output, 0, AmmInstruction::ADD_LIQUIDITY_LEN]; + let (instruction_type_pack, max_token_a_amount_pack, max_token_b_amount_pack) = + mut_array_refs![output, 1, 8, 8]; + + instruction_type_pack[0] = AmmInstructionType::AddLiquidity as u8; + + *max_token_a_amount_pack = max_token_a_amount.to_le_bytes(); + *max_token_b_amount_pack = max_token_b_amount.to_le_bytes(); + + Ok(AmmInstruction::ADD_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_remove_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::REMOVE_LIQUIDITY_LEN)?; + + if let AmmInstruction::RemoveLiquidity { amount } = self { + let output = array_mut_ref![output, 0, AmmInstruction::REMOVE_LIQUIDITY_LEN]; + let (instruction_type_pack, amount_pack) = mut_array_refs![output, 1, 8]; + + instruction_type_pack[0] = AmmInstructionType::RemoveLiquidity as u8; + + *amount_pack = amount.to_le_bytes(); + + Ok(AmmInstruction::REMOVE_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_swap(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::SWAP_LEN)?; + + if let AmmInstruction::Swap { + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + } = self + { + let output = array_mut_ref![output, 0, AmmInstruction::SWAP_LEN]; + let ( + instruction_type_pack, + token_a_amount_in_pack, + token_b_amount_in_pack, + min_token_amount_out_pack, + ) = mut_array_refs![output, 1, 8, 8, 8]; + + instruction_type_pack[0] = AmmInstructionType::Swap as u8; + + *token_a_amount_in_pack = token_a_amount_in.to_le_bytes(); + *token_b_amount_in_pack = token_b_amount_in.to_le_bytes(); + *min_token_amount_out_pack = min_token_amount_out.to_le_bytes(); + + Ok(AmmInstruction::SWAP_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_stake(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::STAKE_LEN)?; + + if let AmmInstruction::Stake { amount } = self { + let output = array_mut_ref![output, 0, AmmInstruction::STAKE_LEN]; + let (instruction_type_pack, amount_pack) = mut_array_refs![output, 1, 8]; + + instruction_type_pack[0] = AmmInstructionType::Stake as u8; + + *amount_pack = amount.to_le_bytes(); + + Ok(AmmInstruction::STAKE_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_unstake(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::UNSTAKE_LEN)?; + + if let AmmInstruction::Unstake { amount } = self { + let output = array_mut_ref![output, 0, AmmInstruction::UNSTAKE_LEN]; + let (instruction_type_pack, amount_pack) = mut_array_refs![output, 1, 8]; + + instruction_type_pack[0] = AmmInstructionType::Unstake as u8; + + *amount_pack = amount.to_le_bytes(); + + Ok(AmmInstruction::UNSTAKE_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_harvest(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::HARVEST_LEN)?; + + if let AmmInstruction::Harvest = self { + let instruction_type_pack = array_mut_ref![output, 0, 1]; + + instruction_type_pack[0] = AmmInstructionType::Harvest as u8; + + Ok(AmmInstruction::HARVEST_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_wrap_token(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::WRAP_TOKEN_LEN)?; + + if let AmmInstruction::WrapToken { amount } = self { + let output = array_mut_ref![output, 0, AmmInstruction::WRAP_TOKEN_LEN]; + let (instruction_type_pack, amount_pack) = mut_array_refs![output, 1, 8]; + + instruction_type_pack[0] = AmmInstructionType::WrapToken as u8; + + *amount_pack = amount.to_le_bytes(); + + Ok(AmmInstruction::WRAP_TOKEN_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_unwrap_token(&self, output: &mut [u8]) -> Result { + check_data_len(output, AmmInstruction::UNWRAP_TOKEN_LEN)?; + + if let AmmInstruction::UnwrapToken { amount } = self { + let output = array_mut_ref![output, 0, AmmInstruction::UNWRAP_TOKEN_LEN]; + let (instruction_type_pack, amount_pack) = mut_array_refs![output, 1, 8]; + + instruction_type_pack[0] = AmmInstructionType::UnwrapToken as u8; + + *amount_pack = amount.to_le_bytes(); + + Ok(AmmInstruction::UNWRAP_TOKEN_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn unpack_user_init(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::USER_INIT_LEN)?; + Ok(Self::UserInit) + } + + fn unpack_add_liquidity(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::ADD_LIQUIDITY_LEN)?; + + let input = array_ref![input, 1, AmmInstruction::ADD_LIQUIDITY_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let (max_token_a_amount, max_token_b_amount) = array_refs![input, 8, 8]; + + Ok(Self::AddLiquidity { + max_token_a_amount: u64::from_le_bytes(*max_token_a_amount), + max_token_b_amount: u64::from_le_bytes(*max_token_b_amount), + }) + } + + fn unpack_remove_liquidity(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::REMOVE_LIQUIDITY_LEN)?; + Ok(Self::RemoveLiquidity { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_swap(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::SWAP_LEN)?; + + let input = array_ref![input, 1, AmmInstruction::SWAP_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let (token_a_amount_in, token_b_amount_in, min_token_amount_out) = + array_refs![input, 8, 8, 8]; + + Ok(Self::Swap { + token_a_amount_in: u64::from_le_bytes(*token_a_amount_in), + token_b_amount_in: u64::from_le_bytes(*token_b_amount_in), + min_token_amount_out: u64::from_le_bytes(*min_token_amount_out), + }) + } + + fn unpack_stake(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::STAKE_LEN)?; + Ok(Self::Stake { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_unstake(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::UNSTAKE_LEN)?; + Ok(Self::Unstake { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_harvest(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::HARVEST_LEN)?; + Ok(Self::Harvest) + } + + fn unpack_wrap_token(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::WRAP_TOKEN_LEN)?; + Ok(Self::WrapToken { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_unwrap_token(input: &[u8]) -> Result { + check_data_len(input, AmmInstruction::UNWRAP_TOKEN_LEN)?; + Ok(Self::UnwrapToken { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } +} + +impl std::fmt::Display for AmmInstructionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + AmmInstructionType::UserInit => write!(f, "UserInit"), + AmmInstructionType::AddLiquidity => write!(f, "AddLiquidity"), + AmmInstructionType::RemoveLiquidity => write!(f, "RemoveLiquidity"), + AmmInstructionType::Swap => write!(f, "Swap"), + AmmInstructionType::Stake => write!(f, "Stake"), + AmmInstructionType::Unstake => write!(f, "Unstake"), + AmmInstructionType::Harvest => write!(f, "Harvest"), + AmmInstructionType::WrapToken => write!(f, "WrapToken"), + AmmInstructionType::UnwrapToken => write!(f, "UnwrapToken"), + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_vec_serialization() { + let ri1 = super::AmmInstruction::AddLiquidity { + max_token_a_amount: 100, + max_token_b_amount: 200, + }; + + let vec = ri1.to_vec().unwrap(); + + let ri2 = super::AmmInstruction::unpack(&vec[..]).unwrap(); + + assert_eq!(ri1, ri2); + } + + #[test] + fn test_slice_serialization() { + let ri1 = super::AmmInstruction::AddLiquidity { + max_token_a_amount: 100, + max_token_b_amount: 200, + }; + + let mut output: [u8; super::AmmInstruction::ADD_LIQUIDITY_LEN] = + [0; super::AmmInstruction::ADD_LIQUIDITY_LEN]; + ri1.pack(&mut output[..]).unwrap(); + + let ri2 = super::AmmInstruction::unpack(&output).unwrap(); + + assert_eq!(ri1, ri2); + } +} diff --git a/farms/farm-sdk/src/instruction/main_router.rs b/farms/farm-sdk/src/instruction/main_router.rs new file mode 100644 index 00000000000..5a9123f1107 --- /dev/null +++ b/farms/farm-sdk/src/instruction/main_router.rs @@ -0,0 +1,400 @@ +//! Main Router instructions. + +use { + crate::{ + farm::Farm, + instruction::refdb::RefDbInstruction, + pack::{check_data_len, pack_array_string64, unpack_array_string64}, + pool::Pool, + string::ArrayString64, + token::Token, + vault::Vault, + }, + arrayref::{array_mut_ref, array_ref, mut_array_refs}, + num_enum::TryFromPrimitive, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MainInstruction { + /// Record Vault's metadata on-chain + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Vault's RefDB index PDA + /// 2. [WRITE] Vault's RefDB data PDA + /// 3. [] Sytem program + AddVault { vault: Vault }, + + /// Delete Vault's metadata + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Vault's RefDB index PDA + /// 2. [WRITE] Vault's RefDB data PDA + /// 3. [] Sytem program + RemoveVault { name: ArrayString64 }, + + /// Record Pool's metadata on-chain + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Pool's RefDB index PDA + /// 2. [WRITE] Pool's RefDB data PDA + /// 3. [] Sytem program + AddPool { pool: Pool }, + + /// Delete Pool's metadata + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Pool's RefDB index PDA + /// 2. [WRITE] Pool's RefDB data PDA + /// 3. [] Sytem program + RemovePool { name: ArrayString64 }, + + /// Record Farm's metadata on-chain + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Farm's RefDB index PDA + /// 2. [WRITE] Farm's RefDB data PDA + /// 3. [] Sytem program + AddFarm { farm: Farm }, + + /// Delete Farm's metadata + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Farm's RefDB index PDA + /// 2. [WRITE] Farm's RefDB data PDA + /// 3. [] Sytem program + RemoveFarm { name: ArrayString64 }, + + /// Record Token's metadata on-chain + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Token's RefDB index PDA + /// 2. [WRITE] Token's RefDB data PDA + /// 3. [] Sytem program + AddToken { token: Token }, + + /// Delete Token's metadata + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] Token's RefDB index PDA + /// 2. [WRITE] Token's RefDB data PDA + /// 3. [] Sytem program + RemoveToken { name: ArrayString64 }, + + /// Perform generic RefDB instruction + /// + /// # Account references are instruction specific, + /// see RefDbInstruction definition for more info + RefDbInstruction { instruction: RefDbInstruction }, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum MainInstructionType { + AddVault, + RemoveVault, + AddPool, + RemovePool, + AddFarm, + RemoveFarm, + AddToken, + RemoveToken, + RefDbInstruction, +} + +impl MainInstruction { + pub const MAX_LEN: usize = MainInstruction::max(Vault::MAX_LEN + 1, Pool::MAX_LEN + 1); + pub const REMOVE_VAULT_LEN: usize = 65; + pub const REMOVE_POOL_LEN: usize = 65; + pub const REMOVE_FARM_LEN: usize = 65; + pub const REMOVE_TOKEN_LEN: usize = 65; + + const fn max(a: usize, b: usize) -> usize { + [a, b][(a < b) as usize] + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, 1)?; + match self { + Self::AddVault { vault } => self.pack_add_vault(output, vault), + Self::RemoveVault { name } => self.pack_remove_vault(output, name), + Self::AddPool { pool } => self.pack_add_pool(output, pool), + Self::RemovePool { name } => self.pack_remove_pool(output, name), + Self::AddFarm { farm } => self.pack_add_farm(output, farm), + Self::RemoveFarm { name } => self.pack_remove_farm(output, name), + Self::AddToken { token } => self.pack_add_token(output, token), + Self::RemoveToken { name } => self.pack_remove_token(output, name), + Self::RefDbInstruction { instruction } => { + self.pack_refdb_instruction(output, instruction) + } + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; MainInstruction::MAX_LEN] = [0; MainInstruction::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let instruction_type = MainInstructionType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidInstructionData))?; + match instruction_type { + MainInstructionType::AddVault => MainInstruction::unpack_add_vault(input), + MainInstructionType::RemoveVault => MainInstruction::unpack_remove_vault(input), + MainInstructionType::AddPool => MainInstruction::unpack_add_pool(input), + MainInstructionType::RemovePool => MainInstruction::unpack_remove_pool(input), + MainInstructionType::AddFarm => MainInstruction::unpack_add_farm(input), + MainInstructionType::RemoveFarm => MainInstruction::unpack_remove_farm(input), + MainInstructionType::AddToken => MainInstruction::unpack_add_token(input), + MainInstructionType::RemoveToken => MainInstruction::unpack_remove_token(input), + MainInstructionType::RefDbInstruction => { + MainInstruction::unpack_refdb_instruction(input) + } + } + } + + fn pack_add_vault(&self, output: &mut [u8], vault: &Vault) -> Result { + let packed = vault.pack(&mut output[1..])?; + let instruction_type_out = array_mut_ref![output, 0, 1]; + instruction_type_out[0] = MainInstructionType::AddVault as u8; + + Ok(packed + 1) + } + + fn pack_remove_vault( + &self, + output: &mut [u8], + name: &ArrayString64, + ) -> Result { + check_data_len(output, MainInstruction::REMOVE_VAULT_LEN)?; + + let output = array_mut_ref![output, 0, MainInstruction::REMOVE_VAULT_LEN]; + let (instruction_type_out, name_out) = mut_array_refs![output, 1, 64]; + + instruction_type_out[0] = MainInstructionType::RemoveVault as u8; + pack_array_string64(name, name_out); + + Ok(MainInstruction::REMOVE_VAULT_LEN) + } + + fn pack_add_pool(&self, output: &mut [u8], pool: &Pool) -> Result { + let packed = pool.pack(&mut output[1..])?; + let instruction_type_out = array_mut_ref![output, 0, 1]; + instruction_type_out[0] = MainInstructionType::AddPool as u8; + + Ok(packed + 1) + } + + fn pack_remove_pool( + &self, + output: &mut [u8], + name: &ArrayString64, + ) -> Result { + check_data_len(output, MainInstruction::REMOVE_POOL_LEN)?; + + let output = array_mut_ref![output, 0, MainInstruction::REMOVE_POOL_LEN]; + let (instruction_type_out, name_out) = mut_array_refs![output, 1, 64]; + + instruction_type_out[0] = MainInstructionType::RemovePool as u8; + pack_array_string64(name, name_out); + + Ok(MainInstruction::REMOVE_POOL_LEN) + } + + fn pack_add_farm(&self, output: &mut [u8], farm: &Farm) -> Result { + let packed = farm.pack(&mut output[1..])?; + let instruction_type_out = array_mut_ref![output, 0, 1]; + instruction_type_out[0] = MainInstructionType::AddFarm as u8; + + Ok(packed + 1) + } + + fn pack_remove_farm( + &self, + output: &mut [u8], + name: &ArrayString64, + ) -> Result { + check_data_len(output, MainInstruction::REMOVE_FARM_LEN)?; + + let output = array_mut_ref![output, 0, MainInstruction::REMOVE_FARM_LEN]; + let (instruction_type_out, name_out) = mut_array_refs![output, 1, 64]; + + instruction_type_out[0] = MainInstructionType::RemoveFarm as u8; + pack_array_string64(name, name_out); + + Ok(MainInstruction::REMOVE_FARM_LEN) + } + + fn pack_add_token(&self, output: &mut [u8], token: &Token) -> Result { + let packed = token.pack(&mut output[1..])?; + let instruction_type_out = array_mut_ref![output, 0, 1]; + instruction_type_out[0] = MainInstructionType::AddToken as u8; + + Ok(packed + 1) + } + + fn pack_remove_token( + &self, + output: &mut [u8], + name: &ArrayString64, + ) -> Result { + check_data_len(output, MainInstruction::REMOVE_TOKEN_LEN)?; + + let output = array_mut_ref![output, 0, MainInstruction::REMOVE_TOKEN_LEN]; + let (instruction_type_out, name_out) = mut_array_refs![output, 1, 64]; + + instruction_type_out[0] = MainInstructionType::RemoveToken as u8; + pack_array_string64(name, name_out); + + Ok(MainInstruction::REMOVE_TOKEN_LEN) + } + + fn pack_refdb_instruction( + &self, + output: &mut [u8], + instruction: &RefDbInstruction, + ) -> Result { + let packed = instruction.pack(&mut output[1..])?; + let instruction_type_out = array_mut_ref![output, 0, 1]; + instruction_type_out[0] = MainInstructionType::RefDbInstruction as u8; + + Ok(packed + 1) + } + + fn unpack_add_vault(input: &[u8]) -> Result { + let vault = Vault::unpack(&input[1..])?; + Ok(Self::AddVault { vault }) + } + + fn unpack_remove_vault(input: &[u8]) -> Result { + check_data_len(input, MainInstruction::REMOVE_VAULT_LEN)?; + let input = array_ref![input, 1, MainInstruction::REMOVE_VAULT_LEN - 1]; + Ok(Self::RemoveVault { + name: unpack_array_string64(input)?, + }) + } + + fn unpack_add_pool(input: &[u8]) -> Result { + let pool = Pool::unpack(&input[1..])?; + Ok(Self::AddPool { pool }) + } + + fn unpack_remove_pool(input: &[u8]) -> Result { + check_data_len(input, MainInstruction::REMOVE_POOL_LEN)?; + let input = array_ref![input, 1, MainInstruction::REMOVE_POOL_LEN - 1]; + Ok(Self::RemovePool { + name: unpack_array_string64(input)?, + }) + } + + fn unpack_add_farm(input: &[u8]) -> Result { + let farm = Farm::unpack(&input[1..])?; + Ok(Self::AddFarm { farm }) + } + + fn unpack_remove_farm(input: &[u8]) -> Result { + check_data_len(input, MainInstruction::REMOVE_FARM_LEN)?; + let input = array_ref![input, 1, MainInstruction::REMOVE_FARM_LEN - 1]; + Ok(Self::RemoveFarm { + name: unpack_array_string64(input)?, + }) + } + + fn unpack_add_token(input: &[u8]) -> Result { + let token = Token::unpack(&input[1..])?; + Ok(Self::AddToken { token }) + } + + fn unpack_remove_token(input: &[u8]) -> Result { + check_data_len(input, MainInstruction::REMOVE_TOKEN_LEN)?; + let input = array_ref![input, 1, MainInstruction::REMOVE_TOKEN_LEN - 1]; + Ok(Self::RemoveToken { + name: unpack_array_string64(input)?, + }) + } + + fn unpack_refdb_instruction(input: &[u8]) -> Result { + let instruction = RefDbInstruction::unpack(&input[1..])?; + Ok(Self::RefDbInstruction { instruction }) + } +} + +impl std::fmt::Display for MainInstructionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + MainInstructionType::AddVault => write!(f, "AddVault"), + MainInstructionType::RemoveVault => write!(f, "RemoveVault"), + MainInstructionType::AddPool => write!(f, "AddPool"), + MainInstructionType::RemovePool => write!(f, "RemovePool"), + MainInstructionType::AddFarm => write!(f, "AddFarm"), + MainInstructionType::RemoveFarm => write!(f, "RemoveFarm"), + MainInstructionType::AddToken => write!(f, "AddToken"), + MainInstructionType::RemoveToken => write!(f, "RemoveToken"), + MainInstructionType::RefDbInstruction => write!(f, "RefDbInstruction"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pool::{PoolRoute, PoolType}; + use crate::string::ArrayString64; + use solana_program::pubkey::Pubkey; + + #[test] + fn test_vec_serialization() { + let ri1 = MainInstruction::AddPool { + pool: Pool { + name: ArrayString64::from_utf8("test").unwrap(), + version: 2, + pool_type: PoolType::Amm, + official: true, + refdb_index: Some(1), + refdb_counter: 2, + token_a_ref: Some(Pubkey::new_unique()), + token_b_ref: Some(Pubkey::new_unique()), + lp_token_ref: Some(Pubkey::new_unique()), + token_a_account: None, + token_b_account: None, + router_program_id: Pubkey::new_unique(), + pool_program_id: Pubkey::new_unique(), + route: PoolRoute::Raydium { + amm_id: Pubkey::new_unique(), + amm_authority: Pubkey::new_unique(), + amm_open_orders: Pubkey::new_unique(), + amm_target: Pubkey::new_unique(), + pool_withdraw_queue: Pubkey::new_unique(), + pool_temp_lp_token_account: Pubkey::new_unique(), + serum_program_id: Pubkey::new_unique(), + serum_market: Pubkey::new_unique(), + serum_coin_vault_account: Pubkey::new_unique(), + serum_pc_vault_account: Pubkey::new_unique(), + serum_vault_signer: Pubkey::new_unique(), + serum_bids: Some(Pubkey::new_unique()), + serum_asks: Some(Pubkey::new_unique()), + serum_event_queue: Some(Pubkey::new_unique()), + }, + }, + }; + + let vec = ri1.to_vec().unwrap(); + + let ri2 = MainInstruction::unpack(&vec[..]).unwrap(); + + assert_eq!(ri1, ri2); + } +} diff --git a/farms/farm-sdk/src/instruction/mod.rs b/farms/farm-sdk/src/instruction/mod.rs new file mode 100644 index 00000000000..7a66589a14a --- /dev/null +++ b/farms/farm-sdk/src/instruction/mod.rs @@ -0,0 +1,6 @@ +pub mod amm; +pub mod main_router; +pub mod orca; +pub mod raydium; +pub mod refdb; +pub mod vault; diff --git a/farms/farm-sdk/src/instruction/orca.rs b/farms/farm-sdk/src/instruction/orca.rs new file mode 100644 index 00000000000..c7a0cfc821c --- /dev/null +++ b/farms/farm-sdk/src/instruction/orca.rs @@ -0,0 +1,149 @@ +//! Orca protocol farming instructions +//! See https://github.com/orca-so/aquafarm-sdk/blob/9ed9db0f04cf7406f1f6e9a3e316639f3d24e68c/src/instructions.ts +//! for more details and accounts references + +use { + crate::pack::check_data_len, + arrayref::{array_mut_ref, mut_array_refs}, + solana_program::program_error::ProgramError, +}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum OrcaInstructionType { + InitGlobalFarm, + InitUserFarm, + ConvertTokens, + RevertTokens, + Harvest, + RemoveRewards, + SetEmissionsPerSecond, +} + +#[derive(Clone, Copy, Debug)] +pub struct OrcaUserInit {} + +#[derive(Clone, Copy, Debug)] +pub struct OrcaStake { + pub amount: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct OrcaUnstake { + pub amount: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct OrcaHarvest {} + +impl OrcaUserInit { + pub const LEN: usize = 1; + + pub fn get_size(&self) -> usize { + Self::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, OrcaUserInit::LEN)?; + + let output = array_mut_ref![output, 0, OrcaUserInit::LEN]; + output[0] = OrcaInstructionType::InitUserFarm as u8; + + Ok(Self::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; OrcaUserInit::LEN] = [0; OrcaUserInit::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl OrcaStake { + pub const LEN: usize = 9; + + pub fn get_size(&self) -> usize { + Self::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, OrcaStake::LEN)?; + + let output = array_mut_ref![output, 0, OrcaStake::LEN]; + + let (instruction_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_out[0] = OrcaInstructionType::ConvertTokens as u8; + *amount_out = self.amount.to_le_bytes(); + + Ok(Self::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; OrcaStake::LEN] = [0; OrcaStake::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl OrcaUnstake { + pub const LEN: usize = 9; + + pub fn get_size(&self) -> usize { + Self::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, OrcaUnstake::LEN)?; + + let output = array_mut_ref![output, 0, OrcaUnstake::LEN]; + + let (instruction_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_out[0] = OrcaInstructionType::RevertTokens as u8; + *amount_out = self.amount.to_le_bytes(); + + Ok(Self::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; OrcaUnstake::LEN] = [0; OrcaUnstake::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl OrcaHarvest { + pub const LEN: usize = 1; + + pub fn get_size(&self) -> usize { + Self::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, OrcaHarvest::LEN)?; + + let output = array_mut_ref![output, 0, OrcaHarvest::LEN]; + output[0] = OrcaInstructionType::Harvest as u8; + + Ok(Self::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; OrcaHarvest::LEN] = [0; OrcaHarvest::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} diff --git a/farms/farm-sdk/src/instruction/raydium.rs b/farms/farm-sdk/src/instruction/raydium.rs new file mode 100644 index 00000000000..d90e113243b --- /dev/null +++ b/farms/farm-sdk/src/instruction/raydium.rs @@ -0,0 +1,227 @@ +//! Raydium protocol native instructions +//! See https://github.com/raydium-io/raydium-contract-instructions/blob/master/amm_instruction.rs +//! for more details and accounts references + +use { + crate::pack::check_data_len, + arrayref::{array_mut_ref, mut_array_refs}, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumAddLiquidity { + pub instruction: u8, + pub max_coin_token_amount: u64, + pub max_pc_token_amount: u64, + pub base_side: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumRemoveLiquidity { + pub instruction: u8, + pub amount: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumSwap { + pub instruction: u8, + pub amount_in: u64, + pub min_amount_out: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumStake { + pub instruction: u8, + pub amount: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumUnstake { + pub instruction: u8, + pub amount: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct RaydiumHarvest { + pub instruction: u8, +} + +impl RaydiumAddLiquidity { + pub const LEN: usize = 25; + + pub fn get_size(&self) -> usize { + RaydiumAddLiquidity::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumAddLiquidity::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumAddLiquidity::LEN]; + + let (instruction_out, max_coin_token_amount_out, max_pc_token_amount_out, base_side_out) = + mut_array_refs![output, 1, 8, 8, 8]; + + instruction_out[0] = self.instruction as u8; + *max_coin_token_amount_out = self.max_coin_token_amount.to_le_bytes(); + *max_pc_token_amount_out = self.max_pc_token_amount.to_le_bytes(); + *base_side_out = self.base_side.to_le_bytes(); + + Ok(RaydiumAddLiquidity::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumAddLiquidity::LEN] = [0; RaydiumAddLiquidity::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl RaydiumRemoveLiquidity { + pub const LEN: usize = 9; + + pub fn get_size(&self) -> usize { + RaydiumRemoveLiquidity::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumRemoveLiquidity::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumRemoveLiquidity::LEN]; + + let (instruction_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_out[0] = self.instruction as u8; + *amount_out = self.amount.to_le_bytes(); + + Ok(RaydiumRemoveLiquidity::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumRemoveLiquidity::LEN] = [0; RaydiumRemoveLiquidity::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl RaydiumSwap { + pub const LEN: usize = 17; + + pub fn get_size(&self) -> usize { + RaydiumSwap::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumSwap::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumSwap::LEN]; + + let (instruction_out, amount_in_out, min_amount_out_out) = mut_array_refs![output, 1, 8, 8]; + + instruction_out[0] = self.instruction as u8; + *amount_in_out = self.amount_in.to_le_bytes(); + *min_amount_out_out = self.min_amount_out.to_le_bytes(); + + Ok(RaydiumSwap::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumSwap::LEN] = [0; RaydiumSwap::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl RaydiumStake { + pub const LEN: usize = 9; + + pub fn get_size(&self) -> usize { + RaydiumStake::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumStake::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumStake::LEN]; + + let (instruction_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_out[0] = self.instruction as u8; + *amount_out = self.amount.to_le_bytes(); + + Ok(RaydiumStake::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumStake::LEN] = [0; RaydiumStake::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl RaydiumUnstake { + pub const LEN: usize = 9; + + pub fn get_size(&self) -> usize { + RaydiumUnstake::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumUnstake::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumUnstake::LEN]; + + let (instruction_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_out[0] = self.instruction as u8; + *amount_out = self.amount.to_le_bytes(); + + Ok(RaydiumUnstake::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumUnstake::LEN] = [0; RaydiumUnstake::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + +impl RaydiumHarvest { + pub const LEN: usize = 1; + + pub fn get_size(&self) -> usize { + RaydiumHarvest::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, RaydiumHarvest::LEN)?; + + let output = array_mut_ref![output, 0, RaydiumHarvest::LEN]; + output[0] = self.instruction as u8; + + Ok(RaydiumHarvest::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RaydiumHarvest::LEN] = [0; RaydiumHarvest::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} diff --git a/farms/farm-sdk/src/instruction/refdb.rs b/farms/farm-sdk/src/instruction/refdb.rs new file mode 100644 index 00000000000..c821300cb31 --- /dev/null +++ b/farms/farm-sdk/src/instruction/refdb.rs @@ -0,0 +1,215 @@ +//! RefDB management instructions. + +use { + crate::{ + pack::{ + as64_deserialize, as64_serialize, check_data_len, pack_array_string64, pack_option_u32, + unpack_array_string64, unpack_bool, unpack_option_u32, + }, + refdb::{Record, Reference, ReferenceType}, + string::ArrayString64, + }, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + solana_program::program_error::ProgramError, +}; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub enum RefDbInstruction { + /// Initialize on-chain RefDB storage + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] RefDB storage PDA + /// 3. [] Sytem program + Init { + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + name: ArrayString64, + reference_type: ReferenceType, + max_records: u32, + init_account: bool, + }, + + /// Delete on-chain RefDB storage + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] RefDB storage PDA + /// 3. [] Sytem program + Drop { close_account: bool }, + + /// Write the record into on-chain RefDB storage + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] RefDB storage PDA + /// 3. [] Sytem program + Write { record: Record }, + + /// Delete the record from on-chain RefDB storage + /// + /// # Account references + /// 0. [SIGNER] Funding account, must be main router admin + /// 1. [WRITE] RefDB storage PDA + /// 3. [] Sytem program + Delete { record: Record }, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum RefDbInstructionType { + Init, + Drop, + Write, + Delete, +} + +impl RefDbInstruction { + pub const MAX_LEN: usize = Record::MAX_LEN + 7; + pub const INIT_LEN: usize = 71; + pub const DROP_LEN: usize = 3; + pub const WRITE_MAX_LEN: usize = Record::MAX_LEN + 7; + pub const DELETE_MAX_LEN: usize = Record::MAX_LEN + 7; + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self { + Self::Init { + name, + reference_type, + max_records, + init_account, + } => { + check_data_len(output, RefDbInstruction::INIT_LEN)?; + + output[0] = RefDbInstructionType::Init as u8; + output[1] = *reference_type as u8; + + let output = array_mut_ref![output, 2, RefDbInstruction::INIT_LEN - 2]; + + let (name_out, max_records_out, init_account_out) = + mut_array_refs![output, 64, 4, 1]; + pack_array_string64(name, name_out); + *max_records_out = max_records.to_le_bytes(); + init_account_out[0] = *init_account as u8; + + Ok(RefDbInstruction::INIT_LEN) + } + Self::Drop { close_account } => { + check_data_len(output, RefDbInstruction::DROP_LEN)?; + output[0] = RefDbInstructionType::Drop as u8; + output[1] = ReferenceType::Empty as u8; + output[2] = *close_account as u8; + Ok(RefDbInstruction::DROP_LEN) + } + Self::Write { record } => { + check_data_len(output, 7)?; + + let header = array_mut_ref![output, 0, 7]; + let (instruction_out, reference_type_out, index_out) = + mut_array_refs![header, 1, 1, 5]; + + instruction_out[0] = RefDbInstructionType::Write as u8; + reference_type_out[0] = match record.reference { + Reference::Pubkey { .. } => ReferenceType::Pubkey as u8, + Reference::U8 { .. } => ReferenceType::U8 as u8, + Reference::U16 { .. } => ReferenceType::U16 as u8, + Reference::U32 { .. } => ReferenceType::U32 as u8, + Reference::U64 { .. } => ReferenceType::U64 as u8, + Reference::F64 { .. } => ReferenceType::F64 as u8, + Reference::Empty => ReferenceType::Empty as u8, + }; + pack_option_u32(record.index, index_out); + record.pack(&mut output[7..])?; + + Ok(7 + record.get_size()) + } + Self::Delete { record } => { + check_data_len(output, 7)?; + + let header = array_mut_ref![output, 0, 7]; + let (instruction_out, reference_type_out, index_out) = + mut_array_refs![header, 1, 1, 5]; + + instruction_out[0] = RefDbInstructionType::Delete as u8; + reference_type_out[0] = match record.reference { + Reference::Pubkey { .. } => ReferenceType::Pubkey as u8, + Reference::U8 { .. } => ReferenceType::U8 as u8, + Reference::U16 { .. } => ReferenceType::U16 as u8, + Reference::U32 { .. } => ReferenceType::U32 as u8, + Reference::U64 { .. } => ReferenceType::U64 as u8, + Reference::F64 { .. } => ReferenceType::F64 as u8, + Reference::Empty => ReferenceType::Empty as u8, + }; + pack_option_u32(record.index, index_out); + record.pack(&mut output[7..])?; + + Ok(7 + record.get_size()) + } + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; RefDbInstruction::MAX_LEN] = [0; RefDbInstruction::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 3)?; + let instruction_type = RefDbInstructionType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidInstructionData))?; + let reference_type = ReferenceType::try_from_primitive(input[1]) + .or(Err(ProgramError::InvalidInstructionData))?; + match instruction_type { + RefDbInstructionType::Init => { + check_data_len(input, RefDbInstruction::INIT_LEN)?; + + let input = array_ref![input, 2, RefDbInstruction::INIT_LEN - 2]; + #[allow(clippy::ptr_offset_with_cast)] + let (name, max_records, init_account) = array_refs![input, 64, 4, 1]; + + Ok(RefDbInstruction::Init { + name: unpack_array_string64(name)?, + reference_type, + max_records: u32::from_le_bytes(*max_records), + init_account: unpack_bool(init_account)?, + }) + } + RefDbInstructionType::Drop => Ok(RefDbInstruction::Drop { + close_account: unpack_bool(&[input[2]])?, + }), + RefDbInstructionType::Write => { + check_data_len(input, 7)?; + let index = array_ref![input, 2, 5]; + Ok(RefDbInstruction::Write { + record: Record::unpack(&input[7..], reference_type, unpack_option_u32(index)?)?, + }) + } + RefDbInstructionType::Delete => { + check_data_len(input, 7)?; + let index = array_ref![input, 2, 5]; + Ok(RefDbInstruction::Delete { + record: Record::unpack(&input[7..], reference_type, unpack_option_u32(index)?)?, + }) + } + } + } +} + +impl std::fmt::Display for RefDbInstructionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + RefDbInstructionType::Init => write!(f, "Init"), + RefDbInstructionType::Drop => write!(f, "Drop"), + RefDbInstructionType::Write => write!(f, "Write"), + RefDbInstructionType::Delete => write!(f, "Delete"), + } + } +} diff --git a/farms/farm-sdk/src/instruction/vault.rs b/farms/farm-sdk/src/instruction/vault.rs new file mode 100644 index 00000000000..0d07456debb --- /dev/null +++ b/farms/farm-sdk/src/instruction/vault.rs @@ -0,0 +1,556 @@ +//! Vault management instructions. + +use { + crate::pack::check_data_len, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum VaultInstruction { + /// Initialize on-chain records for a new user + /// # Account references are strategy specific, + /// see particular Vault instructions handlers for more info + UserInit, + + /// Add liquidity to the Vault + /// # Account references are strategy specific, + /// see particular Vault instructions handlers for more info + AddLiquidity { + max_token_a_amount: u64, + max_token_b_amount: u64, + }, + + /// Lock liquidity in the Vault + /// # Account references are strategy specific, + /// see particular Vault instructions handlers for more info + LockLiquidity { amount: u64 }, + + /// Unlock liquidity in the Vault + /// # Account references are strategy specific, + /// see particular Vault instructions handlers for more info + UnlockLiquidity { amount: u64 }, + + /// Remove liquidity from the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + RemoveLiquidity { amount: u64 }, + + /// Set minimum crank interval for the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + SetMinCrankInterval { min_crank_interval: u32 }, + + /// Set fee for the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + SetFee { fee: f32 }, + + /// Set underlying protocol fee for the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + SetExternalFee { external_fee: f32 }, + + /// Disable new deposits to the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + DisableDeposit, + + /// Allow new deposits to the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + EnableDeposit, + + /// Disable withdrawals from the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + DisableWithdrawal, + + /// Allow withdrawals from the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + EnableWithdrawal, + + /// Run crank operation on the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Crank { step: u64 }, + + /// Initialize the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Init { step: u64 }, + + /// Shutdown the Vault + /// # Account references are protocol specific, + /// see particular Router instructions handlers for more info + Shutdown, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum VaultInstructionType { + UserInit, + AddLiquidity, + LockLiquidity, + UnlockLiquidity, + RemoveLiquidity, + SetMinCrankInterval, + SetFee, + SetExternalFee, + DisableDeposit, + EnableDeposit, + DisableWithdrawal, + EnableWithdrawal, + Crank, + Init, + Shutdown, +} + +impl VaultInstruction { + pub const MAX_LEN: usize = 17; + pub const USER_INIT_LEN: usize = 1; + pub const ADD_LIQUIDITY_LEN: usize = 17; + pub const LOCK_LIQUIDITY_LEN: usize = 9; + pub const UNLOCK_LIQUIDITY_LEN: usize = 9; + pub const REMOVE_LIQUIDITY_LEN: usize = 9; + pub const SET_MIN_CRANK_INTERVAL_LEN: usize = 5; + pub const SET_FEE_LEN: usize = 5; + pub const SET_EXTERNAL_FEE_LEN: usize = 5; + pub const DISABLE_DEPOSIT_LEN: usize = 1; + pub const ENABLE_DEPOSIT_LEN: usize = 1; + pub const DISABLE_WITHDRAWAL_LEN: usize = 1; + pub const ENABLE_WITHDRAWAL_LEN: usize = 1; + pub const CRANK_LEN: usize = 9; + pub const INIT_LEN: usize = 9; + pub const SHUTDOWN_LEN: usize = 1; + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self { + Self::UserInit { .. } => self.pack_user_init(output), + Self::AddLiquidity { .. } => self.pack_add_liquidity(output), + Self::RemoveLiquidity { .. } => self.pack_remove_liquidity(output), + Self::LockLiquidity { .. } => self.pack_lock_liquidity(output), + Self::UnlockLiquidity { .. } => self.pack_unlock_liquidity(output), + Self::SetMinCrankInterval { .. } => self.pack_set_min_crank_interval(output), + Self::SetFee { .. } => self.pack_set_fee(output), + Self::SetExternalFee { .. } => self.pack_set_external_fee(output), + Self::DisableDeposit { .. } => self.pack_disable_deposit(output), + Self::EnableDeposit { .. } => self.pack_enable_deposit(output), + Self::DisableWithdrawal { .. } => self.pack_disable_withdrawal(output), + Self::EnableWithdrawal { .. } => self.pack_enable_withdrawal(output), + Self::Crank { .. } => self.pack_crank(output), + Self::Init { .. } => self.pack_init(output), + Self::Shutdown { .. } => self.pack_shutdown(output), + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; VaultInstruction::MAX_LEN] = [0; VaultInstruction::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let instruction_type = VaultInstructionType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidInstructionData))?; + match instruction_type { + VaultInstructionType::UserInit => VaultInstruction::unpack_user_init(input), + VaultInstructionType::AddLiquidity => VaultInstruction::unpack_add_liquidity(input), + VaultInstructionType::LockLiquidity => VaultInstruction::unpack_lock_liquidity(input), + VaultInstructionType::UnlockLiquidity => { + VaultInstruction::unpack_unlock_liquidity(input) + } + VaultInstructionType::RemoveLiquidity => { + VaultInstruction::unpack_remove_liquidity(input) + } + VaultInstructionType::SetMinCrankInterval => { + VaultInstruction::unpack_set_min_crank_interval(input) + } + VaultInstructionType::SetFee => VaultInstruction::unpack_set_fee(input), + VaultInstructionType::SetExternalFee => { + VaultInstruction::unpack_set_external_fee(input) + } + VaultInstructionType::DisableDeposit => VaultInstruction::unpack_disable_deposit(input), + VaultInstructionType::EnableDeposit => VaultInstruction::unpack_enable_deposit(input), + VaultInstructionType::DisableWithdrawal => { + VaultInstruction::unpack_disable_withdrawal(input) + } + VaultInstructionType::EnableWithdrawal => { + VaultInstruction::unpack_enable_withdrawal(input) + } + VaultInstructionType::Crank => VaultInstruction::unpack_crank(input), + VaultInstructionType::Init => VaultInstruction::unpack_init(input), + VaultInstructionType::Shutdown => VaultInstruction::unpack_shutdown(input), + } + } + + fn pack_user_init(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::USER_INIT_LEN)?; + + if let VaultInstruction::UserInit = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::UserInit as u8; + + Ok(VaultInstruction::USER_INIT_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_add_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::ADD_LIQUIDITY_LEN)?; + + if let VaultInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } = self + { + let output = array_mut_ref![output, 0, VaultInstruction::ADD_LIQUIDITY_LEN]; + let (instruction_type_out, max_token_a_amount_out, max_token_b_amount_out) = + mut_array_refs![output, 1, 8, 8]; + + instruction_type_out[0] = VaultInstructionType::AddLiquidity as u8; + + *max_token_a_amount_out = max_token_a_amount.to_le_bytes(); + *max_token_b_amount_out = max_token_b_amount.to_le_bytes(); + + Ok(VaultInstruction::ADD_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_lock_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::LOCK_LIQUIDITY_LEN)?; + + if let VaultInstruction::LockLiquidity { amount } = self { + let output = array_mut_ref![output, 0, VaultInstruction::LOCK_LIQUIDITY_LEN]; + let (instruction_type_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_type_out[0] = VaultInstructionType::LockLiquidity as u8; + + *amount_out = amount.to_le_bytes(); + + Ok(VaultInstruction::LOCK_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_unlock_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::UNLOCK_LIQUIDITY_LEN)?; + + if let VaultInstruction::UnlockLiquidity { amount } = self { + let output = array_mut_ref![output, 0, VaultInstruction::UNLOCK_LIQUIDITY_LEN]; + let (instruction_type_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_type_out[0] = VaultInstructionType::UnlockLiquidity as u8; + + *amount_out = amount.to_le_bytes(); + + Ok(VaultInstruction::UNLOCK_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_remove_liquidity(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::REMOVE_LIQUIDITY_LEN)?; + + if let VaultInstruction::RemoveLiquidity { amount } = self { + let output = array_mut_ref![output, 0, VaultInstruction::REMOVE_LIQUIDITY_LEN]; + let (instruction_type_out, amount_out) = mut_array_refs![output, 1, 8]; + + instruction_type_out[0] = VaultInstructionType::RemoveLiquidity as u8; + + *amount_out = amount.to_le_bytes(); + + Ok(VaultInstruction::REMOVE_LIQUIDITY_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_set_min_crank_interval(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::SET_MIN_CRANK_INTERVAL_LEN)?; + + if let VaultInstruction::SetMinCrankInterval { min_crank_interval } = self { + let output = array_mut_ref![output, 0, VaultInstruction::SET_MIN_CRANK_INTERVAL_LEN]; + let (instruction_type_out, min_crank_interval_out) = mut_array_refs![output, 1, 4]; + + instruction_type_out[0] = VaultInstructionType::SetMinCrankInterval as u8; + + *min_crank_interval_out = min_crank_interval.to_le_bytes(); + + Ok(VaultInstruction::SET_MIN_CRANK_INTERVAL_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_set_fee(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::SET_FEE_LEN)?; + + if let VaultInstruction::SetFee { fee } = self { + let output = array_mut_ref![output, 0, VaultInstruction::SET_FEE_LEN]; + let (instruction_type_out, fee_out) = mut_array_refs![output, 1, 4]; + + instruction_type_out[0] = VaultInstructionType::SetFee as u8; + + *fee_out = fee.to_le_bytes(); + + Ok(VaultInstruction::SET_FEE_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_set_external_fee(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::SET_EXTERNAL_FEE_LEN)?; + + if let VaultInstruction::SetExternalFee { external_fee } = self { + let output = array_mut_ref![output, 0, VaultInstruction::SET_EXTERNAL_FEE_LEN]; + let (instruction_type_out, external_fee_out) = mut_array_refs![output, 1, 4]; + + instruction_type_out[0] = VaultInstructionType::SetExternalFee as u8; + + *external_fee_out = external_fee.to_le_bytes(); + + Ok(VaultInstruction::SET_EXTERNAL_FEE_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_disable_deposit(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::DISABLE_DEPOSIT_LEN)?; + + if let VaultInstruction::DisableDeposit = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::DisableDeposit as u8; + + Ok(VaultInstruction::DISABLE_DEPOSIT_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_enable_deposit(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::ENABLE_DEPOSIT_LEN)?; + + if let VaultInstruction::EnableDeposit = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::EnableDeposit as u8; + + Ok(VaultInstruction::ENABLE_DEPOSIT_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_disable_withdrawal(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::DISABLE_WITHDRAWAL_LEN)?; + + if let VaultInstruction::DisableWithdrawal = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::DisableWithdrawal as u8; + + Ok(VaultInstruction::DISABLE_WITHDRAWAL_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_enable_withdrawal(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::ENABLE_WITHDRAWAL_LEN)?; + + if let VaultInstruction::EnableWithdrawal = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::EnableWithdrawal as u8; + + Ok(VaultInstruction::ENABLE_WITHDRAWAL_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_crank(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::CRANK_LEN)?; + + if let VaultInstruction::Crank { step } = self { + let output = array_mut_ref![output, 0, VaultInstruction::CRANK_LEN]; + let (instruction_type_out, step_out) = mut_array_refs![output, 1, 8]; + + instruction_type_out[0] = VaultInstructionType::Crank as u8; + + *step_out = step.to_le_bytes(); + + Ok(VaultInstruction::CRANK_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_init(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::INIT_LEN)?; + + if let VaultInstruction::Init { step } = self { + let output = array_mut_ref![output, 0, VaultInstruction::INIT_LEN]; + let (instruction_type_out, step_out) = mut_array_refs![output, 1, 8]; + + instruction_type_out[0] = VaultInstructionType::Init as u8; + + *step_out = step.to_le_bytes(); + + Ok(VaultInstruction::INIT_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn pack_shutdown(&self, output: &mut [u8]) -> Result { + check_data_len(output, VaultInstruction::SHUTDOWN_LEN)?; + + if let VaultInstruction::Shutdown = self { + let instruction_type_out = array_mut_ref![output, 0, 1]; + + instruction_type_out[0] = VaultInstructionType::Shutdown as u8; + + Ok(VaultInstruction::SHUTDOWN_LEN) + } else { + Err(ProgramError::InvalidInstructionData) + } + } + + fn unpack_user_init(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::USER_INIT_LEN)?; + Ok(Self::UserInit) + } + + fn unpack_add_liquidity(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::ADD_LIQUIDITY_LEN)?; + + let input = array_ref![input, 1, VaultInstruction::ADD_LIQUIDITY_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let (max_token_a_amount, max_token_b_amount) = array_refs![input, 8, 8]; + + Ok(Self::AddLiquidity { + max_token_a_amount: u64::from_le_bytes(*max_token_a_amount), + max_token_b_amount: u64::from_le_bytes(*max_token_b_amount), + }) + } + + fn unpack_lock_liquidity(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::LOCK_LIQUIDITY_LEN)?; + Ok(Self::LockLiquidity { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_unlock_liquidity(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::UNLOCK_LIQUIDITY_LEN)?; + Ok(Self::UnlockLiquidity { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_remove_liquidity(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::REMOVE_LIQUIDITY_LEN)?; + Ok(Self::RemoveLiquidity { + amount: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_set_min_crank_interval(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::SET_MIN_CRANK_INTERVAL_LEN)?; + Ok(Self::SetMinCrankInterval { + min_crank_interval: u32::from_le_bytes(*array_ref![input, 1, 4]), + }) + } + + fn unpack_set_fee(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::SET_FEE_LEN)?; + Ok(Self::SetFee { + fee: f32::from_le_bytes(*array_ref![input, 1, 4]), + }) + } + + fn unpack_set_external_fee(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::SET_EXTERNAL_FEE_LEN)?; + Ok(Self::SetExternalFee { + external_fee: f32::from_le_bytes(*array_ref![input, 1, 4]), + }) + } + + fn unpack_disable_deposit(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::DISABLE_DEPOSIT_LEN)?; + Ok(Self::DisableDeposit) + } + + fn unpack_enable_deposit(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::ENABLE_DEPOSIT_LEN)?; + Ok(Self::EnableDeposit) + } + + fn unpack_disable_withdrawal(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::DISABLE_WITHDRAWAL_LEN)?; + Ok(Self::DisableWithdrawal) + } + + fn unpack_enable_withdrawal(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::ENABLE_WITHDRAWAL_LEN)?; + Ok(Self::EnableWithdrawal) + } + + fn unpack_crank(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::CRANK_LEN)?; + Ok(Self::Crank { + step: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_init(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::INIT_LEN)?; + Ok(Self::Init { + step: u64::from_le_bytes(*array_ref![input, 1, 8]), + }) + } + + fn unpack_shutdown(input: &[u8]) -> Result { + check_data_len(input, VaultInstruction::SHUTDOWN_LEN)?; + Ok(Self::Shutdown) + } +} + +impl std::fmt::Display for VaultInstructionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + VaultInstructionType::UserInit => write!(f, "UserInit"), + VaultInstructionType::AddLiquidity => write!(f, "AddLiquidity"), + VaultInstructionType::LockLiquidity => write!(f, "LockLiquidity"), + VaultInstructionType::UnlockLiquidity => write!(f, "UnlockLiquidity"), + VaultInstructionType::RemoveLiquidity => write!(f, "RemoveLiquidity"), + VaultInstructionType::SetMinCrankInterval => write!(f, "SetMinCrankInterval"), + VaultInstructionType::SetFee => write!(f, "SetFee"), + VaultInstructionType::SetExternalFee => write!(f, "SetExternalFee"), + VaultInstructionType::DisableDeposit => write!(f, "DisableDeposit"), + VaultInstructionType::EnableDeposit => write!(f, "EnableDeposit"), + VaultInstructionType::DisableWithdrawal => write!(f, "DisableWithdrawal"), + VaultInstructionType::EnableWithdrawal => write!(f, "EnableWithdrawal"), + VaultInstructionType::Crank => write!(f, "Crank"), + VaultInstructionType::Init => write!(f, "Init"), + VaultInstructionType::Shutdown => write!(f, "Shutdown"), + } + } +} diff --git a/farms/farm-sdk/src/lib.rs b/farms/farm-sdk/src/lib.rs new file mode 100644 index 00000000000..424152656fa --- /dev/null +++ b/farms/farm-sdk/src/lib.rs @@ -0,0 +1,16 @@ +#![forbid(unsafe_code)] + +pub mod farm; +pub mod git_token; +pub mod id; +pub mod instruction; +pub mod log; +pub mod math; +pub mod pack; +pub mod pool; +pub mod program; +pub mod refdb; +pub mod string; +pub mod token; +pub mod traits; +pub mod vault; diff --git a/farms/farm-sdk/src/log.rs b/farms/farm-sdk/src/log.rs new file mode 100644 index 00000000000..664de97a4d2 --- /dev/null +++ b/farms/farm-sdk/src/log.rs @@ -0,0 +1,38 @@ +pub use solana_program::log::*; +use solana_program::{account_info::AccountInfo, msg}; + +#[macro_export] +macro_rules! debug_msg { + ($msg:expr) => { + if cfg!(feature = "debug") { + $crate::log::sol_log($msg); + $crate::log::sol_log_compute_units(); + } + }; + ($arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr) => { + if cfg!(feature = "debug") { + $crate::log::sol_log_64( + $arg1 as u64, + $arg2 as u64, + $arg3 as u64, + $arg4 as u64, + $arg5 as u64, + ); + $crate::log::sol_log_compute_units(); + } + }; + ($($arg:tt)*) => (if cfg!(feature = "debug") { + $crate::log::sol_log(&format!($($arg)*)); + $crate::log::sol_log_compute_units(); + }); +} + +#[allow(dead_code)] +pub fn sol_log_params_short(accounts: &[AccountInfo], data: &[u8]) { + msg!("Accounts:"); + for account in accounts.iter() { + account.key.log(); + } + msg!("Instruction data length:"); + msg!(0, 0, 0, 0, data.len()); +} diff --git a/farms/farm-sdk/src/math.rs b/farms/farm-sdk/src/math.rs new file mode 100644 index 00000000000..7663567e393 --- /dev/null +++ b/farms/farm-sdk/src/math.rs @@ -0,0 +1,67 @@ +//! Common math routines. + +use { + solana_program::{msg, program_error::ProgramError}, + std::fmt::Display, +}; + +pub fn checked_add(arg1: T, arg2: T) -> Result +where + T: num_traits::PrimInt + Display, +{ + if let Some(res) = arg1.checked_add(&arg2) { + Ok(res) + } else { + msg!("Error: Overflow in {} + {}", arg1, arg2); + Err(ProgramError::Custom(999)) + } +} + +pub fn checked_sub(arg1: T, arg2: T) -> Result +where + T: num_traits::PrimInt + Display, +{ + if let Some(res) = arg1.checked_sub(&arg2) { + Ok(res) + } else { + msg!("Error: Overflow in {} - {}", arg1, arg2); + Err(ProgramError::Custom(999)) + } +} + +pub fn checked_div(arg1: T, arg2: T) -> Result +where + T: num_traits::PrimInt + Display, +{ + if let Some(res) = arg1.checked_div(&arg2) { + Ok(res) + } else { + msg!("Error: Overflow in {} / {}", arg1, arg2); + Err(ProgramError::Custom(999)) + } +} + +pub fn checked_mul(arg1: T, arg2: T) -> Result +where + T: num_traits::PrimInt + Display, +{ + if let Some(res) = arg1.checked_mul(&arg2) { + Ok(res) + } else { + msg!("Error: Overflow in {} * {}", arg1, arg2); + Err(ProgramError::Custom(999)) + } +} + +pub fn checked_as_u64(arg: T) -> Result +where + T: Display + num_traits::ToPrimitive + Clone, +{ + let option: Option = num_traits::NumCast::from(arg.clone()); + if let Some(res) = option { + Ok(res) + } else { + msg!("Error: Overflow in {} as u64", arg); + Err(ProgramError::Custom(999)) + } +} diff --git a/farms/farm-sdk/src/pack.rs b/farms/farm-sdk/src/pack.rs new file mode 100644 index 00000000000..162cd999aa7 --- /dev/null +++ b/farms/farm-sdk/src/pack.rs @@ -0,0 +1,192 @@ +//! Common routines for packing/unpacking + +use { + crate::string::ArrayString64, + arrayref::{array_refs, mut_array_refs}, + serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serializer, + }, + solana_program::{program_error::ProgramError, pubkey::Pubkey}, + std::{fmt, str::FromStr}, +}; + +/// Checks if the slice has at least min_len size +pub fn check_data_len(data: &[u8], min_len: usize) -> Result<(), ProgramError> { + if data.len() < min_len { + Err(ProgramError::AccountDataTooSmall) + } else { + Ok(()) + } +} + +/// Converts bool to a byte +pub fn pack_bool(input: bool, output: &mut [u8; 1]) { + output[0] = input as u8; +} + +/// Converts a raw byte to a bool +pub fn unpack_bool(input: &[u8; 1]) -> Result { + let result = match input { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }; + Ok(result) +} + +pub fn pack_option_key(input: &Option, output: &mut [u8; 33]) { + let (tag, data) = mut_array_refs![output, 1, 32]; + match input { + Option::Some(key) => { + tag[0] = 1; + data.copy_from_slice(key.as_ref()); + } + Option::None => { + tag[0] = 0; + } + } +} + +pub fn unpack_option_key(input: &[u8; 33]) -> Result, ProgramError> { + let (tag, data) = array_refs![input, 1, 32]; + match *tag { + [0] => Ok(Option::None), + [1] => Ok(Option::Some(Pubkey::new_from_array(*data))), + _ => Err(ProgramError::InvalidAccountData), + } +} + +pub fn pack_option_u32(input: Option, output: &mut [u8; 5]) { + let (tag, data) = mut_array_refs![output, 1, 4]; + match input { + Option::Some(val) => { + tag[0] = 1; + *data = val.to_le_bytes(); + } + Option::None => { + tag[0] = 0; + } + } +} + +pub fn unpack_option_u32(input: &[u8; 5]) -> Result, ProgramError> { + let (tag, data) = array_refs![input, 1, 4]; + match *tag { + [0] => Ok(Option::None), + [1] => Ok(Option::Some(u32::from_le_bytes(*data))), + _ => Err(ProgramError::InvalidAccountData), + } +} + +pub fn pack_array_string64(input: &ArrayString64, output: &mut [u8; 64]) { + for (dst, src) in output.iter_mut().zip(input.as_bytes()) { + *dst = *src + } +} + +pub fn unpack_array_string64(input: &[u8; 64]) -> Result { + if let Some(i) = input.iter().position(|x| *x == 0) { + ArrayString64::try_from_utf8(&input[0..i]).or(Err(ProgramError::InvalidAccountData)) + } else { + ArrayString64::try_from_utf8(input).or(Err(ProgramError::InvalidAccountData)) + } +} + +/// Custom Pubkey deserializer to use with Serde +pub fn pubkey_deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer).unwrap(); + Pubkey::from_str(s.as_str()).map_err(D::Error::custom) +} + +/// Custom Pubkey serializer to use with Serde +pub fn pubkey_serialize(x: &Pubkey, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(x.to_string().as_str()) +} + +/// Custom Option deserializer to use with Serde +pub fn optional_pubkey_deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer).unwrap(); + if s.is_empty() { + Ok(None) + } else { + Ok(Some( + Pubkey::from_str(s.as_str()).map_err(D::Error::custom)?, + )) + } +} + +/// Custom Option serializer to use with Serde +pub fn optional_pubkey_serialize(x: &Option, s: S) -> Result +where + S: Serializer, +{ + if let Some(key) = x { + s.serialize_str(key.to_string().as_str()) + } else { + s.serialize_str("") + } +} + +/// Custom ArrayString64 serializer to use with Serde +pub fn as64_serialize(x: &ArrayString64, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(x.as_str()) +} + +/// Custom ArrayString64 deserializer to use with Serde +pub fn as64_deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct ArrayStringVisitor; + + impl<'de> Visitor<'de> for ArrayStringVisitor { + type Value = ArrayString64; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + ArrayString64::try_from_str(v).map_err(E::custom) + } + } + + deserializer.deserialize_any(ArrayStringVisitor) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_as64_serialization() { + let as1 = ArrayString64::from_utf8("test").unwrap(); + let mut output: [u8; 64] = [0; 64]; + pack_array_string64(&as1, &mut output); + let as2 = unpack_array_string64(&output).unwrap(); + assert_eq!(as1, as2); + } + + #[test] + fn test_as64_serialization_utf8() { + let as1 = ArrayString64::from_utf8("тест").unwrap(); + let mut output: [u8; 64] = [0; 64]; + pack_array_string64(&as1, &mut output); + let as2 = unpack_array_string64(&output).unwrap(); + assert_eq!(as1, as2); + } +} diff --git a/farms/farm-sdk/src/pool.rs b/farms/farm-sdk/src/pool.rs new file mode 100644 index 00000000000..0c05a3d2c83 --- /dev/null +++ b/farms/farm-sdk/src/pool.rs @@ -0,0 +1,769 @@ +//! Liquidity Pools + +use { + crate::{pack::*, string::ArrayString64, traits::*}, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + serde_json::to_string, + solana_program::program_error::ProgramError, + solana_program::pubkey::Pubkey, +}; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum PoolRoute { + Raydium { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_open_orders: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_target: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pool_withdraw_queue: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pool_temp_lp_token_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + serum_program_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + serum_market: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + serum_coin_vault_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + serum_pc_vault_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + serum_vault_signer: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + serum_bids: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + serum_asks: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + serum_event_queue: Option, + }, + Saber { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + swap_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + swap_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + fees_account_a: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + fees_account_b: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + decimal_wrapper_program: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + wrapped_token_a_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + wrapped_token_a_vault: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + decimal_wrapper_token_a: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + wrapped_token_b_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + wrapped_token_b_vault: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + decimal_wrapper_token_b: Option, + }, + Orca { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + amm_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + fees_account: Pubkey, + }, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum PoolRouteType { + Raydium, + Saber, + Orca, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum PoolType { + Amm, + AmmStable, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum PoolTokenType { + VaultToken, + PoolToken, + FarmToken, + Token, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct Pool { + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, + pub version: u16, + pub pool_type: PoolType, + pub official: bool, + pub refdb_index: Option, + pub refdb_counter: u16, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub token_a_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub token_b_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub lp_token_ref: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub token_a_account: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub token_b_account: Option, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub router_program_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub pool_program_id: Pubkey, + pub route: PoolRoute, +} + +impl Named for Pool { + fn name(&self) -> ArrayString64 { + self.name + } +} + +impl Versioned for Pool { + fn version(&self) -> u16 { + self.version + } +} + +impl Pool { + pub const MAX_LEN: usize = 756; + pub const RAYDIUM_POOL_LEN: usize = 756; + pub const SABER_POOL_LEN: usize = 663; + pub const ORCA_POOL_LEN: usize = 401; + + pub fn get_size(&self) -> usize { + match self.route { + PoolRoute::Raydium { .. } => Pool::RAYDIUM_POOL_LEN, + PoolRoute::Saber { .. } => Pool::SABER_POOL_LEN, + PoolRoute::Orca { .. } => Pool::ORCA_POOL_LEN, + } + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self.route { + PoolRoute::Raydium { .. } => self.pack_raydium(output), + PoolRoute::Saber { .. } => self.pack_saber(output), + PoolRoute::Orca { .. } => self.pack_orca(output), + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Pool::MAX_LEN] = [0; Pool::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let pool_route_type = PoolRouteType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidAccountData))?; + match pool_route_type { + PoolRouteType::Raydium => Pool::unpack_raydium(input), + PoolRouteType::Saber => Pool::unpack_saber(input), + PoolRouteType::Orca => Pool::unpack_orca(input), + } + } + + fn pack_raydium(&self, output: &mut [u8]) -> Result { + check_data_len(output, Pool::RAYDIUM_POOL_LEN)?; + + if let PoolRoute::Raydium { + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue, + pool_temp_lp_token_account, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue, + } = self.route + { + let output = array_mut_ref![output, 0, Pool::RAYDIUM_POOL_LEN]; + + let ( + pool_route_type_out, + name_out, + version_out, + pool_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + token_a_ref_out, + token_b_ref_out, + lp_token_ref_out, + token_a_account_out, + token_b_account_out, + router_program_id_out, + pool_program_id_out, + amm_id_out, + amm_authority_out, + amm_open_orders_out, + amm_target_out, + pool_withdraw_queue_out, + pool_temp_lp_token_account_out, + serum_program_id_out, + serum_market_out, + serum_coin_vault_account_out, + serum_pc_vault_account_out, + serum_vault_signer_out, + serum_bids_out, + serum_asks_out, + serum_event_queue_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 33, 33, 33 + ]; + + pool_route_type_out[0] = PoolRouteType::Raydium as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + pool_type_out[0] = self.pool_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.token_a_ref, token_a_ref_out); + pack_option_key(&self.token_b_ref, token_b_ref_out); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.token_a_account, token_a_account_out); + pack_option_key(&self.token_b_account, token_b_account_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + pool_program_id_out.copy_from_slice(self.pool_program_id.as_ref()); + amm_id_out.copy_from_slice(amm_id.as_ref()); + amm_authority_out.copy_from_slice(amm_authority.as_ref()); + amm_open_orders_out.copy_from_slice(amm_open_orders.as_ref()); + amm_target_out.copy_from_slice(amm_target.as_ref()); + pool_withdraw_queue_out.copy_from_slice(pool_withdraw_queue.as_ref()); + pool_temp_lp_token_account_out.copy_from_slice(pool_temp_lp_token_account.as_ref()); + serum_program_id_out.copy_from_slice(serum_program_id.as_ref()); + serum_market_out.copy_from_slice(serum_market.as_ref()); + serum_coin_vault_account_out.copy_from_slice(serum_coin_vault_account.as_ref()); + serum_pc_vault_account_out.copy_from_slice(serum_pc_vault_account.as_ref()); + serum_vault_signer_out.copy_from_slice(serum_vault_signer.as_ref()); + pack_option_key(&serum_bids, serum_bids_out); + pack_option_key(&serum_asks, serum_asks_out); + pack_option_key(&serum_event_queue, serum_event_queue_out); + + Ok(Pool::RAYDIUM_POOL_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn pack_saber(&self, output: &mut [u8]) -> Result { + check_data_len(output, Pool::SABER_POOL_LEN)?; + + if let PoolRoute::Saber { + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + decimal_wrapper_program, + wrapped_token_a_ref, + wrapped_token_a_vault, + decimal_wrapper_token_a, + wrapped_token_b_ref, + wrapped_token_b_vault, + decimal_wrapper_token_b, + } = self.route + { + let output = array_mut_ref![output, 0, Pool::SABER_POOL_LEN]; + + let ( + pool_route_type_out, + name_out, + version_out, + pool_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + token_a_ref_out, + token_b_ref_out, + lp_token_ref_out, + token_a_account_out, + token_b_account_out, + router_program_id_out, + pool_program_id_out, + swap_account_out, + swap_authority_out, + fees_account_a_out, + fees_account_b_out, + decimal_wrapper_program_out, + wrapped_token_a_ref_out, + wrapped_token_a_vault_out, + decimal_wrapper_token_a_out, + wrapped_token_b_ref_out, + wrapped_token_b_vault_out, + decimal_wrapper_token_b_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 33, + 33, 33, 33, 33, 33 + ]; + + pool_route_type_out[0] = PoolRouteType::Saber as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + pool_type_out[0] = self.pool_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.token_a_ref, token_a_ref_out); + pack_option_key(&self.token_b_ref, token_b_ref_out); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.token_a_account, token_a_account_out); + pack_option_key(&self.token_b_account, token_b_account_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + pool_program_id_out.copy_from_slice(self.pool_program_id.as_ref()); + swap_account_out.copy_from_slice(swap_account.as_ref()); + swap_authority_out.copy_from_slice(swap_authority.as_ref()); + fees_account_a_out.copy_from_slice(fees_account_a.as_ref()); + fees_account_b_out.copy_from_slice(fees_account_b.as_ref()); + decimal_wrapper_program_out.copy_from_slice(decimal_wrapper_program.as_ref()); + pack_option_key(&wrapped_token_a_ref, wrapped_token_a_ref_out); + pack_option_key(&wrapped_token_a_vault, wrapped_token_a_vault_out); + pack_option_key(&decimal_wrapper_token_a, decimal_wrapper_token_a_out); + pack_option_key(&wrapped_token_b_ref, wrapped_token_b_ref_out); + pack_option_key(&wrapped_token_b_vault, wrapped_token_b_vault_out); + pack_option_key(&decimal_wrapper_token_b, decimal_wrapper_token_b_out); + + Ok(Pool::SABER_POOL_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn pack_orca(&self, output: &mut [u8]) -> Result { + check_data_len(output, Pool::ORCA_POOL_LEN)?; + + if let PoolRoute::Orca { + amm_id, + amm_authority, + fees_account, + } = self.route + { + let output = array_mut_ref![output, 0, Pool::ORCA_POOL_LEN]; + + let ( + pool_route_type_out, + name_out, + version_out, + pool_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + token_a_ref_out, + token_b_ref_out, + lp_token_ref_out, + token_a_account_out, + token_b_account_out, + router_program_id_out, + pool_program_id_out, + amm_id_out, + amm_authority_out, + fees_account_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32 + ]; + + pool_route_type_out[0] = PoolRouteType::Orca as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + pool_type_out[0] = self.pool_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + pack_option_key(&self.token_a_ref, token_a_ref_out); + pack_option_key(&self.token_b_ref, token_b_ref_out); + pack_option_key(&self.lp_token_ref, lp_token_ref_out); + pack_option_key(&self.token_a_account, token_a_account_out); + pack_option_key(&self.token_b_account, token_b_account_out); + router_program_id_out.copy_from_slice(self.router_program_id.as_ref()); + pool_program_id_out.copy_from_slice(self.pool_program_id.as_ref()); + amm_id_out.copy_from_slice(amm_id.as_ref()); + amm_authority_out.copy_from_slice(amm_authority.as_ref()); + fees_account_out.copy_from_slice(fees_account.as_ref()); + + Ok(Pool::ORCA_POOL_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn unpack_raydium(input: &[u8]) -> Result { + check_data_len(input, Pool::RAYDIUM_POOL_LEN)?; + + let input = array_ref![input, 1, Pool::RAYDIUM_POOL_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + pool_type, + official, + refdb_index, + refdb_counter, + token_a_ref, + token_b_ref, + lp_token_ref, + token_a_account, + token_b_account, + router_program_id, + pool_program_id, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + pool_withdraw_queue, + pool_temp_lp_token_account, + serum_program_id, + serum_market, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue, + ) = array_refs![ + input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 33, 33, 33 + ]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + pool_type: PoolType::try_from_primitive(pool_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + token_a_ref: unpack_option_key(token_a_ref)?, + token_b_ref: unpack_option_key(token_b_ref)?, + lp_token_ref: unpack_option_key(lp_token_ref)?, + token_a_account: unpack_option_key(token_a_account)?, + token_b_account: unpack_option_key(token_b_account)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + pool_program_id: Pubkey::new_from_array(*pool_program_id), + route: PoolRoute::Raydium { + amm_id: Pubkey::new_from_array(*amm_id), + amm_authority: Pubkey::new_from_array(*amm_authority), + amm_open_orders: Pubkey::new_from_array(*amm_open_orders), + amm_target: Pubkey::new_from_array(*amm_target), + pool_withdraw_queue: Pubkey::new_from_array(*pool_withdraw_queue), + pool_temp_lp_token_account: Pubkey::new_from_array(*pool_temp_lp_token_account), + serum_program_id: Pubkey::new_from_array(*serum_program_id), + serum_market: Pubkey::new_from_array(*serum_market), + serum_coin_vault_account: Pubkey::new_from_array(*serum_coin_vault_account), + serum_pc_vault_account: Pubkey::new_from_array(*serum_pc_vault_account), + serum_vault_signer: Pubkey::new_from_array(*serum_vault_signer), + serum_bids: unpack_option_key(serum_bids)?, + serum_asks: unpack_option_key(serum_asks)?, + serum_event_queue: unpack_option_key(serum_event_queue)?, + }, + }) + } + + fn unpack_saber(input: &[u8]) -> Result { + check_data_len(input, Pool::SABER_POOL_LEN)?; + + let input = array_ref![input, 1, Pool::SABER_POOL_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + pool_type, + official, + refdb_index, + refdb_counter, + token_a_ref, + token_b_ref, + lp_token_ref, + token_a_account, + token_b_account, + router_program_id, + pool_program_id, + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + decimal_wrapper_program, + wrapped_token_a_ref, + wrapped_token_a_vault, + decimal_wrapper_token_a, + wrapped_token_b_ref, + wrapped_token_b_vault, + decimal_wrapper_token_b, + ) = array_refs![ + input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, + 33, 33, 33 + ]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + pool_type: PoolType::try_from_primitive(pool_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + token_a_ref: unpack_option_key(token_a_ref)?, + token_b_ref: unpack_option_key(token_b_ref)?, + lp_token_ref: unpack_option_key(lp_token_ref)?, + token_a_account: unpack_option_key(token_a_account)?, + token_b_account: unpack_option_key(token_b_account)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + pool_program_id: Pubkey::new_from_array(*pool_program_id), + route: PoolRoute::Saber { + swap_account: Pubkey::new_from_array(*swap_account), + swap_authority: Pubkey::new_from_array(*swap_authority), + fees_account_a: Pubkey::new_from_array(*fees_account_a), + fees_account_b: Pubkey::new_from_array(*fees_account_b), + decimal_wrapper_program: Pubkey::new_from_array(*decimal_wrapper_program), + wrapped_token_a_ref: unpack_option_key(wrapped_token_a_ref)?, + wrapped_token_a_vault: unpack_option_key(wrapped_token_a_vault)?, + decimal_wrapper_token_a: unpack_option_key(decimal_wrapper_token_a)?, + wrapped_token_b_ref: unpack_option_key(wrapped_token_b_ref)?, + wrapped_token_b_vault: unpack_option_key(wrapped_token_b_vault)?, + decimal_wrapper_token_b: unpack_option_key(decimal_wrapper_token_b)?, + }, + }) + } + + fn unpack_orca(input: &[u8]) -> Result { + check_data_len(input, Pool::ORCA_POOL_LEN)?; + + let input = array_ref![input, 1, Pool::ORCA_POOL_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + pool_type, + official, + refdb_index, + refdb_counter, + token_a_ref, + token_b_ref, + lp_token_ref, + token_a_account, + token_b_account, + router_program_id, + pool_program_id, + amm_id, + amm_authority, + fees_account, + ) = array_refs![input, 64, 2, 1, 1, 5, 2, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + pool_type: PoolType::try_from_primitive(pool_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + token_a_ref: unpack_option_key(token_a_ref)?, + token_b_ref: unpack_option_key(token_b_ref)?, + lp_token_ref: unpack_option_key(lp_token_ref)?, + token_a_account: unpack_option_key(token_a_account)?, + token_b_account: unpack_option_key(token_b_account)?, + router_program_id: Pubkey::new_from_array(*router_program_id), + pool_program_id: Pubkey::new_from_array(*pool_program_id), + route: PoolRoute::Orca { + amm_id: Pubkey::new_from_array(*amm_id), + amm_authority: Pubkey::new_from_array(*amm_authority), + fees_account: Pubkey::new_from_array(*fees_account), + }, + }) + } +} + +impl std::fmt::Display for PoolType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + PoolType::Amm => write!(f, "Amm"), + PoolType::AmmStable => write!(f, "AmmStable"), + } + } +} + +impl std::fmt::Display for Pool { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vec_serialization() { + let ri1 = Pool { + name: ArrayString64::from_utf8("test").unwrap(), + version: 2, + pool_type: PoolType::Amm, + official: true, + refdb_index: Some(1), + refdb_counter: 2, + token_a_ref: Some(Pubkey::new_unique()), + token_b_ref: Some(Pubkey::new_unique()), + lp_token_ref: Some(Pubkey::new_unique()), + token_a_account: None, + token_b_account: None, + router_program_id: Pubkey::new_unique(), + pool_program_id: Pubkey::new_unique(), + route: PoolRoute::Raydium { + amm_id: Pubkey::new_unique(), + amm_authority: Pubkey::new_unique(), + amm_open_orders: Pubkey::new_unique(), + amm_target: Pubkey::new_unique(), + pool_withdraw_queue: Pubkey::new_unique(), + pool_temp_lp_token_account: Pubkey::new_unique(), + serum_program_id: Pubkey::new_unique(), + serum_market: Pubkey::new_unique(), + serum_coin_vault_account: Pubkey::new_unique(), + serum_pc_vault_account: Pubkey::new_unique(), + serum_vault_signer: Pubkey::new_unique(), + serum_bids: Some(Pubkey::new_unique()), + serum_asks: Some(Pubkey::new_unique()), + serum_event_queue: Some(Pubkey::new_unique()), + }, + }; + + let vec = ri1.to_vec().unwrap(); + + let ri2 = Pool::unpack(&vec[..]).unwrap(); + + assert_eq!(ri1, ri2); + } +} diff --git a/farms/farm-sdk/src/program/account.rs b/farms/farm-sdk/src/program/account.rs new file mode 100644 index 00000000000..375d7ed79ba --- /dev/null +++ b/farms/farm-sdk/src/program/account.rs @@ -0,0 +1,376 @@ +//! Common accounts management functions + +use { + crate::{math, pack::check_data_len}, + arrayref::array_ref, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, + }, + spl_token::state::{Account, Mint}, + std::cmp::Ordering, +}; + +/// Returns Token Mint supply. +/// Extrats supply field without unpacking entire struct. +pub fn get_token_supply(token_mint: &AccountInfo) -> Result { + let data = token_mint.try_borrow_data()?; + check_data_len(&data, spl_token::state::Mint::get_packed_len())?; + let supply = array_ref![data, 36, 8]; + + Ok(u64::from_le_bytes(*supply)) +} + +/// Returns Token decimals. +/// Extrats decimals field without unpacking entire struct. +pub fn get_token_decimals(token_mint: &AccountInfo) -> Result { + let data = token_mint.try_borrow_data()?; + check_data_len(&data, spl_token::state::Mint::get_packed_len())?; + let decimals = array_ref![data, 44, 1]; + + Ok(decimals[0]) +} + +/// Returns Tokens balance. +/// Extrats balance field without unpacking entire struct. +pub fn get_token_balance(token_account: &AccountInfo) -> Result { + let data = token_account.try_borrow_data()?; + check_data_len(&data, spl_token::state::Account::get_packed_len())?; + let amount = array_ref![data, 64, 8]; + + Ok(u64::from_le_bytes(*amount)) +} + +/// Returns Token account owner. +/// Extrats owner field without unpacking entire struct. +pub fn get_token_account_owner(token_account: &AccountInfo) -> Result { + let data = token_account.try_borrow_data()?; + check_data_len(&data, spl_token::state::Account::get_packed_len())?; + let owner = array_ref![data, 32, 32]; + + Ok(Pubkey::new_from_array(*owner)) +} + +/// Returns Token account mint. +/// Extrats mint field without unpacking entire struct. +pub fn get_token_account_mint(token_account: &AccountInfo) -> Result { + let data = token_account.try_borrow_data()?; + check_data_len(&data, spl_token::state::Account::get_packed_len())?; + let mint = array_ref![data, 0, 32]; + + Ok(Pubkey::new_from_array(*mint)) +} + +pub fn get_balance_increase( + account: &AccountInfo, + previous_balance: u64, +) -> Result { + let balance = get_token_balance(account)?; + if balance >= previous_balance { + Ok(balance - previous_balance) + } else { + msg!( + "Error: Balance decrease was not expected. Account: {}", + account.key + ); + Err(ProgramError::Custom(1001)) + } +} + +pub fn get_balance_decrease( + account: &AccountInfo, + previous_balance: u64, +) -> Result { + let balance = get_token_balance(account)?; + if balance <= previous_balance { + Ok(previous_balance - balance) + } else { + msg!( + "Error: Balance increase was not expected. Account: {}", + account.key + ); + Err(ProgramError::Custom(1002)) + } +} + +pub fn check_tokens_spent( + account: &AccountInfo, + previous_balance: u64, + max_amount_spent: u64, +) -> Result { + let tokens_spent = get_balance_decrease(account, previous_balance)?; + if tokens_spent > max_amount_spent { + msg!( + "Error: Invoked program overspent. Account: {}, max expected: {}, actual: {}", + account.key, + max_amount_spent, + tokens_spent + ); + Err(ProgramError::Custom(1003)) + } else { + Ok(tokens_spent) + } +} + +pub fn check_tokens_received( + account: &AccountInfo, + previous_balance: u64, + min_amount_received: u64, +) -> Result { + let tokens_received = get_balance_increase(account, previous_balance)?; + if tokens_received < min_amount_received { + msg!( + "Error: Not enough tokens returned by invoked program. Account: {}, min expected: {}, actual: {}", + account.key, + min_amount_received, + tokens_received + ); + Err(ProgramError::Custom(1004)) + } else { + Ok(tokens_received) + } +} + +/// Returns Token Mint data. +pub fn get_token_mint(token_mint: &AccountInfo) -> Result { + let data = token_mint.try_borrow_data()?; + Mint::unpack(&data) +} + +/// Returns Token Account data. +pub fn get_token_account(token_account: &AccountInfo) -> Result { + let data = token_account.try_borrow_data()?; + Account::unpack(&data) +} + +/// Returns token pair ratio, optimized for on-chain. +pub fn get_token_ratio<'a, 'b>( + token_a_balance: u64, + token_b_balance: u64, + token_a_mint: &'a AccountInfo<'b>, + token_b_mint: &'a AccountInfo<'b>, +) -> Result { + get_token_ratio_with_decimals( + token_a_balance, + token_b_balance, + get_token_decimals(token_a_mint)?, + get_token_decimals(token_b_mint)?, + ) +} + +/// Returns token pair ratio, uses decimals insted of mints, optimized for on-chain. +pub fn get_token_ratio_with_decimals( + token_a_balance: u64, + token_b_balance: u64, + token_a_decimals: u8, + token_b_decimals: u8, +) -> Result { + if token_a_balance == 0 || token_b_balance == 0 { + return Ok(0.0); + } + + let mut ratio = token_b_balance as f64 / token_a_balance as f64; + match token_a_decimals.cmp(&token_b_decimals) { + Ordering::Greater => { + for _ in 0..(token_a_decimals - token_b_decimals) { + ratio *= 10.0; + } + } + Ordering::Less => { + for _ in 0..(token_b_decimals - token_a_decimals) { + ratio /= 10.0; + } + } + Ordering::Equal => {} + } + + Ok(ratio) +} + +/// Returns token pair ratio +pub fn get_token_pair_ratio<'a, 'b>( + token_a_account: &'a AccountInfo<'b>, + token_b_account: &'a AccountInfo<'b>, +) -> Result { + let token_a_balance = get_token_balance(token_a_account)?; + let token_b_balance = get_token_balance(token_b_account)?; + if token_a_balance == 0 || token_b_balance == 0 { + return Ok(0.0); + } + Ok(token_b_balance as f64 / token_a_balance as f64) +} + +pub fn to_ui_amount(amount: u64, decimals: u8) -> f64 { + let mut ui_amount = amount; + for _ in 0..decimals { + ui_amount /= 10; + } + ui_amount as f64 +} + +pub fn to_token_amount(ui_amount: f64, decimals: u8) -> Result { + let mut amount = ui_amount; + for _ in 0..decimals { + amount *= 10.0; + } + math::checked_as_u64(amount) +} + +pub fn to_amount_with_new_decimals( + amount: u64, + original_decimals: u8, + new_decimals: u8, +) -> Result { + match new_decimals.cmp(&original_decimals) { + Ordering::Greater => { + let mut new_amount = amount as f64; + for _ in 0..(new_decimals - original_decimals) { + new_amount *= 10.0; + } + math::checked_as_u64(new_amount) + } + Ordering::Less => { + let mut new_amount = amount; + for _ in 0..(original_decimals - new_decimals) { + new_amount /= 10; + } + Ok(new_amount) + } + Ordering::Equal => Ok(amount), + } +} + +pub fn transfer_tokens<'a, 'b>( + source_account: &'a AccountInfo<'b>, + destination_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, + amount: u64, +) -> ProgramResult { + invoke( + &spl_token::instruction::transfer( + &spl_token::id(), + source_account.key, + destination_account.key, + authority_account.key, + &[], + amount, + )?, + &[ + source_account.clone(), + destination_account.clone(), + authority_account.clone(), + ], + )?; + Ok(()) +} + +pub fn burn_tokens<'a, 'b>( + from_token_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, + amount: u64, +) -> ProgramResult { + invoke( + &spl_token::instruction::burn( + &spl_token::id(), + from_token_account.key, + mint_account.key, + authority_account.key, + &[], + amount, + )?, + &[ + from_token_account.clone(), + mint_account.clone(), + authority_account.clone(), + ], + ) +} + +pub fn close_system_account<'a, 'b>( + receiving_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + authority_account: &Pubkey, +) -> ProgramResult { + if *target_account.owner != *authority_account { + return Err(ProgramError::IllegalOwner); + } + let cur_balance = target_account.try_lamports()?; + **receiving_account.try_borrow_mut_lamports()? += cur_balance; + **target_account.try_borrow_mut_lamports()? -= cur_balance; + + if target_account.data_len() > 1000 { + target_account.try_borrow_mut_data()?[..1000].fill(0); + } else { + target_account.try_borrow_mut_data()?.fill(0); + } + + Ok(()) +} + +pub fn close_token_account<'a, 'b>( + receiving_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, +) -> ProgramResult { + invoke( + &spl_token::instruction::close_account( + &spl_token::id(), + receiving_account.key, + target_account.key, + authority_account.key, + &[], + )?, + &[ + target_account.clone(), + receiving_account.clone(), + authority_account.clone(), + ], + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use spl_token::state::{Account, Mint}; + + #[test] + fn test_mint_supply_offset() { + let mint = Mint { + supply: 1234567891011, + ..Mint::default() + }; + let mut packed: [u8; 82] = [0; 82]; + Mint::pack(mint, &mut packed).unwrap(); + + let supply = array_ref![packed, 36, 8]; + assert_eq!(1234567891011, u64::from_le_bytes(*supply)); + } + + #[test] + fn test_mint_decimals_offset() { + let mint = Mint { + decimals: 123, + ..Mint::default() + }; + let mut packed: [u8; 82] = [0; 82]; + Mint::pack(mint, &mut packed).unwrap(); + + let decimals = array_ref![packed, 44, 1]; + assert_eq!(123, decimals[0]); + } + + #[test] + fn test_account_amount_offset() { + let account = Account { + amount: 1234567891011, + ..Account::default() + }; + let mut packed: [u8; 165] = [0; 165]; + Account::pack(account, &mut packed).unwrap(); + + let amount = array_ref![packed, 64, 8]; + assert_eq!(1234567891011, u64::from_le_bytes(*amount)); + } +} diff --git a/farms/farm-sdk/src/program/mod.rs b/farms/farm-sdk/src/program/mod.rs new file mode 100644 index 00000000000..16e5bfdd4c3 --- /dev/null +++ b/farms/farm-sdk/src/program/mod.rs @@ -0,0 +1,3 @@ +pub mod account; +pub mod pda; +pub mod protocol; diff --git a/farms/farm-sdk/src/program/pda.rs b/farms/farm-sdk/src/program/pda.rs new file mode 100644 index 00000000000..f0b9840cf35 --- /dev/null +++ b/farms/farm-sdk/src/program/pda.rs @@ -0,0 +1,387 @@ +//! Common PDA functions + +use { + crate::{ + id::{main_router, main_router_admin}, + refdb, + string::ArrayString64, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program, program_error::ProgramError, + program_pack::Pack, pubkey::Pubkey, rent::Rent, system_instruction, sysvar, sysvar::Sysvar, + }, +}; + +/// Derives the RefDB storage address and the bump seed for the given string +pub fn find_refdb_pda(refdb_name: &str) -> (Pubkey, u8) { + if refdb::REFDB_ONCHAIN_INIT { + Pubkey::find_program_address(&[refdb_name.as_bytes()], &main_router::id()) + } else { + ( + Pubkey::create_with_seed(&main_router_admin::id(), refdb_name, &main_router::id()) + .unwrap(), + 0, + ) + } +} + +/// Derives the target metadata object address for the given storage type and object name +pub fn find_target_pda( + storage_type: refdb::StorageType, + target_name: &ArrayString64, +) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[storage_type.to_string().as_bytes(), target_name.as_bytes()], + &main_router::id(), + ) +} + +/// Returns the target metadata object address for the given storage type, object name, and bump +pub fn find_target_pda_with_bump( + storage_type: refdb::StorageType, + target_name: &ArrayString64, + bump: u8, +) -> Result { + Pubkey::create_program_address( + &[ + storage_type.to_string().as_bytes(), + target_name.as_bytes(), + &[bump], + ], + &main_router::id(), + ) + .map_err(|_| ProgramError::InvalidSeeds) +} + +pub fn init_token_account<'a, 'b>( + funding_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + owner_account: &'a AccountInfo<'b>, + rent_program: &'a AccountInfo<'b>, + base_address: &Pubkey, + seeds: &[&[u8]], +) -> ProgramResult { + if !target_account.data_is_empty() { + return Ok(()); + } + + init_system_account( + funding_account, + target_account, + &spl_token::id(), + base_address, + seeds, + spl_token::state::Account::get_packed_len(), + )?; + + program::invoke( + &spl_token::instruction::initialize_account( + &spl_token::id(), + target_account.key, + mint_account.key, + owner_account.key, + )?, + &[ + target_account.clone(), + mint_account.clone(), + owner_account.clone(), + rent_program.clone(), + ], + ) +} + +pub fn init_associated_token_account<'a, 'b>( + funding_account: &'a AccountInfo<'b>, + wallet_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + rent_program: &'a AccountInfo<'b>, +) -> ProgramResult { + if !target_account.data_is_empty() { + return Ok(()); + } + + program::invoke( + &spl_associated_token_account::create_associated_token_account( + funding_account.key, + wallet_account.key, + mint_account.key, + ), + &[ + funding_account.clone(), + target_account.clone(), + wallet_account.clone(), + mint_account.clone(), + rent_program.clone(), + ], + ) +} + +pub fn close_token_account<'a, 'b>( + receiving_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, + base_address: &Pubkey, + seeds: &[&[u8]], +) -> ProgramResult { + if target_account.data_is_empty() { + return Ok(()); + } + let (_, bump) = Pubkey::find_program_address(seeds, base_address); + + program::invoke_signed( + &spl_token::instruction::close_account( + &spl_token::id(), + target_account.key, + receiving_account.key, + authority_account.key, + &[], + )?, + &[ + target_account.clone(), + receiving_account.clone(), + authority_account.clone(), + ], + &[&[seeds, &[&[bump]]].concat()], + )?; + Ok(()) +} + +pub fn transfer_tokens_with_seeds<'a, 'b>( + source_account: &'a AccountInfo<'b>, + destination_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if source_account.key == destination_account.key { + return Err(ProgramError::InvalidArgument); + } + program::invoke_signed( + &spl_token::instruction::transfer( + &spl_token::id(), + source_account.key, + destination_account.key, + authority_account.key, + &[], + amount, + )?, + &[ + source_account.clone(), + destination_account.clone(), + authority_account.clone(), + ], + seeds, + ) +} + +pub fn transfer_tokens<'a, 'b>( + source_account: &'a AccountInfo<'b>, + destination_account: &'a AccountInfo<'b>, + authority_account: &'a AccountInfo<'b>, + base_address: &Pubkey, + seeds: &[&[u8]], + amount: u64, +) -> ProgramResult { + let (_, bump) = Pubkey::find_program_address(seeds, base_address); + + transfer_tokens_with_seeds( + source_account, + destination_account, + authority_account, + &[&[seeds, &[&[bump]]].concat()], + amount, + ) +} + +pub fn init_system_account<'a, 'b>( + funding_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + owner_key: &Pubkey, + base_address: &Pubkey, + seeds: &[&[u8]], + data_size: usize, +) -> ProgramResult { + if !target_account.data_is_empty() || target_account.try_lamports()? != 0 { + return Ok(()); + } + + let (key, bump) = Pubkey::find_program_address(seeds, base_address); + if target_account.key != &key { + return Err(ProgramError::InvalidSeeds); + } + + let min_balance = sysvar::rent::Rent::get() + .unwrap() + .minimum_balance(data_size); + program::invoke_signed( + &system_instruction::create_account( + funding_account.key, + target_account.key, + min_balance, + data_size as u64, + owner_key, + ), + &[funding_account.clone(), target_account.clone()], + &[&[seeds, &[&[bump]]].concat()], + ) +} + +pub fn init_mint<'a, 'b>( + funding_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + owner_account: &'a AccountInfo<'b>, + rent_program: &'a AccountInfo<'b>, + base_address: &Pubkey, + seeds: &[&[u8]], + decimals: u8, +) -> ProgramResult { + if !mint_account.data_is_empty() { + return Ok(()); + } + + let acc_size = spl_token::state::Mint::get_packed_len(); + init_system_account( + funding_account, + mint_account, + &spl_token::id(), + base_address, + seeds, + acc_size, + )?; + + program::invoke( + &spl_token::instruction::initialize_mint( + &spl_token::id(), + mint_account.key, + owner_account.key, + Some(owner_account.key), + decimals, + )?, + &[ + mint_account.clone(), + owner_account.clone(), + rent_program.clone(), + ], + ) +} + +pub fn mint_to_with_seeds<'a, 'b>( + target_token_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + mint_authority_account: &'a AccountInfo<'b>, + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + solana_program::program::invoke_signed( + &spl_token::instruction::mint_to( + &spl_token::id(), + mint_account.key, + target_token_account.key, + mint_authority_account.key, + &[], + amount, + )?, + &[ + mint_account.clone(), + target_token_account.clone(), + mint_authority_account.clone(), + ], + seeds, + )?; + Ok(()) +} + +pub fn mint_to<'a, 'b>( + target_token_account: &'a AccountInfo<'b>, + mint_account: &'a AccountInfo<'b>, + mint_authority_account: &'a AccountInfo<'b>, + base_address: &Pubkey, + seeds: &[&[u8]], + amount: u64, +) -> ProgramResult { + let (_, bump) = Pubkey::find_program_address(seeds, base_address); + + mint_to_with_seeds( + target_token_account, + mint_account, + mint_authority_account, + &[&[seeds, &[&[bump]]].concat()], + amount, + ) +} + +pub fn check_pda_data_size<'a, 'b>( + target_account: &'a AccountInfo<'b>, + seeds: &[&[u8]], + data_size: usize, + fix: bool, +) -> ProgramResult { + if fix && target_account.data_is_empty() { + program::invoke_signed( + &system_instruction::allocate(target_account.key, data_size as u64), + &[target_account.clone()], + &[seeds], + )?; + } + if target_account.data_len() < data_size { + Err(ProgramError::AccountDataTooSmall) + } else { + Ok(()) + } +} + +pub fn check_pda_rent_exempt<'a, 'b>( + signer_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + seeds: &[&[u8]], + data_size: usize, + fix: bool, +) -> ProgramResult { + let rent = Rent::get()?; + let cur_balance = target_account.try_lamports()?; + let min_balance = rent.minimum_balance(data_size); + if cur_balance < min_balance { + let signer_balance = signer_account.try_lamports()?; + let signer_min_balance = rent.minimum_balance(signer_account.data_len()); + if !fix + || signer_balance <= signer_min_balance + || min_balance - cur_balance > signer_balance - signer_min_balance + { + return Err(ProgramError::InsufficientFunds); + } + program::invoke_signed( + &system_instruction::transfer( + signer_account.key, + target_account.key, + min_balance - cur_balance, + ), + &[signer_account.clone(), target_account.clone()], + &[seeds], + )?; + assert!(target_account.try_lamports()? >= min_balance); + } + Ok(()) +} + +pub fn check_pda_owner<'a, 'b>( + program_id: &Pubkey, + target_account: &'a AccountInfo<'b>, + seeds: &[&[u8]], + fix: bool, +) -> ProgramResult { + if *target_account.owner != *program_id { + if fix { + program::invoke_signed( + &system_instruction::assign(target_account.key, program_id), + &[target_account.clone()], + &[seeds], + )?; + assert!(*target_account.owner == *program_id); + } else { + return Err(ProgramError::IllegalOwner); + } + } + Ok(()) +} diff --git a/farms/farm-sdk/src/program/protocol/mod.rs b/farms/farm-sdk/src/program/protocol/mod.rs new file mode 100644 index 00000000000..8590c4ec0f0 --- /dev/null +++ b/farms/farm-sdk/src/program/protocol/mod.rs @@ -0,0 +1,3 @@ +pub mod orca; +pub mod raydium; +pub mod saber; diff --git a/farms/farm-sdk/src/program/protocol/orca.rs b/farms/farm-sdk/src/program/protocol/orca.rs new file mode 100644 index 00000000000..2ba771986ff --- /dev/null +++ b/farms/farm-sdk/src/program/protocol/orca.rs @@ -0,0 +1,297 @@ +//! Orca specific functions + +use { + crate::{math, pack::check_data_len, program::account}, + arrayref::{array_ref, array_refs}, + solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}, +}; + +pub mod orca_swap { + solana_program::declare_id!("9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP"); +} + +pub mod orca_stake { + solana_program::declare_id!("82yxjeMsvaURa4MbZZ7WZZHfobirZYkH1zF8fmeGtyaQ"); +} + +pub const ORCA_FEE: f64 = 0.003; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct OrcaUserStakeInfo { + pub is_initialized: u8, + pub account_type: u8, + pub global_farm: Pubkey, + pub owner: Pubkey, + pub base_tokens_converted: u64, + pub cumulative_emissions_checkpoint: [u8; 32], +} + +impl OrcaUserStakeInfo { + pub const LEN: usize = 106; + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, OrcaUserStakeInfo::LEN)?; + + let input = array_ref![input, 0, OrcaUserStakeInfo::LEN]; + + #[allow(clippy::ptr_offset_with_cast)] + let ( + is_initialized, + account_type, + global_farm, + owner, + base_tokens_converted, + cumulative_emissions_checkpoint, + ) = array_refs![input, 1, 1, 32, 32, 8, 32]; + + Ok(Self { + is_initialized: is_initialized[0], + account_type: account_type[0], + global_farm: Pubkey::new_from_array(*global_farm), + owner: Pubkey::new_from_array(*owner), + base_tokens_converted: u64::from_le_bytes(*base_tokens_converted), + cumulative_emissions_checkpoint: *cumulative_emissions_checkpoint, + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct OrcaFarmState { + pub is_initialized: u8, + pub account_type: u8, + pub nonce: u8, + pub token_program: Pubkey, + pub emissions_authority: Pubkey, + pub remove_rewards_authority: Pubkey, + pub base_token_mint: Pubkey, + pub base_token_vault: Pubkey, + pub reward_token_vault: Pubkey, + pub farm_token_mint: Pubkey, + pub emissions_per_sec_numerator: u64, + pub emissions_per_sec_denominator: u64, + pub last_updated_timestamp: u64, + pub cumulative_emissions_per_farm_token: [u8; 32], +} + +impl OrcaFarmState { + pub const LEN: usize = 283; + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, OrcaFarmState::LEN)?; + + let input = array_ref![input, 0, OrcaFarmState::LEN]; + + #[allow(clippy::ptr_offset_with_cast)] + let ( + is_initialized, + account_type, + nonce, + token_program, + emissions_authority, + remove_rewards_authority, + base_token_mint, + base_token_vault, + reward_token_vault, + farm_token_mint, + emissions_per_sec_numerator, + emissions_per_sec_denominator, + last_updated_timestamp, + cumulative_emissions_per_farm_token, + ) = array_refs![input, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 8, 8, 8, 32]; + + Ok(Self { + is_initialized: is_initialized[0], + account_type: account_type[0], + nonce: nonce[0], + token_program: Pubkey::new_from_array(*token_program), + emissions_authority: Pubkey::new_from_array(*emissions_authority), + remove_rewards_authority: Pubkey::new_from_array(*remove_rewards_authority), + base_token_mint: Pubkey::new_from_array(*base_token_mint), + base_token_vault: Pubkey::new_from_array(*base_token_vault), + reward_token_vault: Pubkey::new_from_array(*reward_token_vault), + farm_token_mint: Pubkey::new_from_array(*farm_token_mint), + emissions_per_sec_numerator: u64::from_le_bytes(*emissions_per_sec_numerator), + emissions_per_sec_denominator: u64::from_le_bytes(*emissions_per_sec_denominator), + last_updated_timestamp: u64::from_le_bytes(*last_updated_timestamp), + cumulative_emissions_per_farm_token: *cumulative_emissions_per_farm_token, + }) + } +} + +pub fn check_pool_program_id(program_id: &Pubkey) -> bool { + program_id == &orca_swap::id() +} + +pub fn check_stake_program_id(program_id: &Pubkey) -> bool { + program_id == &orca_stake::id() +} + +/// Returns amount of LP tokens staked as recorded in the specified stake account +pub fn get_stake_account_balance(stake_account: &AccountInfo) -> Result { + let data = stake_account.try_borrow_data()?; + Ok(OrcaUserStakeInfo::unpack(&data)?.base_tokens_converted) +} + +pub fn get_pool_token_balances<'a, 'b>( + pool_token_a_account: &'a AccountInfo<'b>, + pool_token_b_account: &'a AccountInfo<'b>, +) -> Result<(u64, u64), ProgramError> { + Ok(( + account::get_token_balance(pool_token_a_account)?, + account::get_token_balance(pool_token_b_account)?, + )) +} + +pub fn get_pool_deposit_amounts<'a, 'b>( + pool_token_a_account: &'a AccountInfo<'b>, + pool_token_b_account: &'a AccountInfo<'b>, + lp_token_mint: &'a AccountInfo<'b>, + max_token_a_amount: u64, + max_token_b_amount: u64, +) -> Result<(u64, u64, u64), ProgramError> { + if max_token_a_amount == 0 && max_token_b_amount == 0 { + msg!("Error: At least one of token amounts must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let mut token_a_amount = max_token_a_amount; + let mut token_b_amount = max_token_b_amount; + let (token_a_balance, token_b_balance) = + get_pool_token_balances(pool_token_a_account, pool_token_b_account)?; + + if token_a_balance == 0 || token_b_balance == 0 { + if max_token_a_amount == 0 || max_token_b_amount == 0 { + msg!("Error: Both amounts must be specified for the initial deposit to an empty pool"); + return Err(ProgramError::InvalidArgument); + } else { + return Ok((1, max_token_a_amount, max_token_b_amount)); + } + } + + if max_token_a_amount == 0 { + let estimated_coin_amount = math::checked_as_u64( + token_a_balance as f64 * max_token_b_amount as f64 / (token_b_balance as f64), + )?; + token_a_amount = if estimated_coin_amount > 1 { + estimated_coin_amount - 1 + } else { + 0 + }; + } else if max_token_b_amount == 0 { + token_b_amount = math::checked_as_u64( + token_b_balance as f64 * max_token_a_amount as f64 / (token_a_balance as f64), + )?; + } + + let min_lp_tokens_out = estimate_lp_tokens_amount( + lp_token_mint, + token_a_amount, + token_b_amount, + token_a_balance, + token_b_balance, + )?; + + Ok(( + min_lp_tokens_out, + token_a_amount, + math::checked_add(token_b_amount, 1)?, + )) +} + +pub fn get_pool_withdrawal_amounts<'a, 'b>( + pool_token_a_account: &'a AccountInfo<'b>, + pool_token_b_account: &'a AccountInfo<'b>, + lp_token_mint: &'a AccountInfo<'b>, + lp_token_amount: u64, +) -> Result<(u64, u64), ProgramError> { + if lp_token_amount == 0 { + msg!("Error: LP token amount must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let (token_a_balance, token_b_balance) = + get_pool_token_balances(pool_token_a_account, pool_token_b_account)?; + if token_a_balance == 0 && token_b_balance == 0 { + return Ok((0, 0)); + } + let lp_token_supply = account::get_token_supply(lp_token_mint)?; + if lp_token_supply == 0 { + return Ok((0, 0)); + } + let stake = lp_token_amount as f64 / lp_token_supply as f64; + + Ok(( + math::checked_as_u64(token_a_balance as f64 * stake)?, + math::checked_as_u64(token_b_balance as f64 * stake)?, + )) +} + +pub fn get_pool_swap_amounts<'a, 'b>( + pool_token_a_account: &'a AccountInfo<'b>, + pool_token_b_account: &'a AccountInfo<'b>, + token_a_amount_in: u64, + token_b_amount_in: u64, +) -> Result<(u64, u64), ProgramError> { + if (token_a_amount_in == 0 && token_b_amount_in == 0) + || (token_a_amount_in > 0 && token_b_amount_in > 0) + { + msg!("Error: One and only one of token amounts must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let (token_a_balance, token_b_balance) = + get_pool_token_balances(pool_token_a_account, pool_token_b_account)?; + if token_a_balance == 0 || token_b_balance == 0 { + msg!("Error: Can't swap in an empty pool"); + return Err(ProgramError::Custom(412)); + } + let token_a_balance = token_a_balance as f64; + let token_b_balance = token_b_balance as f64; + if token_a_amount_in == 0 { + // b to a + let amount_in_no_fee = ((token_b_amount_in as f64 * (1.0 - ORCA_FEE)) as u64) as f64; + let estimated_token_a_amount = (token_a_balance + - token_a_balance * token_b_balance / (token_b_balance + amount_in_no_fee)) + as u64; + + Ok((token_b_amount_in, estimated_token_a_amount)) + } else { + // a to b + let amount_in_no_fee = ((token_a_amount_in as f64 * (1.0 - ORCA_FEE)) as u64) as f64; + let estimated_token_b_amount = (token_b_balance + - token_a_balance * token_b_balance / (token_a_balance + amount_in_no_fee)) + as u64; + + Ok((token_a_amount_in, estimated_token_b_amount)) + } +} + +pub fn estimate_lp_tokens_amount( + lp_token_mint: &AccountInfo, + token_a_deposit: u64, + token_b_deposit: u64, + pool_token_a_balance: u64, + pool_token_b_balance: u64, +) -> Result { + if pool_token_a_balance != 0 && pool_token_b_balance != 0 { + Ok(std::cmp::min( + math::checked_as_u64( + (token_a_deposit as f64 / pool_token_a_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + )?, + math::checked_as_u64( + (token_b_deposit as f64 / pool_token_b_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + )?, + )) + } else if pool_token_a_balance != 0 { + math::checked_as_u64( + (token_a_deposit as f64 / pool_token_a_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + ) + } else if pool_token_b_balance != 0 { + math::checked_as_u64( + (token_b_deposit as f64 / pool_token_b_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + ) + } else { + Ok(0) + } +} diff --git a/farms/farm-sdk/src/program/protocol/raydium.rs b/farms/farm-sdk/src/program/protocol/raydium.rs new file mode 100644 index 00000000000..1311ebf0f12 --- /dev/null +++ b/farms/farm-sdk/src/program/protocol/raydium.rs @@ -0,0 +1,811 @@ +//! Raydium specific functions + +use { + crate::{ + id::zero, + instruction::raydium::{ + RaydiumAddLiquidity, RaydiumRemoveLiquidity, RaydiumStake, RaydiumSwap, RaydiumUnstake, + }, + math, + pack::check_data_len, + program::account, + }, + arrayref::{array_ref, array_refs}, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::{invoke, invoke_signed}, + program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +pub mod raydium_v2 { + solana_program::declare_id!("RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr"); +} +pub mod raydium_v3 { + solana_program::declare_id!("27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv"); +} +pub mod raydium_v4 { + solana_program::declare_id!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); +} + +pub mod raydium_stake { + solana_program::declare_id!("EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q"); +} +pub mod raydium_stake_v4 { + solana_program::declare_id!("CBuCnLe26faBpcBP2fktp4rp8abpcAnTWft6ZrP5Q4T"); +} +pub mod raydium_stake_v5 { + solana_program::declare_id!("9KEPoZmtHUrBbhWN1v1KWLMkkvwY6WLtAVUCPRtRjP4z"); +} + +pub const RAYDIUM_FEE: f64 = 0.0025; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RaydiumUserStakeInfo { + pub state: u64, + pub farm_id: Pubkey, + pub stake_owner: Pubkey, + pub deposit_balance: u64, + pub reward_debt: u64, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RaydiumUserStakeInfoV4 { + pub state: u64, + pub farm_id: Pubkey, + pub stake_owner: Pubkey, + pub deposit_balance: u64, + pub reward_debt: u64, + pub reward_debt_b: u64, +} + +impl RaydiumUserStakeInfo { + pub const LEN: usize = 88; + + pub fn get_size(&self) -> usize { + RaydiumUserStakeInfo::LEN + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, RaydiumUserStakeInfo::LEN)?; + + let input = array_ref![input, 0, RaydiumUserStakeInfo::LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (state, farm_id, stake_owner, deposit_balance, reward_debt) = + array_refs![input, 8, 32, 32, 8, 8]; + + Ok(Self { + state: u64::from_le_bytes(*state), + farm_id: Pubkey::new_from_array(*farm_id), + stake_owner: Pubkey::new_from_array(*stake_owner), + deposit_balance: u64::from_le_bytes(*deposit_balance), + reward_debt: u64::from_le_bytes(*reward_debt), + }) + } +} + +impl RaydiumUserStakeInfoV4 { + pub const LEN: usize = 96; + + pub fn get_size(&self) -> usize { + RaydiumUserStakeInfoV4::LEN + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, RaydiumUserStakeInfoV4::LEN)?; + + let input = array_ref![input, 0, RaydiumUserStakeInfoV4::LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (state, farm_id, stake_owner, deposit_balance, reward_debt, reward_debt_b) = + array_refs![input, 8, 32, 32, 8, 8, 8]; + + Ok(Self { + state: u64::from_le_bytes(*state), + farm_id: Pubkey::new_from_array(*farm_id), + stake_owner: Pubkey::new_from_array(*stake_owner), + deposit_balance: u64::from_le_bytes(*deposit_balance), + reward_debt: u64::from_le_bytes(*reward_debt), + reward_debt_b: u64::from_le_bytes(*reward_debt_b), + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct AmmInfoV4 { + pub status: u64, + pub nonce: u64, + pub order_num: u64, + pub depth: u64, + pub coin_decimals: u64, + pub pc_decimals: u64, + pub state: u64, + pub reset_flag: u64, + pub min_size: u64, + pub vol_max_cut_ratio: u64, + pub amount_wave: u64, + pub coin_lot_size: u64, + pub pc_lot_size: u64, + pub min_price_multiplier: u64, + pub max_price_multiplier: u64, + pub sys_decimal_value: u64, + pub min_separate_numerator: u64, + pub min_separate_denominator: u64, + pub trade_fee_numerator: u64, + pub trade_fee_denominator: u64, + pub pnl_numerator: u64, + pub pnl_denominator: u64, + pub swap_fee_numerator: u64, + pub swap_fee_denominator: u64, + pub need_take_pnl_coin: u64, + pub need_take_pnl_pc: u64, + pub total_pnl_pc: u64, + pub total_pnl_coin: u64, + pub pool_total_deposit_pc: u128, + pub pool_total_deposit_coin: u128, + pub swap_coin_in_amount: u128, + pub swap_pc_out_amount: u128, + pub swap_coin_to_pc_fee: u64, + pub swap_pc_in_amount: u128, + pub swap_coin_out_amount: u128, + pub swap_pc_to_coin_fee: u64, + pub token_coin: Pubkey, + pub token_pc: Pubkey, + pub coin_mint: Pubkey, + pub pc_mint: Pubkey, + pub lp_mint: Pubkey, + pub open_orders: Pubkey, + pub market: Pubkey, + pub serum_dex: Pubkey, + pub target_orders: Pubkey, + pub withdraw_queue: Pubkey, + pub token_temp_lp: Pubkey, + pub amm_owner: Pubkey, + pub pnl_owner: Pubkey, +} + +impl AmmInfoV4 { + pub const LEN: usize = 752; + + pub fn get_size(&self) -> usize { + AmmInfoV4::LEN + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, AmmInfoV4::LEN)?; + + let input = array_ref![input, 0, AmmInfoV4::LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + status, + nonce, + order_num, + depth, + coin_decimals, + pc_decimals, + state, + reset_flag, + min_size, + vol_max_cut_ratio, + amount_wave, + coin_lot_size, + pc_lot_size, + min_price_multiplier, + max_price_multiplier, + sys_decimal_value, + min_separate_numerator, + min_separate_denominator, + trade_fee_numerator, + trade_fee_denominator, + pnl_numerator, + pnl_denominator, + swap_fee_numerator, + swap_fee_denominator, + need_take_pnl_coin, + need_take_pnl_pc, + total_pnl_pc, + total_pnl_coin, + pool_total_deposit_pc, + pool_total_deposit_coin, + swap_coin_in_amount, + swap_pc_out_amount, + swap_coin_to_pc_fee, + swap_pc_in_amount, + swap_coin_out_amount, + swap_pc_to_coin_fee, + token_coin, + token_pc, + coin_mint, + pc_mint, + lp_mint, + open_orders, + market, + serum_dex, + target_orders, + withdraw_queue, + token_temp_lp, + amm_owner, + pnl_owner, + ) = array_refs![ + input, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 16, 16, 16, 16, 8, 16, 16, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 + ]; + + Ok(Self { + status: u64::from_le_bytes(*status), + nonce: u64::from_le_bytes(*nonce), + order_num: u64::from_le_bytes(*order_num), + depth: u64::from_le_bytes(*depth), + coin_decimals: u64::from_le_bytes(*coin_decimals), + pc_decimals: u64::from_le_bytes(*pc_decimals), + state: u64::from_le_bytes(*state), + reset_flag: u64::from_le_bytes(*reset_flag), + min_size: u64::from_le_bytes(*min_size), + vol_max_cut_ratio: u64::from_le_bytes(*vol_max_cut_ratio), + amount_wave: u64::from_le_bytes(*amount_wave), + coin_lot_size: u64::from_le_bytes(*coin_lot_size), + pc_lot_size: u64::from_le_bytes(*pc_lot_size), + min_price_multiplier: u64::from_le_bytes(*min_price_multiplier), + max_price_multiplier: u64::from_le_bytes(*max_price_multiplier), + sys_decimal_value: u64::from_le_bytes(*sys_decimal_value), + min_separate_numerator: u64::from_le_bytes(*min_separate_numerator), + min_separate_denominator: u64::from_le_bytes(*min_separate_denominator), + trade_fee_numerator: u64::from_le_bytes(*trade_fee_numerator), + trade_fee_denominator: u64::from_le_bytes(*trade_fee_denominator), + pnl_numerator: u64::from_le_bytes(*pnl_numerator), + pnl_denominator: u64::from_le_bytes(*pnl_denominator), + swap_fee_numerator: u64::from_le_bytes(*swap_fee_numerator), + swap_fee_denominator: u64::from_le_bytes(*swap_fee_denominator), + need_take_pnl_coin: u64::from_le_bytes(*need_take_pnl_coin), + need_take_pnl_pc: u64::from_le_bytes(*need_take_pnl_pc), + total_pnl_pc: u64::from_le_bytes(*total_pnl_pc), + total_pnl_coin: u64::from_le_bytes(*total_pnl_coin), + pool_total_deposit_pc: u128::from_le_bytes(*pool_total_deposit_pc), + pool_total_deposit_coin: u128::from_le_bytes(*pool_total_deposit_coin), + swap_coin_in_amount: u128::from_le_bytes(*swap_coin_in_amount), + swap_pc_out_amount: u128::from_le_bytes(*swap_pc_out_amount), + swap_coin_to_pc_fee: u64::from_le_bytes(*swap_coin_to_pc_fee), + swap_pc_in_amount: u128::from_le_bytes(*swap_pc_in_amount), + swap_coin_out_amount: u128::from_le_bytes(*swap_coin_out_amount), + swap_pc_to_coin_fee: u64::from_le_bytes(*swap_pc_to_coin_fee), + token_coin: Pubkey::new_from_array(*token_coin), + token_pc: Pubkey::new_from_array(*token_pc), + coin_mint: Pubkey::new_from_array(*coin_mint), + pc_mint: Pubkey::new_from_array(*pc_mint), + lp_mint: Pubkey::new_from_array(*lp_mint), + open_orders: Pubkey::new_from_array(*open_orders), + market: Pubkey::new_from_array(*market), + serum_dex: Pubkey::new_from_array(*serum_dex), + target_orders: Pubkey::new_from_array(*target_orders), + withdraw_queue: Pubkey::new_from_array(*withdraw_queue), + token_temp_lp: Pubkey::new_from_array(*token_temp_lp), + amm_owner: Pubkey::new_from_array(*amm_owner), + pnl_owner: Pubkey::new_from_array(*pnl_owner), + }) + } +} + +pub fn check_pool_program_id(program_id: &Pubkey) -> bool { + program_id == &raydium_v2::id() + || program_id == &raydium_v3::id() + || program_id == &raydium_v4::id() +} + +pub fn check_stake_program_id(program_id: &Pubkey) -> bool { + program_id == &raydium_stake::id() + || program_id == &raydium_stake_v4::id() + || program_id == &raydium_stake_v5::id() +} + +/// Returns amount of LP tokens staked as recorded in the specified stake account +pub fn get_stake_account_balance(stake_account: &AccountInfo) -> Result { + let data = stake_account.try_borrow_data()?; + if data.len() == RaydiumUserStakeInfoV4::LEN { + Ok(RaydiumUserStakeInfoV4::unpack(&data)?.deposit_balance) + } else if data.len() == RaydiumUserStakeInfo::LEN { + Ok(RaydiumUserStakeInfo::unpack(&data)?.deposit_balance) + } else { + Err(ProgramError::InvalidAccountData) + } +} + +pub fn get_pool_token_balances<'a, 'b>( + pool_coin_token_account: &'a AccountInfo<'b>, + pool_pc_token_account: &'a AccountInfo<'b>, + amm_open_orders: &'a AccountInfo<'b>, + amm_id: &'a AccountInfo<'b>, +) -> Result<(u64, u64), ProgramError> { + // get token balances + let mut token_a_balance = account::get_token_balance(pool_coin_token_account)?; + let mut token_b_balance = account::get_token_balance(pool_pc_token_account)?; + + // adjust with open orders + if amm_open_orders.data_len() == 3228 { + let open_orders_data = amm_open_orders.try_borrow_data()?; + let base_token_total = array_ref![open_orders_data, 85, 8]; + let quote_token_total = array_ref![open_orders_data, 101, 8]; + + token_a_balance += u64::from_le_bytes(*base_token_total); + token_b_balance += u64::from_le_bytes(*quote_token_total); + } + + // adjust with amm take pnl + let (pnl_coin_offset, pnl_pc_offset) = if amm_id.data_len() == 624 { + (136, 144) + } else if amm_id.data_len() == 680 { + (144, 152) + } else if amm_id.data_len() == 752 { + (192, 200) + } else { + (0, 0) + }; + if pnl_coin_offset > 0 { + let amm_id_data = amm_id.try_borrow_data()?; + let need_take_pnl_coin = u64::from_le_bytes(*array_ref![amm_id_data, pnl_coin_offset, 8]); + let need_take_pnl_pc = u64::from_le_bytes(*array_ref![amm_id_data, pnl_pc_offset, 8]); + + token_a_balance -= if need_take_pnl_coin < token_a_balance { + need_take_pnl_coin + } else { + token_a_balance + }; + token_b_balance -= if need_take_pnl_pc < token_b_balance { + need_take_pnl_pc + } else { + token_b_balance + }; + } + + Ok((token_a_balance, token_b_balance)) +} + +pub fn get_pool_deposit_amounts<'a, 'b>( + pool_coin_token_account: &'a AccountInfo<'b>, + pool_pc_token_account: &'a AccountInfo<'b>, + amm_open_orders: &'a AccountInfo<'b>, + amm_id: &'a AccountInfo<'b>, + max_coin_token_amount: u64, + max_pc_token_amount: u64, +) -> Result<(u64, u64), ProgramError> { + if max_coin_token_amount > 0 && max_pc_token_amount > 0 { + return Ok((max_coin_token_amount, max_pc_token_amount)); + } + if max_coin_token_amount == 0 && max_pc_token_amount == 0 { + msg!("Error: At least one of token amounts must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let mut coin_token_amount = max_coin_token_amount; + let mut pc_token_amount = max_pc_token_amount; + let (coin_balance, pc_balance) = get_pool_token_balances( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + )?; + if coin_balance == 0 || pc_balance == 0 { + msg!("Error: Both amounts must be specified for the initial deposit to an empty pool"); + return Err(ProgramError::InvalidArgument); + } + if max_coin_token_amount == 0 { + let estimated_coin_amount = math::checked_as_u64( + coin_balance as f64 * max_pc_token_amount as f64 / (pc_balance as f64), + )?; + coin_token_amount = if estimated_coin_amount > 1 { + estimated_coin_amount - 1 + } else { + 0 + }; + } else { + pc_token_amount = math::checked_as_u64( + pc_balance as f64 * max_coin_token_amount as f64 / (coin_balance as f64), + )?; + } + Ok((coin_token_amount, math::checked_add(pc_token_amount, 1)?)) +} + +pub fn get_pool_withdrawal_amounts<'a, 'b>( + pool_coin_token_account: &'a AccountInfo<'b>, + pool_pc_token_account: &'a AccountInfo<'b>, + amm_open_orders: &'a AccountInfo<'b>, + amm_id: &'a AccountInfo<'b>, + lp_token_mint: &'a AccountInfo<'b>, + lp_token_amount: u64, +) -> Result<(u64, u64), ProgramError> { + if lp_token_amount == 0 { + msg!("Error: LP token amount must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let (coin_balance, pc_balance) = get_pool_token_balances( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + )?; + if coin_balance == 0 && pc_balance == 0 { + return Ok((0, 0)); + } + let lp_token_supply = account::get_token_supply(lp_token_mint)?; + if lp_token_supply == 0 { + return Ok((0, 0)); + } + let stake = lp_token_amount as f64 / lp_token_supply as f64; + + Ok(( + math::checked_as_u64(coin_balance as f64 * stake)?, + math::checked_as_u64(pc_balance as f64 * stake)?, + )) +} + +pub fn get_pool_swap_amounts<'a, 'b>( + pool_coin_token_account: &'a AccountInfo<'b>, + pool_pc_token_account: &'a AccountInfo<'b>, + amm_open_orders: &'a AccountInfo<'b>, + amm_id: &'a AccountInfo<'b>, + coin_token_amount_in: u64, + pc_token_amount_in: u64, +) -> Result<(u64, u64), ProgramError> { + if (coin_token_amount_in == 0 && pc_token_amount_in == 0) + || (coin_token_amount_in > 0 && pc_token_amount_in > 0) + { + msg!("Error: One and only one of token amounts must be non-zero"); + return Err(ProgramError::InvalidArgument); + } + let (coin_balance, pc_balance) = get_pool_token_balances( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + )?; + if coin_balance == 0 || pc_balance == 0 { + msg!("Error: Can't swap in an empty pool"); + return Err(ProgramError::Custom(412)); + } + if coin_token_amount_in == 0 { + // pc to coin + let amount_in_no_fee = (pc_token_amount_in as f64 * (1.0 - RAYDIUM_FEE)) as u64; + let estimated_coin_amount = math::checked_as_u64( + coin_balance as f64 * amount_in_no_fee as f64 + / (pc_balance as f64 + amount_in_no_fee as f64), + )?; + Ok(( + pc_token_amount_in, + if estimated_coin_amount > 1 { + estimated_coin_amount - 1 + } else { + 0 + }, + )) + } else { + // coin to pc + let amount_in_no_fee = (coin_token_amount_in as f64 * (1.0 - RAYDIUM_FEE)) as u64; + let estimated_pc_amount = math::checked_as_u64( + pc_balance as f64 * amount_in_no_fee as f64 + / (coin_balance as f64 + amount_in_no_fee as f64), + )?; + Ok(( + coin_token_amount_in, + if estimated_pc_amount > 1 { + estimated_pc_amount - 1 + } else { + 0 + }, + )) + } +} + +pub fn estimate_lp_tokens_amount( + lp_token_mint: &AccountInfo, + token_a_deposit: u64, + token_b_deposit: u64, + pool_coin_balance: u64, + pool_pc_balance: u64, +) -> Result { + if pool_coin_balance != 0 && pool_pc_balance != 0 { + Ok(std::cmp::min( + math::checked_as_u64( + (token_a_deposit as f64 / pool_coin_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + )?, + math::checked_as_u64( + (token_b_deposit as f64 / pool_pc_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + )?, + )) + } else if pool_coin_balance != 0 { + math::checked_as_u64( + (token_a_deposit as f64 / pool_coin_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + ) + } else if pool_pc_balance != 0 { + math::checked_as_u64( + (token_b_deposit as f64 / pool_pc_balance as f64) + * account::get_token_supply(lp_token_mint)? as f64, + ) + } else { + Ok(0) + } +} + +pub fn add_liquidity( + accounts: &[AccountInfo], + max_coin_token_amount: u64, + max_pc_token_amount: u64, +) -> ProgramResult { + if let [user_account, user_token_a_account, user_token_b_account, user_lp_token_account, pool_program_id, pool_coin_token_account, pool_pc_token_account, lp_token_mint, spl_token_id, amm_id, amm_authority, amm_open_orders, amm_target, serum_market] = + accounts + { + if !check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new_readonly(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new_readonly(*serum_market.key, false), + AccountMeta::new(*user_token_a_account.key, false), + AccountMeta::new(*user_token_b_account.key, false), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new_readonly(*user_account.key, true), + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumAddLiquidity { + instruction: 3, + max_coin_token_amount, + max_pc_token_amount, + base_side: 0, + } + .to_vec()?, + }; + invoke(&instruction, accounts) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn add_liquidity_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + max_coin_token_amount: u64, + max_pc_token_amount: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, lp_token_custody_account, pool_program_id, pool_coin_token_account, pool_pc_token_account, lp_token_mint, spl_token_id, amm_id, amm_authority, amm_open_orders, amm_target, serum_market] = + accounts + { + if !check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new_readonly(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new_readonly(*serum_market.key, false), + AccountMeta::new(*token_a_custody_account.key, false), + AccountMeta::new(*token_b_custody_account.key, false), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumAddLiquidity { + instruction: 3, + max_coin_token_amount, + max_pc_token_amount, + base_side: 0, + } + .to_vec()?, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn remove_liquidity_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, lp_token_custody_account, pool_program_id, pool_withdraw_queue, pool_temp_lp_token_account, pool_coin_token_account, pool_pc_token_account, lp_token_mint, spl_token_id, amm_id, amm_authority, amm_open_orders, amm_target, serum_market, serum_program_id, serum_coin_vault_account, serum_pc_vault_account, serum_vault_signer] = + accounts + { + if !check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new(*pool_withdraw_queue.key, false), + AccountMeta::new(*pool_temp_lp_token_account.key, false), + AccountMeta::new_readonly(*serum_program_id.key, false), + AccountMeta::new(*serum_market.key, false), + AccountMeta::new(*serum_coin_vault_account.key, false), + AccountMeta::new(*serum_pc_vault_account.key, false), + AccountMeta::new_readonly(*serum_vault_signer.key, false), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new(*token_a_custody_account.key, false), + AccountMeta::new(*token_b_custody_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumRemoveLiquidity { + instruction: 4, + amount: if amount > 0 { + amount + } else { + account::get_token_balance(lp_token_custody_account)? + }, + } + .to_vec()?, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn stake_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, stake_info_account, lp_token_custody_account, token_a_custody_account, token_b_custody_account, pool_program_id, farm_lp_token_account, farm_reward_token_a_account, farm_reward_token_b_account, clock_id, spl_token_id, farm_id, farm_authority] = + accounts + { + if !check_stake_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let mut raydium_accounts = vec![ + AccountMeta::new(*farm_id.key, false), + AccountMeta::new_readonly(*farm_authority.key, false), + AccountMeta::new(*stake_info_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new(*farm_lp_token_account.key, false), + AccountMeta::new(*token_a_custody_account.key, false), + AccountMeta::new(*farm_reward_token_a_account.key, false), + AccountMeta::new_readonly(*clock_id.key, false), + AccountMeta::new_readonly(*spl_token_id.key, false), + ]; + if *farm_reward_token_b_account.key != zero::id() { + raydium_accounts.push(AccountMeta::new(*token_b_custody_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_b_account.key, false)); + } + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumStake { + instruction: 1, + amount, + } + .to_vec()?, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn swap_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount_in: u64, + min_amount_out: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, pool_program_id, pool_coin_token_account, pool_pc_token_account, spl_token_id, amm_id, amm_authority, amm_open_orders, amm_target, serum_market, serum_program_id, serum_bids, serum_asks, serum_event_queue, serum_coin_vault_account, serum_pc_vault_account, serum_vault_signer] = + accounts + { + if !check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new_readonly(*serum_program_id.key, false), + AccountMeta::new(*serum_market.key, false), + AccountMeta::new(*serum_bids.key, false), + AccountMeta::new(*serum_asks.key, false), + AccountMeta::new(*serum_event_queue.key, false), + AccountMeta::new(*serum_coin_vault_account.key, false), + AccountMeta::new(*serum_pc_vault_account.key, false), + AccountMeta::new_readonly(*serum_vault_signer.key, false), + AccountMeta::new(*token_a_custody_account.key, false), + AccountMeta::new(*token_b_custody_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumSwap { + instruction: 9, + amount_in, + min_amount_out, + } + .to_vec()?, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn unstake_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, stake_info_account, lp_token_custody_account, token_a_custody_account, token_b_custody_account, pool_program_id, farm_lp_token_account, farm_reward_token_a_account, farm_reward_token_b_account, clock_id, spl_token_id, farm_id, farm_authority] = + accounts + { + if !check_stake_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let mut raydium_accounts = vec![ + AccountMeta::new(*farm_id.key, false), + AccountMeta::new_readonly(*farm_authority.key, false), + AccountMeta::new(*stake_info_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new(*farm_lp_token_account.key, false), + AccountMeta::new(*token_a_custody_account.key, false), + AccountMeta::new(*farm_reward_token_a_account.key, false), + AccountMeta::new_readonly(*clock_id.key, false), + AccountMeta::new_readonly(*spl_token_id.key, false), + ]; + if *farm_reward_token_b_account.key != zero::id() { + raydium_accounts.push(AccountMeta::new(*token_b_custody_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_b_account.key, false)); + } + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumUnstake { + instruction: 2, + amount, + } + .to_vec()?, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/farm-sdk/src/program/protocol/saber.rs b/farms/farm-sdk/src/program/protocol/saber.rs new file mode 100644 index 00000000000..037719e0e4c --- /dev/null +++ b/farms/farm-sdk/src/program/protocol/saber.rs @@ -0,0 +1,623 @@ +//! Saber specific functions + +use { + crate::{id::zero, pack::check_data_len, program::account}, + arrayref::{array_ref, array_refs}, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + program::{invoke, invoke_signed}, + program_error::ProgramError, + pubkey::Pubkey, + system_program, + }, + stable_swap_client::instruction, +}; + +pub const SABER_FEE: f64 = 0.1; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Miner { + /// Key of the [Quarry] this [Miner] works on. + pub quarry_key: Pubkey, + /// Authority who manages this [Miner]. + /// All withdrawals of tokens must accrue to [TokenAccount]s owned by this account. + pub authority: Pubkey, + + /// Bump. + pub bump: u8, + + /// [TokenAccount] to hold the [Miner]'s staked LP tokens. + pub token_vault_key: Pubkey, + + /// Stores the amount of tokens that the [Miner] may claim. + /// Whenever the [Miner] claims tokens, this is reset to 0. + pub rewards_earned: u64, + + /// A checkpoint of the [Quarry]'s reward tokens paid per staked token. + /// + /// When the [Miner] is initialized, this number starts at 0. + /// On the first [quarry_mine::stake_tokens], the [Quarry]#update_rewards_and_miner + /// method is called, which updates this checkpoint to the current quarry value. + /// + /// On a [quarry_mine::claim_rewards], the difference in checkpoints is used to calculate + /// the amount of tokens owed. + pub rewards_per_token_paid: u128, + + /// Number of tokens the [Miner] holds. + pub balance: u64, + + /// Index of the [Miner]. + pub index: u64, +} + +impl Miner { + pub const LEN: usize = 145; + + pub fn get_size(&self) -> usize { + Miner::LEN + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, Miner::LEN)?; + + let input = array_ref![input, 8, Miner::LEN - 8]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + quarry_key, + authority, + bump, + token_vault_key, + rewards_earned, + rewards_per_token_paid, + balance, + index, + ) = array_refs![input, 32, 32, 1, 32, 8, 16, 8, 8]; + + Ok(Self { + quarry_key: Pubkey::new_from_array(*quarry_key), + authority: Pubkey::new_from_array(*authority), + bump: bump[0], + token_vault_key: Pubkey::new_from_array(*token_vault_key), + rewards_earned: u64::from_le_bytes(*rewards_earned), + rewards_per_token_paid: u128::from_le_bytes(*rewards_per_token_paid), + balance: u64::from_le_bytes(*balance), + index: u64::from_le_bytes(*index), + }) + } +} + +/// Returns amount of LP tokens staked as recorded in the specified stake account +pub fn get_stake_account_balance(stake_account: &AccountInfo) -> Result { + let data = stake_account.try_borrow_data()?; + Ok(Miner::unpack(&data)?.balance) +} + +pub fn get_pool_token_balances<'a, 'b>( + pool_token_a_account: &'a AccountInfo<'b>, + pool_token_b_account: &'a AccountInfo<'b>, +) -> Result<(u64, u64), ProgramError> { + Ok(( + account::get_token_balance(pool_token_a_account)?, + account::get_token_balance(pool_token_b_account)?, + )) +} + +#[allow(clippy::too_many_arguments)] +pub fn wrap_token<'a, 'b>( + wrapper: &'a AccountInfo<'b>, + wrapped_token_mint: &'a AccountInfo<'b>, + wrapper_vault: &'a AccountInfo<'b>, + owner: &'a AccountInfo<'b>, + underlying_token_account: &'a AccountInfo<'b>, + wrapped_token_account: &'a AccountInfo<'b>, + decimal_wrapper_program: &Pubkey, + amount: u64, +) -> ProgramResult { + decimal_wrapper_invoke( + wrapper, + wrapped_token_mint, + wrapper_vault, + owner, + underlying_token_account, + wrapped_token_account, + decimal_wrapper_program, + "global:deposit", + &[&[&[]]], + amount, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn unwrap_token<'a, 'b>( + wrapper: &'a AccountInfo<'b>, + wrapped_token_mint: &'a AccountInfo<'b>, + wrapper_vault: &'a AccountInfo<'b>, + owner: &'a AccountInfo<'b>, + underlying_token_account: &'a AccountInfo<'b>, + wrapped_token_account: &'a AccountInfo<'b>, + decimal_wrapper_program: &Pubkey, + amount: u64, +) -> ProgramResult { + decimal_wrapper_invoke( + wrapper, + wrapped_token_mint, + wrapper_vault, + owner, + underlying_token_account, + wrapped_token_account, + decimal_wrapper_program, + "global:withdraw", + &[&[&[]]], + amount, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn wrap_token_with_seeds<'a, 'b>( + wrapper: &'a AccountInfo<'b>, + wrapped_token_mint: &'a AccountInfo<'b>, + wrapper_vault: &'a AccountInfo<'b>, + authority: &'a AccountInfo<'b>, + underlying_token_account: &'a AccountInfo<'b>, + wrapped_token_account: &'a AccountInfo<'b>, + decimal_wrapper_program: &Pubkey, + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + decimal_wrapper_invoke( + wrapper, + wrapped_token_mint, + wrapper_vault, + authority, + underlying_token_account, + wrapped_token_account, + decimal_wrapper_program, + "global:deposit", + seeds, + amount, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn unwrap_token_with_seeds<'a, 'b>( + wrapper: &'a AccountInfo<'b>, + wrapped_token_mint: &'a AccountInfo<'b>, + wrapper_vault: &'a AccountInfo<'b>, + authority: &'a AccountInfo<'b>, + underlying_token_account: &'a AccountInfo<'b>, + wrapped_token_account: &'a AccountInfo<'b>, + decimal_wrapper_program: &Pubkey, + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + decimal_wrapper_invoke( + wrapper, + wrapped_token_mint, + wrapper_vault, + authority, + underlying_token_account, + wrapped_token_account, + decimal_wrapper_program, + "global:withdraw", + seeds, + amount, + ) +} + +#[allow(clippy::too_many_arguments)] +fn decimal_wrapper_invoke<'a, 'b>( + wrapper: &'a AccountInfo<'b>, + wrapped_token_mint: &'a AccountInfo<'b>, + wrapper_vault: &'a AccountInfo<'b>, + owner: &'a AccountInfo<'b>, + underlying_token_account: &'a AccountInfo<'b>, + wrapped_token_account: &'a AccountInfo<'b>, + decimal_wrapper_program: &Pubkey, + instruction: &str, + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + let mut hasher = Hasher::default(); + hasher.hash(instruction.as_bytes()); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.extend_from_slice(&amount.to_le_bytes()); + + let accounts = vec![ + AccountMeta::new_readonly(*wrapper.key, false), + AccountMeta::new(*wrapped_token_mint.key, false), + AccountMeta::new(*wrapper_vault.key, false), + AccountMeta::new_readonly(*owner.key, true), + AccountMeta::new(*underlying_token_account.key, false), + AccountMeta::new(*wrapped_token_account.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + if seeds[0][0].is_empty() { + invoke( + &Instruction { + program_id: *decimal_wrapper_program, + data, + accounts, + }, + &[ + wrapper.clone(), + wrapped_token_mint.clone(), + wrapper_vault.clone(), + owner.clone(), + underlying_token_account.clone(), + wrapped_token_account.clone(), + ], + ) + } else { + invoke_signed( + &Instruction { + program_id: *decimal_wrapper_program, + data, + accounts, + }, + &[ + wrapper.clone(), + wrapped_token_mint.clone(), + wrapper_vault.clone(), + owner.clone(), + underlying_token_account.clone(), + wrapped_token_account.clone(), + ], + seeds, + ) + } +} + +pub fn user_init_with_seeds(accounts: &[AccountInfo], seeds: &[&[&[u8]]]) -> ProgramResult { + if let [authority_account, funding_account, farm_program_id, lp_token_mint, miner, miner_vault, quarry, rewarder] = + accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let (miner_derived, bump) = Pubkey::find_program_address( + &[ + b"Miner", + &quarry.key.to_bytes(), + &authority_account.key.to_bytes(), + ], + &quarry_mine::id(), + ); + + if &miner_derived != miner.key { + return Err(ProgramError::InvalidSeeds); + } + + let mut hasher = Hasher::default(); + hasher.hash(b"global:create_miner"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.push(bump); + + let saber_accounts = vec![ + AccountMeta::new(*authority_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*rewarder.key, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(*funding_account.key, true), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn add_liquidity( + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, +) -> ProgramResult { + if let [user_account, user_token_a_account, user_token_b_account, user_lp_token_account, pool_program_id, pool_token_a_account, pool_token_b_account, lp_token_mint, _spl_token_id, _clock_id, swap_account, swap_authority] = + accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let instruction = instruction::deposit( + &spl_token::id(), + swap_account.key, + swap_authority.key, + user_account.key, + user_token_a_account.key, + user_token_b_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + lp_token_mint.key, + user_lp_token_account.key, + max_token_a_amount, + max_token_b_amount, + 1, + )?; + + invoke(&instruction, accounts) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn add_liquidity_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + max_token_a_amount: u64, + max_token_b_amount: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, lp_token_custody_account, pool_program_id, pool_token_a_account, pool_token_b_account, lp_token_mint, _spl_token_id, _clock_id, swap_account, swap_authority] = + accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let instruction = instruction::deposit( + &spl_token::id(), + swap_account.key, + swap_authority.key, + authority_account.key, + token_a_custody_account.key, + token_b_custody_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + lp_token_mint.key, + lp_token_custody_account.key, + max_token_a_amount, + max_token_b_amount, + 1, + )?; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn remove_liquidity_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, lp_token_custody_account, pool_program_id, pool_token_a_account, pool_token_b_account, lp_token_mint, _spl_token_id, swap_account, swap_authority, fees_account_a, fees_account_b] = + accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let instruction = instruction::withdraw( + &spl_token::id(), + swap_account.key, + swap_authority.key, + authority_account.key, + lp_token_mint.key, + lp_token_custody_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + token_a_custody_account.key, + token_b_custody_account.key, + fees_account_a.key, + fees_account_b.key, + amount, + 1, + 1, + )?; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn stake_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, lp_token_custody_account, farm_program_id, _spl_token_id, miner, miner_vault, quarry, rewarder] = + accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let mut hasher = Hasher::default(); + hasher.hash(b"global:stake_tokens"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.extend_from_slice(&amount.to_le_bytes()); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn claim_rewards_with_seeds(accounts: &[AccountInfo], seeds: &[&[&[u8]]]) -> ProgramResult { + if let [authority_account, iou_token_custody_account, farm_program_id, _spl_token_id, _zero_id, miner, rewarder, minter, mint_wrapper, mint_wrapper_program, iou_token_mint, iou_fees_account, quarry] = + accounts + { + if &quarry_mine::id() != farm_program_id.key + || &quarry_mint_wrapper::id() != mint_wrapper_program.key + { + return Err(ProgramError::IncorrectProgramId); + } + + // harvest IOU rewards + let mut hasher = Hasher::default(); + hasher.hash(b"global:claim_rewards"); + + let data = hasher.result().as_ref()[..8].to_vec(); + + let saber_accounts = vec![ + AccountMeta::new(*mint_wrapper.key, false), + AccountMeta::new_readonly(*mint_wrapper_program.key, false), + AccountMeta::new(*minter.key, false), + AccountMeta::new(*iou_token_mint.key, false), + AccountMeta::new(*iou_token_custody_account.key, false), + AccountMeta::new(*iou_fees_account.key, false), + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(zero::id(), false), + AccountMeta::new(zero::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn redeem_rewards_with_seeds(accounts: &[AccountInfo], seeds: &[&[&[u8]]]) -> ProgramResult { + if let [authority_account, iou_token_custody_account, sbr_token_custody_account, _spl_token_id, redeemer, redeemer_program, sbr_token_mint, iou_token_mint, saber_vault, saber_mint_proxy_program, mint_proxy_authority, mint_proxy_state, minter_info] = + accounts + { + // convert IOU to Saber + let mut hasher = Hasher::default(); + hasher.hash(b"global:redeem_all_tokens_from_mint_proxy"); + + let data = hasher.result().as_ref()[..8].to_vec(); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*redeemer.key, false), + AccountMeta::new(*iou_token_mint.key, false), + AccountMeta::new(*sbr_token_mint.key, false), + AccountMeta::new(*saber_vault.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*iou_token_custody_account.key, false), + AccountMeta::new(*sbr_token_custody_account.key, false), + AccountMeta::new_readonly(*mint_proxy_authority.key, false), + AccountMeta::new_readonly(*mint_proxy_state.key, false), + AccountMeta::new_readonly(*saber_mint_proxy_program.key, false), + AccountMeta::new(*minter_info.key, false), + ]; + + let instruction = Instruction { + program_id: *redeemer_program.key, + accounts: saber_accounts, + data, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn swap_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount_in: u64, + min_amount_out: u64, +) -> ProgramResult { + if let [authority_account, token_a_custody_account, token_b_custody_account, pool_program_id, pool_token_a_account, pool_token_b_account, _spl_token_id, _clock_id, swap_account, swap_authority, fees_account] = + accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let instruction = instruction::swap( + &spl_token::id(), + swap_account.key, + swap_authority.key, + authority_account.key, + token_a_custody_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + token_b_custody_account.key, + fees_account.key, + amount_in, + min_amount_out, + )?; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} + +pub fn unstake_with_seeds( + accounts: &[AccountInfo], + seeds: &[&[&[u8]]], + amount: u64, +) -> ProgramResult { + if let [authority_account, lp_token_custody_account, farm_program_id, _spl_token_id, miner, miner_vault, quarry, rewarder] = + accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let mut hasher = Hasher::default(); + hasher.hash(b"global:withdraw_tokens"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.extend_from_slice(&amount.to_le_bytes()); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*authority_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new(*lp_token_custody_account.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke_signed(&instruction, accounts, seeds) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/farm-sdk/src/refdb.rs b/farms/farm-sdk/src/refdb.rs new file mode 100644 index 00000000000..2088fa38144 --- /dev/null +++ b/farms/farm-sdk/src/refdb.rs @@ -0,0 +1,1323 @@ +//! On-chain reference database + +use { + crate::{ + pack::{ + as64_deserialize, as64_serialize, check_data_len, pack_array_string64, + unpack_array_string64, + }, + string::ArrayString64, + traits::*, + }, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + solana_program::{program_error::ProgramError, pubkey::Pubkey}, + std::mem::size_of, +}; + +/// Whether to init refdb accounts from on-chain program or off-chain. +/// Main Router admin key is used as the Base address to derive refdb address +/// if off-chain initialization is selected. +/// Off-chain is required for accounts with data size > 10K. +/// This is temporary solution until realloc is implemented. +pub const REFDB_ONCHAIN_INIT: bool = false; + +/// Storage Header, one per account +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct Header { + pub counter: u32, + pub active_records: u32, + pub reference_type: ReferenceType, + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, +} + +impl Header { + pub const LEN: usize = 73; + const REF_TYPE_OFFSET: usize = 8; + const NAME_OFFSET: usize = 9; + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, Header::LEN)?; + + let output = array_mut_ref![output, 0, Header::LEN]; + + let (counter_out, active_records_out, reference_type_out, name_out) = + mut_array_refs![output, 4, 4, 1, 64]; + *counter_out = self.counter.to_le_bytes(); + *active_records_out = self.active_records.to_le_bytes(); + reference_type_out[0] = self.reference_type as u8; + pack_array_string64(&self.name, name_out); + + Ok(Header::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Header::LEN] = [0; Header::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, Header::LEN)?; + + let input = array_ref![input, 0, Header::LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, active_records, reference_type, name) = array_refs![input, 4, 4, 1, 64]; + + Ok(Self { + counter: u32::from_le_bytes(*counter), + active_records: u32::from_le_bytes(*active_records), + reference_type: ReferenceType::try_from_primitive(reference_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + name: unpack_array_string64(name)?, + }) + } +} + +/// Reference is a short, fixed size data field, used to store homogeneous value +/// or a link to the account with more detailed data +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub enum Reference { + Pubkey { data: Pubkey }, + U8 { data: u8 }, + U16 { data: u16 }, + U32 { data: u32 }, + U64 { data: u64 }, + F64 { data: f64 }, + Empty, +} + +impl Reference { + pub const MAX_LEN: usize = 32; + pub const PUBKEY_LEN: usize = size_of::(); + pub const U8_LEN: usize = size_of::(); + pub const U16_LEN: usize = size_of::(); + pub const U32_LEN: usize = size_of::(); + pub const U64_LEN: usize = size_of::(); + pub const F64_LEN: usize = size_of::(); + + pub const fn get_type(&self) -> ReferenceType { + match self { + Reference::Pubkey { .. } => ReferenceType::Pubkey, + Reference::U8 { .. } => ReferenceType::U8, + Reference::U16 { .. } => ReferenceType::U16, + Reference::U32 { .. } => ReferenceType::U32, + Reference::U64 { .. } => ReferenceType::U64, + Reference::F64 { .. } => ReferenceType::F64, + Reference::Empty => ReferenceType::Empty, + } + } +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum ReferenceType { + Pubkey, + U8, + U16, + U32, + U64, + F64, + Empty, +} + +impl ReferenceType { + pub const fn get_size(&self) -> usize { + match self { + ReferenceType::Pubkey => size_of::(), + ReferenceType::U8 => size_of::(), + ReferenceType::U16 => size_of::(), + ReferenceType::U32 => size_of::(), + ReferenceType::U64 => size_of::(), + ReferenceType::F64 => size_of::(), + ReferenceType::Empty => 0, + } + } +} + +impl std::fmt::Display for ReferenceType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + ReferenceType::Pubkey => write!(f, "Pubkey"), + ReferenceType::U8 => write!(f, "U8"), + ReferenceType::U16 => write!(f, "U16"), + ReferenceType::U32 => write!(f, "U32"), + ReferenceType::U64 => write!(f, "U64"), + ReferenceType::F64 => write!(f, "F64"), + ReferenceType::Empty => write!(f, "Empty"), + } + } +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum StorageType { + Program, + Vault, + Pool, + Farm, + Token, + Other, +} + +impl StorageType { + pub const fn get_default_size(storage_type: StorageType) -> usize { + match storage_type { + StorageType::Program => 25000usize, + StorageType::Vault => 25000usize, + StorageType::Pool => 50000usize, + StorageType::Farm => 25000usize, + StorageType::Token => 500000usize, + _ => 0usize, + } + } + + pub const fn get_default_max_records( + storage_type: StorageType, + reference_type: ReferenceType, + ) -> usize { + let record_size = Record::get_size_with_reference(reference_type); + (StorageType::get_default_size(storage_type) - Header::LEN) / record_size + } + + pub const fn get_storage_size_for_records( + reference_type: ReferenceType, + records_num: usize, + ) -> usize { + if records_num > u32::MAX as usize { + return 0; + } + let record_size = Record::get_size_with_reference(reference_type); + records_num * record_size + Header::LEN + } + + pub const fn get_storage_size_for_max_records( + storage_type: StorageType, + reference_type: ReferenceType, + ) -> usize { + StorageType::get_storage_size_for_records( + reference_type, + StorageType::get_default_max_records(storage_type, reference_type), + ) + } +} + +impl std::fmt::Display for StorageType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + StorageType::Program => write!(f, "Program"), + StorageType::Vault => write!(f, "Vault"), + StorageType::Pool => write!(f, "Pool"), + StorageType::Farm => write!(f, "Farm"), + StorageType::Token => write!(f, "Token"), + StorageType::Other => write!(f, "Other"), + } + } +} + +impl std::str::FromStr for StorageType { + type Err = ProgramError; + + fn from_str(s: &str) -> Result { + match s { + "Program" => Ok(StorageType::Program), + "Vault" => Ok(StorageType::Vault), + "Pool" => Ok(StorageType::Pool), + "Farm" => Ok(StorageType::Farm), + "Token" => Ok(StorageType::Token), + "Other" => Ok(StorageType::Other), + _ => Err(ProgramError::InvalidArgument), + } + } +} + +/// Data record; All records have the same reference type for single storage +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct Record { + // index is the record location index [0..total_records-1] and is not stored on-chain, + // but returned to the reader for more efficient consecutive read/writes. + // if index is set to None record will be looked up by name with linear search. + pub index: Option, + pub counter: u16, + pub tag: u16, + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, + pub reference: Reference, +} + +impl Named for Record { + fn name(&self) -> ArrayString64 { + self.name + } +} + +impl Record { + pub const NO_REF_LEN: usize = 68; + pub const MAX_LEN: usize = Record::NO_REF_LEN + Reference::MAX_LEN; + + pub const fn get_size(&self) -> usize { + match self.reference { + Reference::Pubkey { .. } => Record::NO_REF_LEN + size_of::(), + Reference::U8 { .. } => Record::NO_REF_LEN + size_of::(), + Reference::U16 { .. } => Record::NO_REF_LEN + size_of::(), + Reference::U32 { .. } => Record::NO_REF_LEN + size_of::(), + Reference::U64 { .. } => Record::NO_REF_LEN + size_of::(), + Reference::F64 { .. } => Record::NO_REF_LEN + size_of::(), + Reference::Empty => Record::NO_REF_LEN, + } + } + + pub const fn get_size_with_reference(reference_type: ReferenceType) -> usize { + Record::NO_REF_LEN + reference_type.get_size() + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + let record_size = self.get_size(); + check_data_len(output, record_size)?; + + match self.reference { + Reference::Pubkey { data } => self.pack_with_pubkey(output, &data), + Reference::U8 { data } => self.pack_with_u8(output, data), + Reference::U16 { data } => self.pack_with_u16(output, data), + Reference::U32 { data } => self.pack_with_u32(output, data), + Reference::U64 { data } => self.pack_with_u64(output, data), + Reference::F64 { data } => self.pack_with_f64(output, data), + Reference::Empty => self.pack_with_empty(output), + } + + Ok(record_size) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Record::MAX_LEN] = [0; Record::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack( + input: &[u8], + reference_type: ReferenceType, + index: Option, + ) -> Result { + let record_size = Record::NO_REF_LEN + reference_type.get_size(); + check_data_len(input, record_size)?; + + match reference_type { + ReferenceType::Pubkey => Record::unpack_with_pubkey(input, index), + ReferenceType::U8 => Record::unpack_with_u8(input, index), + ReferenceType::U16 => Record::unpack_with_u16(input, index), + ReferenceType::U32 => Record::unpack_with_u32(input, index), + ReferenceType::U64 => Record::unpack_with_u64(input, index), + ReferenceType::F64 => Record::unpack_with_f64(input, index), + ReferenceType::Empty => Record::unpack_with_empty(input, index), + } + } + + pub fn unpack_counter(input: &[u8]) -> Result { + check_data_len(input, Record::NO_REF_LEN)?; + let counter = array_ref![input, 0, 2]; + Ok(u16::from_le_bytes(*counter) as usize) + } + + pub fn unpack_counter_and_name(input: &[u8]) -> Result<(usize, ArrayString64), ProgramError> { + check_data_len(input, Record::NO_REF_LEN)?; + let counter = array_ref![input, 0, 2]; + let name = array_ref![input, 4, 64]; + Ok(( + u16::from_le_bytes(*counter) as usize, + unpack_array_string64(name)?, + )) + } + + fn pack_with_pubkey(&self, output: &mut [u8], data: &Pubkey) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::PUBKEY_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::PUBKEY_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + reference_out.copy_from_slice(data.as_ref()); + } + + fn pack_with_u8(&self, output: &mut [u8], data: u8) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::U8_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::U8_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + *reference_out = data.to_le_bytes(); + } + + fn pack_with_u16(&self, output: &mut [u8], data: u16) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::U16_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::U16_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + *reference_out = data.to_le_bytes(); + } + + fn pack_with_u32(&self, output: &mut [u8], data: u32) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::U32_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::U32_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + *reference_out = data.to_le_bytes(); + } + + fn pack_with_u64(&self, output: &mut [u8], data: u64) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::U64_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::U64_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + *reference_out = data.to_le_bytes(); + } + + fn pack_with_f64(&self, output: &mut [u8], data: f64) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN + Reference::F64_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter_out, tag_out, name_out, reference_out) = + mut_array_refs![output, 2, 2, 64, Reference::F64_LEN]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + *reference_out = data.to_le_bytes(); + } + + fn pack_with_empty(&self, output: &mut [u8]) { + let output = array_mut_ref![output, 0, Record::NO_REF_LEN]; + let (counter_out, tag_out, name_out) = mut_array_refs![output, 2, 2, 64]; + *counter_out = self.counter.to_le_bytes(); + *tag_out = self.tag.to_le_bytes(); + pack_array_string64(&self.name, name_out); + } + + fn unpack_with_pubkey(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::PUBKEY_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::PUBKEY_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::Pubkey { + data: Pubkey::new_from_array(*reference), + }, + }) + } + + fn unpack_with_u8(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::U8_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::U8_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::U8 { data: reference[0] }, + }) + } + + fn unpack_with_u16(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::U16_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::U16_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::U16 { + data: u16::from_le_bytes(*reference), + }, + }) + } + + fn unpack_with_u32(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::U32_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::U32_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::U32 { + data: u32::from_le_bytes(*reference), + }, + }) + } + + fn unpack_with_u64(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::U64_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::U64_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::U64 { + data: u64::from_le_bytes(*reference), + }, + }) + } + + fn unpack_with_f64(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN + Reference::F64_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name, reference) = array_refs![input, 2, 2, 64, Reference::F64_LEN]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::F64 { + data: f64::from_le_bytes(*reference), + }, + }) + } + + fn unpack_with_empty(input: &[u8], index: Option) -> Result { + let input = array_ref![input, 0, Record::NO_REF_LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (counter, tag, name) = array_refs![input, 2, 2, 64]; + Ok(Self { + index, + counter: u16::from_le_bytes(*counter), + tag: u16::from_le_bytes(*tag), + name: unpack_array_string64(name)?, + reference: Reference::Empty, + }) + } +} + +/// RefDB manages homogeneous records in a given continuous storage +pub struct RefDB {} + +impl RefDB { + /// Initializes the storage, must be called before first read/write + pub fn init( + data: &mut [u8], + name: &ArrayString64, + reference_type: ReferenceType, + ) -> Result<(), ProgramError> { + if RefDB::is_initialized(data) { + return Err(ProgramError::AccountAlreadyInitialized); + } + let record_size = Record::NO_REF_LEN + reference_type.get_size(); + check_data_len(data, Header::LEN + record_size)?; + let header = Header { + counter: 1, + active_records: 0, + reference_type, + name: *name, + }; + header.pack(data)?; + Ok(()) + } + + /// Clears out underlying storage + pub fn drop(data: &mut [u8]) -> Result { + check_data_len(data, Header::LEN)?; + if data.len() > 2000 { + Err(ProgramError::Custom(431)) + } else { + data.fill(0); + Ok(data.len()) + } + } + + /// Checks if the storage is empty + pub fn is_empty(data: &[u8]) -> Result { + Ok(RefDB::get_active_records(data)? == 0) + } + + /// Checks if the storage is full + pub fn is_full(data: &[u8]) -> Result { + Ok(RefDB::get_free_records(data)? == 0) + } + + /// Checks if the storage has been updated + pub fn is_updated(data: &[u8], last_counter: usize) -> Result { + Ok(RefDB::get_storage_counter(data)? > last_counter) + } + + /// Checks if data storage has been initialized. + /// It can return false positives if storage is not managed by RefDB. + pub fn is_initialized(data: &[u8]) -> bool { + if let Ok(header) = Header::unpack(data) { + if let Ok(rec_size) = RefDB::get_record_size(data) { + if header.counter > 0 + && header.active_records as usize <= (data.len() - Header::LEN) / rec_size + { + return true; + } + } + } + false + } + + /// Returns unpacked storage header + pub fn get_storage_header(data: &[u8]) -> Result { + Header::unpack(data) + } + + /// Returns the storage updates counter + pub fn get_storage_counter(data: &[u8]) -> Result { + check_data_len(data, Header::LEN)?; + let counter = u32::from_le_bytes(*array_ref![data, 0, 4]) as usize; + if counter > 0 { + Ok(counter) + } else { + Err(ProgramError::UninitializedAccount) + } + } + + /// Sets the storage counter to the new value + pub fn set_storage_counter(data: &mut [u8], counter: usize) -> Result { + if counter == 0 { + return Err(ProgramError::InvalidArgument); + } + check_data_len(data, Header::LEN)?; + + let counter_out = array_mut_ref![data, 0, 4]; + *counter_out = (counter as u32).to_le_bytes(); + + Ok(counter) + } + + /// Returns the number of active records (not marked as deleted) + pub fn get_active_records(data: &[u8]) -> Result { + check_data_len(data, Header::LEN)?; + Ok(u32::from_le_bytes(*array_ref![data, 4, 4]) as usize) + } + + /// Sets the number of active records to the new value + pub fn set_active_records(data: &mut [u8], records: usize) -> Result { + check_data_len(data, Header::LEN)?; + + let records_out = array_mut_ref![data, 4, 4]; + *records_out = (records as u32).to_le_bytes(); + + Ok(records) + } + + /// Returns the number of free records + pub fn get_free_records(data: &[u8]) -> Result { + let rec_size = RefDB::get_record_size(data)?; + Ok((data.len() - RefDB::get_active_records(data)? * rec_size - Header::LEN) / rec_size) + } + + /// Returns total number of allocated records + pub fn get_total_records(data: &[u8]) -> Result { + Ok((data.len() - Header::LEN) / RefDB::get_record_size(data)?) + } + + /// Returns the length of a single record + pub fn get_record_size(data: &[u8]) -> Result { + Ok(Record::NO_REF_LEN + RefDB::get_reference_type(data)?.get_size()) + } + + /// Returns the type of reference data + pub fn get_reference_type(data: &[u8]) -> Result { + check_data_len(data, Header::LEN)?; + ReferenceType::try_from_primitive(data[Header::REF_TYPE_OFFSET]) + .or(Err(ProgramError::InvalidAccountData)) + } + + /// Returns the name of the DB + pub fn get_name(data: &[u8]) -> Result { + check_data_len(data, Header::LEN)?; + let name = array_ref![data, Header::NAME_OFFSET, 64]; + unpack_array_string64(name) + } + + /// Returns the record associated with the given name + pub fn read(data: &[u8], name: &ArrayString64) -> Result, ProgramError> { + if let Some(index) = RefDB::find_index(data, name)? { + RefDB::read_at(data, index) + } else { + Ok(None) + } + } + + /// Returns the record at the given index + pub fn read_at(data: &[u8], index: usize) -> Result, ProgramError> { + let offset = RefDB::get_offset(data, index)?; + let ref_type = RefDB::get_reference_type(data)?; + let record = Record::unpack(&data[offset..], ref_type, Some(index as u32))?; + if record.counter > 0 { + Ok(Some(record)) + } else { + Ok(None) + } + } + + /// Returns the record only if it has been updated + pub fn read_if_changed( + data: &[u8], + name: &ArrayString64, + last_counter: usize, + ) -> Result, ProgramError> { + if let Some(index) = RefDB::find_index(data, name)? { + RefDB::read_at_if_changed(data, index, last_counter) + } else { + Ok(None) + } + } + + /// Returns the record at the given index only if it has been updated + pub fn read_at_if_changed( + data: &[u8], + index: usize, + last_counter: usize, + ) -> Result, ProgramError> { + let offset = RefDB::get_offset(data, index)?; + let counter = RefDB::get_record_counter(data, offset)?; + if counter > last_counter { + RefDB::read_at(data, index) + } else { + Ok(None) + } + } + + /// Returns all active records in the storage + pub fn read_all(data: &[u8]) -> Result, ProgramError> { + let rec_num = RefDB::get_total_records(data)?; + let active_rec = RefDB::get_active_records(data)?; + let mut vec: Vec = vec![]; + if active_rec == 0 { + return Ok(vec); + } + for i in 0..rec_num { + if let Some(rec) = RefDB::read_at(data, i)? { + vec.push(rec); + if vec.len() == active_rec { + return Ok(vec); + } + } + } + Err(ProgramError::InvalidAccountData) + } + + /// Returns all active records in the storage if any of them was updated + pub fn read_all_if_changed( + data: &[u8], + last_counter: usize, + ) -> Result, ProgramError> { + if RefDB::get_storage_counter(data)? > last_counter { + RefDB::read_all(data) + } else { + Ok(Vec::::default()) + } + } + + /// Writes the record to the storage. + /// Uses the index if provided or searches the record by name. + /// If counter is provided it must be equal to stored value or error is returned. + pub fn write(data: &mut [u8], record: &Record) -> Result { + let offset = if let Some(idx) = record.index { + // if the index was specified we will update existing record + RefDB::get_offset(data, idx as usize)? + } else { + // otherwise either find a record with the supplied name or first available slot + RefDB::find_write_offset(data, &record.name)? + }; + let (cur_counter, cur_name) = RefDB::get_record_counter_and_name(data, offset)?; + if cur_counter > 0 { + // if the counter was specified we check that value is equal to stored, + // to make sure there were no intermediate updates + if record.counter > 0 && cur_counter != record.counter as usize { + return Err(ProgramError::Custom(409)); + } + // check that we are either writing to an empty slot or record name matches + if record.index.is_some() && record.name != cur_name { + return Err(ProgramError::Custom(409)); + } + } + // check that reference data type matches storage + if RefDB::get_reference_type(data)? != record.reference.get_type() { + return Err(ProgramError::Custom(409)); + } + // update storage counters + let storage_counter = RefDB::get_storage_counter(data)?; + if (storage_counter as u32) < u32::MAX { + RefDB::set_storage_counter(data, storage_counter + 1)?; + } else { + RefDB::set_storage_counter(data, 1)?; + } + if cur_counter == 0 { + let active_records = RefDB::get_active_records(data)?; + if (active_records as u32) < u32::MAX { + RefDB::set_active_records(data, active_records + 1)?; + } + } + // write record + let res = record.pack(&mut data[offset..]); + // update record counter + if (cur_counter as u16) < u16::MAX { + RefDB::set_record_counter(data, offset, cur_counter + 1); + } else { + RefDB::set_record_counter(data, offset, 1); + } + res + } + + /// Updates the reference value in the storage for the record with the given name. + /// It doesn't validate storage type, counter or name. Should be used only if + /// record is active (i.e. to update existing record), you are certain that + /// the storage and index are correct, and you don't care if the record was + /// updated since last read time. + pub fn update( + data: &mut [u8], + name: &ArrayString64, + reference: &Reference, + ) -> Result { + if let Some(index) = RefDB::find_index(data, name)? { + RefDB::update_at(data, index, reference) + } else { + Err(ProgramError::Custom(404)) + } + } + + /// Updates the reference value in the storage at the given index. + /// It doesn't validate storage type, counter or name. Should be used only if + /// record is active (i.e. to update existing record), you are certain that + /// the storage and index are correct, and you don't care if the record was + /// updated since last read time. + pub fn update_at( + data: &mut [u8], + index: usize, + reference: &Reference, + ) -> Result { + let offset = RefDB::get_offset(data, index)?; + let mut cur_record = Record::unpack(&data[offset..], reference.get_type(), None)?; + // update storage counters + let storage_counter = RefDB::get_storage_counter(data)?; + if (storage_counter as u32) < u32::MAX { + RefDB::set_storage_counter(data, storage_counter + 1)?; + } else { + RefDB::set_storage_counter(data, 1)?; + } + if cur_record.counter == 0 { + return Err(ProgramError::UninitializedAccount); + } + // write record + if (cur_record.counter as u16) < u16::MAX { + cur_record.counter += 1; + } else { + cur_record.counter = 1; + } + cur_record.reference = *reference; + cur_record.pack(&mut data[offset..]) + } + + /// Deletes the record from the storage. + /// Uses the index if provided or searches the record by name. + /// If counter is provided it checks that it is equal to stored or error is returned. + pub fn delete(data: &mut [u8], record: &Record) -> Result { + let offset = if let Some(idx) = record.index { + // if the index was specified we will update existing record + RefDB::get_offset(data, idx as usize)? + } else { + // otherwise either find a record with the supplied name or first available slot + RefDB::find_write_offset(data, &record.name)? + }; + let data_end = offset + record.get_size(); + check_data_len(data, data_end)?; + let (stored_counter, stored_name) = RefDB::get_record_counter_and_name(data, offset)?; + if stored_counter == 0 { + return Ok(0); + } + // if the counter was specified we check that value is equal to stored, + // to make sure there were no intermediate updates + if record.counter > 0 && stored_counter != record.counter as usize { + return Err(ProgramError::Custom(409)); + } + // check that we are deleting record with matching name + if record.name != stored_name { + return Err(ProgramError::Custom(409)); + } + // update data and counters + let counter = RefDB::get_storage_counter(data)?; + if (counter as u32) < u32::MAX { + RefDB::set_storage_counter(data, counter + 1)?; + } else { + RefDB::set_storage_counter(data, 1)?; + } + let active_records = RefDB::get_active_records(data)?; + if active_records > 0 { + RefDB::set_active_records(data, active_records - 1)?; + } + data[offset..data_end].fill(0); + + Ok(data_end - offset) + } + + /// Deletes the record from the storage using the name only. + pub fn delete_with_name(data: &mut [u8], name: &ArrayString64) -> Result { + RefDB::delete( + data, + &Record { + index: None, + counter: 0, + tag: 0, + name: *name, + reference: Reference::Empty, + }, + ) + } + + /// Returns index of the record with the given name or None if not found + pub fn find_index(data: &[u8], name: &ArrayString64) -> Result, ProgramError> { + let rec_active = RefDB::get_active_records(data)?; + if rec_active == 0 { + return Ok(None); + } + let rec_num = RefDB::get_total_records(data)?; + let rec_size = RefDB::get_record_size(data)?; + let mut offset = Header::LEN; + let mut checked = 0; + for index in 0..rec_num { + let (counter, rec_name) = RefDB::get_record_counter_and_name(data, offset)?; + if counter > 0 { + if rec_name == *name { + return Ok(Some(index)); + } + checked += 1; + if checked == rec_active { + return Ok(None); + } + } + offset += rec_size; + } + Ok(None) + } + + /// Returns the index of the first empty record at the back of the storage, + /// i.e. there will be no active records after the index + pub fn find_last_index(data: &[u8]) -> Result { + let rec_active = RefDB::get_active_records(data)?; + if rec_active == 0 { + return Ok(0); + } + let rec_num = RefDB::get_total_records(data)?; + let rec_size = RefDB::get_record_size(data)?; + let mut offset = Header::LEN; + let mut checked = 0; + for index in 0..rec_num { + let counter = RefDB::get_record_counter(data, offset)?; + if counter > 0 { + checked += 1; + if checked == rec_active { + return Ok((index + 1) as u32); + } + } + offset += rec_size; + } + Err(ProgramError::InvalidAccountData) + } + + /// Returns the index of the next empty record to write to in the storage + pub fn find_next_index(data: &[u8]) -> Result { + let rec_active = RefDB::get_active_records(data)?; + if rec_active == 0 { + return Ok(0); + } + let rec_num = RefDB::get_total_records(data)?; + let rec_size = RefDB::get_record_size(data)?; + let mut offset = Header::LEN; + let mut checked = 0; + for index in 0..rec_num { + let counter = RefDB::get_record_counter(data, offset)?; + if counter == 0 { + return Ok(index as u32); + } else { + checked += 1; + if checked == rec_active { + return Ok((index + 1) as u32); + } + } + offset += rec_size; + } + Err(ProgramError::InvalidAccountData) + } + + // private helpers + + /// Returns offset of the record given its index + fn get_offset(data: &[u8], index: usize) -> Result { + let rec_size = RefDB::get_record_size(data)?; + let offset = Header::LEN + index * rec_size; + check_data_len(data, offset)?; + Ok(offset) + } + + fn find_write_offset(data: &[u8], name: &ArrayString64) -> Result { + let rec_active = RefDB::get_active_records(data)?; + if rec_active == 0 { + return Ok(Header::LEN); + } + let rec_num = RefDB::get_total_records(data)?; + let rec_size = RefDB::get_record_size(data)?; + let mut offset = Header::LEN; + let mut checked = 0; + let mut free_slot = 0; + for _ in 0..rec_num { + let (counter, rec_name) = RefDB::get_record_counter_and_name(data, offset)?; + if counter > 0 { + if rec_name == *name { + return Ok(offset); + } + checked += 1; + if checked == rec_active { + offset += rec_size; + break; + } + } else if free_slot == 0 { + free_slot = offset; + } + offset += rec_size; + } + if free_slot > 0 { + Ok(free_slot) + } else { + Ok(offset) + } + } + + fn get_record_counter(data: &[u8], offset: usize) -> Result { + Record::unpack_counter(&data[offset..]) + } + + fn set_record_counter(data: &mut [u8], offset: usize, counter: usize) { + if counter == 0 { + return; + } + let counter_out = array_mut_ref![data, offset, 2]; + *counter_out = (counter as u16).to_le_bytes(); + } + + fn get_record_counter_and_name( + data: &[u8], + offset: usize, + ) -> Result<(usize, ArrayString64), ProgramError> { + Record::unpack_counter_and_name(&data[offset..]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init_test() { + let mut data = vec![0; Header::LEN + Record::MAX_LEN * 6]; + assert!(!RefDB::is_initialized(data.as_slice())); + + assert!(RefDB::init( + data.as_mut_slice(), + &ArrayString64::from_utf8("test").unwrap(), + ReferenceType::Pubkey + ) + .is_ok()); + + assert!(RefDB::is_initialized(data.as_slice())); + assert_eq!( + Header { + counter: 1, + active_records: 0, + reference_type: ReferenceType::Pubkey, + name: ArrayString64::from_utf8("test").unwrap() + }, + RefDB::get_storage_header(data.as_slice()).unwrap() + ); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 0); + assert_eq!( + RefDB::get_reference_type(data.as_slice()).unwrap(), + ReferenceType::Pubkey + ); + assert!(RefDB::init( + data.as_mut_slice(), + &ArrayString64::from_utf8("test").unwrap(), + ReferenceType::Pubkey + ) + .is_err()); + let _ = RefDB::drop(data.as_mut_slice()); + assert!(!RefDB::is_initialized(data.as_slice())); + + assert!(RefDB::init( + data.as_mut_slice(), + &ArrayString64::from_utf8("test2").unwrap(), + ReferenceType::U8 + ) + .is_ok()); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 0); + assert_eq!( + RefDB::get_reference_type(data.as_slice()).unwrap(), + ReferenceType::U8 + ); + + assert_eq!( + Header { + counter: 1, + active_records: 0, + reference_type: ReferenceType::U8, + name: ArrayString64::from_utf8("test2").unwrap() + }, + RefDB::get_storage_header(data.as_slice()).unwrap() + ); + } + + #[test] + fn read_write_test() { + // init + let mut data = vec![0; Header::LEN + Record::MAX_LEN * 3]; + assert!(RefDB::init( + data.as_mut_slice(), + &ArrayString64::from_utf8("test").unwrap(), + ReferenceType::Pubkey + ) + .is_ok()); + + // write + let mut record = Record { + index: Some(1), + counter: 0, + tag: 123, + name: ArrayString64::from_utf8("test record").unwrap(), + reference: Reference::Pubkey { + data: Pubkey::new_unique(), + }, + }; + assert_eq!( + RefDB::get_record_size(data.as_slice()).unwrap(), + Record::NO_REF_LEN + Reference::PUBKEY_LEN + ); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 0); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 1); + assert!(RefDB::write(data.as_mut_slice(), &record).is_ok()); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 2); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 2); + + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record").unwrap(), + ) + .unwrap() + .unwrap(); + + record.index = Some(1); + record.counter = 1; + assert_eq!(read, record); + + // update + record.tag = 321; + record.reference = Reference::Pubkey { + data: Pubkey::new_unique(), + }; + RefDB::write(data.as_mut_slice(), &record).unwrap(); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 2); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 3); + + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record").unwrap(), + ) + .unwrap() + .unwrap(); + + record.counter = 2; + assert_eq!(read, record); + + // fast update + let new_ref = Reference::Pubkey { + data: Pubkey::new_unique(), + }; + assert!( + RefDB::update( + data.as_mut_slice(), + &ArrayString64::from_utf8("test record").unwrap(), + &new_ref + ) + .unwrap() + > 0 + ); + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record").unwrap(), + ) + .unwrap() + .unwrap(); + assert_eq!(read.reference, new_ref); + + // update should fail if counter is stale + record.counter = 1; + assert!(RefDB::write(data.as_mut_slice(), &record).is_err()); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 2); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 4); + + // write another record + let mut record2 = Record { + index: None, + counter: 0, + tag: 123, + name: ArrayString64::from_utf8("test record2").unwrap(), + reference: Reference::U8 { data: 0 }, + }; + // update should fail if reference type mismatch + assert!(RefDB::write(data.as_mut_slice(), &record2).is_err()); + + record2.reference = Reference::Pubkey { + data: Pubkey::new_unique(), + }; + RefDB::write(data.as_mut_slice(), &record2).unwrap(); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 2); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 5); + + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record2").unwrap(), + ) + .unwrap() + .unwrap(); + + record2.index = Some(0); + record2.counter = 1; + assert_eq!(read, record2); + + // check old record is still there + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record").unwrap(), + ) + .unwrap() + .unwrap(); + + record.counter = 3; + record.reference = new_ref; + assert_eq!(read, record); + + // update record with index + record2.tag = 567; + RefDB::write(data.as_mut_slice(), &record2).unwrap(); + let read = RefDB::read_at(data.as_slice(), record2.index.unwrap() as usize) + .unwrap() + .unwrap(); + record2.counter = 2; + assert_eq!(read, record2); + + // write another + let mut record3 = Record { + index: None, + counter: 0, + tag: 3, + name: ArrayString64::from_utf8("test record3").unwrap(), + reference: Reference::Pubkey { + data: Pubkey::new_unique(), + }, + }; + RefDB::write(data.as_mut_slice(), &record3).unwrap(); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 0); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 7); + + // full storage write + record3.name = ArrayString64::from_utf8("test record4").unwrap(); + assert!(RefDB::write(data.as_mut_slice(), &record3).is_err()); + + // delete record + assert!(RefDB::delete_with_name( + data.as_mut_slice(), + &ArrayString64::from_utf8("test record4").unwrap() + ) + .is_err()); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 0); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 7); + + assert!(RefDB::delete_with_name( + data.as_mut_slice(), + &ArrayString64::from_utf8("test record2").unwrap() + ) + .is_ok()); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 1); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 2); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 8); + + assert!( + RefDB::read_at(data.as_slice(), record2.index.unwrap() as usize) + .unwrap() + .is_none() + ); + record2.index = None; + assert!(RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record2").unwrap(), + ) + .unwrap() + .is_none()); + + // write again + record2.counter = 0; + assert!(RefDB::write(data.as_mut_slice(), &record2).is_ok()); + assert_eq!(RefDB::get_total_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_free_records(data.as_slice()).unwrap(), 0); + assert_eq!(RefDB::get_active_records(data.as_slice()).unwrap(), 3); + assert_eq!(RefDB::get_storage_counter(data.as_slice()).unwrap(), 9); + + let read = RefDB::read( + data.as_slice(), + &ArrayString64::from_utf8("test record2").unwrap(), + ) + .unwrap() + .unwrap(); + + record2.index = Some(0); + record2.counter = 1; + assert_eq!(read, record2); + } +} diff --git a/farms/farm-sdk/src/string.rs b/farms/farm-sdk/src/string.rs new file mode 100644 index 00000000000..1f6552ef05d --- /dev/null +++ b/farms/farm-sdk/src/string.rs @@ -0,0 +1,66 @@ +//! Fixed length string types + +//use arrayvec::ArrayString; +use { + arraystring::{typenum::U64, ArrayString}, + serde::Serialize, + solana_program::{instruction::Instruction, program_error::ProgramError, pubkey::Pubkey}, + std::collections::HashMap, +}; + +/// Fixed size array to store UTF-8 strings on blockchain. +pub type ArrayString64 = ArrayString; + +pub fn to_pretty_json(object: &T) -> Result +where + T: ?Sized + Serialize, +{ + serde_json::to_string_pretty(&object) +} + +// Custom serializer that prints base58 addresses instead of arrays +pub fn instruction_to_string(inst: &Instruction) -> String { + let len = 145 + inst.data.len() * 4 + inst.accounts.len() * 40; + let mut s = String::with_capacity(len); + s += format!("{{\"program_id\":\"{}\",\"accounts\":[", inst.program_id).as_str(); + let mut first_object = true; + for val in &inst.accounts { + if !first_object { + s += ","; + } else { + first_object = false; + } + s += format!( + "{{\"pubkey\":\"{}\",\"is_signer\":{},\"is_writable\":{}}}", + val.pubkey, val.is_signer, val.is_writable + ) + .as_str(); + } + s += format!("],\"data\":{:?}}}", inst.data).as_str(); + s +} + +// Custom serializer that prints base58 addresses instead of arrays +pub fn pubkey_map_to_string(map: &HashMap) -> String { + if map.is_empty() { + return "{}".to_string(); + } + let mut len = 1; + for key in map.keys() { + len += key.len() + 50; + } + let mut s = String::with_capacity(len); + s += "{"; + for (key, val) in map { + if s.len() != 1 { + s += ","; + } + s += format!("\"{}\":\"{}\"", key, val.to_string()).as_str(); + } + s += "}"; + s +} + +pub fn str_to_as64(input: &str) -> Result { + ArrayString64::try_from_str(input).or(Err(ProgramError::InvalidArgument)) +} diff --git a/farms/farm-sdk/src/token.rs b/farms/farm-sdk/src/token.rs new file mode 100644 index 00000000000..9f2f6057157 --- /dev/null +++ b/farms/farm-sdk/src/token.rs @@ -0,0 +1,137 @@ +//! Token + +use { + crate::{pack::*, string::ArrayString64, traits::*}, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + serde_json::to_string, + solana_program::{program_error::ProgramError, pubkey::Pubkey}, +}; + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum TokenType { + NativeSol, + WrappedSol, + WrappedSollet, + WrappedWarmhole, + SplToken, + LpToken, + VtToken, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct Token { + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub description: ArrayString64, + pub token_type: TokenType, + pub refdb_index: Option, + pub refdb_counter: u16, + pub decimals: u8, + pub chain_id: u16, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub mint: Pubkey, +} + +impl Named for Token { + fn name(&self) -> ArrayString64 { + self.name + } +} + +impl Token { + pub const LEN: usize = 171; + + pub fn get_size(&self) -> usize { + Token::LEN + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + check_data_len(output, Token::LEN)?; + + let output = array_mut_ref![output, 0, Token::LEN]; + + let ( + name_out, + description_out, + token_type_out, + refdb_index_out, + refdb_counter_out, + decimals_out, + chain_id_out, + mint_out, + ) = mut_array_refs![output, 64, 64, 1, 5, 2, 1, 2, 32]; + pack_array_string64(&self.name, name_out); + pack_array_string64(&self.description, description_out); + token_type_out[0] = self.token_type as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + decimals_out[0] = self.decimals; + *chain_id_out = self.chain_id.to_le_bytes(); + mint_out.copy_from_slice(self.mint.as_ref()); + + Ok(Token::LEN) + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Token::LEN] = [0; Token::LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, Token::LEN)?; + + let input = array_ref![input, 0, Token::LEN]; + #[allow(clippy::ptr_offset_with_cast)] + let (name, description, token_type, refdb_index, refdb_counter, decimals, chain_id, mint) = + array_refs![input, 64, 64, 1, 5, 2, 1, 2, 32]; + + Ok(Self { + name: unpack_array_string64(name)?, + description: unpack_array_string64(description)?, + token_type: TokenType::try_from_primitive(token_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + decimals: decimals[0], + chain_id: u16::from_le_bytes(*chain_id), + mint: Pubkey::new_from_array(*mint), + }) + } +} + +impl std::fmt::Display for TokenType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + TokenType::NativeSol => write!(f, "NativeSol"), + TokenType::WrappedSol => write!(f, "WrappedSol"), + TokenType::WrappedSollet => write!(f, "WrappedSollet"), + TokenType::WrappedWarmhole => write!(f, "WrappedWarmhole"), + TokenType::SplToken => write!(f, "SplToken"), + TokenType::LpToken => write!(f, "LpToken"), + TokenType::VtToken => write!(f, "VtToken"), + } + } +} + +impl std::fmt::Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} diff --git a/farms/farm-sdk/src/traits.rs b/farms/farm-sdk/src/traits.rs new file mode 100644 index 00000000000..6b7942197ee --- /dev/null +++ b/farms/farm-sdk/src/traits.rs @@ -0,0 +1,9 @@ +use crate::string::ArrayString64; + +pub trait Named { + fn name(&self) -> ArrayString64; +} + +pub trait Versioned { + fn version(&self) -> u16; +} diff --git a/farms/farm-sdk/src/vault.rs b/farms/farm-sdk/src/vault.rs new file mode 100644 index 00000000000..19e224a46be --- /dev/null +++ b/farms/farm-sdk/src/vault.rs @@ -0,0 +1,400 @@ +//! Solana Vault + +use { + crate::{pack::*, string::ArrayString64, traits::*}, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + serde::{Deserialize, Serialize}, + serde_json::to_string, + solana_program::{clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey}, +}; + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum VaultType { + AmmStake, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum VaultStrategy { + StakeLpCompoundRewards { + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pool_id_ref: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + farm_id_ref: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + lp_token_custody: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + token_a_custody: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + token_b_custody: Option, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + token_a_reward_custody: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + token_b_reward_custody: Option, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + vault_stake_info: Pubkey, + }, + DynamicHedge, +} + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum VaultStrategyType { + StakeLpCompoundRewards, + DynamicHedge, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct Vault { + #[serde( + serialize_with = "as64_serialize", + deserialize_with = "as64_deserialize" + )] + pub name: ArrayString64, + pub version: u16, + pub vault_type: VaultType, + pub official: bool, + #[serde(skip_serializing, skip_deserializing)] + pub refdb_index: Option, + #[serde(skip_serializing, skip_deserializing)] + pub refdb_counter: u16, + pub metadata_bump: u8, + pub authority_bump: u8, + pub vault_token_bump: u8, + pub lock_required: bool, + pub unlock_required: bool, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub vault_program_id: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub vault_authority: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub vault_token_ref: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub info_account: Pubkey, + #[serde( + deserialize_with = "pubkey_deserialize", + serialize_with = "pubkey_serialize" + )] + pub admin_account: Pubkey, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub fees_account_a: Option, + #[serde( + deserialize_with = "optional_pubkey_deserialize", + serialize_with = "optional_pubkey_serialize" + )] + pub fees_account_b: Option, + pub strategy: VaultStrategy, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct UserInfo { + pub last_deposit_time: UnixTimestamp, + pub last_withdrawal_time: UnixTimestamp, + pub tokens_a_added: u64, + pub tokens_b_added: u64, + pub tokens_a_removed: u64, + pub tokens_b_removed: u64, + pub lp_tokens_debt: u64, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq)] +pub struct VaultInfo { + pub crank_time: UnixTimestamp, + pub crank_step: u64, + pub tokens_a_added: u64, + pub tokens_b_added: u64, + pub tokens_a_removed: u64, + pub tokens_b_removed: u64, + pub tokens_a_rewards: u64, + pub tokens_b_rewards: u64, + pub stake_balance: f64, + pub deposit_allowed: bool, + pub withdrawal_allowed: bool, + pub min_crank_interval: u64, + pub fee: f64, + pub external_fee: f64, +} + +impl Named for Vault { + fn name(&self) -> ArrayString64 { + self.name + } +} + +impl Versioned for Vault { + fn version(&self) -> u16 { + self.version + } +} + +impl Vault { + pub const MAX_LEN: usize = 565; + pub const STAKE_LP_COMPOUND_REWARDS_LEN: usize = 565; + pub const DYNAMIC_HEDGE_LEN: usize = 1; + + pub fn get_size(&self) -> usize { + match self.strategy { + VaultStrategy::StakeLpCompoundRewards { .. } => Vault::STAKE_LP_COMPOUND_REWARDS_LEN, + VaultStrategy::DynamicHedge { .. } => Vault::DYNAMIC_HEDGE_LEN, + } + } + + pub fn pack(&self, output: &mut [u8]) -> Result { + match self.strategy { + VaultStrategy::StakeLpCompoundRewards { .. } => { + self.pack_stake_lp_compound_rewards(output) + } + VaultStrategy::DynamicHedge { .. } => Err(ProgramError::UnsupportedSysvar), + } + } + + pub fn to_vec(&self) -> Result, ProgramError> { + let mut output: [u8; Vault::MAX_LEN] = [0; Vault::MAX_LEN]; + if let Ok(len) = self.pack(&mut output[..]) { + Ok(output[..len].to_vec()) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + pub fn unpack(input: &[u8]) -> Result { + check_data_len(input, 1)?; + let strategy_type = VaultStrategyType::try_from_primitive(input[0]) + .or(Err(ProgramError::InvalidAccountData))?; + match strategy_type { + VaultStrategyType::StakeLpCompoundRewards => { + Vault::unpack_stake_lp_compound_rewards(input) + } + VaultStrategyType::DynamicHedge { .. } => Err(ProgramError::UnsupportedSysvar), + } + } + + fn pack_stake_lp_compound_rewards(&self, output: &mut [u8]) -> Result { + check_data_len(output, Vault::STAKE_LP_COMPOUND_REWARDS_LEN)?; + + if let VaultStrategy::StakeLpCompoundRewards { + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + } = self.strategy + { + let output = array_mut_ref![output, 0, Vault::STAKE_LP_COMPOUND_REWARDS_LEN]; + + let ( + strategy_type_out, + name_out, + version_out, + vault_type_out, + official_out, + refdb_index_out, + refdb_counter_out, + metadata_bump_out, + authority_bump_out, + vault_token_bump_out, + lock_required_out, + unlock_required_out, + vault_program_id_out, + vault_authority_out, + vault_token_ref_out, + vault_info_account_out, + admin_account_out, + fees_account_a_out, + fees_account_b_out, + pool_id_ref_out, + farm_id_ref_out, + lp_token_custody_out, + token_a_custody_out, + token_b_custody_out, + token_a_reward_custody_out, + token_b_reward_custody_out, + vault_stake_info_out, + ) = mut_array_refs![ + output, 1, 64, 2, 1, 1, 5, 2, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 33, 33, 32, 32, + 32, 32, 33, 32, 33, 32 + ]; + + strategy_type_out[0] = VaultStrategyType::StakeLpCompoundRewards as u8; + + pack_array_string64(&self.name, name_out); + *version_out = self.version.to_le_bytes(); + vault_type_out[0] = self.vault_type as u8; + official_out[0] = self.official as u8; + pack_option_u32(self.refdb_index, refdb_index_out); + *refdb_counter_out = self.refdb_counter.to_le_bytes(); + metadata_bump_out[0] = self.metadata_bump as u8; + authority_bump_out[0] = self.authority_bump as u8; + vault_token_bump_out[0] = self.vault_token_bump as u8; + lock_required_out[0] = self.lock_required as u8; + unlock_required_out[0] = self.unlock_required as u8; + vault_program_id_out.copy_from_slice(self.vault_program_id.as_ref()); + vault_authority_out.copy_from_slice(self.vault_authority.as_ref()); + vault_token_ref_out.copy_from_slice(self.vault_token_ref.as_ref()); + vault_info_account_out.copy_from_slice(self.info_account.as_ref()); + admin_account_out.copy_from_slice(self.admin_account.as_ref()); + pack_option_key(&self.fees_account_a, fees_account_a_out); + pack_option_key(&self.fees_account_b, fees_account_b_out); + pool_id_ref_out.copy_from_slice(pool_id_ref.as_ref()); + farm_id_ref_out.copy_from_slice(farm_id_ref.as_ref()); + lp_token_custody_out.copy_from_slice(lp_token_custody.as_ref()); + token_a_custody_out.copy_from_slice(token_a_custody.as_ref()); + pack_option_key(&token_b_custody, token_b_custody_out); + token_a_reward_custody_out.copy_from_slice(token_a_reward_custody.as_ref()); + pack_option_key(&token_b_reward_custody, token_b_reward_custody_out); + vault_stake_info_out.copy_from_slice(vault_stake_info.as_ref()); + + Ok(Vault::STAKE_LP_COMPOUND_REWARDS_LEN) + } else { + Err(ProgramError::InvalidAccountData) + } + } + + fn unpack_stake_lp_compound_rewards(input: &[u8]) -> Result { + check_data_len(input, Vault::STAKE_LP_COMPOUND_REWARDS_LEN)?; + + let input = array_ref![input, 1, Vault::STAKE_LP_COMPOUND_REWARDS_LEN - 1]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + name, + version, + vault_type, + official, + refdb_index, + refdb_counter, + metadata_bump, + authority_bump, + vault_token_bump, + lock_required, + unlock_required, + vault_program_id, + vault_authority, + vault_token_ref, + info_account, + admin_account, + fees_account_a, + fees_account_b, + pool_id_ref, + farm_id_ref, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + ) = array_refs![ + input, 64, 2, 1, 1, 5, 2, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 33, 33, 32, 32, 32, 32, + 33, 32, 33, 32 + ]; + + Ok(Self { + name: unpack_array_string64(name)?, + version: u16::from_le_bytes(*version), + vault_type: VaultType::try_from_primitive(vault_type[0]) + .or(Err(ProgramError::InvalidAccountData))?, + official: unpack_bool(official)?, + refdb_index: unpack_option_u32(refdb_index)?, + refdb_counter: u16::from_le_bytes(*refdb_counter), + metadata_bump: metadata_bump[0], + authority_bump: authority_bump[0], + vault_token_bump: vault_token_bump[0], + lock_required: unpack_bool(lock_required)?, + unlock_required: unpack_bool(unlock_required)?, + vault_program_id: Pubkey::new_from_array(*vault_program_id), + vault_authority: Pubkey::new_from_array(*vault_authority), + vault_token_ref: Pubkey::new_from_array(*vault_token_ref), + info_account: Pubkey::new_from_array(*info_account), + admin_account: Pubkey::new_from_array(*admin_account), + fees_account_a: unpack_option_key(fees_account_a)?, + fees_account_b: unpack_option_key(fees_account_b)?, + strategy: VaultStrategy::StakeLpCompoundRewards { + pool_id_ref: Pubkey::new_from_array(*pool_id_ref), + farm_id_ref: Pubkey::new_from_array(*farm_id_ref), + lp_token_custody: Pubkey::new_from_array(*lp_token_custody), + token_a_custody: Pubkey::new_from_array(*token_a_custody), + token_b_custody: unpack_option_key(token_b_custody)?, + token_a_reward_custody: Pubkey::new_from_array(*token_a_reward_custody), + token_b_reward_custody: unpack_option_key(token_b_reward_custody)?, + vault_stake_info: Pubkey::new_from_array(*vault_stake_info), + }, + }) + } +} + +impl std::fmt::Display for VaultStrategyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + VaultStrategyType::StakeLpCompoundRewards => write!(f, "StakeLpCompoundRewards"), + VaultStrategyType::DynamicHedge => write!(f, "DynamicHedge"), + } + } +} + +impl std::fmt::Display for VaultType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + VaultType::AmmStake => write!(f, "AmmStake"), + } + } +} + +impl std::fmt::Display for Vault { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} + +impl std::fmt::Display for UserInfo { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} + +impl std::fmt::Display for VaultInfo { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", to_string(&self).unwrap()) + } +} diff --git a/farms/router-main/.gitignore b/farms/router-main/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/farms/router-main/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/farms/router-main/Cargo.toml b/farms/router-main/Cargo.toml new file mode 100644 index 00000000000..a722de71c67 --- /dev/null +++ b/farms/router-main/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "solana-farm-router-main" +version = "0.0.1" +description = "Solana Farm Main Router" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +no-entrypoint = [] +debug = [] + +[dependencies] +solana-farm-sdk = { path = "../farm-sdk" } +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } +solana-program = "1.8.1" +arrayref = "0.3.6" +arrayvec = "0.7.2" + +[dev-dependencies] +solana-program-test = "1.8.1" + +[lib] +crate-type = ["cdylib", "lib"] + diff --git a/farms/router-main/Xargo.toml b/farms/router-main/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/farms/router-main/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/farms/router-main/src/add_farm.rs b/farms/router-main/src/add_farm.rs new file mode 100644 index 00000000000..4c03fd714b6 --- /dev/null +++ b/farms/router-main/src/add_farm.rs @@ -0,0 +1,64 @@ +//! Saves Farm's metadata on-chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{farm::Farm, refdb, refdb::RefDB}, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn add_farm(program_id: &Pubkey, accounts: &[AccountInfo], farm: &Farm) -> ProgramResult { + msg!("Processing MainInstruction::AddFarm {}", farm.name); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Farm, + 0, + false, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Farm, + &farm.name, + farm.get_size(), + false, + )?; + + // update refdb storage + msg!("Updating refdb storage"); + RefDB::write( + *refdb_account.try_borrow_mut_data()?, + &refdb::Record { + index: farm.refdb_index, + counter: farm.refdb_counter, + tag: refdb::StorageType::Farm as u16, + name: farm.name, + reference: refdb::Reference::Pubkey { + data: *target_account.key, + }, + }, + )?; + + // fill in data + msg!("Writing metadata account"); + farm.pack(*target_account.try_borrow_mut_data()?)?; + + msg!("AddFarm complete"); + + Ok(()) +} diff --git a/farms/router-main/src/add_pool.rs b/farms/router-main/src/add_pool.rs new file mode 100644 index 00000000000..507d8cfc657 --- /dev/null +++ b/farms/router-main/src/add_pool.rs @@ -0,0 +1,64 @@ +//! Saves Pool's metadata on-chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{pool::Pool, refdb, refdb::RefDB}, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn add_pool(program_id: &Pubkey, accounts: &[AccountInfo], pool: &Pool) -> ProgramResult { + msg!("Processing MainInstruction::AddPool {}", pool.name); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Pool, + 0, + false, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Pool, + &pool.name, + pool.get_size(), + false, + )?; + + // update refdb storage + msg!("Updating refdb storage"); + RefDB::write( + *refdb_account.try_borrow_mut_data()?, + &refdb::Record { + index: pool.refdb_index, + counter: pool.refdb_counter, + tag: refdb::StorageType::Pool as u16, + name: pool.name, + reference: refdb::Reference::Pubkey { + data: *target_account.key, + }, + }, + )?; + + // fill in data + msg!("Writing metadata account"); + pool.pack(*target_account.try_borrow_mut_data()?)?; + + msg!("AddPool complete"); + + Ok(()) +} diff --git a/farms/router-main/src/add_token.rs b/farms/router-main/src/add_token.rs new file mode 100644 index 00000000000..a3559564b3b --- /dev/null +++ b/farms/router-main/src/add_token.rs @@ -0,0 +1,64 @@ +//! Saves Token's metadata on-chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{refdb, refdb::RefDB, token::Token}, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn add_token(program_id: &Pubkey, accounts: &[AccountInfo], token: &Token) -> ProgramResult { + msg!("Processing MainInstruction::AddToken {}", token.name); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Token, + 0, + false, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Token, + &token.name, + token.get_size(), + false, + )?; + + // update refdb storage + msg!("Updating refdb storage"); + RefDB::write( + *refdb_account.try_borrow_mut_data()?, + &refdb::Record { + index: token.refdb_index, + counter: token.refdb_counter, + tag: refdb::StorageType::Token as u16, + name: token.name, + reference: refdb::Reference::Pubkey { + data: *target_account.key, + }, + }, + )?; + + // fill in data + msg!("Writing metadata account"); + token.pack(*target_account.try_borrow_mut_data()?)?; + + msg!("AddToken complete"); + + Ok(()) +} diff --git a/farms/router-main/src/add_vault.rs b/farms/router-main/src/add_vault.rs new file mode 100644 index 00000000000..bc5bfbd6f2e --- /dev/null +++ b/farms/router-main/src/add_vault.rs @@ -0,0 +1,64 @@ +//! Saves Vault's metadata on-chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{refdb, refdb::RefDB, vault::Vault}, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn add_vault(program_id: &Pubkey, accounts: &[AccountInfo], vault: &Vault) -> ProgramResult { + msg!("Processing MainInstruction::AddVault {}", vault.name); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Vault, + 0, + false, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Vault, + &vault.name, + vault.get_size(), + false, + )?; + + // update refdb storage + msg!("Updating refdb storage"); + RefDB::write( + *refdb_account.try_borrow_mut_data()?, + &refdb::Record { + index: vault.refdb_index, + counter: vault.refdb_counter, + tag: refdb::StorageType::Vault as u16, + name: vault.name, + reference: refdb::Reference::Pubkey { + data: *target_account.key, + }, + }, + )?; + + // fill in data + msg!("Writing metadata account"); + vault.pack(*target_account.try_borrow_mut_data()?)?; + + msg!("AddVault complete"); + + Ok(()) +} diff --git a/farms/router-main/src/entrypoint.rs b/farms/router-main/src/entrypoint.rs new file mode 100644 index 00000000000..82641a41587 --- /dev/null +++ b/farms/router-main/src/entrypoint.rs @@ -0,0 +1,16 @@ +//! Program entrypoint + +#![cfg(not(feature = "no-entrypoint"))] + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/farms/router-main/src/lib.rs b/farms/router-main/src/lib.rs new file mode 100644 index 00000000000..818b2e00a19 --- /dev/null +++ b/farms/router-main/src/lib.rs @@ -0,0 +1,14 @@ +#![forbid(unsafe_code)] + +pub mod add_farm; +pub mod add_pool; +pub mod add_token; +pub mod add_vault; +mod entrypoint; +pub mod processor; +mod refdb_init; +pub mod refdb_instruction; +pub mod remove_farm; +pub mod remove_pool; +pub mod remove_token; +pub mod remove_vault; diff --git a/farms/router-main/src/processor.rs b/farms/router-main/src/processor.rs new file mode 100644 index 00000000000..112811a956b --- /dev/null +++ b/farms/router-main/src/processor.rs @@ -0,0 +1,77 @@ +//! Main router implementation. + +use { + crate::{ + add_farm::add_farm, add_pool::add_pool, add_token::add_token, add_vault::add_vault, + refdb_instruction::process_refdb_instruction, remove_farm::remove_farm, + remove_pool::remove_pool, remove_token::remove_token, remove_vault::remove_vault, + }, + solana_farm_sdk::{ + id::{main_router, main_router_admin}, + instruction::main_router::MainInstruction, + log::sol_log_params_short, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + log::sol_log_compute_units, + msg, + program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +/// Program's entrypoint. +/// +/// # Arguments +/// * `program_id` - Public key of the router. +/// * `accounts` - Accounts, see particular instruction handler for the list. +/// * `instructions_data` - Packed MainInstruction. +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("Main router entrypoint"); + if cfg!(feature = "debug") { + sol_log_params_short(accounts, instruction_data); + } + + if *program_id != main_router::id() { + return Err(ProgramError::IncorrectProgramId); + } + + let accounts_iter = &mut accounts.iter(); + let signer_account = next_account_info(accounts_iter)?; + if signer_account.key != &main_router_admin::id() { + msg!( + "Error: Main router must be called with the admin account {}", + main_router_admin::id() + ); + return Err(ProgramError::IllegalOwner); + } + if !signer_account.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + + // Read and unpack instruction data + let instruction = MainInstruction::unpack(instruction_data)?; + + match instruction { + MainInstruction::AddVault { vault } => add_vault(program_id, accounts, &vault)?, + MainInstruction::RemoveVault { name } => remove_vault(program_id, accounts, &name)?, + MainInstruction::AddPool { pool } => add_pool(program_id, accounts, &pool)?, + MainInstruction::RemovePool { name } => remove_pool(program_id, accounts, &name)?, + MainInstruction::AddFarm { farm } => add_farm(program_id, accounts, &farm)?, + MainInstruction::RemoveFarm { name } => remove_farm(program_id, accounts, &name)?, + MainInstruction::AddToken { token } => add_token(program_id, accounts, &token)?, + MainInstruction::RemoveToken { name } => remove_token(program_id, accounts, &name)?, + MainInstruction::RefDbInstruction { instruction } => { + process_refdb_instruction(program_id, accounts, instruction)? + } + } + + sol_log_compute_units(); + msg!("Main router end of instruction"); + Ok(()) +} diff --git a/farms/router-main/src/refdb_init.rs b/farms/router-main/src/refdb_init.rs new file mode 100644 index 00000000000..7a593a6d8ca --- /dev/null +++ b/farms/router-main/src/refdb_init.rs @@ -0,0 +1,106 @@ +//! Common accounts management functions + +use { + solana_farm_sdk::{ + program::pda, + refdb, + refdb::RefDB, + string::{str_to_as64, ArrayString64}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +pub fn check_or_init_refdb<'a, 'b>( + program_id: &Pubkey, + signer_account: &'a AccountInfo<'b>, + refdb_account: &'a AccountInfo<'b>, + storage_type: refdb::StorageType, + storage_size: usize, + delete_mode: bool, +) -> ProgramResult { + msg!("Executing check_or_init_refdb"); + + // check address + let type_name = storage_type.to_string(); + let (derived_address, bump_seed) = pda::find_refdb_pda(&type_name); + + if derived_address != *refdb_account.key { + return Err(ProgramError::IncorrectProgramId); + } + let seeds = &[type_name.as_bytes(), &[bump_seed]]; + + // check if account is initialized + let data_size = if storage_size > 0 { + storage_size + } else { + refdb::StorageType::get_storage_size_for_max_records( + storage_type, + refdb::ReferenceType::Pubkey, + ) + }; + + if !delete_mode { + pda::check_pda_data_size(refdb_account, seeds, data_size, refdb::REFDB_ONCHAIN_INIT)?; + pda::check_pda_rent_exempt( + signer_account, + refdb_account, + seeds, + data_size, + refdb::REFDB_ONCHAIN_INIT, + )?; + } + pda::check_pda_owner(program_id, refdb_account, seeds, refdb::REFDB_ONCHAIN_INIT)?; + + if !delete_mode { + // check or init storage + let data = &mut refdb_account.try_borrow_mut_data()?; + if !RefDB::is_initialized(data) { + msg!("Executing RefDB::init for {}", type_name); + RefDB::init( + data, + &str_to_as64(&type_name)?, + refdb::ReferenceType::Pubkey, + )?; + msg!("RefDB::init complete"); + } + } + + msg!("check_or_init_refdb complete"); + + Ok(()) +} + +pub fn check_or_init_refdb_target<'a, 'b>( + program_id: &Pubkey, + signer_account: &'a AccountInfo<'b>, + target_account: &'a AccountInfo<'b>, + storage_type: refdb::StorageType, + data_name: &ArrayString64, + data_size: usize, + delete_mode: bool, +) -> ProgramResult { + msg!("Executing check_or_init_refdb_target"); + + // check address + let type_name = storage_type.to_string(); + let (derived_address, bump_seed) = pda::find_target_pda(storage_type, data_name); + + if derived_address != *target_account.key { + return Err(ProgramError::IncorrectProgramId); + } + let seeds = &[type_name.as_bytes(), data_name.as_bytes(), &[bump_seed]]; + + if !delete_mode { + pda::check_pda_data_size(target_account, seeds, data_size, true)?; + pda::check_pda_rent_exempt(signer_account, target_account, seeds, data_size, true)?; + } + + pda::check_pda_owner(program_id, target_account, seeds, true)?; + + msg!("check_or_init_refdb_target complete"); + + Ok(()) +} diff --git a/farms/router-main/src/refdb_instruction.rs b/farms/router-main/src/refdb_instruction.rs new file mode 100644 index 00000000000..1b1eaaec733 --- /dev/null +++ b/farms/router-main/src/refdb_instruction.rs @@ -0,0 +1,77 @@ +//! Processes raw RefDB instruction + +use { + solana_farm_sdk::{ + instruction::refdb::RefDbInstruction, + program::{account, pda}, + refdb, + refdb::RefDB, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +pub fn process_refdb_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction: RefDbInstruction, +) -> ProgramResult { + msg!("Processing MainInstruction::RefDbInstruction"); + + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + + let res = match instruction { + RefDbInstruction::Init { + name, + reference_type, + max_records, + init_account, + } => { + if max_records == 0 { + return Err(ProgramError::InvalidArgument); + } + if init_account { + let (derived_address, bump_seed) = + Pubkey::find_program_address(&[name.as_bytes()], program_id); + if derived_address != *refdb_account.key { + return Err(ProgramError::IncorrectProgramId); + } + let seeds = &[name.as_bytes(), &[bump_seed]]; + + let data_size = refdb::StorageType::get_storage_size_for_records( + reference_type, + max_records as usize, + ); + pda::check_pda_data_size(refdb_account, seeds, data_size, true)?; + pda::check_pda_rent_exempt(signer_account, refdb_account, seeds, data_size, true)?; + pda::check_pda_owner(program_id, refdb_account, seeds, true)?; + } + RefDB::init(*refdb_account.try_borrow_mut_data()?, &name, reference_type) + } + RefDbInstruction::Drop { close_account } => { + let _ = RefDB::drop(*refdb_account.try_borrow_mut_data()?); + if close_account { + account::close_system_account(signer_account, refdb_account, program_id)?; + } + Ok(()) + } + RefDbInstruction::Write { record } => { + RefDB::write(*refdb_account.try_borrow_mut_data()?, &record).map(|_v| ()) + } + RefDbInstruction::Delete { record } => { + RefDB::delete(*refdb_account.try_borrow_mut_data()?, &record).map(|_v| ()) + } + }; + + msg!("MainInstruction::RefDbInstruction complete"); + + res +} diff --git a/farms/router-main/src/remove_farm.rs b/farms/router-main/src/remove_farm.rs new file mode 100644 index 00000000000..aecbdcfd550 --- /dev/null +++ b/farms/router-main/src/remove_farm.rs @@ -0,0 +1,59 @@ +//! Removes Farm's metadata from chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{ + program::account::close_system_account, refdb, refdb::RefDB, string::ArrayString64, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn remove_farm( + program_id: &Pubkey, + accounts: &[AccountInfo], + name: &ArrayString64, +) -> ProgramResult { + msg!("Processing MainInstruction::RemoveFarm"); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Farm, + 0, + true, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Farm, + name, + 0, + true, + )?; + + // update ref storage + msg!("Updating refdb storage"); + let _ = RefDB::delete_with_name(*refdb_account.try_borrow_mut_data()?, name); + + // close metadata account + msg!("Closing metadata account"); + close_system_account(signer_account, target_account, program_id)?; + + msg!("RemoveFarm complete"); + + Ok(()) +} diff --git a/farms/router-main/src/remove_pool.rs b/farms/router-main/src/remove_pool.rs new file mode 100644 index 00000000000..0df36c18f00 --- /dev/null +++ b/farms/router-main/src/remove_pool.rs @@ -0,0 +1,59 @@ +//! Removes Pool's metadata from chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{ + program::account::close_system_account, refdb, refdb::RefDB, string::ArrayString64, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn remove_pool( + program_id: &Pubkey, + accounts: &[AccountInfo], + name: &ArrayString64, +) -> ProgramResult { + msg!("Processing MainInstruction::RemovePool"); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Pool, + 0, + true, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Pool, + name, + 0, + true, + )?; + + // update ref storage + msg!("Updating refdb storage"); + let _ = RefDB::delete_with_name(*refdb_account.try_borrow_mut_data()?, name); + + // close metadata account + msg!("Closing metadata account"); + close_system_account(signer_account, target_account, program_id)?; + + msg!("RemovePool complete"); + + Ok(()) +} diff --git a/farms/router-main/src/remove_token.rs b/farms/router-main/src/remove_token.rs new file mode 100644 index 00000000000..a9a669f28df --- /dev/null +++ b/farms/router-main/src/remove_token.rs @@ -0,0 +1,59 @@ +//! Removes Token's metadata from chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{ + program::account::close_system_account, refdb, refdb::RefDB, string::ArrayString64, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn remove_token( + program_id: &Pubkey, + accounts: &[AccountInfo], + name: &ArrayString64, +) -> ProgramResult { + msg!("Processing MainInstruction::RemoveToken"); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Token, + 0, + true, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Token, + name, + 0, + true, + )?; + + // update ref storage + msg!("Updating refdb storage"); + let _ = RefDB::delete_with_name(*refdb_account.try_borrow_mut_data()?, name); + + // close metadata account + msg!("Closing metadata account"); + close_system_account(signer_account, target_account, program_id)?; + + msg!("RemoveToken complete"); + + Ok(()) +} diff --git a/farms/router-main/src/remove_vault.rs b/farms/router-main/src/remove_vault.rs new file mode 100644 index 00000000000..570570ef982 --- /dev/null +++ b/farms/router-main/src/remove_vault.rs @@ -0,0 +1,59 @@ +//! Removes Vault's metadata from chain + +use { + crate::refdb_init::{check_or_init_refdb, check_or_init_refdb_target}, + solana_farm_sdk::{ + program::account::close_system_account, refdb, refdb::RefDB, string::ArrayString64, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + pubkey::Pubkey, + }, +}; + +pub fn remove_vault( + program_id: &Pubkey, + accounts: &[AccountInfo], + name: &ArrayString64, +) -> ProgramResult { + msg!("Processing MainInstruction::RemoveVault"); + + // validate accounts + let accounts_iter = &mut accounts.iter(); + + let signer_account = next_account_info(accounts_iter)?; + let refdb_account = next_account_info(accounts_iter)?; + let target_account = next_account_info(accounts_iter)?; + + check_or_init_refdb( + program_id, + signer_account, + refdb_account, + refdb::StorageType::Vault, + 0, + true, + )?; + check_or_init_refdb_target( + program_id, + signer_account, + target_account, + refdb::StorageType::Vault, + name, + 0, + true, + )?; + + // update ref storage + msg!("Updating refdb storage"); + let _ = RefDB::delete_with_name(*refdb_account.try_borrow_mut_data()?, name); + + // close metadata account + msg!("Closing metadata account"); + close_system_account(signer_account, target_account, program_id)?; + + msg!("RemoveVault complete"); + + Ok(()) +} diff --git a/farms/router-orca/.gitignore b/farms/router-orca/.gitignore new file mode 100644 index 00000000000..da0b2a84eb6 --- /dev/null +++ b/farms/router-orca/.gitignore @@ -0,0 +1,3 @@ +/target +/include +Cargo.lock diff --git a/farms/router-orca/Cargo.toml b/farms/router-orca/Cargo.toml new file mode 100644 index 00000000000..0f821eab3c1 --- /dev/null +++ b/farms/router-orca/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "solana-farm-router-orca" +version = "0.0.1" +description = "Solana Farm Orca Router" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +no-entrypoint = [] +debug = [] + +[dependencies] +solana-farm-sdk = { path = "../farm-sdk" } +solana-program = "1.8.1" +arrayref = "0.3.6" +spl-token-swap = { version = "2.1.0", features = ["no-entrypoint"] } +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } + +[dev-dependencies] +solana-program-test = "1.8.1" + +[lib] +crate-type = ["cdylib", "lib"] + diff --git a/farms/router-orca/Xargo.toml b/farms/router-orca/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/farms/router-orca/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/farms/router-orca/src/add_liquidity.rs b/farms/router-orca/src/add_liquidity.rs new file mode 100644 index 00000000000..968b542a92e --- /dev/null +++ b/farms/router-orca/src/add_liquidity.rs @@ -0,0 +1,98 @@ +//! Add liquidity to the Orca pool instruction + +use { + solana_farm_sdk::program::{account, protocol::orca}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + spl_token_swap::instruction, +}; + +pub fn add_liquidity( + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::AddLiquidity"); + msg!("max_token_a_amount {} ", max_token_a_amount); + msg!("max_token_b_amount {} ", max_token_b_amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + _spl_token_id, + amm_id, + amm_authority + ] = accounts + { + if !orca::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let (lp_token_amount, token_a_amount, token_b_amount) = orca::get_pool_deposit_amounts( + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + max_token_a_amount, + max_token_b_amount, + )?; + + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let data = instruction::DepositAllTokenTypes { + pool_token_amount: lp_token_amount, + maximum_token_a_amount: token_a_amount, + maximum_token_b_amount: token_b_amount, + }; + + msg!("Deposit tokens into the pool. lp_token_amount: {}, token_a_amount: {}, token_b_amount: {}", lp_token_amount, token_a_amount, token_b_amount); + let instruction = instruction::deposit_all_token_types( + pool_program_id.key, + &spl_token::id(), + amm_id.key, + amm_authority.key, + user_account.key, + user_token_a_account.key, + user_token_b_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + lp_token_mint.key, + user_lp_token_account.key, + data, + )?; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_token_a_account, + initial_token_a_user_balance, + token_a_amount, + )?; + account::check_tokens_spent( + user_token_b_account, + initial_token_b_user_balance, + token_b_amount, + )?; + account::check_tokens_received( + user_lp_token_account, + initial_lp_token_user_balance, + lp_token_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::AddLiquidity complete"); + Ok(()) +} diff --git a/farms/router-orca/src/entrypoint.rs b/farms/router-orca/src/entrypoint.rs new file mode 100644 index 00000000000..82641a41587 --- /dev/null +++ b/farms/router-orca/src/entrypoint.rs @@ -0,0 +1,16 @@ +//! Program entrypoint + +#![cfg(not(feature = "no-entrypoint"))] + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/farms/router-orca/src/harvest.rs b/farms/router-orca/src/harvest.rs new file mode 100644 index 00000000000..ddc127892ed --- /dev/null +++ b/farms/router-orca/src/harvest.rs @@ -0,0 +1,70 @@ +//! Harvest rewards from an Orca farm instruction + +use { + solana_farm_sdk::{ + instruction::orca::OrcaHarvest, + program::{account, protocol::orca}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn harvest(accounts: &[AccountInfo]) -> ProgramResult { + msg!("Processing AmmInstruction::Harvest"); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + user_reward_token_account, + farm_program_id, + base_token_vault, + reward_token_vault, + _spl_token_id, + farm_id, + farm_authority + ] = accounts + { + if !orca::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_reward_token_user_balance = + account::get_token_balance(user_reward_token_account)?; + + let orca_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*farm_id.key, false), + AccountMeta::new(*user_info_account.key, false), + AccountMeta::new_readonly(*base_token_vault.key, false), + AccountMeta::new(*reward_token_vault.key, false), + AccountMeta::new(*user_reward_token_account.key, false), + AccountMeta::new_readonly(*farm_authority.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: orca_accounts, + data: OrcaHarvest {}.to_vec()?, + }; + invoke(&instruction, accounts)?; + + let _ = account::get_balance_increase( + user_reward_token_account, + initial_reward_token_user_balance, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Stake complete"); + Ok(()) +} diff --git a/farms/router-orca/src/lib.rs b/farms/router-orca/src/lib.rs new file mode 100644 index 00000000000..5ffd940e7b7 --- /dev/null +++ b/farms/router-orca/src/lib.rs @@ -0,0 +1,10 @@ +#![forbid(unsafe_code)] + +pub mod add_liquidity; +mod entrypoint; +pub mod harvest; +pub mod processor; +pub mod remove_liquidity; +pub mod stake; +pub mod swap; +pub mod unstake; diff --git a/farms/router-orca/src/processor.rs b/farms/router-orca/src/processor.rs new file mode 100644 index 00000000000..25168353b6a --- /dev/null +++ b/farms/router-orca/src/processor.rs @@ -0,0 +1,59 @@ +//! Orca router implementation. + +use { + crate::{ + add_liquidity::add_liquidity, harvest::harvest, remove_liquidity::remove_liquidity, + stake::stake, swap::swap, unstake::unstake, + }, + solana_farm_sdk::{instruction::amm::AmmInstruction, log::sol_log_params_short}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units, msg, + pubkey::Pubkey, + }, +}; + +/// Program's entrypoint. +/// +/// # Arguments +/// * `program_id` - Public key of the router. +/// * `accounts` - Accounts, see particular instruction handler for the list. +/// * `instructions_data` - Packed AmmInstruction. +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("Orca router entrypoint"); + if cfg!(feature = "debug") { + sol_log_params_short(accounts, instruction_data); + } + + // Read and unpack instruction data + let instruction = AmmInstruction::unpack(instruction_data)?; + + match instruction { + AmmInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } => add_liquidity(accounts, max_token_a_amount, max_token_b_amount)?, + AmmInstruction::RemoveLiquidity { amount } => remove_liquidity(accounts, amount)?, + AmmInstruction::Swap { + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + } => swap( + accounts, + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + )?, + AmmInstruction::Stake { amount } => stake(accounts, amount)?, + AmmInstruction::Unstake { amount } => unstake(accounts, amount)?, + AmmInstruction::Harvest => harvest(accounts)?, + _ => {} + } + + sol_log_compute_units(); + msg!("Orca router end of instruction"); + Ok(()) +} diff --git a/farms/router-orca/src/remove_liquidity.rs b/farms/router-orca/src/remove_liquidity.rs new file mode 100644 index 00000000000..c675b09f3c7 --- /dev/null +++ b/farms/router-orca/src/remove_liquidity.rs @@ -0,0 +1,104 @@ +//! Remove liquidity from the Orca pool instruction + +use { + solana_farm_sdk::program::{account, protocol::orca}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + spl_token_swap::instruction, +}; + +pub fn remove_liquidity(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::RemoveLiquidity"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + _spl_token_id, + amm_id, + amm_authority, + fees_account + ] = accounts + { + if !orca::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_lp_token_account)? + }; + + let (token_a_amount, token_b_amount) = orca::get_pool_withdrawal_amounts( + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + lp_amount, + )?; + + let data = instruction::WithdrawAllTokenTypes { + pool_token_amount: lp_amount, + minimum_token_a_amount: token_a_amount, + minimum_token_b_amount: token_b_amount, + }; + + msg!( + "Removing tokens from the pool. lp_amount: {}, token_a_amount: {}, token_b_amount: {}", + lp_amount, + token_a_amount, + token_b_amount + ); + let instruction = instruction::withdraw_all_token_types( + pool_program_id.key, + &spl_token::id(), + amm_id.key, + amm_authority.key, + user_account.key, + lp_token_mint.key, + fees_account.key, + user_lp_token_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + user_token_a_account.key, + user_token_b_account.key, + data, + )?; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + account::check_tokens_received( + user_token_a_account, + initial_token_a_user_balance, + token_a_amount, + )?; + account::check_tokens_received( + user_token_b_account, + initial_token_b_user_balance, + token_b_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::RemoveLiquidity complete"); + Ok(()) +} diff --git a/farms/router-orca/src/stake.rs b/farms/router-orca/src/stake.rs new file mode 100644 index 00000000000..b35fe0570ca --- /dev/null +++ b/farms/router-orca/src/stake.rs @@ -0,0 +1,96 @@ +//! Stake LP tokens to an Orca farm instruction + +use { + solana_farm_sdk::{ + instruction::orca::OrcaStake, + program::{account, protocol::orca}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn stake(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::Stake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + user_lp_token_account, + user_reward_token_account, + user_farm_lp_token_account, + farm_lp_token_mint, + farm_program_id, + base_token_vault, + reward_token_vault, + _spl_token_id, + farm_id, + farm_authority + ] = accounts + { + if !orca::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_reward_token_user_balance = + account::get_token_balance(user_reward_token_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + let initial_farm_token_user_balance = + account::get_token_balance(user_farm_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + initial_lp_token_user_balance + }; + + let orca_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new(*base_token_vault.key, false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*farm_lp_token_mint.key, false), + AccountMeta::new(*user_farm_lp_token_account.key, false), + AccountMeta::new(*farm_id.key, false), + AccountMeta::new(*user_info_account.key, false), + AccountMeta::new(*reward_token_vault.key, false), + AccountMeta::new(*user_reward_token_account.key, false), + AccountMeta::new_readonly(*farm_authority.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: orca_accounts, + data: OrcaStake { amount: lp_amount }.to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + let _ = account::get_balance_increase( + user_reward_token_account, + initial_reward_token_user_balance, + )?; + let _ = account::get_balance_increase( + user_farm_lp_token_account, + initial_farm_token_user_balance, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Stake complete"); + Ok(()) +} diff --git a/farms/router-orca/src/swap.rs b/farms/router-orca/src/swap.rs new file mode 100644 index 00000000000..78dbdc4e5fe --- /dev/null +++ b/farms/router-orca/src/swap.rs @@ -0,0 +1,125 @@ +//! Swap tokens with the Orca pool instruction + +use { + solana_farm_sdk::program::{account, protocol::orca}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + spl_token_swap::instruction, +}; + +pub fn swap( + accounts: &[AccountInfo], + token_a_amount_in: u64, + token_b_amount_in: u64, + min_token_amount_out: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::Swap"); + msg!("token_a_amount_in {} ", token_a_amount_in); + msg!("token_b_amount_in {} ", token_b_amount_in); + msg!("min_token_amount_out {} ", min_token_amount_out); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + _spl_token_id, + amm_id, + amm_authority, + fees_account + ] = accounts + { + if !orca::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let (amount_in, mut minimum_amount_out) = orca::get_pool_swap_amounts( + pool_token_a_account, + pool_token_b_account, + token_a_amount_in, + token_b_amount_in, + )?; + if min_token_amount_out > minimum_amount_out { + minimum_amount_out = min_token_amount_out; + } + + let data = instruction::Swap { + amount_in, + minimum_amount_out, + }; + + msg!( + "Swap tokens in the pool. amount_in: {}, minimum_amount_out: {}", + amount_in, + minimum_amount_out + ); + + if token_a_amount_in == 0 { + let initial_balance_in = account::get_token_balance(user_token_b_account)?; + let initial_balance_out = account::get_token_balance(user_token_a_account)?; + + let instruction = instruction::swap( + pool_program_id.key, + &spl_token::id(), + amm_id.key, + amm_authority.key, + user_account.key, + user_token_b_account.key, + pool_token_b_account.key, + pool_token_a_account.key, + user_token_a_account.key, + lp_token_mint.key, + fees_account.key, + None, + data, + )?; + invoke(&instruction, accounts)?; + + account::check_tokens_spent(user_token_b_account, initial_balance_in, amount_in)?; + account::check_tokens_received( + user_token_a_account, + initial_balance_out, + minimum_amount_out, + )?; + } else { + let initial_balance_in = account::get_token_balance(user_token_a_account)?; + let initial_balance_out = account::get_token_balance(user_token_b_account)?; + + let instruction = instruction::swap( + pool_program_id.key, + &spl_token::id(), + amm_id.key, + amm_authority.key, + user_account.key, + user_token_a_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + user_token_b_account.key, + lp_token_mint.key, + fees_account.key, + None, + data, + )?; + invoke(&instruction, accounts)?; + + account::check_tokens_spent(user_token_a_account, initial_balance_in, amount_in)?; + account::check_tokens_received( + user_token_b_account, + initial_balance_out, + minimum_amount_out, + )?; + } + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Swap complete"); + Ok(()) +} diff --git a/farms/router-orca/src/unstake.rs b/farms/router-orca/src/unstake.rs new file mode 100644 index 00000000000..39161f54542 --- /dev/null +++ b/farms/router-orca/src/unstake.rs @@ -0,0 +1,97 @@ +//! Unstake LP tokens from an Orca farm instruction + +use { + solana_farm_sdk::{ + instruction::orca::OrcaUnstake, + program::{account, protocol::orca}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn unstake(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::Unstake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + user_lp_token_account, + user_reward_token_account, + user_farm_lp_token_account, + farm_lp_token_mint, + farm_program_id, + base_token_vault, + reward_token_vault, + _spl_token_id, + farm_id, + farm_authority + ] = accounts + { + if !orca::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_reward_token_user_balance = + account::get_token_balance(user_reward_token_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + let initial_farm_token_user_balance = + account::get_token_balance(user_farm_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + initial_farm_token_user_balance + }; + + let orca_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new(*base_token_vault.key, false), + AccountMeta::new(*farm_lp_token_mint.key, false), + AccountMeta::new(*user_farm_lp_token_account.key, false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*farm_id.key, false), + AccountMeta::new(*user_info_account.key, false), + AccountMeta::new(*reward_token_vault.key, false), + AccountMeta::new(*user_reward_token_account.key, false), + AccountMeta::new_readonly(*farm_authority.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: orca_accounts, + data: OrcaUnstake { amount: lp_amount }.to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_received( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + account::check_tokens_spent( + user_farm_lp_token_account, + initial_farm_token_user_balance, + lp_amount, + )?; + let _ = account::get_balance_increase( + user_reward_token_account, + initial_reward_token_user_balance, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Unstake complete"); + Ok(()) +} diff --git a/farms/router-orca/src/user_init.rs b/farms/router-orca/src/user_init.rs new file mode 100644 index 00000000000..eddf2222213 --- /dev/null +++ b/farms/router-orca/src/user_init.rs @@ -0,0 +1,66 @@ +//! Initialize a new user for an Orca farm instruction + +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + pubkey::Pubkey, + system_program, +}; + +pub fn user_init(accounts: &[AccountInfo]) -> ProgramResult { + msg!("Processing AmmInstruction::UserInit"); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + farm_id, + farm_program_id, + _system_program, + ] = accounts + { + if !orca::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let farmer_derived = Pubkey::find_program_address( + &[ + &farm_id.key.to_bytes(), + &user_account.key.to_bytes(), + &spl_token::id().to_bytes(), + ], + &orca_farm_program, + ) + .0; + if &farmer_derived != user_info_account.key { + msg!("Error: Invalid Farmer address"); + return Err(ProgramError::InvalidSeeds); + } + + let orca_accounts = vec![ + AccountMeta::new_readonly(*farm_id.key, false), + AccountMeta::new(*user_info_account.key, false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: orca_accounts, + data: OrcaUserInit {}.to_vec()?, + }; + + invoke(&instruction, accounts)?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::UserInit complete"); + Ok(()) +} diff --git a/farms/router-raydium/.gitignore b/farms/router-raydium/.gitignore new file mode 100644 index 00000000000..da0b2a84eb6 --- /dev/null +++ b/farms/router-raydium/.gitignore @@ -0,0 +1,3 @@ +/target +/include +Cargo.lock diff --git a/farms/router-raydium/Cargo.toml b/farms/router-raydium/Cargo.toml new file mode 100644 index 00000000000..fc120610099 --- /dev/null +++ b/farms/router-raydium/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "solana-farm-router-raydium" +version = "0.0.1" +description = "Solana Farm Raydium Router" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +no-entrypoint = [] +debug = [] + +[dependencies] +solana-farm-sdk = { path = "../farm-sdk" } +solana-program = "1.8.1" +arrayref = "0.3.6" + +[dev-dependencies] +solana-program-test = "1.8.1" + +[lib] +crate-type = ["cdylib", "lib"] + diff --git a/farms/router-raydium/Xargo.toml b/farms/router-raydium/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/farms/router-raydium/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/farms/router-raydium/src/add_liquidity.rs b/farms/router-raydium/src/add_liquidity.rs new file mode 100644 index 00000000000..d9af17fa062 --- /dev/null +++ b/farms/router-raydium/src/add_liquidity.rs @@ -0,0 +1,109 @@ +//! Add liquidity to the Raydium pool instruction + +use { + solana_farm_sdk::{ + instruction::raydium::RaydiumAddLiquidity, + program::{account, protocol::raydium}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn add_liquidity( + accounts: &[AccountInfo], + max_coin_token_amount: u64, + max_pc_token_amount: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::AddLiquidity"); + msg!("max_coin_token_amount {} ", max_coin_token_amount); + msg!("max_pc_token_amount {} ", max_pc_token_amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + lp_token_mint, + spl_token_id, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market + ] = accounts + { + if !raydium::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let (coin_token_amount, pc_token_amount) = raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + max_coin_token_amount, + max_pc_token_amount, + )?; + + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new_readonly(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new_readonly(*serum_market.key, false), + AccountMeta::new(*user_token_a_account.key, false), + AccountMeta::new(*user_token_b_account.key, false), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new_readonly(*user_account.key, true) + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumAddLiquidity { + instruction: 3, + max_coin_token_amount: coin_token_amount, + max_pc_token_amount: pc_token_amount, + base_side: 0, + } + .to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_token_a_account, + initial_token_a_user_balance, + coin_token_amount, + )?; + account::check_tokens_spent( + user_token_b_account, + initial_token_b_user_balance, + pc_token_amount, + )?; + account::check_tokens_received(user_lp_token_account, initial_lp_token_user_balance, 1)?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::AddLiquidity complete"); + Ok(()) +} diff --git a/farms/router-raydium/src/entrypoint.rs b/farms/router-raydium/src/entrypoint.rs new file mode 100644 index 00000000000..82641a41587 --- /dev/null +++ b/farms/router-raydium/src/entrypoint.rs @@ -0,0 +1,16 @@ +//! Program entrypoint + +#![cfg(not(feature = "no-entrypoint"))] + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/farms/router-raydium/src/lib.rs b/farms/router-raydium/src/lib.rs new file mode 100644 index 00000000000..f5ddf2f2d95 --- /dev/null +++ b/farms/router-raydium/src/lib.rs @@ -0,0 +1,9 @@ +#![forbid(unsafe_code)] + +pub mod add_liquidity; +mod entrypoint; +pub mod processor; +pub mod remove_liquidity; +pub mod stake; +pub mod swap; +pub mod unstake; diff --git a/farms/router-raydium/src/processor.rs b/farms/router-raydium/src/processor.rs new file mode 100644 index 00000000000..e71a64ca9b7 --- /dev/null +++ b/farms/router-raydium/src/processor.rs @@ -0,0 +1,59 @@ +//! Raydium router implementation. + +use { + crate::{ + add_liquidity::add_liquidity, remove_liquidity::remove_liquidity, stake::stake, swap::swap, + unstake::unstake, + }, + solana_farm_sdk::{instruction::amm::AmmInstruction, log::sol_log_params_short}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units, msg, + pubkey::Pubkey, + }, +}; + +/// Program's entrypoint. +/// +/// # Arguments +/// * `program_id` - Public key of the router. +/// * `accounts` - Accounts, see particular instruction handler for the list. +/// * `instructions_data` - Packed AmmInstruction. +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("Raydium router entrypoint"); + if cfg!(feature = "debug") { + sol_log_params_short(accounts, instruction_data); + } + + // Read and unpack instruction data + let instruction = AmmInstruction::unpack(instruction_data)?; + + match instruction { + AmmInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } => add_liquidity(accounts, max_token_a_amount, max_token_b_amount)?, + AmmInstruction::RemoveLiquidity { amount } => remove_liquidity(accounts, amount)?, + AmmInstruction::Swap { + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + } => swap( + accounts, + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + )?, + AmmInstruction::Stake { amount } => stake(accounts, amount, false)?, + AmmInstruction::Unstake { amount } => unstake(accounts, amount)?, + AmmInstruction::Harvest => stake(accounts, 0, true)?, + _ => {} + } + + sol_log_compute_units(); + msg!("Raydium router end of instruction"); + Ok(()) +} diff --git a/farms/router-raydium/src/remove_liquidity.rs b/farms/router-raydium/src/remove_liquidity.rs new file mode 100644 index 00000000000..5a745d07940 --- /dev/null +++ b/farms/router-raydium/src/remove_liquidity.rs @@ -0,0 +1,123 @@ +//! Remove liquidity from the Raydium pool instruction + +use { + solana_farm_sdk::{ + instruction::raydium::RaydiumRemoveLiquidity, + program::{account, protocol::raydium}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn remove_liquidity(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::RemoveLiquidity"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_withdraw_queue, + pool_temp_lp_token_account, + pool_coin_token_account, + pool_pc_token_account, + lp_token_mint, + spl_token_id, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + serum_program_id, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer + ] = accounts + { + if !raydium::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_lp_token_account)? + }; + + let (coin_token_amount, pc_token_amount) = raydium::get_pool_withdrawal_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + lp_token_mint, + lp_amount, + )?; + + let raydium_accounts = vec![ + AccountMeta::new_readonly(*spl_token_id.key, false), + AccountMeta::new(*amm_id.key, false), + AccountMeta::new_readonly(*amm_authority.key, false), + AccountMeta::new(*amm_open_orders.key, false), + AccountMeta::new(*amm_target.key, false), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*pool_coin_token_account.key, false), + AccountMeta::new(*pool_pc_token_account.key, false), + AccountMeta::new(*pool_withdraw_queue.key, false), + AccountMeta::new(*pool_temp_lp_token_account.key, false), + AccountMeta::new_readonly(*serum_program_id.key, false), + AccountMeta::new(*serum_market.key, false), + AccountMeta::new(*serum_coin_vault_account.key, false), + AccountMeta::new(*serum_pc_vault_account.key, false), + AccountMeta::new_readonly(*serum_vault_signer.key, false), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new(*user_token_a_account.key, false), + AccountMeta::new(*user_token_b_account.key, false), + AccountMeta::new_readonly(*user_account.key, true) + ]; + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumRemoveLiquidity { + instruction: 4, + amount: lp_amount, + } + .to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + account::check_tokens_received( + user_token_a_account, + initial_token_a_user_balance, + coin_token_amount, + )?; + account::check_tokens_received( + user_token_b_account, + initial_token_b_user_balance, + pc_token_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::RemoveLiquidity complete"); + Ok(()) +} diff --git a/farms/router-raydium/src/stake.rs b/farms/router-raydium/src/stake.rs new file mode 100644 index 00000000000..dbfc40333f7 --- /dev/null +++ b/farms/router-raydium/src/stake.rs @@ -0,0 +1,111 @@ +//! Stake LP tokens to a Raydium farm instruction + +use { + solana_farm_sdk::{ + id::zero, + instruction::raydium::RaydiumStake, + program::{account, protocol::raydium}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn stake(accounts: &[AccountInfo], amount: u64, harvest: bool) -> ProgramResult { + msg!("Processing AmmInstruction::Stake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + user_lp_token_account, + user_reward_token_a_account, + user_reward_token_b_account, + farm_program_id, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_id, + spl_token_id, + farm_id, + farm_authority + ] = accounts + { + if !raydium::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + let initial_token_a_user_balance = account::get_token_balance(user_reward_token_a_account)?; + let initial_token_b_user_balance = if dual_rewards { + account::get_token_balance(user_reward_token_b_account)? + } else { + 0 + }; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let mut raydium_accounts = Vec::with_capacity(12); + raydium_accounts.push(AccountMeta::new(*farm_id.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*farm_authority.key, false)); + raydium_accounts.push(AccountMeta::new(*user_info_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*user_account.key, true)); + raydium_accounts.push(AccountMeta::new(*user_lp_token_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_lp_token_account.key, false)); + raydium_accounts.push(AccountMeta::new(*user_reward_token_a_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_a_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*clock_id.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*spl_token_id.key, false)); + if dual_rewards { + raydium_accounts.push(AccountMeta::new(*user_reward_token_b_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_b_account.key, false)); + } + + let lp_amount = if harvest { + 0 + } else if amount > 0 { + amount + } else { + initial_lp_token_user_balance + }; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: raydium_accounts, + data: RaydiumStake { + instruction: 1, + amount: lp_amount, + } + .to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + if user_lp_token_account.key != user_reward_token_a_account.key { + let _ = account::get_balance_increase( + user_reward_token_a_account, + initial_token_a_user_balance, + )?; + } + if dual_rewards && user_lp_token_account.key != user_reward_token_b_account.key { + let _ = account::get_balance_increase( + user_reward_token_b_account, + initial_token_b_user_balance, + )?; + } + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Stake complete"); + Ok(()) +} diff --git a/farms/router-raydium/src/swap.rs b/farms/router-raydium/src/swap.rs new file mode 100644 index 00000000000..3711b2bcb60 --- /dev/null +++ b/farms/router-raydium/src/swap.rs @@ -0,0 +1,141 @@ +//! Swap tokens with the Raydium pool instruction + +use { + solana_farm_sdk::{ + instruction::raydium::RaydiumSwap, + program::{account, protocol::raydium}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn swap( + accounts: &[AccountInfo], + token_a_amount_in: u64, + token_b_amount_in: u64, + min_token_amount_out: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::Swap"); + msg!("token_a_amount_in {} ", token_a_amount_in); + msg!("token_b_amount_in {} ", token_b_amount_in); + msg!("min_token_amount_out {} ", min_token_amount_out); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + spl_token_id, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + serum_program_id, + serum_bids, + serum_asks, + serum_event_queue, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer + ] = accounts + { + if !raydium::check_pool_program_id(pool_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + + let (amount_in, mut min_amount_out) = raydium::get_pool_swap_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + token_a_amount_in, + token_b_amount_in, + )?; + if min_token_amount_out > min_amount_out { + min_amount_out = min_token_amount_out; + } + + let initial_balance_in = if token_a_amount_in == 0 { + account::get_token_balance(user_token_b_account)? + } else { + account::get_token_balance(user_token_a_account)? + }; + let initial_balance_out = if token_a_amount_in == 0 { + account::get_token_balance(user_token_a_account)? + } else { + account::get_token_balance(user_token_b_account)? + }; + + let mut raydium_accounts = Vec::with_capacity(18); + raydium_accounts.push(AccountMeta::new_readonly(*spl_token_id.key, false)); + raydium_accounts.push(AccountMeta::new(*amm_id.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*amm_authority.key, false)); + raydium_accounts.push(AccountMeta::new(*amm_open_orders.key, false)); + raydium_accounts.push(AccountMeta::new(*amm_target.key, false)); + raydium_accounts.push(AccountMeta::new(*pool_coin_token_account.key, false)); + raydium_accounts.push(AccountMeta::new(*pool_pc_token_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*serum_program_id.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_market.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_bids.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_asks.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_event_queue.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_coin_vault_account.key, false)); + raydium_accounts.push(AccountMeta::new(*serum_pc_vault_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*serum_vault_signer.key, false)); + if token_a_amount_in == 0 { + raydium_accounts.push(AccountMeta::new(*user_token_b_account.key, false)); + raydium_accounts.push(AccountMeta::new(*user_token_a_account.key, false)); + } else { + raydium_accounts.push(AccountMeta::new(*user_token_a_account.key, false)); + raydium_accounts.push(AccountMeta::new(*user_token_b_account.key, false)); + } + raydium_accounts.push(AccountMeta::new_readonly(*user_account.key, true)); + + let instruction = Instruction { + program_id: *pool_program_id.key, + accounts: raydium_accounts, + data: RaydiumSwap { + instruction: 9, + amount_in, + min_amount_out, + } + .to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + if token_a_amount_in == 0 { + user_token_b_account + } else { + user_token_a_account + }, + initial_balance_in, + amount_in, + )?; + account::check_tokens_received( + if token_a_amount_in == 0 { + user_token_a_account + } else { + user_token_b_account + }, + initial_balance_out, + min_amount_out, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Swap complete"); + Ok(()) +} diff --git a/farms/router-raydium/src/unstake.rs b/farms/router-raydium/src/unstake.rs new file mode 100644 index 00000000000..aceb3c19029 --- /dev/null +++ b/farms/router-raydium/src/unstake.rs @@ -0,0 +1,107 @@ +//! Unstake LP tokens from a Raydium farm instruction + +use { + solana_farm_sdk::{ + id::zero, + instruction::raydium::RaydiumUnstake, + program::{account, protocol::raydium}, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn unstake(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::Unstake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_info_account, + user_lp_token_account, + user_reward_token_a_account, + user_reward_token_b_account, + farm_program_id, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_id, + spl_token_id, + farm_id, + farm_authority + ] = accounts + { + if !raydium::check_stake_program_id(farm_program_id.key) { + return Err(ProgramError::IncorrectProgramId); + } + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + let initial_token_a_user_balance = account::get_token_balance(user_reward_token_a_account)?; + let initial_token_b_user_balance = if dual_rewards { + account::get_token_balance(user_reward_token_b_account)? + } else { + 0 + }; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let mut raydium_accounts = Vec::with_capacity(12); + raydium_accounts.push(AccountMeta::new(*farm_id.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*farm_authority.key, false)); + raydium_accounts.push(AccountMeta::new(*user_info_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*user_account.key, true)); + raydium_accounts.push(AccountMeta::new(*user_lp_token_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_lp_token_account.key, false)); + raydium_accounts.push(AccountMeta::new(*user_reward_token_a_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_a_account.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*clock_id.key, false)); + raydium_accounts.push(AccountMeta::new_readonly(*spl_token_id.key, false)); + if dual_rewards { + raydium_accounts.push(AccountMeta::new(*user_reward_token_b_account.key, false)); + raydium_accounts.push(AccountMeta::new(*farm_reward_token_b_account.key, false)); + } + + let lp_amount = if amount > 0 { + amount + } else { + raydium::get_stake_account_balance(user_info_account)? + }; + + let instruction = Instruction { + program_id: *farm_program_id.key, + accounts: raydium_accounts, + data: RaydiumUnstake { + instruction: 2, + amount: lp_amount, + } + .to_vec()?, + }; + invoke(&instruction, accounts)?; + + account::check_tokens_received( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + let _ = account::get_balance_increase( + user_reward_token_a_account, + initial_token_a_user_balance, + )?; + if dual_rewards { + let _ = account::get_balance_increase( + user_reward_token_b_account, + initial_token_b_user_balance, + )?; + } + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Unstake complete"); + Ok(()) +} diff --git a/farms/router-saber/.gitignore b/farms/router-saber/.gitignore new file mode 100644 index 00000000000..da0b2a84eb6 --- /dev/null +++ b/farms/router-saber/.gitignore @@ -0,0 +1,3 @@ +/target +/include +Cargo.lock diff --git a/farms/router-saber/Cargo.toml b/farms/router-saber/Cargo.toml new file mode 100644 index 00000000000..a3f0a49c976 --- /dev/null +++ b/farms/router-saber/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "solana-farm-router-saber" +version = "0.0.1" +description = "Solana Farm Saber Router" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +no-entrypoint = [] +debug = [] + +[dependencies] +solana-farm-sdk = { path = "../farm-sdk" } +solana-program = "1.8.1" +arrayref = "0.3.6" +stable-swap-client = "1.5.2" +quarry-mine = { version = "1.10.0", features = ["no-entrypoint"] } +quarry-mint-wrapper = { version = "1.10.0", features = ["no-entrypoint"] } +quarry-redeemer = { version = "1.10.0", features = ["no-entrypoint"] } +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } + +[dev-dependencies] +solana-program-test = "1.8.1" + +[lib] +crate-type = ["cdylib", "lib"] + diff --git a/farms/router-saber/Xargo.toml b/farms/router-saber/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/farms/router-saber/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/farms/router-saber/src/add_liquidity.rs b/farms/router-saber/src/add_liquidity.rs new file mode 100644 index 00000000000..575ac2cb3dc --- /dev/null +++ b/farms/router-saber/src/add_liquidity.rs @@ -0,0 +1,81 @@ +//! Add liquidity to the Saber pool instruction + +use { + solana_farm_sdk::program::account, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + stable_swap_client::instruction, +}; + +pub fn add_liquidity( + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::AddLiquidity"); + msg!("max_token_a_amount {} ", max_token_a_amount); + msg!("max_token_b_amount {} ", max_token_b_amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + _spl_token_id, + _clock_id, + swap_account, + swap_authority + ] = accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let instruction = instruction::deposit( + &spl_token::id(), + swap_account.key, + swap_authority.key, + user_account.key, + user_token_a_account.key, + user_token_b_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + lp_token_mint.key, + user_lp_token_account.key, + max_token_a_amount, + max_token_b_amount, + 1, + )?; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_token_a_account, + initial_token_a_user_balance, + max_token_a_amount, + )?; + account::check_tokens_spent( + user_token_b_account, + initial_token_b_user_balance, + max_token_b_amount, + )?; + account::check_tokens_received(user_lp_token_account, initial_lp_token_user_balance, 1)?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::AddLiquidity complete"); + Ok(()) +} diff --git a/farms/router-saber/src/entrypoint.rs b/farms/router-saber/src/entrypoint.rs new file mode 100644 index 00000000000..82641a41587 --- /dev/null +++ b/farms/router-saber/src/entrypoint.rs @@ -0,0 +1,16 @@ +//! Program entrypoint + +#![cfg(not(feature = "no-entrypoint"))] + +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/farms/router-saber/src/harvest.rs b/farms/router-saber/src/harvest.rs new file mode 100644 index 00000000000..d81860ddbec --- /dev/null +++ b/farms/router-saber/src/harvest.rs @@ -0,0 +1,132 @@ +//! Harvest rewards from a Saber farm instruction + +use { + solana_farm_sdk::{id::zero, program::account}, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn harvest(accounts: &[AccountInfo]) -> ProgramResult { + msg!("Processing AmmInstruction::Harvest"); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_iou_token_account, + user_sbr_token_account, + farm_program_id, + _spl_token_id, + _zero_id, + miner, + rewarder, + redeemer, + redeemer_program, + minter, + mint_wrapper, + mint_wrapper_program, + sbr_token_mint, + iou_token_mint, + iou_fees_account, + quarry, + saber_vault, + saber_mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info + ] = accounts + { + if &quarry_mine::id() != farm_program_id.key + || &quarry_mint_wrapper::id() != mint_wrapper_program.key + { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_iou_token_user_balance = account::get_token_balance(user_iou_token_account)?; + let initial_sbr_token_user_balance = account::get_token_balance(user_sbr_token_account)?; + + // harvest IOU rewards + let mut hasher = Hasher::default(); + hasher.hash(b"global:claim_rewards"); + + let data = hasher.result().as_ref()[..8].to_vec(); + + let saber_accounts = vec![ + AccountMeta::new(*mint_wrapper.key, false), + AccountMeta::new_readonly(*mint_wrapper_program.key, false), + AccountMeta::new(*minter.key, false), + AccountMeta::new(*iou_token_mint.key, false), + AccountMeta::new(*user_iou_token_account.key, false), + AccountMeta::new(*iou_fees_account.key, false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(zero::id(), false), + AccountMeta::new(zero::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke(&instruction, accounts)?; + + let iou_rewards = + account::get_balance_increase(user_iou_token_account, initial_iou_token_user_balance)?; + + if iou_rewards == 0 { + return Ok(()); + } + + // convert IOU to Saber + let mut hasher = Hasher::default(); + hasher.hash(b"global:redeem_all_tokens_from_mint_proxy"); + + let data = hasher.result().as_ref()[..8].to_vec(); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*redeemer.key, false), + AccountMeta::new(*iou_token_mint.key, false), + AccountMeta::new(*sbr_token_mint.key, false), + AccountMeta::new(*saber_vault.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*user_iou_token_account.key, false), + AccountMeta::new(*user_sbr_token_account.key, false), + AccountMeta::new_readonly(*mint_proxy_authority.key, false), + AccountMeta::new_readonly(*mint_proxy_state.key, false), + AccountMeta::new_readonly(*saber_mint_proxy_program.key, false), + AccountMeta::new(*minter_info.key, false), + ]; + + let instruction = Instruction { + program_id: *redeemer_program.key, + accounts: saber_accounts, + data, + }; + + invoke(&instruction, accounts)?; + + account::check_tokens_received( + user_sbr_token_account, + initial_sbr_token_user_balance, + iou_rewards, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Harvest complete"); + Ok(()) +} diff --git a/farms/router-saber/src/lib.rs b/farms/router-saber/src/lib.rs new file mode 100644 index 00000000000..7288e535c52 --- /dev/null +++ b/farms/router-saber/src/lib.rs @@ -0,0 +1,13 @@ +#![forbid(unsafe_code)] + +pub mod add_liquidity; +mod entrypoint; +pub mod harvest; +pub mod processor; +pub mod remove_liquidity; +pub mod stake; +pub mod swap; +pub mod unstake; +pub mod unwrap_token; +pub mod user_init; +pub mod wrap_token; diff --git a/farms/router-saber/src/processor.rs b/farms/router-saber/src/processor.rs new file mode 100644 index 00000000000..c387e97b485 --- /dev/null +++ b/farms/router-saber/src/processor.rs @@ -0,0 +1,62 @@ +//! Saber router implementation. + +use { + crate::{ + add_liquidity::add_liquidity, harvest::harvest, remove_liquidity::remove_liquidity, + stake::stake, swap::swap, unstake::unstake, unwrap_token::unwrap_token, + user_init::user_init, wrap_token::wrap_token, + }, + solana_farm_sdk::{instruction::amm::AmmInstruction, log::sol_log_params_short}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units, msg, + pubkey::Pubkey, + }, +}; + +/// Program's entrypoint. +/// +/// # Arguments +/// * `program_id` - Public key of the router. +/// * `accounts` - Accounts, see particular instruction handler for the list. +/// * `instructions_data` - Packed AmmInstruction. +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("Saber router entrypoint"); + if cfg!(feature = "debug") { + sol_log_params_short(accounts, instruction_data); + } + + // Read and unpack instruction data + let instruction = AmmInstruction::unpack(instruction_data)?; + + match instruction { + AmmInstruction::UserInit => user_init(accounts)?, + AmmInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } => add_liquidity(accounts, max_token_a_amount, max_token_b_amount)?, + AmmInstruction::RemoveLiquidity { amount } => remove_liquidity(accounts, amount)?, + AmmInstruction::Swap { + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + } => swap( + accounts, + token_a_amount_in, + token_b_amount_in, + min_token_amount_out, + )?, + AmmInstruction::Stake { amount } => stake(accounts, amount)?, + AmmInstruction::Unstake { amount } => unstake(accounts, amount)?, + AmmInstruction::Harvest => harvest(accounts)?, + AmmInstruction::WrapToken { amount } => wrap_token(accounts, amount)?, + AmmInstruction::UnwrapToken { amount } => unwrap_token(accounts, amount)?, + } + + sol_log_compute_units(); + msg!("Saber router end of instruction"); + Ok(()) +} diff --git a/farms/router-saber/src/remove_liquidity.rs b/farms/router-saber/src/remove_liquidity.rs new file mode 100644 index 00000000000..862db87e04d --- /dev/null +++ b/farms/router-saber/src/remove_liquidity.rs @@ -0,0 +1,81 @@ +//! Remove liquidity from the Saber pool instruction + +use { + solana_farm_sdk::program::account, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + stable_swap_client::instruction, +}; + +pub fn remove_liquidity(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::RemoveLiquidity"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + _spl_token_id, + swap_account, + swap_authority, + fees_account_a, + fees_account_b + ] = accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_lp_token_account)? + }; + + let instruction = instruction::withdraw( + &spl_token::id(), + swap_account.key, + swap_authority.key, + user_account.key, + lp_token_mint.key, + user_lp_token_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + user_token_a_account.key, + user_token_b_account.key, + fees_account_a.key, + fees_account_b.key, + lp_amount, + 1, + 1, + )?; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + account::check_tokens_received(user_token_a_account, initial_token_a_user_balance, 1)?; + account::check_tokens_received(user_token_b_account, initial_token_b_user_balance, 1)?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::RemoveLiquidity complete"); + Ok(()) +} diff --git a/farms/router-saber/src/stake.rs b/farms/router-saber/src/stake.rs new file mode 100644 index 00000000000..a923d40b8c7 --- /dev/null +++ b/farms/router-saber/src/stake.rs @@ -0,0 +1,80 @@ +//! Stake LP tokens to a Saber farm instruction + +use { + solana_farm_sdk::program::account, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn stake(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::Stake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_lp_token_account, + farm_program_id, + _spl_token_id, + miner, + miner_vault, + quarry, + rewarder + ] = accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_lp_token_account)? + }; + + let mut hasher = Hasher::default(); + hasher.hash(b"global:stake_tokens"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.extend_from_slice(&lp_amount.to_le_bytes()); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Stake complete"); + Ok(()) +} diff --git a/farms/router-saber/src/swap.rs b/farms/router-saber/src/swap.rs new file mode 100644 index 00000000000..9a00d941f5d --- /dev/null +++ b/farms/router-saber/src/swap.rs @@ -0,0 +1,117 @@ +//! Swap tokens with the Saber pool instruction + +use { + solana_farm_sdk::program::account, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, + }, + stable_swap_client::instruction, +}; + +pub fn swap( + accounts: &[AccountInfo], + token_a_amount_in: u64, + token_b_amount_in: u64, + min_token_amount_out: u64, +) -> ProgramResult { + msg!("Processing AmmInstruction::Swap"); + msg!("token_a_amount_in {} ", token_a_amount_in); + msg!("token_b_amount_in {} ", token_b_amount_in); + msg!("min_token_amount_out {} ", min_token_amount_out); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_token_a_account, + user_token_b_account, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + _spl_token_id, + _clock_id, + swap_account, + swap_authority, + fees_account_a, + fees_account_b + ] = accounts + { + if &stable_swap_client::id() != pool_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let amount_in = if token_a_amount_in == 0 { + token_b_amount_in + } else { + token_a_amount_in + }; + + let initial_balance_in = if token_a_amount_in == 0 { + account::get_token_balance(user_token_b_account)? + } else { + account::get_token_balance(user_token_a_account)? + }; + let initial_balance_out = if token_a_amount_in == 0 { + account::get_token_balance(user_token_a_account)? + } else { + account::get_token_balance(user_token_b_account)? + }; + + let instruction = if token_a_amount_in > 0 { + instruction::swap( + &spl_token::id(), + swap_account.key, + swap_authority.key, + user_account.key, + user_token_a_account.key, + pool_token_a_account.key, + pool_token_b_account.key, + user_token_b_account.key, + fees_account_b.key, + amount_in, + min_token_amount_out, + )? + } else { + instruction::swap( + &spl_token::id(), + swap_account.key, + swap_authority.key, + user_account.key, + user_token_b_account.key, + pool_token_b_account.key, + pool_token_a_account.key, + user_token_a_account.key, + fees_account_a.key, + amount_in, + min_token_amount_out, + )? + }; + + invoke(&instruction, accounts)?; + + account::check_tokens_spent( + if token_a_amount_in == 0 { + user_token_b_account + } else { + user_token_a_account + }, + initial_balance_in, + amount_in, + )?; + account::check_tokens_received( + if token_a_amount_in == 0 { + user_token_a_account + } else { + user_token_b_account + }, + initial_balance_out, + min_token_amount_out, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Swap complete"); + Ok(()) +} diff --git a/farms/router-saber/src/unstake.rs b/farms/router-saber/src/unstake.rs new file mode 100644 index 00000000000..63f65d9b825 --- /dev/null +++ b/farms/router-saber/src/unstake.rs @@ -0,0 +1,80 @@ +//! Unstake LP tokens from a Saber farm instruction + +use { + solana_farm_sdk::program::{account, protocol::saber}, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + }, +}; + +pub fn unstake(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::Unstake"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_lp_token_account, + farm_program_id, + _spl_token_id, + miner, + miner_vault, + quarry, + rewarder + ] = accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?; + + let lp_amount = if amount > 0 { + amount + } else { + saber::get_stake_account_balance(miner)? + }; + + let mut hasher = Hasher::default(); + hasher.hash(b"global:withdraw_tokens"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.extend_from_slice(&lp_amount.to_le_bytes()); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new(*user_lp_token_account.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(*rewarder.key, false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke(&instruction, accounts)?; + + account::check_tokens_received( + user_lp_token_account, + initial_lp_token_user_balance, + lp_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::Unstake complete"); + Ok(()) +} diff --git a/farms/router-saber/src/unwrap_token.rs b/farms/router-saber/src/unwrap_token.rs new file mode 100644 index 00000000000..923ac6be809 --- /dev/null +++ b/farms/router-saber/src/unwrap_token.rs @@ -0,0 +1,73 @@ +//! Unwrap token from a Saber decimal token instruction + +use { + solana_farm_sdk::program::{account, protocol::saber}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn unwrap_token(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::UnwrapToken"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_underlying_token_account, + underlying_token_mint, + _spl_token_id, + decimal_wrapper_program, + user_wrapped_token_account, + wrapped_token_mint, + wrapped_token_vault, + decimal_wrapper + ] = accounts + { + let initial_underlying_token_user_balance = + account::get_token_balance(user_underlying_token_account)?; + let initial_wrapped_token_user_balance = + account::get_token_balance(user_wrapped_token_account)?; + + let underlying_decimals = account::get_token_decimals(underlying_token_mint)?; + let wrapped_decimals = account::get_token_decimals(wrapped_token_mint)?; + + let unwrap_amount = if amount > 0 { + amount + } else { + initial_wrapped_token_user_balance + }; + + saber::unwrap_token( + decimal_wrapper, + wrapped_token_mint, + wrapped_token_vault, + user_account, + user_underlying_token_account, + user_wrapped_token_account, + decimal_wrapper_program.key, + unwrap_amount, + )?; + + account::check_tokens_received( + user_underlying_token_account, + initial_underlying_token_user_balance, + account::to_amount_with_new_decimals( + unwrap_amount, + wrapped_decimals, + underlying_decimals, + )?, + )?; + account::check_tokens_spent( + user_wrapped_token_account, + initial_wrapped_token_user_balance, + unwrap_amount, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::UnwrapToken complete"); + Ok(()) +} diff --git a/farms/router-saber/src/user_init.rs b/farms/router-saber/src/user_init.rs new file mode 100644 index 00000000000..e8ce9706b89 --- /dev/null +++ b/farms/router-saber/src/user_init.rs @@ -0,0 +1,81 @@ +//! Initialize a new user for a Saber farm instruction + +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + hash::Hasher, + instruction::{AccountMeta, Instruction}, + msg, + program::invoke, + program_error::ProgramError, + pubkey::Pubkey, + system_program, +}; + +pub fn user_init(accounts: &[AccountInfo]) -> ProgramResult { + msg!("Processing AmmInstruction::UserInit"); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + farm_program_id, + lp_token_mint, + _spl_token_id, + _system_program, + miner, + miner_vault, + quarry, + rewarder + ] = accounts + { + if &quarry_mine::id() != farm_program_id.key { + return Err(ProgramError::IncorrectProgramId); + } + + let (miner_derived, bump) = Pubkey::find_program_address( + &[ + b"Miner", + &quarry.key.to_bytes(), + &user_account.key.to_bytes(), + ], + &quarry_mine::id(), + ); + + if &miner_derived != miner.key { + msg!("Error: Invalid Miner address"); + return Err(ProgramError::InvalidSeeds); + } + + let mut hasher = Hasher::default(); + hasher.hash(b"global:create_miner"); + + let mut data = hasher.result().as_ref()[..8].to_vec(); + data.push(bump); + + let saber_accounts = vec![ + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*miner.key, false), + AccountMeta::new(*quarry.key, false), + AccountMeta::new(*rewarder.key, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(*user_account.key, true), + AccountMeta::new(*lp_token_mint.key, false), + AccountMeta::new(*miner_vault.key, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let instruction = Instruction { + program_id: quarry_mine::id(), + accounts: saber_accounts, + data, + }; + + invoke(&instruction, accounts)?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::UserInit complete"); + Ok(()) +} diff --git a/farms/router-saber/src/wrap_token.rs b/farms/router-saber/src/wrap_token.rs new file mode 100644 index 00000000000..c48c725f409 --- /dev/null +++ b/farms/router-saber/src/wrap_token.rs @@ -0,0 +1,63 @@ +//! Wrap token to a Saber decimal token instruction + +use { + solana_farm_sdk::program::{account, protocol::saber}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn wrap_token(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + msg!("Processing AmmInstruction::WrapToken"); + msg!("amount {} ", amount); + + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + user_underlying_token_account, + underlying_token_mint, + _spl_token_id, + decimal_wrapper_program, + user_wrapped_token_account, + wrapped_token_mint, + wrapped_token_vault, + decimal_wrapper + ] = accounts + { + let initial_underlying_token_user_balance = + account::get_token_balance(user_underlying_token_account)?; + let initial_wrapped_token_user_balance = + account::get_token_balance(user_wrapped_token_account)?; + + let underlying_decimals = account::get_token_decimals(underlying_token_mint)?; + let wrapped_decimals = account::get_token_decimals(wrapped_token_mint)?; + + saber::wrap_token( + decimal_wrapper, + wrapped_token_mint, + wrapped_token_vault, + user_account, + user_underlying_token_account, + user_wrapped_token_account, + decimal_wrapper_program.key, + amount, + )?; + + account::check_tokens_spent( + user_underlying_token_account, + initial_underlying_token_user_balance, + amount, + )?; + account::check_tokens_received( + user_wrapped_token_account, + initial_wrapped_token_user_balance, + account::to_amount_with_new_decimals(amount, underlying_decimals, wrapped_decimals)?, + )?; + } else { + return Err(ProgramError::NotEnoughAccountKeys); + } + + msg!("AmmInstruction::WrapToken complete"); + Ok(()) +} diff --git a/farms/vaults/.gitignore b/farms/vaults/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/farms/vaults/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/farms/vaults/Cargo.toml b/farms/vaults/Cargo.toml new file mode 100644 index 00000000000..1a10c8842b4 --- /dev/null +++ b/farms/vaults/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "solana-farm-vaults" +version = "0.0.1" +description = "Solana Farm Vaults" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[features] +no-entrypoint = [] +debug = [] +test-bpf = [] +RDM-STAKE-LP-COMPOUND = [] +SBR-STAKE-LP-COMPOUND = [] +default = ["RDM-STAKE-LP-COMPOUND"] + +[dependencies] +solana-farm-sdk = { path = "../farm-sdk" } +solana-program = "1.8.1" +spl-token = { version = "3.2.0", features = ["no-entrypoint"] } +arrayref = "0.3.6" + +[dev-dependencies] +solana-program-test = "1.8.1" + +[lib] +crate-type = ["cdylib", "lib"] + diff --git a/farms/vaults/Xargo.toml b/farms/vaults/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/farms/vaults/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/farms/vaults/src/clock.rs b/farms/vaults/src/clock.rs new file mode 100644 index 00000000000..d09de28fe4d --- /dev/null +++ b/farms/vaults/src/clock.rs @@ -0,0 +1,48 @@ +//! Timing functions + +use std::cmp; +use { + crate::vault_info::VaultInfo, + solana_farm_sdk::math, + solana_program::{ + clock::UnixTimestamp, entrypoint::ProgramResult, msg, program_error::ProgramError, sysvar, + sysvar::Sysvar, + }, +}; + +pub fn get_time() -> Result { + Ok(sysvar::clock::Clock::get()?.unix_timestamp) +} + +pub fn get_time_as_u64() -> Result { + math::checked_as_u64(sysvar::clock::Clock::get()?.unix_timestamp) +} + +pub fn check_min_crank_interval(vault_info: &VaultInfo) -> ProgramResult { + let min_crank_interval = vault_info.get_min_crank_interval()?; + if min_crank_interval == 0 { + return Ok(()); + } + let last_crank_time = vault_info.get_crank_time()?; + let cur_time = cmp::max(get_time()?, last_crank_time); + if cur_time < last_crank_time.wrapping_add(min_crank_interval) { + msg!( + "Error: Too early, please wait for the additional {} sec", + last_crank_time + .wrapping_add(min_crank_interval) + .wrapping_sub(cur_time) + ); + Err(ProgramError::Custom(309)) + } else { + Ok(()) + } +} + +/* +pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String { + match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) { + Some(ndt) => DateTime::::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true), + None => format!("UnixTimestamp {}", unix_timestamp), + } +} +*/ diff --git a/farms/vaults/src/entrypoint.rs b/farms/vaults/src/entrypoint.rs new file mode 100644 index 00000000000..b9ef46fbedb --- /dev/null +++ b/farms/vaults/src/entrypoint.rs @@ -0,0 +1,213 @@ +//! Vaults entrypoint. + +#![cfg(not(feature = "no-entrypoint"))] + +use { + crate::{ + traits::{ + AddLiquidity, Crank, Features, Init, LockLiquidity, RemoveLiquidity, Shutdown, + UnlockLiquidity, UserInit, + }, + vault_info::VaultInfo, + }, + solana_farm_sdk::{ + id::main_router, instruction::vault::VaultInstruction, log::sol_log_params_short, + program::pda, refdb, string::ArrayString64, vault::Vault, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint, + entrypoint::ProgramResult, + log::sol_log_compute_units, + msg, + program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +fn log_start(instruction: &str, vault_name: &ArrayString64) { + msg!( + "Processing VaultInstruction::{} for {}", + instruction, + vault_name.as_str() + ); + sol_log_compute_units(); +} + +fn log_end(vault_name: &ArrayString64) { + sol_log_compute_units(); + msg!("Vault {} end of instruction", vault_name.as_str()); +} + +fn check_authority(user_account: &AccountInfo, vault: &Vault) -> ProgramResult { + if user_account.key != &vault.admin_account { + msg!( + "Error: Instruction must be performed by the admin {}", + vault.admin_account + ); + Err(ProgramError::IllegalOwner) + } else if !user_account.is_signer { + Err(ProgramError::MissingRequiredSignature) + } else { + Ok(()) + } +} + +entrypoint!(process_instruction); +/// Program's entrypoint. +/// +/// # Arguments +/// * `program_id` - Public key of the vault. +/// * `accounts` - Accounts, see handlers in particular strategy for the list. +/// * `instructions_data` - Packed VaultInstruction. +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("Vault entrypoint"); + if cfg!(feature = "debug") { + sol_log_params_short(accounts, instruction_data); + } + + let account_info_iter = &mut accounts.iter(); + let user_account = next_account_info(account_info_iter)?; + let vault_metadata = next_account_info(account_info_iter)?; + let vault_info_account = next_account_info(account_info_iter)?; + + // unpack Vault's metadata and validate Vault accounts + let vault = Vault::unpack(&vault_metadata.try_borrow_data()?)?; + let derived_vault_metadata = pda::find_target_pda_with_bump( + refdb::StorageType::Vault, + &vault.name, + vault.metadata_bump, + )?; + if &vault.info_account != vault_info_account.key + || &derived_vault_metadata != vault_metadata.key + || vault_metadata.owner != &main_router::id() + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if &vault.vault_program_id != program_id { + msg!("Error: Invalid Vault program id"); + return Err(ProgramError::IncorrectProgramId); + } + + // Read and unpack instruction data + let instruction = VaultInstruction::unpack(instruction_data)?; + + match instruction { + VaultInstruction::UserInit => { + log_start("UserInit", &vault.name); + VaultInstruction::user_init(&vault, accounts)? + } + VaultInstruction::AddLiquidity { + max_token_a_amount, + max_token_b_amount, + } => { + log_start("AddLiquidity", &vault.name); + VaultInstruction::add_liquidity( + &vault, + accounts, + max_token_a_amount, + max_token_b_amount, + )? + } + VaultInstruction::LockLiquidity { amount } => { + log_start("LockLiquidity", &vault.name); + VaultInstruction::lock_liquidity(&vault, accounts, amount)? + } + VaultInstruction::UnlockLiquidity { amount } => { + log_start("UnlockLiquidity", &vault.name); + VaultInstruction::unlock_liquidity(&vault, accounts, amount)? + } + VaultInstruction::RemoveLiquidity { amount } => { + log_start("RemoveLiquidity", &vault.name); + VaultInstruction::remove_liquidity(&vault, accounts, amount)? + } + VaultInstruction::SetMinCrankInterval { min_crank_interval } => { + log_start("SetMinCrankInterval", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::set_min_crank_interval( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + min_crank_interval as u64, + )? + } + VaultInstruction::SetFee { fee } => { + log_start("SetFee", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::set_fee( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + fee as f64, + )? + } + VaultInstruction::SetExternalFee { external_fee } => { + log_start("SetExternalFee", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::set_external_fee( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + external_fee as f64, + )? + } + VaultInstruction::EnableDeposit => { + log_start("EnableDeposit", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::enable_deposit( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + )? + } + VaultInstruction::DisableDeposit => { + log_start("DisableDeposit", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::disable_deposit( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + )? + } + VaultInstruction::EnableWithdrawal => { + log_start("EnableWithdrawal", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::enable_withdrawal( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + )? + } + VaultInstruction::DisableWithdrawal => { + log_start("DisableWithdrawal", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::disable_withdrawal( + &vault, + &mut VaultInfo::new(vault_info_account), + accounts, + )? + } + VaultInstruction::Crank { step } => { + log_start("Crank", &vault.name); + VaultInstruction::crank(&vault, accounts, step)? + } + VaultInstruction::Init { step } => { + log_start("Init", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::init(&vault, accounts, step)? + } + VaultInstruction::Shutdown => { + log_start("Shutdown", &vault.name); + check_authority(user_account, &vault)?; + VaultInstruction::shutdown(&vault, accounts)? + } + } + + log_end(&vault.name); + Ok(()) +} diff --git a/farms/vaults/src/lib.rs b/farms/vaults/src/lib.rs new file mode 100644 index 00000000000..ae369b84114 --- /dev/null +++ b/farms/vaults/src/lib.rs @@ -0,0 +1,8 @@ +#![forbid(unsafe_code)] + +pub mod clock; +mod entrypoint; +pub mod strategies; +pub mod traits; +pub mod user_info; +pub mod vault_info; diff --git a/farms/vaults/src/strategies/common.rs b/farms/vaults/src/strategies/common.rs new file mode 100644 index 00000000000..098a1f1779e --- /dev/null +++ b/farms/vaults/src/strategies/common.rs @@ -0,0 +1,62 @@ +//! Common functions + +use { + solana_farm_sdk::{ + id::zero, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +#[allow(clippy::too_many_arguments)] +pub fn check_custody_accounts<'a, 'b>( + vault: &Vault, + lp_token_custody: &'a AccountInfo<'b>, + token_a_custody: &'a AccountInfo<'b>, + token_b_custody: &'a AccountInfo<'b>, + token_a_reward_custody: &'a AccountInfo<'b>, + token_b_reward_custody: &'a AccountInfo<'b>, + vault_stake_info: &'a AccountInfo<'b>, + check_non_reward_custody: bool, +) -> ProgramResult { + if let VaultStrategy::StakeLpCompoundRewards { + pool_id_ref: _, + farm_id_ref: _, + lp_token_custody: lp_token_custody_key, + token_a_custody: token_a_custody_key, + token_b_custody: token_b_custody_key, + token_a_reward_custody: token_a_reward_custody_key, + token_b_reward_custody: token_b_reward_custody_key, + vault_stake_info: vault_stake_info_key, + } = vault.strategy + { + if &vault_stake_info_key != vault_stake_info.key { + msg!("Error: Invalid Vault Stake Info account"); + return Err(ProgramError::InvalidArgument); + } + if &token_a_reward_custody_key != token_a_reward_custody.key + || &token_b_reward_custody_key + .or_else(|| Some(zero::id())) + .unwrap() + != token_b_reward_custody.key + || &lp_token_custody_key != lp_token_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + if check_non_reward_custody + && (&token_a_custody_key != token_a_custody.key + || &token_b_custody_key.or_else(|| Some(zero::id())).unwrap() + != token_b_custody.key) + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + Ok(()) +} diff --git a/farms/vaults/src/strategies/mod.rs b/farms/vaults/src/strategies/mod.rs new file mode 100644 index 00000000000..6e484d4dafe --- /dev/null +++ b/farms/vaults/src/strategies/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "RDM-STAKE-LP-COMPOUND")] +pub mod rdm_stake_lp_compound; + +#[cfg(feature = "SBR-STAKE-LP-COMPOUND")] +pub mod sbr_stake_lp_compound; + +pub mod common; diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/add_liquidity.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/add_liquidity.rs new file mode 100644 index 00000000000..8408c0bf415 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/add_liquidity.rs @@ -0,0 +1,266 @@ +//! Add Liquidity to the Vault instruction handler + +use { + crate::{strategies::common, traits::AddLiquidity, user_info::UserInfo, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + instruction::vault::VaultInstruction, + program::{account, pda, protocol::raydium}, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl AddLiquidity for VaultInstruction { + fn add_liquidity( + vault: &Vault, + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, + ) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + vault_token_mint, + user_info_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + user_vt_token_account, + token_a_reward_custody, + token_b_reward_custody, + lp_token_custody, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + lp_token_mint, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + farm_program, + vault_stake_info, + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_program + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if &account::get_token_account_owner(user_vt_token_account)? != user_account.key { + msg!("Error: Invalid VT token account owner"); + return Err(ProgramError::IllegalOwner); + } + common::check_custody_accounts( + vault, + lp_token_custody, + vault_authority, + vault_authority, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + false, + )?; + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_deposit_allowed()? { + msg!("Error: Deposits are not allowed for this Vault"); + return Err(ProgramError::Custom(220)); + } + + // read user balances + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_user_balance = account::get_token_balance(user_lp_token_account)?; + + // calculate deposit amounts + let (max_token_a_deposit_amount, max_token_b_deposit_amount) = + raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + max_token_a_amount, + max_token_b_amount, + )?; + + // Deposit tokens into the pool + msg!("Deposit tokens into the pool. max_token_a_deposit_amount: {}, max_token_b_deposit_amount: {}", max_token_a_deposit_amount, max_token_b_deposit_amount); + if max_token_a_deposit_amount == 0 || max_token_b_deposit_amount == 0 { + msg!("Error: Zero deposit amount"); + return Err(ProgramError::InsufficientFunds); + } + raydium::add_liquidity( + &[ + user_account.clone(), + user_token_a_account.clone(), + user_token_b_account.clone(), + user_lp_token_account.clone(), + pool_program_id.clone(), + pool_coin_token_account.clone(), + pool_pc_token_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + amm_id.clone(), + amm_authority.clone(), + amm_open_orders.clone(), + amm_target.clone(), + serum_market.clone(), + ], + max_token_a_deposit_amount, + max_token_b_deposit_amount, + )?; + + // check amounts spent and received + let tokens_a_spent = account::check_tokens_spent( + user_token_a_account, + initial_token_a_user_balance, + max_token_a_deposit_amount, + )?; + let tokens_b_spent = account::check_tokens_spent( + user_token_b_account, + initial_token_b_user_balance, + max_token_b_deposit_amount, + )?; + let lp_tokens_received = + account::check_tokens_received(user_lp_token_account, initial_lp_user_balance, 1)?; + let initial_lp_token_custody_balance = account::get_token_balance(lp_token_custody)?; + + // transfer LP tokens to the custody + msg!( + "Transfer LP tokens from user. tokens_a_spent: {}, tokens_b_spent: {}, lp_tokens_received: {}", + tokens_a_spent, + tokens_b_spent, + lp_tokens_received + ); + account::transfer_tokens( + user_lp_token_account, + lp_token_custody, + user_account, + lp_tokens_received, + )?; + + // Stake LP tokens + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + let initial_token_a_reward_balance = + account::get_token_balance(token_a_reward_custody)?; + let initial_token_b_reward_balance = if dual_rewards { + account::get_token_balance(token_b_reward_custody)? + } else { + 0 + }; + + msg!("Stake LP tokens"); + let stake_balance = raydium::get_stake_account_balance(vault_stake_info)?; + + raydium::stake_with_seeds( + &[ + vault_authority.clone(), + vault_stake_info.clone(), + lp_token_custody.clone(), + token_a_reward_custody.clone(), + token_b_reward_custody.clone(), + farm_program.clone(), + farm_lp_token_account.clone(), + farm_reward_token_a_account.clone(), + farm_reward_token_b_account.clone(), + clock_program.clone(), + spl_token_program.clone(), + farm_id.clone(), + farm_authority.clone(), + ], + seeds, + lp_tokens_received, + )?; + if initial_lp_token_custody_balance != account::get_token_balance(lp_token_custody)? { + msg!( + "Error: Stake instruction didn't result in expected amount of LP tokens spent" + ); + return Err(ProgramError::Custom(165)); + } + + // update user stats + msg!("Update user stats"); + let mut user_info = UserInfo::new(user_info_account); + user_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + + // update Vault stats + let token_a_rewards = account::get_balance_increase( + token_a_reward_custody, + initial_token_a_reward_balance, + )?; + let token_b_rewards = if dual_rewards { + account::get_balance_increase( + token_b_reward_custody, + initial_token_b_reward_balance, + )? + } else { + 0 + }; + msg!( + "Update Vault stats. token_a_rewards: {}, token_b_rewards: {}", + token_a_rewards, + token_b_rewards + ); + vault_info.add_rewards(token_a_rewards, token_b_rewards)?; + vault_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + + // compute Vault tokens to mint + let vt_supply_amount = account::get_token_supply(vault_token_mint)?; + let vt_to_mint = if vt_supply_amount == 0 || stake_balance == 0 { + lp_tokens_received + } else { + account::to_token_amount( + lp_tokens_received as f64 / stake_balance as f64 * vt_supply_amount as f64, + 0, + )? + }; + + // mint vault tokens to user + msg!( + "Mint Vault tokens to the user. vt_to_mint: {}, vt_supply_amount: {}, stake_balance: {}", + vt_to_mint, vt_supply_amount, + stake_balance + ); + if vt_to_mint == 0 { + msg!("Error: Add liquidity instruction didn't result in Vault tokens mint"); + return Err(ProgramError::Custom(170)); + } + pda::mint_to_with_seeds( + user_vt_token_account, + vault_token_mint, + vault_authority, + seeds, + vt_to_mint, + )?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/crank.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank.rs new file mode 100644 index 00000000000..18de582a00d --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank.rs @@ -0,0 +1,26 @@ +//! Vault Crank instruction handler + +use { + crate::{ + strategies::rdm_stake_lp_compound::{crank1::crank1, crank2::crank2, crank3::crank3}, + traits::Crank, + }, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Crank for VaultInstruction { + fn crank(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult { + match step { + 1 => crank1(vault, accounts), + 2 => crank2(vault, accounts), + 3 => crank3(vault, accounts), + _ => { + msg!("Error: Invalid Crank step"); + Err(ProgramError::InvalidArgument) + } + } + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/crank1.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank1.rs new file mode 100644 index 00000000000..f859493e2d4 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank1.rs @@ -0,0 +1,158 @@ +//! Crank step 1 instruction handler + +use { + crate::{clock::check_min_crank_interval, strategies::common, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + program::{account, pda, protocol::raydium}, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank1(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + token_a_reward_custody, + token_b_reward_custody, + lp_token_custody, + fees_account_a, + fees_account_b, + farm_program, + vault_stake_info, + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_program + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + common::check_custody_accounts( + vault, + lp_token_custody, + vault_authority, + vault_authority, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + false, + )?; + + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + + if Some(*fees_account_a.key) != vault.fees_account_a + || (dual_rewards && Some(*fees_account_b.key) != vault.fees_account_b) + { + msg!("Error: Invalid fee accounts"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + + // harvest + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let initial_token_a_reward_balance = account::get_token_balance(token_a_reward_custody)?; + let initial_token_b_reward_balance = if dual_rewards { + account::get_token_balance(token_b_reward_custody)? + } else { + 0 + }; + let initial_lp_tokens_balance = account::get_token_balance(lp_token_custody)?; + + msg!("Harvest rewards"); + raydium::stake_with_seeds( + &[ + vault_authority.clone(), + vault_stake_info.clone(), + lp_token_custody.clone(), + token_a_reward_custody.clone(), + token_b_reward_custody.clone(), + farm_program.clone(), + farm_lp_token_account.clone(), + farm_reward_token_a_account.clone(), + farm_reward_token_b_account.clone(), + clock_program.clone(), + spl_token_program.clone(), + farm_id.clone(), + farm_authority.clone(), + ], + seeds, + 0, + )?; + let _ = account::check_tokens_spent(lp_token_custody, initial_lp_tokens_balance, 0)?; + + // calculate rewards + let token_a_rewards = + account::get_balance_increase(token_a_reward_custody, initial_token_a_reward_balance)?; + let token_b_rewards = if dual_rewards { + account::get_balance_increase(token_b_reward_custody, initial_token_b_reward_balance)? + } else { + 0 + }; + msg!( + "Rewards received. token_a_rewards: {}, token_b_rewards: {}", + token_a_rewards, + token_b_rewards + ); + // take fees + let fee = vault_info.get_fee()?; + if !(0.0..=1.0).contains(&fee) { + msg!("Error: Invalid fee. fee: {}", fee); + return Err(ProgramError::Custom(260)); + } + let fees_a = account::to_token_amount(token_a_rewards as f64 * fee, 0)?; + let fees_b = account::to_token_amount(token_b_rewards as f64 * fee, 0)?; + msg!( + "Apply fees. fee: {}, fees_a: {}, fees_b: {}", + fee, + fees_a, + fees_b + ); + pda::transfer_tokens_with_seeds( + token_a_reward_custody, + fees_account_a, + vault_authority, + seeds, + fees_a, + )?; + if dual_rewards { + pda::transfer_tokens_with_seeds( + token_b_reward_custody, + fees_account_b, + vault_authority, + seeds, + fees_b, + )?; + } + + // update Vault stats + msg!("Update Vault stats",); + vault_info.add_rewards(token_a_rewards, token_b_rewards)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(1)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/crank2.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank2.rs new file mode 100644 index 00000000000..5371a500ba9 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank2.rs @@ -0,0 +1,288 @@ +//! Crank step 2 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + program::{account, pda, protocol::raydium}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank2(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + token_a_reward_custody, + token_b_reward_custody, + token_a_custody, + token_b_custody, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + serum_program_id, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + token_a_custody: token_a_custody_key, + token_b_custody: token_b_custody_key, + token_a_reward_custody: token_a_reward_custody_key, + token_b_reward_custody: token_b_reward_custody_key, + .. + } = vault.strategy + { + if &token_a_reward_custody_key != token_a_reward_custody.key + || &token_b_reward_custody_key.or_else(||Some(zero::id())).unwrap() + != token_b_reward_custody.key + || &token_a_custody_key != token_a_custody.key + || &token_b_custody_key.or_else(||Some(zero::id())).unwrap() != token_b_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(2)?; + + // read reward balances + let dual_rewards = *token_b_reward_custody.key != zero::id(); + let token_a_reward_balance = account::get_token_balance(token_a_reward_custody)?; + let token_b_reward_balance = if dual_rewards { + account::get_token_balance(token_b_reward_custody)? + } else { + 0 + }; + msg!( + "Read reward balances. token_a_reward_balance: {}, token_b_reward_balance: {}", + token_a_reward_balance, + token_b_reward_balance + ); + + // move rewards to token custodies + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let token_a_reward_mint = account::get_token_account_mint(token_a_reward_custody)?; + let token_a_custody_mint = account::get_token_account_mint(token_a_custody)?; + let token_b_custody_mint = account::get_token_account_mint(token_b_custody)?; + + if token_a_reward_mint == token_a_custody_mint { + pda::transfer_tokens_with_seeds( + token_a_reward_custody, + token_a_custody, + vault_authority, + seeds, + token_a_reward_balance, + )?; + } else if token_a_reward_mint == token_b_custody_mint { + pda::transfer_tokens_with_seeds( + token_a_reward_custody, + token_b_custody, + vault_authority, + seeds, + token_a_reward_balance, + )?; + } + if dual_rewards { + let token_b_reward_mint = account::get_token_account_mint(token_b_reward_custody)?; + if token_b_reward_mint == token_b_custody_mint { + pda::transfer_tokens_with_seeds( + token_b_reward_custody, + token_b_custody, + vault_authority, + seeds, + token_b_reward_balance, + )?; + } else if token_b_reward_mint == token_a_custody_mint { + pda::transfer_tokens_with_seeds( + token_b_reward_custody, + token_a_custody, + vault_authority, + seeds, + token_b_reward_balance, + )?; + } + } + + // read balances + let token_a_balance = account::get_token_balance(token_a_custody)?; + let token_b_balance = account::get_token_balance(token_b_custody)?; + msg!( + "Read balances. token_a_balance: {}, token_b_balance: {}", + token_a_balance, + token_b_balance + ); + if token_a_balance < 10 && token_b_balance < 10 { + msg!("Nothing to do: Not enough tokens to balance"); + return Ok(()); + } + + // rebalance + // compute and check pool ratios + let (pool_coin_balance, pool_pc_balance) = raydium::get_pool_token_balances( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + )?; + let pool_ratio = if pool_coin_balance != 0 { + pool_pc_balance as f64 / pool_coin_balance as f64 + } else { + 0.0 + }; + let custody_ratio = account::get_token_pair_ratio(token_a_custody, token_b_custody)?; + msg!( + "Compute pool ratios. custody_ratio: {}, pool_ratio: {}", + custody_ratio, + pool_ratio + ); + if pool_ratio == 0.0 { + msg!("Can't balance: Pool ratio is zero"); + return Ok(()); + } + if custody_ratio > 0.0 && (custody_ratio - pool_ratio).abs() * 100.0 / pool_ratio < 3.0 { + msg!("Nothing to do: Already balanced"); + return Ok(()); + } + + // compute ui amount to exchange + let extra_a_tokens = + (token_a_balance as f64 * pool_ratio - token_b_balance as f64) / (2.0 * pool_ratio); + let extra_b_tokens = extra_a_tokens * pool_ratio; + let reverse = extra_a_tokens < 0.0; + msg!( + "Rebalance tokens. reverse: {}, extra_a_tokens: {}, extra_b_tokens: {}", + reverse, + extra_a_tokens, + extra_b_tokens + ); + + let token_a_swap_custody = if reverse { + token_b_custody + } else { + token_a_custody + }; + let token_b_swap_custody = if reverse { + token_a_custody + } else { + token_b_custody + }; + let coint_extra_amount_in = if !reverse { + account::to_token_amount(extra_a_tokens.abs(), 0)? + } else { + 0 + }; + let pc_extra_amount_in = if !reverse { + 0 + } else { + account::to_token_amount(extra_b_tokens.abs(), 0)? + }; + if coint_extra_amount_in < 2 && pc_extra_amount_in < 2 { + msg!("Nothing to do: Not enough tokens to balance"); + return Ok(()); + } + + // get exact swap amounts + let (amount_in, min_amount_out) = raydium::get_pool_swap_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + coint_extra_amount_in, + pc_extra_amount_in, + )?; + msg!( + "Swap. amount_in: {}, min_amount_out {}", + amount_in, + min_amount_out + ); + if amount_in == 0 || min_amount_out == 0 { + msg!("Nothing to do: Not enough tokens to balance"); + return Ok(()); + } + + let initial_tokens_spent_balance = account::get_token_balance(token_a_swap_custody)?; + let initial_tokens_received_balance = account::get_token_balance(token_b_swap_custody)?; + + raydium::swap_with_seeds( + &[ + vault_authority.clone(), + token_a_swap_custody.clone(), + token_b_swap_custody.clone(), + pool_program_id.clone(), + pool_coin_token_account.clone(), + pool_pc_token_account.clone(), + spl_token_program.clone(), + amm_id.clone(), + amm_authority.clone(), + amm_open_orders.clone(), + amm_target.clone(), + serum_market.clone(), + serum_program_id.clone(), + serum_bids.clone(), + serum_asks.clone(), + serum_event_queue.clone(), + serum_coin_vault_account.clone(), + serum_pc_vault_account.clone(), + serum_vault_signer.clone(), + ], + seeds, + amount_in, + min_amount_out, + )?; + let _ = account::check_tokens_spent( + token_a_swap_custody, + initial_tokens_spent_balance, + amount_in, + )?; + let tokens_received = account::check_tokens_received( + token_b_swap_custody, + initial_tokens_received_balance, + min_amount_out, + )?; + + msg!( + "Done. tokens_received: {}, token_a_balance: {}, token_b_balance: {}", + tokens_received, + account::get_token_balance(token_a_custody)?, + account::get_token_balance(token_b_custody)? + ); + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/crank3.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank3.rs new file mode 100644 index 00000000000..3f69bb78622 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/crank3.rs @@ -0,0 +1,275 @@ +//! Crank step 3 instruction handler + +use { + crate::{clock::check_min_crank_interval, strategies::common, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + program::{account, protocol::raydium}, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank3(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + token_a_reward_custody, + token_b_reward_custody, + lp_token_custody, + token_a_custody, + token_b_custody, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + lp_token_mint, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + farm_program, + vault_stake_info, + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_program + ] = accounts + { + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + common::check_custody_accounts( + vault, + lp_token_custody, + token_a_custody, + token_b_custody, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + true, + )?; + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(3)?; + + // read balances + let token_a_balance = account::get_token_balance(token_a_custody)?; + let token_b_balance = account::get_token_balance(token_b_custody)?; + let lp_token_balance = account::get_token_balance(lp_token_custody)?; + msg!( + "Read balances. token_a_balance: {}, token_b_balance: {}", + token_a_balance, + token_b_balance + ); + if token_a_balance < 10 || token_b_balance < 10 { + msg!("Nothing to do: Not enough tokens to compound"); + return Ok(()); + } + + // compute and check pool ratios + let (pool_coin_balance, pool_pc_balance) = raydium::get_pool_token_balances( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + )?; + let pool_ratio = if pool_coin_balance != 0 { + pool_pc_balance as f64 / pool_coin_balance as f64 + } else { + 0.0 + }; + let custody_ratio = account::get_token_pair_ratio(token_a_custody, token_b_custody)?; + msg!( + "Compute pool ratios. custody_ratio: {}, pool_ratio: {}", + custody_ratio, + pool_ratio + ); + if custody_ratio == 0.0 || pool_ratio == 0.0 { + msg!("Pool ratio is zero"); + return Ok(()); + } + if (custody_ratio - pool_ratio).abs() * 100.0 / pool_ratio > 10.0 { + msg!("Unbalanced tokens, run Crank2 first"); + return Ok(()); + } + + // Deposit tokens into the pool + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + // calculate deposit amounts + let (max_token_a_deposit_amount, max_token_b_deposit_amount) = + if custody_ratio >= pool_ratio { + raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + token_a_balance, + 0, + )? + } else { + raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + 0, + token_b_balance, + )? + }; + // one of the amounts can come out over the balance because ratios didn't reflect + // deposited volume, while get_pool_deposit_amounts does include it. + // in this case we just flip the side. + let (max_token_a_deposit_amount, max_token_b_deposit_amount) = + if max_token_b_deposit_amount > token_b_balance { + raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + 0, + token_b_balance, + )? + } else if max_token_a_deposit_amount > token_a_balance { + raydium::get_pool_deposit_amounts( + pool_coin_token_account, + pool_pc_token_account, + amm_open_orders, + amm_id, + token_a_balance, + 0, + )? + } else { + (max_token_a_deposit_amount, max_token_b_deposit_amount) + }; + + msg!("Deposit tokens into the pool. max_token_a_deposit_amount: {}, max_token_b_deposit_amount: {}", + max_token_a_deposit_amount, + max_token_b_deposit_amount); + if max_token_a_deposit_amount == 0 + || max_token_b_deposit_amount == 0 + || raydium::estimate_lp_tokens_amount( + lp_token_mint, + max_token_a_deposit_amount, + max_token_b_deposit_amount, + pool_coin_balance, + pool_pc_balance, + )? < 2 + { + msg!("Nothing to do: Tokens balance is not large enough"); + return Ok(()); + } + + raydium::add_liquidity_with_seeds( + &[ + vault_authority.clone(), + token_a_custody.clone(), + token_b_custody.clone(), + lp_token_custody.clone(), + pool_program_id.clone(), + pool_coin_token_account.clone(), + pool_pc_token_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + amm_id.clone(), + amm_authority.clone(), + amm_open_orders.clone(), + amm_target.clone(), + serum_market.clone(), + ], + seeds, + max_token_a_deposit_amount, + max_token_b_deposit_amount, + )?; + + // Check tokens spent and return change back to user + let tokens_a_spent = account::check_tokens_spent( + token_a_custody, + token_a_balance, + max_token_a_deposit_amount, + )?; + let tokens_b_spent = account::check_tokens_spent( + token_b_custody, + token_b_balance, + max_token_b_deposit_amount, + )?; + + // Stake LP tokens + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + let lp_tokens_received = + account::check_tokens_received(lp_token_custody, lp_token_balance, 1)?; + msg!( + "Stake LP tokens. tokens_a_spent: {}, tokens_b_spent: {}, lp_tokens_received: {}", + tokens_a_spent, + tokens_b_spent, + lp_tokens_received + ); + let token_a_reward_balance = account::get_token_balance(token_a_reward_custody)?; + let token_b_reward_balance = if dual_rewards { + account::get_token_balance(token_b_reward_custody)? + } else { + 0 + }; + + raydium::stake_with_seeds( + &[ + vault_authority.clone(), + vault_stake_info.clone(), + lp_token_custody.clone(), + token_a_reward_custody.clone(), + token_b_reward_custody.clone(), + farm_program.clone(), + farm_lp_token_account.clone(), + farm_reward_token_a_account.clone(), + farm_reward_token_b_account.clone(), + clock_program.clone(), + spl_token_program.clone(), + farm_id.clone(), + farm_authority.clone(), + ], + seeds, + lp_tokens_received, + )?; + if lp_token_balance != account::get_token_balance(lp_token_custody)? { + msg!("Error: Stake instruction didn't result in expected amount of LP tokens spent"); + return Err(ProgramError::Custom(165)); + } + + // update Vault stats + let token_a_rewards = + account::get_balance_increase(token_a_reward_custody, token_a_reward_balance)?; + let token_b_rewards = if dual_rewards { + account::get_balance_increase(token_b_reward_custody, token_b_reward_balance)? + } else { + 0 + }; + msg!( + "Update Vault stats. token_a_rewards: {}, token_b_rewards: {}", + token_a_rewards, + token_b_rewards + ); + vault_info.add_rewards(token_a_rewards, token_b_rewards)?; + vault_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/features.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/features.rs new file mode 100644 index 00000000000..c93ad039917 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/features.rs @@ -0,0 +1,85 @@ +//! Feature toggling instructions handlers + +use { + crate::{traits::Features, vault_info::VaultInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Features for VaultInstruction { + fn set_min_crank_interval( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + min_crank_interval_sec: u64, + ) -> ProgramResult { + msg!("set_min_crank_interval: {}", min_crank_interval_sec); + vault_info.set_min_crank_interval(min_crank_interval_sec) + } + + fn set_fee( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + fee: f64, + ) -> ProgramResult { + msg!("set_fee: {}", fee); + if !(0.0..=1.0).contains(&fee) { + msg!("Error: Invalid new value for fee"); + return Err(ProgramError::InvalidArgument); + } + vault_info.set_fee(fee) + } + + fn set_external_fee( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + external_fee: f64, + ) -> ProgramResult { + msg!("external_fee: {}", external_fee); + if !(0.0..=1.0).contains(&external_fee) { + msg!("Error: Invalid new value for external_fee"); + return Err(ProgramError::InvalidArgument); + } + vault_info.set_external_fee(external_fee) + } + + fn enable_deposit( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("enable_deposit"); + vault_info.enable_deposit() + } + + fn disable_deposit( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("disable_deposit"); + vault_info.disable_deposit() + } + + fn enable_withdrawal( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("enable_withdrawal"); + vault_info.enable_withdrawal() + } + + fn disable_withdrawal( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("disable_withdrawal"); + vault_info.disable_withdrawal() + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/init.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/init.rs new file mode 100644 index 00000000000..1c0a4b47670 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/init.rs @@ -0,0 +1,215 @@ +//! Vault Init instruction handler + +use { + crate::{traits::Init, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + instruction::vault::VaultInstruction, + program::{ + pda, + protocol::raydium::{RaydiumUserStakeInfo, RaydiumUserStakeInfoV4}, + }, + token::Token, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Init for VaultInstruction { + fn init(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + admin_account, + _vault_metadata, + vault_info_account, + vault_authority, + vault_program, + _system_program, + _spl_token_program, + rent_program, + farm_program, + vault_token_mint, + vault_token_ref, + vault_stake_info, + vault_stake_info_v4, + fees_account_a, + fees_account_b, + token_a_custody, + token_b_custody, + lp_token_custody, + token_a_mint, + token_b_mint, + lp_token_mint, + token_a_reward_custody, + token_b_reward_custody, + token_a_reward_mint, + token_b_reward_mint + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority + || vault_token_ref.key != &vault.vault_token_ref + || vault_program.key != &vault.vault_program_id + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + + if step <= 1 { + // init vault authority account + msg!("Init vault authority"); + pda::init_system_account( + admin_account, + vault_authority, + &vault.vault_program_id, + &vault.vault_program_id, + &[b"vault_authority", vault.name.as_bytes()], + 0, + )?; + + // init vault info account + msg!("Init vault info"); + pda::init_system_account( + admin_account, + vault_info_account, + &vault.vault_program_id, + &vault.vault_program_id, + &[b"info_account", vault.name.as_bytes()], + VaultInfo::LEN, + )?; + let mut vault_info = VaultInfo::new(vault_info_account); + vault_info.init(&vault.name)?; + + // init vault token mint + msg!("Init vault token mint"); + let vault_token = Token::unpack(&vault_token_ref.try_borrow_data()?)?; + if vault_token_mint.key != &vault_token.mint { + msg!("Error: Invalid Vault token mint"); + return Err(ProgramError::InvalidArgument); + } + pda::init_mint( + admin_account, + vault_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"vault_token_mint", vault.name.as_bytes()], + vault_token.decimals, + )?; + + // init stake info + msg!("Init stake info"); + if vault_stake_info.key != &zero::id() { + pda::init_system_account( + admin_account, + vault_stake_info, + farm_program.key, + &vault.vault_program_id, + &[b"vault_stake_info", vault.name.as_bytes()], + RaydiumUserStakeInfo::LEN, + )?; + } else { + pda::init_system_account( + admin_account, + vault_stake_info_v4, + farm_program.key, + &vault.vault_program_id, + &[b"vault_stake_info_v4", vault.name.as_bytes()], + RaydiumUserStakeInfoV4::LEN, + )?; + } + } + + if step == 0 || step == 2 { + // init token accounts + msg!("Init fees account a"); + pda::init_token_account( + admin_account, + fees_account_a, + token_a_reward_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"fees_account_a", vault.name.as_bytes()], + )?; + + if *fees_account_b.key != zero::id() { + msg!("Init fees account b"); + pda::init_token_account( + admin_account, + fees_account_b, + token_b_reward_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"fees_account_b", vault.name.as_bytes()], + )?; + } + + msg!("Init lp token custody account"); + pda::init_token_account( + admin_account, + lp_token_custody, + lp_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"lp_token_custody", vault.name.as_bytes()], + )?; + + msg!("Init token a custody account"); + pda::init_token_account( + admin_account, + token_a_custody, + token_a_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_a_custody", vault.name.as_bytes()], + )?; + + msg!("Init token b custody account"); + pda::init_token_account( + admin_account, + token_b_custody, + token_b_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_b_custody", vault.name.as_bytes()], + )?; + + msg!("Init token a reward custody account"); + pda::init_token_account( + admin_account, + token_a_reward_custody, + token_a_reward_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_a_reward_custody", vault.name.as_bytes()], + )?; + + if *token_b_reward_custody.key != zero::id() { + msg!("Init token b reward custody account"); + pda::init_token_account( + admin_account, + token_b_reward_custody, + token_b_reward_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_b_reward_custody", vault.name.as_bytes()], + )?; + } + } + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/lock_liquidity.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/lock_liquidity.rs new file mode 100644 index 00000000000..423f8c60eed --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/lock_liquidity.rs @@ -0,0 +1,16 @@ +//! Lock Liquidity in the Vault instruction handler + +use { + crate::traits::LockLiquidity, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl LockLiquidity for VaultInstruction { + fn lock_liquidity(_vault: &Vault, _accounts: &[AccountInfo], _amount: u64) -> ProgramResult { + msg!("Error: Liquidity Lock is not required for this Vault"); + Err(ProgramError::InvalidArgument) + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/mod.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/mod.rs new file mode 100644 index 00000000000..561e56d59bb --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/mod.rs @@ -0,0 +1,13 @@ +pub mod add_liquidity; +pub mod crank; +mod crank1; +mod crank2; +mod crank3; +pub mod features; +pub mod init; +pub mod lock_liquidity; +pub mod params; +pub mod remove_liquidity; +pub mod shutdown; +pub mod unlock_liquidity; +pub mod user_init; diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/params.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/params.rs new file mode 100644 index 00000000000..11e744b5763 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/params.rs @@ -0,0 +1,17 @@ +//! Vault related parameters and accounts + +use crate::{traits::VaultParams, vault_info::VaultInfo}; + +impl VaultParams for VaultInfo<'_, '_> { + fn default_min_crank_interval() -> u64 { + 60 + } + + fn default_fee() -> f64 { + 0.003 + } + + fn default_external_fee() -> f64 { + 0.0025 + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/remove_liquidity.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/remove_liquidity.rs new file mode 100644 index 00000000000..4365fede537 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/remove_liquidity.rs @@ -0,0 +1,207 @@ +//! Remove Liquidity from the Vault instruction handler + +use { + crate::{traits::RemoveLiquidity, user_info::UserInfo, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + instruction::vault::VaultInstruction, + program::{account, pda, protocol::raydium}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl RemoveLiquidity for VaultInstruction { + fn remove_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + user_info_account, + user_token_a_account, + user_token_b_account, + token_a_custody, + token_b_custody, + lp_token_custody, + pool_program_id, + pool_withdraw_queue, + pool_temp_lp_token_account, + pool_coin_token_account, + pool_pc_token_account, + lp_token_mint, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + serum_program_id, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if !user_account.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if &account::get_token_account_owner(user_token_a_account)? != user_account.key + || &account::get_token_account_owner(user_token_b_account)? != user_account.key + { + msg!("Error: Invalid token account owner"); + return Err(ProgramError::IllegalOwner); + } + if let VaultStrategy::StakeLpCompoundRewards { + pool_id_ref: _, + farm_id_ref: _, + lp_token_custody: lp_token_custody_key, + token_a_custody: token_a_custody_key, + token_b_custody: token_b_custody_key, + token_a_reward_custody: _, + token_b_reward_custody: _, + vault_stake_info: _, + } = vault.strategy + { + if &token_a_custody_key != token_a_custody.key + || &token_b_custody_key.or_else(|| Some(zero::id())).unwrap() != token_b_custody.key + || &lp_token_custody_key != lp_token_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_withdrawal_allowed()? { + msg!("Error: Withdrawals are not allowed for this Vault"); + return Err(ProgramError::Custom(230)); + } + + // check lp balance + let mut user_info = UserInfo::new(user_info_account); + let lp_tokens_debt = user_info.get_lp_tokens_debt()?; + msg!("Read balances. lp_tokens_debt: {}", lp_tokens_debt); + + let lp_remove_amount = if amount > 0 { + if lp_tokens_debt < amount { + msg!("Error: Insufficient funds"); + return Err(ProgramError::InsufficientFunds); + } + amount + } else { + lp_tokens_debt + }; + if lp_remove_amount == 0 { + msg!("Error: Zero balance. Forgot to unlock funds?"); + return Err(ProgramError::InsufficientFunds); + } + + // remove liquidity from the pool + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let initial_token_a_custody_balance = account::get_token_balance(token_a_custody)?; + let initial_token_b_custody_balance = account::get_token_balance(token_b_custody)?; + let initial_lp_tokens_balance = account::get_token_balance(lp_token_custody)?; + + msg!( + "Remove liquidity from the pool. lp_remove_amount: {}", + lp_remove_amount + ); + raydium::remove_liquidity_with_seeds( + &[ + vault_authority.clone(), + token_a_custody.clone(), + token_b_custody.clone(), + lp_token_custody.clone(), + pool_program_id.clone(), + pool_withdraw_queue.clone(), + pool_temp_lp_token_account.clone(), + pool_coin_token_account.clone(), + pool_pc_token_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + amm_id.clone(), + amm_authority.clone(), + amm_open_orders.clone(), + amm_target.clone(), + serum_market.clone(), + serum_program_id.clone(), + serum_coin_vault_account.clone(), + serum_pc_vault_account.clone(), + serum_vault_signer.clone(), + ], + seeds, + lp_remove_amount, + )?; + + // check tokens received + let tokens_a_received = + account::get_balance_increase(token_a_custody, initial_token_a_custody_balance)?; + let tokens_b_received = + account::get_balance_increase(token_b_custody, initial_token_b_custody_balance)?; + if tokens_a_received == 0 && tokens_b_received == 0 { + msg!("Error: Remove liquidity instruction didn't result in any of the tokens received"); + return Err(ProgramError::Custom(190)); + } + let _ = account::check_tokens_spent( + lp_token_custody, + initial_lp_tokens_balance, + lp_remove_amount, + )?; + + // send tokens to the user + msg!( + "Transfer tokens to the user. tokens_a_received: {}, tokens_b_received: {}", + tokens_a_received, + tokens_b_received + ); + pda::transfer_tokens_with_seeds( + token_a_custody, + user_token_a_account, + vault_authority, + seeds, + tokens_a_received, + )?; + pda::transfer_tokens_with_seeds( + token_b_custody, + user_token_b_account, + vault_authority, + seeds, + tokens_b_received, + )?; + + // update user stats + msg!("Update user stats"); + user_info.remove_liquidity(tokens_a_received, tokens_b_received)?; + user_info.remove_lp_tokens_debt(lp_remove_amount)?; + + // update vault stats + msg!("Update Vault stats"); + vault_info.remove_liquidity(tokens_a_received, tokens_b_received)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/shutdown.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/shutdown.rs new file mode 100644 index 00000000000..37c56ea172f --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/shutdown.rs @@ -0,0 +1,22 @@ +//! Vault Shutdown instruction handler + +use { + crate::{traits::Shutdown, vault_info::VaultInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}, +}; + +impl Shutdown for VaultInstruction { + fn shutdown(_vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + if let [_admin_account, _vault_metadata, vault_info_account] = accounts { + // Don't do anything special on shutdown for this Vault, just disable deposits and withdrawals + let mut vault_info = VaultInfo::new(vault_info_account); + msg!("disable_deposit"); + vault_info.disable_deposit()?; + msg!("disable_withdrawal"); + vault_info.disable_withdrawal()?; + //pda::close_account(admin_account, vault_info_account) + } + Ok(()) + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/unlock_liquidity.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/unlock_liquidity.rs new file mode 100644 index 00000000000..0e5bc6c3290 --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/unlock_liquidity.rs @@ -0,0 +1,203 @@ +//! Unlock Liquidity in the Vault instruction handler + +use { + crate::{ + strategies::common, traits::UnlockLiquidity, user_info::UserInfo, vault_info::VaultInfo, + }, + solana_farm_sdk::{ + id::zero, + instruction::vault::VaultInstruction, + program::{account, protocol::raydium}, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +impl UnlockLiquidity for VaultInstruction { + fn unlock_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + vault_token_mint, + user_info_account, + user_vt_token_account, + token_a_reward_custody, + token_b_reward_custody, + lp_token_custody, + farm_program, + vault_stake_info, + farm_id, + farm_authority, + farm_lp_token_account, + farm_reward_token_a_account, + farm_reward_token_b_account, + clock_program + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if !user_account.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if &account::get_token_account_owner(user_vt_token_account)? != user_account.key { + msg!("Error: Invalid VT token account owner"); + return Err(ProgramError::IllegalOwner); + } + common::check_custody_accounts( + vault, + lp_token_custody, + vault_authority, + vault_authority, + token_a_reward_custody, + token_b_reward_custody, + vault_stake_info, + false, + )?; + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_withdrawal_allowed()? { + msg!("Error: Withdrawals are not allowed for this Vault"); + return Err(ProgramError::Custom(230)); + } + + // calculate amounts to unstake + let vt_remove_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_vt_token_account)? + }; + let vt_supply_amount = account::get_token_supply(vault_token_mint)?; + let stake_balance = raydium::get_stake_account_balance(vault_stake_info)?; + + msg!( + "Read balances. vt_remove_amount: {}, vt_supply_amount: {}, stake_balance: {}", + vt_remove_amount, + vt_supply_amount, + stake_balance + ); + if vt_remove_amount == 0 || vt_supply_amount == 0 || stake_balance == 0 { + msg!("Error: Zero balance"); + return Err(ProgramError::InsufficientFunds); + } + let lp_remove_amount = account::to_token_amount( + stake_balance as f64 * (vt_remove_amount as f64 / vt_supply_amount as f64), + 0, + )?; + + // unstake + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let dual_rewards = *farm_reward_token_b_account.key != zero::id(); + let initial_token_a_reward_balance = + account::get_token_balance(token_a_reward_custody)?; + let initial_token_b_reward_balance = if dual_rewards { + account::get_token_balance(token_b_reward_custody)? + } else { + 0 + }; + let initial_lp_tokens_balance = account::get_token_balance(lp_token_custody)?; + + msg!( + "Unstake user's lp tokens. amount: {}, lp_remove_amount: {}", + amount, + lp_remove_amount + ); + raydium::unstake_with_seeds( + &[ + vault_authority.clone(), + vault_stake_info.clone(), + lp_token_custody.clone(), + token_a_reward_custody.clone(), + token_b_reward_custody.clone(), + farm_program.clone(), + farm_lp_token_account.clone(), + farm_reward_token_a_account.clone(), + farm_reward_token_b_account.clone(), + clock_program.clone(), + spl_token_program.clone(), + farm_id.clone(), + farm_authority.clone(), + ], + seeds, + lp_remove_amount, + )?; + let _ = account::check_tokens_received( + lp_token_custody, + initial_lp_tokens_balance, + lp_remove_amount, + )?; + + // update user stats + msg!("Update user stats"); + let mut user_info = UserInfo::new(user_info_account); + user_info.add_lp_tokens_debt(lp_remove_amount)?; + + // update Vault stats + let token_a_rewards = account::get_balance_increase( + token_a_reward_custody, + initial_token_a_reward_balance, + )?; + let token_b_rewards = if dual_rewards { + account::get_balance_increase( + token_b_reward_custody, + initial_token_b_reward_balance, + )? + } else { + 0 + }; + msg!( + "Update Vault stats. token_a_rewards: {}, token_b_rewards: {}", + token_a_rewards, + token_b_rewards + ); + vault_info.add_rewards(token_a_rewards, token_b_rewards)?; + + // brun vault tokens + msg!( + "Burn Vault tokens from the user. vt_remove_amount: {}", + vt_remove_amount + ); + let key = Pubkey::create_program_address( + &[ + b"vault_token_mint", + vault.name.as_bytes(), + &[vault.vault_token_bump], + ], + &vault.vault_program_id, + )?; + if vault_token_mint.key != &key { + msg!("Error: Invalid Vault token mint"); + return Err(ProgramError::InvalidSeeds); + } + account::burn_tokens( + user_vt_token_account, + vault_token_mint, + user_account, + vt_remove_amount, + )?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/rdm_stake_lp_compound/user_init.rs b/farms/vaults/src/strategies/rdm_stake_lp_compound/user_init.rs new file mode 100644 index 00000000000..1527beb3ddd --- /dev/null +++ b/farms/vaults/src/strategies/rdm_stake_lp_compound/user_init.rs @@ -0,0 +1,52 @@ +//! Vault User Init instruction handler + +use { + crate::{traits::UserInit, user_info::UserInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, program::pda, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +impl UserInit for VaultInstruction { + fn user_init(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + _vault_info_account, + user_info_account, + _system_program + ] = accounts + { + if user_info_account.data_is_empty() { + msg!("Create user info account"); + let seeds: &[&[u8]] = &[ + b"user_info_account", + &user_account.key.to_bytes()[..], + vault.name.as_bytes(), + ]; + let bump = Pubkey::find_program_address(seeds, &vault.vault_program_id).1; + pda::init_system_account( + user_account, + user_info_account, + &vault.vault_program_id, + &vault.vault_program_id, + seeds, + UserInfo::LEN, + )?; + let mut user_info = UserInfo::new(user_info_account); + user_info.init(&vault.name, bump)?; + } else if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/add_liquidity.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/add_liquidity.rs new file mode 100644 index 00000000000..b2bf3dfbcb7 --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/add_liquidity.rs @@ -0,0 +1,136 @@ +//! Add Liquidity to the Vault instruction handler + +use { + crate::{traits::AddLiquidity, user_info::UserInfo, vault_info::VaultInfo}, + solana_farm_sdk::{ + instruction::vault::VaultInstruction, + program::{account, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl AddLiquidity for VaultInstruction { + fn add_liquidity( + vault: &Vault, + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, + ) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + spl_token_program, + user_info_account, + user_token_a_account, + user_token_b_account, + user_lp_token_account, + lp_token_custody, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + clock_program, + swap_account, + swap_authority + ] = accounts + { + // validate accounts + if let VaultStrategy::StakeLpCompoundRewards { + lp_token_custody: lp_token_custody_key, + .. + } = vault.strategy + { + if &lp_token_custody_key != lp_token_custody.key { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_deposit_allowed()? { + msg!("Error: Deposits are not allowed for this Vault"); + return Err(ProgramError::Custom(220)); + } + + // read user balances + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + let initial_lp_user_balance = account::get_token_balance(user_lp_token_account)?; + + saber::add_liquidity( + &[ + user_account.clone(), + user_token_a_account.clone(), + user_token_b_account.clone(), + user_lp_token_account.clone(), + pool_program_id.clone(), + pool_token_a_account.clone(), + pool_token_b_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + clock_program.clone(), + swap_account.clone(), + swap_authority.clone(), + ], + max_token_a_amount, + max_token_b_amount, + )?; + + // check amounts spent and received + let tokens_a_spent = account::check_tokens_spent( + user_token_a_account, + initial_token_a_user_balance, + max_token_a_amount, + )?; + let tokens_b_spent = account::check_tokens_spent( + user_token_b_account, + initial_token_b_user_balance, + max_token_b_amount, + )?; + let lp_tokens_received = + account::check_tokens_received(user_lp_token_account, initial_lp_user_balance, 1)?; + + // transfer LP tokens to the custody + msg!( + "Transfer LP tokens from user. tokens_a_spent: {}, tokens_b_spent: {}, lp_tokens_received: {}", + tokens_a_spent, + tokens_b_spent, + lp_tokens_received + ); + account::transfer_tokens( + user_lp_token_account, + lp_token_custody, + user_account, + lp_tokens_received, + )?; + + // update user stats + msg!("Update user stats"); + let mut user_info = UserInfo::new(user_info_account); + user_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + user_info.add_lp_tokens_debt(lp_tokens_received)?; + + // update Vault stats + msg!("Update Vault stats",); + vault_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank.rs new file mode 100644 index 00000000000..21cfb8f634d --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank.rs @@ -0,0 +1,30 @@ +//! Vault Crank instruction handler + +use { + crate::{ + strategies::sbr_stake_lp_compound::{ + crank1::crank1, crank2::crank2, crank3::crank3, crank4::crank4, crank5::crank5, + }, + traits::Crank, + }, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Crank for VaultInstruction { + fn crank(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult { + match step { + 1 => crank1(vault, accounts), + 2 => crank2(vault, accounts), + 3 => crank3(vault, accounts), + 4 => crank4(vault, accounts), + 5 => crank5(vault, accounts), + _ => { + msg!("Error: Invalid Crank step"); + Err(ProgramError::InvalidArgument) + } + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank1.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank1.rs new file mode 100644 index 00000000000..b8b90b5ffbe --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank1.rs @@ -0,0 +1,110 @@ +//! Crank step 1 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + program::{account, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank1(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + iou_token_reward_custody, + farm_program, + vault_stake_info, + mint_wrapper, + mint_wrapper_program, + minter, + iou_token_mint, + iou_fees_account, + quarry, + rewarder, + zero_id + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + token_b_reward_custody: token_b_reward_custody_key, + vault_stake_info: vault_stake_info_key, + .. + } = vault.strategy + { + if &vault_stake_info_key != vault_stake_info.key { + msg!("Error: Invalid Vault Stake Info account"); + return Err(ProgramError::InvalidArgument); + } + if token_b_reward_custody_key != Some(*iou_token_reward_custody.key) { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + + // harvest + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let initial_iou_token_reward_balance = + account::get_token_balance(iou_token_reward_custody)?; + + msg!("Claim rewards"); + saber::claim_rewards_with_seeds( + &[ + vault_authority.clone(), + iou_token_reward_custody.clone(), + farm_program.clone(), + spl_token_program.clone(), + zero_id.clone(), + vault_stake_info.clone(), + rewarder.clone(), + minter.clone(), + mint_wrapper.clone(), + mint_wrapper_program.clone(), + iou_token_mint.clone(), + iou_fees_account.clone(), + quarry.clone(), + ], + seeds, + )?; + // calculate rewards + let iou_token_rewards = account::get_balance_increase( + iou_token_reward_custody, + initial_iou_token_reward_balance, + )?; + + msg!("Rewards received. iou_token_rewards: {}", iou_token_rewards); + + // update Vault stats + msg!("Update Vault stats",); + vault_info.add_rewards(0, iou_token_rewards)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(1)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank2.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank2.rs new file mode 100644 index 00000000000..2df215eab2d --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank2.rs @@ -0,0 +1,135 @@ +//! Crank step 2 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + program::{account, pda, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank2(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + sbr_token_reward_custody, + iou_token_reward_custody, + fees_account_sbr, + redeemer, + redeemer_program, + sbr_token_mint, + iou_token_mint, + sbr_vault, + mint_proxy_program, + mint_proxy_authority, + mint_proxy_state, + minter_info + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + token_a_reward_custody: sbr_token_reward_custody_key, + token_b_reward_custody: iou_token_reward_custody_key, + .. + } = vault.strategy + { + if &sbr_token_reward_custody_key != sbr_token_reward_custody.key + || &iou_token_reward_custody_key.or(Some(zero::id())).unwrap() + != iou_token_reward_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + if Some(*fees_account_sbr.key) != vault.fees_account_a { + msg!("Error: Invalid fee account"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + + // redeem rewards + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let initial_sbr_tokens_balance = account::get_token_balance(sbr_token_reward_custody)?; + let iou_tokens_balance = account::get_token_balance(iou_token_reward_custody)?; + + msg!("Redeem rewards: {}", iou_tokens_balance); + if iou_tokens_balance < 10 { + msg!("Nothing to do: Not enough tokens to redeem"); + return Ok(()); + } + saber::redeem_rewards_with_seeds( + &[ + vault_authority.clone(), + iou_token_reward_custody.clone(), + sbr_token_reward_custody.clone(), + spl_token_program.clone(), + redeemer.clone(), + redeemer_program.clone(), + sbr_token_mint.clone(), + iou_token_mint.clone(), + sbr_vault.clone(), + mint_proxy_program.clone(), + mint_proxy_authority.clone(), + mint_proxy_state.clone(), + minter_info.clone(), + ], + seeds, + )?; + let _ = account::check_tokens_received( + sbr_token_reward_custody, + initial_sbr_tokens_balance, + iou_tokens_balance, + )?; + + // take fees + let fee = vault_info.get_fee()?; + if fee < 0.0 || fee > 1.0 { + msg!("Error: Invalid fee. fee: {}", fee); + return Err(ProgramError::Custom(260)); + } + let sbr_fees = account::to_token_amount(iou_tokens_balance as f64 * fee, 0)?; + + msg!("Apply fees. fee: {}, sbr_fees: {}", fee, sbr_fees); + pda::transfer_tokens_with_seeds( + sbr_token_reward_custody, + fees_account_sbr, + vault_authority, + seeds, + sbr_fees, + )?; + + // update Vault stats + msg!("Update Vault stats",); + vault_info.add_rewards(iou_tokens_balance, 0)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(2)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank3.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank3.rs new file mode 100644 index 00000000000..a228476155e --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank3.rs @@ -0,0 +1,167 @@ +//! Crank step 3 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + program::{ + account, + protocol::{raydium, saber}, + }, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank3(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + sbr_token_custody, + usdc_token_custody, + wrapped_token_custody, + usdc_token_mint, + wrapped_token_mint, + wrapped_token_vault, + decimal_wrapper, + decimal_wrapper_program, + pool_program_id, + pool_coin_token_account, + pool_pc_token_account, + amm_id, + amm_authority, + amm_open_orders, + amm_target, + serum_market, + serum_program_id, + serum_coin_vault_account, + serum_pc_vault_account, + serum_vault_signer, + serum_bids, + serum_asks, + serum_event_queue + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + token_a_custody: usdc_token_custody_key, + token_b_custody: wrapped_token_custody_key, + token_a_reward_custody: sbr_token_custody_key, + .. + } = vault.strategy + { + if &usdc_token_custody_key != usdc_token_custody.key + || &wrapped_token_custody_key.or(Some(zero::id())).unwrap() + != wrapped_token_custody.key + || &sbr_token_custody_key != sbr_token_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + vault_info.update_crank_time()?; + vault_info.set_crank_step(3)?; + + // read balances + let sbr_token_balance = account::get_token_balance(sbr_token_custody)?; + let usdc_token_balance = account::get_token_balance(usdc_token_custody)?; + msg!("SBR rewards balance: {}", sbr_token_balance); + if sbr_token_balance < 10 { + msg!("Nothing to do: Not enough SBR tokens to swap"); + return Ok(()); + } + + // move rewards to token custodies + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + msg!("Swap SBR to USDC"); + raydium::swap_with_seeds( + &[ + vault_authority.clone(), + sbr_token_custody.clone(), + usdc_token_custody.clone(), + pool_program_id.clone(), + pool_coin_token_account.clone(), + pool_pc_token_account.clone(), + spl_token_program.clone(), + amm_id.clone(), + amm_authority.clone(), + amm_open_orders.clone(), + amm_target.clone(), + serum_market.clone(), + serum_program_id.clone(), + serum_bids.clone(), + serum_asks.clone(), + serum_event_queue.clone(), + serum_coin_vault_account.clone(), + serum_pc_vault_account.clone(), + serum_vault_signer.clone(), + ], + seeds, + sbr_token_balance, + 1, + )?; + let _ = + account::check_tokens_spent(sbr_token_custody, sbr_token_balance, sbr_token_balance)?; + let usdc_tokens_received = + account::check_tokens_received(usdc_token_custody, usdc_token_balance, 1)?; + + msg!("USDC tokens received: {}", usdc_tokens_received); + + if wrapped_token_mint.key != &zero::id() { + msg!("Wrap USDC tokens"); + let initial_usdc_token_balance = account::get_token_balance(usdc_token_custody)?; + let initial_wrapped_token_balance = account::get_token_balance(wrapped_token_custody)?; + + let usdc_decimals = account::get_token_decimals(&usdc_token_mint)?; + let wrapped_decimals = account::get_token_decimals(&wrapped_token_mint)?; + + saber::wrap_token_with_seeds( + decimal_wrapper, + wrapped_token_mint, + wrapped_token_vault, + vault_authority, + usdc_token_custody, + wrapped_token_custody, + decimal_wrapper_program.key, + seeds, + initial_usdc_token_balance, + )?; + + account::check_tokens_received( + wrapped_token_custody, + initial_wrapped_token_balance, + account::to_amount_with_new_decimals( + initial_usdc_token_balance, + usdc_decimals, + wrapped_decimals, + )?, + )?; + } + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank4.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank4.rs new file mode 100644 index 00000000000..009f2e7054f --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank4.rs @@ -0,0 +1,131 @@ +//! Crank step 4 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + program::{account, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank4(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + token_a_custody, + token_b_custody, + lp_token_custody, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + clock_program, + swap_account, + swap_authority + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + token_a_custody: token_a_custody_key, + token_b_custody: token_b_custody_key, + lp_token_custody: lp_token_custody_key, + .. + } = vault.strategy + { + if vault.fees_account_b.is_none() + || (token_a_custody.key != &token_a_custody_key + && (token_b_custody_key.is_none() + || token_a_custody.key != &token_b_custody_key.unwrap()) + && token_a_custody.key != &vault.fees_account_b.unwrap()) + || (token_b_custody.key != &token_a_custody_key + && (token_b_custody_key.is_none() + || token_b_custody.key != &token_b_custody_key.unwrap()) + && token_b_custody.key != &vault.fees_account_b.unwrap()) + || &lp_token_custody_key != lp_token_custody.key + { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + + // read balances + let token_a_balance = account::get_token_balance(token_a_custody)?; + let token_b_balance = account::get_token_balance(token_b_custody)?; + let lp_token_balance = account::get_token_balance(lp_token_custody)?; + msg!( + "Read balances. token_a_balance: {}, token_b_balance: {}", + token_a_balance, + token_b_balance + ); + if token_a_balance < 10 && token_b_balance < 10 { + msg!("Nothing to do: Not enough tokens to deposit"); + return Ok(()); + } + + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + msg!("Deposit tokens into the pool"); + saber::add_liquidity_with_seeds( + &[ + vault_authority.clone(), + token_a_custody.clone(), + token_b_custody.clone(), + lp_token_custody.clone(), + pool_program_id.clone(), + pool_token_a_account.clone(), + pool_token_b_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + clock_program.clone(), + swap_account.clone(), + swap_authority.clone(), + ], + seeds, + token_a_balance, + token_b_balance, + )?; + + // check amounts spent and received + let tokens_a_spent = + account::check_tokens_spent(token_a_custody, token_a_balance, token_a_balance)?; + let tokens_b_spent = + account::check_tokens_spent(token_b_custody, token_b_balance, token_b_balance)?; + let lp_tokens_received = + account::check_tokens_received(lp_token_custody, lp_token_balance, 1)?; + + // update Vault stats + msg!( + "Update Vault stats. tokens_a_spent {}, tokens_b_spent {}, lp_tokens_received {}", + tokens_a_spent, + tokens_b_spent, + lp_tokens_received + ); + vault_info.add_liquidity(tokens_a_spent, tokens_b_spent)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/crank5.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank5.rs new file mode 100644 index 00000000000..4c5d187511c --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/crank5.rs @@ -0,0 +1,94 @@ +//! Crank step 5 instruction handler + +use { + crate::{clock::check_min_crank_interval, vault_info::VaultInfo}, + solana_farm_sdk::{ + program::{account, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +pub fn crank5(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + _funding_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + lp_token_custody, + farm_program, + vault_stake_info, + vault_miner_account, + quarry, + rewarder + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority + || &account::get_token_account_owner(vault_miner_account)? != vault_stake_info.key + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if let VaultStrategy::StakeLpCompoundRewards { + lp_token_custody: lp_token_custody_key, + vault_stake_info: vault_stake_info_key, + .. + } = vault.strategy + { + if &vault_stake_info_key != vault_stake_info.key { + msg!("Error: Invalid Vault Stake Info account"); + return Err(ProgramError::InvalidArgument); + } + if &lp_token_custody_key != lp_token_custody.key { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + let vault_info = VaultInfo::new(vault_info_account); + check_min_crank_interval(&vault_info)?; + + // read balances + let lp_token_balance = account::get_token_balance(lp_token_custody)?; + msg!("Read balances. lp_token_balance: {}", lp_token_balance,); + if lp_token_balance == 0 { + msg!("Nothing to do: Not enough LP tokens to stake"); + return Ok(()); + } + + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + msg!("Stake LP tokens"); + saber::stake_with_seeds( + &[ + vault_authority.clone(), + lp_token_custody.clone(), + farm_program.clone(), + spl_token_program.clone(), + vault_stake_info.clone(), + vault_miner_account.clone(), + quarry.clone(), + rewarder.clone(), + ], + seeds, + lp_token_balance, + )?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/features.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/features.rs new file mode 100644 index 00000000000..37442fa40a6 --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/features.rs @@ -0,0 +1,85 @@ +//! Feature toggling instructions handlers + +use { + crate::{traits::Features, vault_info::VaultInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Features for VaultInstruction { + fn set_min_crank_interval( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + min_crank_interval_sec: u64, + ) -> ProgramResult { + msg!("set_min_crank_interval: {}", min_crank_interval_sec); + vault_info.set_min_crank_interval(min_crank_interval_sec) + } + + fn set_fee( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + fee: f64, + ) -> ProgramResult { + msg!("set_fee: {}", fee); + if fee < 0.0 || fee > 1.0 { + msg!("Error: Invalid new value for fee"); + return Err(ProgramError::InvalidArgument); + } + vault_info.set_fee(fee) + } + + fn set_external_fee( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + external_fee: f64, + ) -> ProgramResult { + msg!("external_fee: {}", external_fee); + if external_fee < 0.0 || external_fee > 1.0 { + msg!("Error: Invalid new value for external_fee"); + return Err(ProgramError::InvalidArgument); + } + vault_info.set_external_fee(external_fee) + } + + fn enable_deposit( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("enable_deposit"); + vault_info.enable_deposit() + } + + fn disable_deposit( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("disable_deposit"); + vault_info.disable_deposit() + } + + fn enable_withdrawal( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("enable_withdrawal"); + vault_info.enable_withdrawal() + } + + fn disable_withdrawal( + _vault: &Vault, + vault_info: &mut VaultInfo, + _accounts: &[AccountInfo], + ) -> ProgramResult { + msg!("disable_withdrawal"); + vault_info.disable_withdrawal() + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/init.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/init.rs new file mode 100644 index 00000000000..4821562011d --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/init.rs @@ -0,0 +1,226 @@ +//! Vault Init instruction handler + +use { + crate::{traits::Init, vault_info::VaultInfo}, + solana_farm_sdk::{ + id::zero, + instruction::vault::VaultInstruction, + program::{pda, protocol::saber}, + token::Token, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl Init for VaultInstruction { + fn init(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + admin_account, + _vault_metadata, + vault_info_account, + vault_authority, + vault_program, + _system_program, + _spl_token_program, + _associated_token_program, + rent_program, + farm_program, + vault_token_mint, + vault_token_ref, + vault_stake_info, + vault_miner_account, + fees_account_a, + fees_account_b, + usdc_token_custody, + wrapped_token_custody, + lp_token_custody, + usdc_token_mint, + non_usdc_token_mint, + wrapped_token_mint, + lp_token_mint, + sbr_token_reward_custody, + iou_token_reward_custody, + sbr_token_mint, + iou_token_mint, + quarry, + rewarder + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority + || vault_token_ref.key != &vault.vault_token_ref + || vault_program.key != &vault.vault_program_id + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + + if step <= 1 { + // init vault authority account + msg!("Init vault authority"); + pda::init_system_account( + admin_account, + vault_authority, + &vault.vault_program_id, + &vault.vault_program_id, + &[b"vault_authority", vault.name.as_bytes()], + 0, + )?; + + // init vault info account + msg!("Init vault info"); + pda::init_system_account( + admin_account, + vault_info_account, + &vault.vault_program_id, + &vault.vault_program_id, + &[b"info_account", vault.name.as_bytes()], + VaultInfo::LEN, + )?; + let mut vault_info = VaultInfo::new(vault_info_account); + vault_info.init(&vault.name)?; + + // init vault token mint + msg!("Init vault token mint"); + let vault_token = Token::unpack(&vault_token_ref.try_borrow_data()?)?; + if vault_token_mint.key != &vault_token.mint { + msg!("Error: Invalid Vault token mint"); + return Err(ProgramError::InvalidArgument); + } + pda::init_mint( + admin_account, + vault_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"vault_token_mint", vault.name.as_bytes()], + vault_token.decimals, + )?; + + // init stake info + msg!("Init vault miner"); + pda::init_associated_token_account( + admin_account, + vault_stake_info, + vault_miner_account, + lp_token_mint, + rent_program, + )?; + + if vault_stake_info.data_is_empty() { + msg!("Init stake info"); + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + saber::user_init_with_seeds( + &[ + vault_authority.clone(), + admin_account.clone(), + farm_program.clone(), + lp_token_mint.clone(), + vault_stake_info.clone(), + vault_miner_account.clone(), + quarry.clone(), + rewarder.clone(), + ], + seeds, + )?; + } + } + + if step == 0 || step == 2 { + // init token accounts + msg!("Init fees account a"); + pda::init_token_account( + admin_account, + fees_account_a, + sbr_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"fees_account_a", vault.name.as_bytes()], + )?; + + msg!("Init fees account b"); + pda::init_token_account( + admin_account, + fees_account_b, + non_usdc_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"fees_account_b", vault.name.as_bytes()], + )?; + + msg!("Init lp token custody account"); + pda::init_token_account( + admin_account, + lp_token_custody, + lp_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"lp_token_custody", vault.name.as_bytes()], + )?; + + msg!("Init USDC token custody account"); + pda::init_token_account( + admin_account, + usdc_token_custody, + usdc_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_a_custody", vault.name.as_bytes()], + )?; + + if wrapped_token_custody.key != &zero::id() { + msg!("Init wrapped USDC token custody account"); + pda::init_token_account( + admin_account, + wrapped_token_custody, + wrapped_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_b_custody", vault.name.as_bytes()], + )?; + } + + msg!("Init SBR token reward custody account"); + pda::init_token_account( + admin_account, + sbr_token_reward_custody, + sbr_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_a_reward_custody", vault.name.as_bytes()], + )?; + + if *iou_token_reward_custody.key != zero::id() { + msg!("Init IOU token reward custody account"); + pda::init_token_account( + admin_account, + iou_token_reward_custody, + iou_token_mint, + vault_authority, + rent_program, + &vault.vault_program_id, + &[b"token_b_reward_custody", vault.name.as_bytes()], + )?; + } + } + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/lock_liquidity.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/lock_liquidity.rs new file mode 100644 index 00000000000..2da7ffa7aec --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/lock_liquidity.rs @@ -0,0 +1,170 @@ +//! Lock Liquidity in the Vault instruction handler + +use { + crate::{traits::LockLiquidity, user_info::UserInfo, vault_info::VaultInfo}, + solana_farm_sdk::{ + instruction::vault::VaultInstruction, + program::{account, pda, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl LockLiquidity for VaultInstruction { + fn lock_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + vault_token_mint, + user_info_account, + user_vt_token_account, + lp_token_custody, + farm_program, + vault_stake_info, + vault_miner_account, + quarry, + rewarder + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority + || &account::get_token_account_owner(vault_miner_account)? != vault_stake_info.key + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if !user_account.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if &account::get_token_account_owner(user_vt_token_account)? != user_account.key { + msg!("Error: Invalid VT token account owner"); + return Err(ProgramError::IllegalOwner); + } + + if let VaultStrategy::StakeLpCompoundRewards { + lp_token_custody: lp_token_custody_key, + vault_stake_info: vault_stake_info_key, + .. + } = vault.strategy + { + if &vault_stake_info_key != vault_stake_info.key { + msg!("Error: Invalid Vault Stake Info account"); + return Err(ProgramError::InvalidArgument); + } + if &lp_token_custody_key != lp_token_custody.key { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_deposit_allowed()? { + msg!("Error: Deposits are not allowed for this Vault"); + return Err(ProgramError::Custom(220)); + } + + // check lp balance + let mut user_info = UserInfo::new(user_info_account); + let lp_tokens_debt = user_info.get_lp_tokens_debt()?; + msg!("Read balances. lp_tokens_debt: {}", lp_tokens_debt); + + let lp_stake_amount = if amount > 0 { + if lp_tokens_debt < amount { + msg!("Error: Insufficient funds"); + return Err(ProgramError::InsufficientFunds); + } + amount + } else { + lp_tokens_debt + }; + if lp_stake_amount == 0 { + msg!("Error: Zero balance. Forgot to deposit funds?"); + return Err(ProgramError::InsufficientFunds); + } + + let initial_lp_custody_balance = account::get_token_balance(lp_token_custody)?; + + // Stake LP tokens + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + msg!("Stake LP tokens. lp_stake_amount: {}", lp_stake_amount); + let stake_balance = saber::get_stake_account_balance(vault_stake_info)?; + + saber::stake_with_seeds( + &[ + vault_authority.clone(), + lp_token_custody.clone(), + farm_program.clone(), + spl_token_program.clone(), + vault_stake_info.clone(), + vault_miner_account.clone(), + quarry.clone(), + rewarder.clone(), + ], + seeds, + lp_stake_amount, + )?; + let _ = account::check_tokens_spent( + lp_token_custody, + initial_lp_custody_balance, + lp_stake_amount, + )?; + + // update user stats + msg!("Update user stats"); + user_info.remove_lp_tokens_debt(lp_stake_amount)?; + + // compute Vault tokens to mint + let vt_supply_amount = account::get_token_supply(vault_token_mint)?; + let vt_to_mint = if vt_supply_amount == 0 || stake_balance == 0 { + lp_stake_amount + } else { + account::to_token_amount( + lp_stake_amount as f64 / stake_balance as f64 * vt_supply_amount as f64, + 0, + )? + }; + + // mint vault tokens to user + msg!( + "Mint Vault tokens to the user. vt_to_mint: {}, vt_supply_amount: {}, stake_balance: {}", + vt_to_mint, vt_supply_amount, + stake_balance + ); + if vt_to_mint == 0 { + msg!("Error: Add liquidity instruction didn't result in Vault tokens mint"); + return Err(ProgramError::Custom(170)); + } + pda::mint_to_with_seeds( + user_vt_token_account, + vault_token_mint, + vault_authority, + seeds, + vt_to_mint, + )?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/mod.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/mod.rs new file mode 100644 index 00000000000..1f49cd28b8a --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/mod.rs @@ -0,0 +1,15 @@ +pub mod add_liquidity; +pub mod crank; +mod crank1; +mod crank2; +mod crank3; +mod crank4; +mod crank5; +pub mod features; +pub mod init; +pub mod lock_liquidity; +pub mod params; +pub mod remove_liquidity; +pub mod shutdown; +pub mod unlock_liquidity; +pub mod user_init; diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/params.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/params.rs new file mode 100644 index 00000000000..11e744b5763 --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/params.rs @@ -0,0 +1,17 @@ +//! Vault related parameters and accounts + +use crate::{traits::VaultParams, vault_info::VaultInfo}; + +impl VaultParams for VaultInfo<'_, '_> { + fn default_min_crank_interval() -> u64 { + 60 + } + + fn default_fee() -> f64 { + 0.003 + } + + fn default_external_fee() -> f64 { + 0.0025 + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/remove_liquidity.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/remove_liquidity.rs new file mode 100644 index 00000000000..4d98033d5b8 --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/remove_liquidity.rs @@ -0,0 +1,240 @@ +//! Remove Liquidity from the Vault instruction handler + +use { + crate::{traits::RemoveLiquidity, user_info::UserInfo, vault_info::VaultInfo}, + solana_farm_sdk::{ + instruction::vault::VaultInstruction, + program::{account, protocol::saber}, + vault::{Vault, VaultStrategy}, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +impl RemoveLiquidity for VaultInstruction { + fn remove_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + vault_info_account, + vault_authority, + spl_token_program, + vault_token_mint, + user_info_account, + user_token_a_account, + user_token_b_account, + user_vt_token_account, + lp_token_custody, + pool_program_id, + pool_token_a_account, + pool_token_b_account, + lp_token_mint, + swap_account, + swap_authority, + fees_account_a, + fees_account_b, + farm_program, + vault_stake_info, + vault_miner_account, + quarry, + rewarder + ] = accounts + { + // validate accounts + if vault_authority.key != &vault.vault_authority + || &account::get_token_account_owner(vault_miner_account)? != vault_stake_info.key + { + msg!("Error: Invalid Vault accounts"); + return Err(ProgramError::InvalidArgument); + } + if !user_account.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if &account::get_token_account_owner(user_token_a_account)? != user_account.key + || &account::get_token_account_owner(user_token_b_account)? != user_account.key + || &account::get_token_account_owner(user_vt_token_account)? != user_account.key + { + msg!("Error: Invalid token account owner"); + return Err(ProgramError::IllegalOwner); + } + if let VaultStrategy::StakeLpCompoundRewards { + lp_token_custody: lp_token_custody_key, + vault_stake_info: vault_stake_info_key, + .. + } = vault.strategy + { + if &vault_stake_info_key != vault_stake_info.key { + msg!("Error: Invalid Vault Stake Info account"); + return Err(ProgramError::InvalidArgument); + } + if &lp_token_custody_key != lp_token_custody.key { + msg!("Error: Invalid custody accounts"); + return Err(ProgramError::InvalidArgument); + } + } else { + msg!("Error: Vault strategy mismatch"); + return Err(ProgramError::InvalidArgument); + } + if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + let mut vault_info = VaultInfo::new(vault_info_account); + if !vault_info.is_withdrawal_allowed()? { + msg!("Error: Withdrawals are not allowed for this Vault"); + return Err(ProgramError::Custom(230)); + } + + // calculate amounts to unstake + let vt_remove_amount = if amount > 0 { + amount + } else { + account::get_token_balance(user_vt_token_account)? + }; + let vt_supply_amount = account::get_token_supply(vault_token_mint)?; + let stake_balance = saber::get_stake_account_balance(vault_stake_info)?; + + msg!( + "Read balances. vt_remove_amount: {}, vt_supply_amount: {}, stake_balance: {}", + vt_remove_amount, + vt_supply_amount, + stake_balance + ); + if vt_remove_amount == 0 || vt_supply_amount == 0 || stake_balance == 0 { + msg!("Error: Zero balance"); + return Err(ProgramError::InsufficientFunds); + } + let lp_remove_amount = account::to_token_amount( + stake_balance as f64 * (vt_remove_amount as f64 / vt_supply_amount as f64), + 0, + )?; + + // unstake + let seeds: &[&[&[u8]]] = &[&[ + b"vault_authority", + vault.name.as_bytes(), + &[vault.authority_bump], + ]]; + + let initial_lp_tokens_balance = account::get_token_balance(lp_token_custody)?; + + msg!( + "Unstake user's lp tokens. amount: {}, lp_remove_amount: {}", + amount, + lp_remove_amount + ); + saber::unstake_with_seeds( + &[ + vault_authority.clone(), + lp_token_custody.clone(), + farm_program.clone(), + spl_token_program.clone(), + vault_stake_info.clone(), + vault_miner_account.clone(), + quarry.clone(), + rewarder.clone(), + ], + seeds, + lp_remove_amount, + )?; + let _ = account::check_tokens_received( + lp_token_custody, + initial_lp_tokens_balance, + lp_remove_amount, + )?; + + // brun vault tokens + msg!( + "Burn Vault tokens from the user. vt_remove_amount: {}", + vt_remove_amount + ); + let key = Pubkey::create_program_address( + &[ + b"vault_token_mint", + vault.name.as_bytes(), + &[vault.vault_token_bump], + ], + &vault.vault_program_id, + )?; + if vault_token_mint.key != &key { + msg!("Error: Invalid Vault token mint"); + return Err(ProgramError::InvalidSeeds); + } + account::burn_tokens( + user_vt_token_account, + vault_token_mint, + user_account, + vt_remove_amount, + )?; + + // remove liquidity from the pool + let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?; + let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?; + + msg!( + "Remove liquidity from the pool. lp_remove_amount: {}", + lp_remove_amount + ); + saber::remove_liquidity_with_seeds( + &[ + vault_authority.clone(), + user_token_a_account.clone(), + user_token_b_account.clone(), + lp_token_custody.clone(), + pool_program_id.clone(), + pool_token_a_account.clone(), + pool_token_b_account.clone(), + lp_token_mint.clone(), + spl_token_program.clone(), + swap_account.clone(), + swap_authority.clone(), + fees_account_a.clone(), + fees_account_b.clone(), + ], + seeds, + lp_remove_amount, + )?; + + // check tokens received + let tokens_a_received = + account::get_balance_increase(user_token_a_account, initial_token_a_user_balance)?; + let tokens_b_received = + account::get_balance_increase(user_token_b_account, initial_token_b_user_balance)?; + if tokens_a_received == 0 && tokens_b_received == 0 { + msg!("Error: Remove liquidity instruction didn't result in any of the tokens received"); + return Err(ProgramError::Custom(190)); + } + if initial_lp_tokens_balance != account::get_token_balance(lp_token_custody)? { + msg!( + "Error: Remove liquidity instruction didn't result in expected amount of LP tokens spent" + ); + return Err(ProgramError::Custom(165)); + } + + // send tokens to the user + msg!( + "Update stats. tokens_a_received: {}, tokens_b_received: {}", + tokens_a_received, + tokens_b_received + ); + + // update user stats + msg!("Update user stats"); + let mut user_info = UserInfo::new(user_info_account); + user_info.remove_liquidity(tokens_a_received, tokens_b_received)?; + + // update vault stats + msg!("Update Vault stats"); + vault_info.remove_liquidity(tokens_a_received, tokens_b_received)?; + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/shutdown.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/shutdown.rs new file mode 100644 index 00000000000..37c56ea172f --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/shutdown.rs @@ -0,0 +1,22 @@ +//! Vault Shutdown instruction handler + +use { + crate::{traits::Shutdown, vault_info::VaultInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}, +}; + +impl Shutdown for VaultInstruction { + fn shutdown(_vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + if let [_admin_account, _vault_metadata, vault_info_account] = accounts { + // Don't do anything special on shutdown for this Vault, just disable deposits and withdrawals + let mut vault_info = VaultInfo::new(vault_info_account); + msg!("disable_deposit"); + vault_info.disable_deposit()?; + msg!("disable_withdrawal"); + vault_info.disable_withdrawal()?; + //pda::close_account(admin_account, vault_info_account) + } + Ok(()) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/unlock_liquidity.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/unlock_liquidity.rs new file mode 100644 index 00000000000..89b74c5d40a --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/unlock_liquidity.rs @@ -0,0 +1,16 @@ +//! Unlock Liquidity in the Vault instruction handler + +use { + crate::traits::UnlockLiquidity, + solana_farm_sdk::{instruction::vault::VaultInstruction, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + }, +}; + +impl UnlockLiquidity for VaultInstruction { + fn unlock_liquidity(_vault: &Vault, _accounts: &[AccountInfo], _amount: u64) -> ProgramResult { + msg!("Error: Liquidity Unlock is not required for this Vault"); + Err(ProgramError::InvalidArgument) + } +} diff --git a/farms/vaults/src/strategies/sbr_stake_lp_compound/user_init.rs b/farms/vaults/src/strategies/sbr_stake_lp_compound/user_init.rs new file mode 100644 index 00000000000..1527beb3ddd --- /dev/null +++ b/farms/vaults/src/strategies/sbr_stake_lp_compound/user_init.rs @@ -0,0 +1,52 @@ +//! Vault User Init instruction handler + +use { + crate::{traits::UserInit, user_info::UserInfo}, + solana_farm_sdk::{instruction::vault::VaultInstruction, program::pda, vault::Vault}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, + }, +}; + +impl UserInit for VaultInstruction { + fn user_init(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult { + #[allow(clippy::deprecated_cfg_attr)] + #[cfg_attr(rustfmt, rustfmt_skip)] + if let [ + user_account, + _vault_metadata, + _vault_info_account, + user_info_account, + _system_program + ] = accounts + { + if user_info_account.data_is_empty() { + msg!("Create user info account"); + let seeds: &[&[u8]] = &[ + b"user_info_account", + &user_account.key.to_bytes()[..], + vault.name.as_bytes(), + ]; + let bump = Pubkey::find_program_address(seeds, &vault.vault_program_id).1; + pda::init_system_account( + user_account, + user_info_account, + &vault.vault_program_id, + &vault.vault_program_id, + seeds, + UserInfo::LEN, + )?; + let mut user_info = UserInfo::new(user_info_account); + user_info.init(&vault.name, bump)?; + } else if !UserInfo::validate_account(vault, user_info_account, user_account.key) { + msg!("Error: Invalid user info account"); + return Err(ProgramError::Custom(140)); + } + + Ok(()) + } else { + Err(ProgramError::NotEnoughAccountKeys) + } + } +} diff --git a/farms/vaults/src/traits.rs b/farms/vaults/src/traits.rs new file mode 100644 index 00000000000..d3210a5033b --- /dev/null +++ b/farms/vaults/src/traits.rs @@ -0,0 +1,97 @@ +//! Vaults traits and common features. + +use { + crate::vault_info::VaultInfo, + solana_farm_sdk::vault::Vault, + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}, +}; + +pub trait VaultParams { + fn default_min_crank_interval() -> u64; + fn default_fee() -> f64; + fn default_external_fee() -> f64; +} + +pub trait UserInit { + fn user_init(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult; +} + +pub trait AddLiquidity { + fn add_liquidity( + vault: &Vault, + accounts: &[AccountInfo], + max_token_a_amount: u64, + max_token_b_amount: u64, + ) -> ProgramResult; +} + +pub trait LockLiquidity { + fn lock_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult; +} + +pub trait UnlockLiquidity { + fn unlock_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult; +} + +pub trait RemoveLiquidity { + fn remove_liquidity(vault: &Vault, accounts: &[AccountInfo], amount: u64) -> ProgramResult; +} + +pub trait Features { + fn set_min_crank_interval( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + min_crank_interval_sec: u64, + ) -> ProgramResult; + + fn set_fee( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + fee: f64, + ) -> ProgramResult; + + fn set_external_fee( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + external_fee: f64, + ) -> ProgramResult; + + fn enable_deposit( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + ) -> ProgramResult; + + fn disable_deposit( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + ) -> ProgramResult; + + fn enable_withdrawal( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + ) -> ProgramResult; + + fn disable_withdrawal( + vault: &Vault, + vault_info: &mut VaultInfo, + accounts: &[AccountInfo], + ) -> ProgramResult; +} + +pub trait Crank { + fn crank(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult; +} + +pub trait Init { + fn init(vault: &Vault, accounts: &[AccountInfo], step: u64) -> ProgramResult; +} + +pub trait Shutdown { + fn shutdown(vault: &Vault, accounts: &[AccountInfo]) -> ProgramResult; +} diff --git a/farms/vaults/src/user_info.rs b/farms/vaults/src/user_info.rs new file mode 100644 index 00000000000..24c09f0e4b0 --- /dev/null +++ b/farms/vaults/src/user_info.rs @@ -0,0 +1,348 @@ +//! User info account management. + +use { + crate::clock, + solana_farm_sdk::{ + math::checked_add, + refdb, + refdb::{RefDB, Reference, ReferenceType}, + string::{str_to_as64, ArrayString64}, + vault::Vault, + }, + solana_program::{ + account_info::AccountInfo, clock::UnixTimestamp, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, + }, + std::cell::RefMut, +}; + +pub struct UserInfo<'a, 'b> { + pub key: &'a Pubkey, + pub data: RefMut<'a, &'b mut [u8]>, +} + +impl<'a, 'b> UserInfo<'a, 'b> { + pub const LEN: usize = 681; //StorageType::get_storage_size_for_records(ReferenceType::U64, 8); + pub const LAST_DEPOSIT_INDEX: usize = 0; + pub const LAST_WITHDRAWAL_INDEX: usize = 1; + pub const TOKEN_A_ADDED_INDEX: usize = 2; + pub const TOKEN_A_REMOVED_INDEX: usize = 3; + pub const TOKEN_B_ADDED_INDEX: usize = 4; + pub const TOKEN_B_REMOVED_INDEX: usize = 5; + pub const LP_TOKENS_DEBT: usize = 6; + pub const USER_BUMP: usize = 7; + + pub fn new(account: &'a AccountInfo<'b>) -> Self { + Self { + key: account.key, + data: account.data.borrow_mut(), + } + } + + pub fn init(&mut self, refdb_name: &ArrayString64, user_bump: u8) -> ProgramResult { + RefDB::init(&mut self.data, refdb_name, ReferenceType::U64)?; + + self.init_refdb_field( + UserInfo::LAST_DEPOSIT_INDEX, + "LastDeposit", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::LAST_WITHDRAWAL_INDEX, + "LastWithdrawal", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::TOKEN_A_ADDED_INDEX, + "TokenAAdded", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::TOKEN_A_REMOVED_INDEX, + "TokenARemoved", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::TOKEN_B_ADDED_INDEX, + "TokenBAdded", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::TOKEN_B_REMOVED_INDEX, + "TokenBRemoved", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::LP_TOKENS_DEBT, + "LpTokensDebt", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + UserInfo::USER_BUMP, + "UserBump", + Reference::U64 { + data: user_bump as u64, + }, + ) + } + + pub fn update_deposit_time(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + UserInfo::LAST_DEPOSIT_INDEX, + &Reference::U64 { + data: clock::get_time_as_u64()?, + }, + ) + .map(|_| ()) + } + + pub fn update_withdrawal_time(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + UserInfo::LAST_WITHDRAWAL_INDEX, + &Reference::U64 { + data: clock::get_time_as_u64()?, + }, + ) + .map(|_| ()) + } + + pub fn add_liquidity(&mut self, token_a_added: u64, token_b_added: u64) -> ProgramResult { + if token_a_added > 0 { + let mut token_a_balance = token_a_added; + if let Some(token_a_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_A_ADDED_INDEX)? { + if let Reference::U64 { data } = token_a_rec.reference { + token_a_balance = token_a_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + UserInfo::TOKEN_A_ADDED_INDEX, + &Reference::U64 { + data: token_a_balance, + }, + )?; + } + if token_b_added > 0 { + let mut token_b_balance = token_b_added; + if let Some(token_b_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_B_ADDED_INDEX)? { + if let Reference::U64 { data } = token_b_rec.reference { + token_b_balance = token_b_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + UserInfo::TOKEN_B_ADDED_INDEX, + &Reference::U64 { + data: token_b_balance, + }, + )?; + } + if token_a_added > 0 || token_b_added > 0 { + self.update_deposit_time()?; + } + Ok(()) + } + + pub fn remove_liquidity( + &mut self, + token_a_removed: u64, + token_b_removed: u64, + ) -> ProgramResult { + if token_a_removed > 0 { + let mut token_a_balance = token_a_removed; + if let Some(token_a_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_A_REMOVED_INDEX)? + { + if let Reference::U64 { data } = token_a_rec.reference { + token_a_balance = token_a_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + UserInfo::TOKEN_A_REMOVED_INDEX, + &Reference::U64 { + data: token_a_balance, + }, + )?; + } + if token_b_removed > 0 { + let mut token_b_balance = token_b_removed; + if let Some(token_b_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_B_REMOVED_INDEX)? + { + if let Reference::U64 { data } = token_b_rec.reference { + token_b_balance = token_b_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + UserInfo::TOKEN_B_REMOVED_INDEX, + &Reference::U64 { + data: token_b_balance, + }, + )?; + } + if token_a_removed > 0 || token_b_removed > 0 { + self.update_withdrawal_time()?; + } + Ok(()) + } + + pub fn add_lp_tokens_debt(&mut self, token_added: u64) -> ProgramResult { + let mut token_debt_total = token_added; + if let Some(token_debt_rec) = RefDB::read_at(&self.data, UserInfo::LP_TOKENS_DEBT)? { + if let Reference::U64 { data } = token_debt_rec.reference { + token_debt_total = checked_add(token_debt_total, data)?; + } + } + RefDB::update_at( + &mut self.data, + UserInfo::LP_TOKENS_DEBT, + &Reference::U64 { + data: token_debt_total, + }, + )?; + Ok(()) + } + + pub fn remove_lp_tokens_debt(&mut self, token_removed: u64) -> ProgramResult { + let mut token_debt_total = 0; + if let Some(token_debt_rec) = RefDB::read_at(&self.data, UserInfo::LP_TOKENS_DEBT)? { + if let Reference::U64 { data } = token_debt_rec.reference { + token_debt_total = data; + } + } + if token_debt_total <= token_removed { + token_debt_total = 0; + } else { + token_debt_total -= token_removed; + } + RefDB::update_at( + &mut self.data, + UserInfo::LP_TOKENS_DEBT, + &Reference::U64 { + data: token_debt_total, + }, + )?; + Ok(()) + } + + pub fn get_lp_tokens_debt(&self) -> Result { + if let Some(token_debt_rec) = RefDB::read_at(&self.data, UserInfo::LP_TOKENS_DEBT)? { + if let Reference::U64 { data } = token_debt_rec.reference { + return Ok(data); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_deposit_time(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::LAST_DEPOSIT_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data as UnixTimestamp); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_withdrawal_time(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::LAST_WITHDRAWAL_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data as UnixTimestamp); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_token_a_added(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_A_ADDED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_token_b_added(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_B_ADDED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_token_a_removed(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_A_REMOVED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_token_b_removed(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, UserInfo::TOKEN_B_REMOVED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn get_user_bump(&self) -> Result { + if let Some(user_bump_rec) = RefDB::read_at(&self.data, UserInfo::USER_BUMP)? { + if let Reference::U64 { data } = user_bump_rec.reference { + return Ok(data as u8); + } + } + Err(ProgramError::UninitializedAccount) + } + + pub fn validate_account( + vault: &Vault, + user_info_account: &'a AccountInfo<'b>, + user_account: &Pubkey, + ) -> bool { + if let Ok(refdb) = user_info_account.try_borrow_data() { + if let Ok(Some(user_bump_rec)) = RefDB::read_at(&refdb, UserInfo::USER_BUMP) { + if let Reference::U64 { data } = user_bump_rec.reference { + if let Ok(key) = Pubkey::create_program_address( + &[ + b"user_info_account", + &user_account.to_bytes()[..], + vault.name.as_bytes(), + &[data as u8], + ], + &vault.vault_program_id, + ) { + if user_info_account.key == &key { + return true; + } + } + } + } + } + false + } + + // private helpers + fn init_refdb_field( + &mut self, + index: usize, + field_name: &str, + reference: Reference, + ) -> ProgramResult { + RefDB::write( + &mut self.data, + &refdb::Record { + index: Some(index as u32), + counter: 0, + tag: 0, + name: str_to_as64(field_name)?, + reference, + }, + ) + .map(|_| ()) + } +} diff --git a/farms/vaults/src/vault_info.rs b/farms/vaults/src/vault_info.rs new file mode 100644 index 00000000000..d0da342bd77 --- /dev/null +++ b/farms/vaults/src/vault_info.rs @@ -0,0 +1,467 @@ +//! Vault info account management. + +use { + crate::{clock, traits::VaultParams}, + solana_farm_sdk::{ + refdb, + refdb::{RefDB, Reference, ReferenceType}, + string::{str_to_as64, ArrayString64}, + }, + solana_program::{ + account_info::AccountInfo, clock::UnixTimestamp, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, + }, + std::cell::RefMut, +}; + +pub struct VaultInfo<'a, 'b> { + pub key: &'a Pubkey, + pub data: RefMut<'a, &'b mut [u8]>, +} + +impl<'a, 'b> VaultInfo<'a, 'b> { + pub const LEN: usize = 1061; //StorageType::get_storage_size_for_records(ReferenceType::U64, 13); + pub const CRANK_TIME_INDEX: usize = 0; + pub const CRANK_STEP_INDEX: usize = 1; + pub const TOKEN_A_ADDED_INDEX: usize = 2; + pub const TOKEN_A_REMOVED_INDEX: usize = 3; + pub const TOKEN_B_ADDED_INDEX: usize = 4; + pub const TOKEN_B_REMOVED_INDEX: usize = 5; + pub const TOKEN_A_REWARDS_INDEX: usize = 6; + pub const TOKEN_B_REWARDS_INDEX: usize = 7; + pub const DEPOSIT_ALLOWED_INDEX: usize = 8; + pub const WITHDRAWAL_ALLOWED_INDEX: usize = 9; + pub const MIN_CRANK_INTERVAL_INDEX: usize = 10; + pub const FEE_INDEX: usize = 11; + pub const EXTERNAL_FEE_INDEX: usize = 12; + + pub fn new(account: &'a AccountInfo<'b>) -> Self { + Self { + key: account.key, + data: account.data.borrow_mut(), + } + } + + pub fn init(&mut self, refdb_name: &ArrayString64) -> ProgramResult { + if RefDB::is_initialized(&self.data) { + return Ok(()); + } + RefDB::init(&mut self.data, refdb_name, ReferenceType::U64)?; + + self.init_refdb_field( + VaultInfo::CRANK_TIME_INDEX, + "CrankTime", + Reference::U64 { + data: clock::get_time_as_u64()?, + }, + )?; + self.init_refdb_field( + VaultInfo::CRANK_STEP_INDEX, + "CrankStep", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_A_ADDED_INDEX, + "TokenAAdded", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_A_REMOVED_INDEX, + "TokenARemoved", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_B_ADDED_INDEX, + "TokenBAdded", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_B_REMOVED_INDEX, + "TokenBRemoved", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_A_REWARDS_INDEX, + "TokenARewards", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::TOKEN_B_REWARDS_INDEX, + "TokenBRewards", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::DEPOSIT_ALLOWED_INDEX, + "DepositAllowed", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::WITHDRAWAL_ALLOWED_INDEX, + "WithdrawalAllowed", + Reference::U64 { data: 0 }, + )?; + self.init_refdb_field( + VaultInfo::MIN_CRANK_INTERVAL_INDEX, + "MinCrankInterval", + Reference::U64 { + data: VaultInfo::default_min_crank_interval(), + }, + )?; + self.init_refdb_field( + VaultInfo::FEE_INDEX, + "Fee", + Reference::U64 { + data: VaultInfo::default_fee().to_bits(), + }, + )?; + self.init_refdb_field( + VaultInfo::EXTERNAL_FEE_INDEX, + "ExternalFee", + Reference::U64 { + data: VaultInfo::default_external_fee().to_bits(), + }, + ) + } + + pub fn update_crank_time(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::CRANK_TIME_INDEX, + &Reference::U64 { + data: clock::get_time_as_u64()?, + }, + ) + .map(|_| ()) + } + + pub fn set_crank_step(&mut self, step: u64) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::CRANK_STEP_INDEX, + &Reference::U64 { data: step }, + ) + .map(|_| ()) + } + + pub fn set_min_crank_interval(&mut self, min_crank_interval_sec: u64) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::MIN_CRANK_INTERVAL_INDEX, + &Reference::U64 { + data: min_crank_interval_sec, + }, + ) + .map(|_| ()) + } + + pub fn set_fee(&mut self, fee: f64) -> ProgramResult { + if !(0.0..=1.0).contains(&fee) { + return Err(ProgramError::InvalidArgument); + } + RefDB::update_at( + &mut self.data, + VaultInfo::FEE_INDEX, + &Reference::U64 { + data: fee.to_bits(), + }, + ) + .map(|_| ()) + } + + pub fn set_external_fee(&mut self, external_fee: f64) -> ProgramResult { + if !(0.0..=1.0).contains(&external_fee) { + return Err(ProgramError::InvalidArgument); + } + RefDB::update_at( + &mut self.data, + VaultInfo::EXTERNAL_FEE_INDEX, + &Reference::U64 { + data: external_fee.to_bits(), + }, + ) + .map(|_| ()) + } + + pub fn enable_deposit(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::DEPOSIT_ALLOWED_INDEX, + &Reference::U64 { data: 1 }, + ) + .map(|_| ()) + } + + pub fn disable_deposit(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::DEPOSIT_ALLOWED_INDEX, + &Reference::U64 { data: 0 }, + ) + .map(|_| ()) + } + + pub fn enable_withdrawal(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::WITHDRAWAL_ALLOWED_INDEX, + &Reference::U64 { data: 1 }, + ) + .map(|_| ()) + } + + pub fn disable_withdrawal(&mut self) -> ProgramResult { + RefDB::update_at( + &mut self.data, + VaultInfo::WITHDRAWAL_ALLOWED_INDEX, + &Reference::U64 { data: 0 }, + ) + .map(|_| ()) + } + + pub fn add_liquidity(&mut self, token_a_added: u64, token_b_added: u64) -> ProgramResult { + if token_a_added > 0 { + let mut token_a_balance = token_a_added; + if let Some(token_a_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_ADDED_INDEX)? { + if let Reference::U64 { data } = token_a_rec.reference { + token_a_balance = token_a_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_A_ADDED_INDEX, + &Reference::U64 { + data: token_a_balance, + }, + )?; + } + if token_b_added > 0 { + let mut token_b_balance = token_b_added; + if let Some(token_b_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_ADDED_INDEX)? { + if let Reference::U64 { data } = token_b_rec.reference { + token_b_balance = token_b_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_B_ADDED_INDEX, + &Reference::U64 { + data: token_b_balance, + }, + )?; + } + Ok(()) + } + + pub fn remove_liquidity( + &mut self, + token_a_removed: u64, + token_b_removed: u64, + ) -> ProgramResult { + if token_a_removed > 0 { + let mut token_a_balance = token_a_removed; + if let Some(token_a_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_REMOVED_INDEX)? + { + if let Reference::U64 { data } = token_a_rec.reference { + token_a_balance = token_a_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_A_REMOVED_INDEX, + &Reference::U64 { + data: token_a_balance, + }, + )?; + } + if token_b_removed > 0 { + let mut token_b_balance = token_b_removed; + if let Some(token_b_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_REMOVED_INDEX)? + { + if let Reference::U64 { data } = token_b_rec.reference { + token_b_balance = token_b_balance.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_B_REMOVED_INDEX, + &Reference::U64 { + data: token_b_balance, + }, + )?; + } + Ok(()) + } + + pub fn add_rewards(&mut self, token_a_rewards: u64, token_b_rewards: u64) -> ProgramResult { + if token_a_rewards > 0 { + let mut token_a_total = token_a_rewards; + if let Some(token_a_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_REWARDS_INDEX)? + { + if let Reference::U64 { data } = token_a_rec.reference { + token_a_total = token_a_total.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_A_REWARDS_INDEX, + &Reference::U64 { + data: token_a_total, + }, + )?; + } + if token_b_rewards > 0 { + let mut token_b_total = token_b_rewards; + if let Some(token_b_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_REWARDS_INDEX)? + { + if let Reference::U64 { data } = token_b_rec.reference { + token_b_total = token_b_total.wrapping_add(data); + } + } + RefDB::update_at( + &mut self.data, + VaultInfo::TOKEN_B_REWARDS_INDEX, + &Reference::U64 { + data: token_b_total, + }, + )?; + } + Ok(()) + } + + pub fn get_crank_time(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::CRANK_TIME_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data as UnixTimestamp); + } + } + Ok(0) + } + + pub fn get_crank_step(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::CRANK_STEP_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_min_crank_interval(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::MIN_CRANK_INTERVAL_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data as i64); + } + } + Ok(0) + } + + pub fn get_fee(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::FEE_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(f64::from_bits(data)); + } + } + Ok(0.0) + } + + pub fn get_external_fee(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::EXTERNAL_FEE_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(f64::from_bits(data)); + } + } + Ok(0.0) + } + + pub fn is_deposit_allowed(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::DEPOSIT_ALLOWED_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data > 0); + } + } + Ok(false) + } + + pub fn is_withdrawal_allowed(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::WITHDRAWAL_ALLOWED_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data > 0); + } + } + Ok(false) + } + + pub fn get_token_a_added(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_ADDED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_token_b_added(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_ADDED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_token_a_removed(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_REMOVED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_token_b_removed(&self) -> Result { + if let Some(deposit_rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_REMOVED_INDEX)? { + if let Reference::U64 { data } = deposit_rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_token_a_rewards(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_A_REWARDS_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data); + } + } + Ok(0) + } + + pub fn get_token_b_rewards(&self) -> Result { + if let Some(rec) = RefDB::read_at(&self.data, VaultInfo::TOKEN_B_REWARDS_INDEX)? { + if let Reference::U64 { data } = rec.reference { + return Ok(data); + } + } + Ok(0) + } + + // private helpers + fn init_refdb_field( + &mut self, + index: usize, + field_name: &str, + reference: Reference, + ) -> ProgramResult { + RefDB::write( + &mut self.data, + &refdb::Record { + index: Some(index as u32), + counter: 0, + tag: 0, + name: str_to_as64(field_name)?, + reference, + }, + ) + .map(|_| ()) + } +} diff --git a/update-solana-dependencies.sh b/update-solana-dependencies.sh index ac2f65ebb83..b0d90fbfe7b 100755 --- a/update-solana-dependencies.sh +++ b/update-solana-dependencies.sh @@ -36,6 +36,7 @@ crates=( solana-stake-program solana-transaction-status solana-vote-program + solana-version ) set -x