diff --git a/Cargo.lock b/Cargo.lock index bb2dc6f..0bb1712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,167 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anchor-attribute-access-control" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7d535e1381be3de2c0716c0a1c1e32ad9df1042cddcf7bc18d743569e53319" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.63", + "quote 1.0.29", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bcd731f21048a032be27c7791701120e44f3f6371358fc4261a7f716283d29" +dependencies = [ + "anchor-syn", + "anyhow", + "bs58 0.4.0", + "proc-macro2 1.0.63", + "quote 1.0.29", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1be64a48e395fe00b8217287f226078be2cf32dae42fdf8a885b997945c3d28" +dependencies = [ + "anchor-syn", + "proc-macro2 1.0.63", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ea6713d1938c0da03656ff8a693b17dc0396da66d1ba320557f07e86eca0d4" +dependencies = [ + "anchor-syn", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401f11efb3644285685f8339829a9786d43ed7490bb1699f33c478d04d5a582" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-interface" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6700a6f5c888a9c33fe8afc0c64fd8575fa28d05446037306d0f96102ae4480" +dependencies = [ + "anchor-syn", + "anyhow", + "heck 0.3.3", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad769993b5266714e8939e47fbdede90e5c030333c7522d99a4d4748cf26712" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-state" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e677fae4a016a554acdd0e3b7f178d3acafaa7e7ffac6b8690cf4e171f1c116" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340beef6809d1c3fcc7ae219153d981e95a8a277ff31985bd7050e32645dc9a8" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662ceafe667448ee4199a4be2ee83b6bb76da28566eee5cea05f96ab38255af8" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-interface", + "anchor-attribute-program", + "anchor-attribute-state", + "anchor-derive-accounts", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.9.3", + "bytemuck", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-syn" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0418bcb5daac3b8cb1b60d8fdb1d468ca36f5509f31fb51179326fae1028fdcc" +dependencies = [ + "anyhow", + "bs58 0.3.1", + "heck 0.3.3", + "proc-macro2 1.0.63", + "proc-macro2-diagnostics", + "quote 1.0.29", + "serde", + "serde_json", + "sha2 0.9.9", + "syn 1.0.109", + "thiserror", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -231,8 +392,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", "synstructure", ] @@ -243,8 +404,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -260,7 +421,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ - "quote 1.0.28", + "quote 1.0.29", "syn 1.0.109", ] @@ -401,9 +562,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -414,13 +575,13 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -546,8 +707,8 @@ checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -575,6 +736,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "bigdecimal" version = "0.3.1" @@ -705,7 +875,7 @@ dependencies = [ "borsh-derive-internal 0.9.3", "borsh-schema-derive-internal 0.9.3", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.60", + "proc-macro2 1.0.63", "syn 1.0.109", ] @@ -718,7 +888,7 @@ dependencies = [ "borsh-derive-internal 0.10.3", "borsh-schema-derive-internal 0.10.3", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.60", + "proc-macro2 1.0.63", "syn 1.0.109", ] @@ -728,8 +898,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -739,8 +909,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -750,8 +920,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -761,8 +931,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -787,6 +957,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + [[package]] name = "bs58" version = "0.4.0" @@ -835,8 +1011,8 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -855,9 +1031,9 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -997,8 +1173,8 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -1009,9 +1185,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -1258,10 +1434,10 @@ checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "strsim 0.10.0", - "syn 2.0.18", + "syn 2.0.23", ] [[package]] @@ -1271,8 +1447,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", - "quote 1.0.28", - "syn 2.0.18", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -1402,9 +1578,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -1531,8 +1707,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -1543,8 +1719,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2" dependencies = [ "once_cell", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -1735,9 +1911,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -2057,17 +2233,23 @@ dependencies = [ name = "holaplex-hub-nfts-solana" version = "0.1.0" dependencies = [ + "anchor-lang", "bincode", + "bs58 0.5.0", "holaplex-hub-core", "holaplex-hub-nfts-solana-core", "holaplex-hub-nfts-solana-entity", + "jsonrpsee", + "mpl-bubblegum", "mpl-token-metadata", "serde", "serde_json", "solana-client", "solana-program", "solana-sdk", + "spl-account-compression", "spl-associated-token-account", + "spl-noop", "spl-token", ] @@ -2078,6 +2260,7 @@ dependencies = [ "holaplex-hub-core", "holaplex-hub-core-build", "holaplex-hub-nfts-solana-entity", + "mpl-bubblegum", "mpl-token-metadata", "prost", "sea-orm 0.11.3", @@ -2174,7 +2357,9 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", + "log", "rustls 0.21.1", + "rustls-native-certs", "tokio", "tokio-rustls 0.24.1", ] @@ -2377,6 +2562,84 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonrpsee" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1822d18e4384a5e79d94dc9e4d1239cfa9fad24e55b44d2efeff5b394c9fece4" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c6832a55f662b5a6ecc844db24b8b9c387453f923de863062c60ce33d62b81" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-util", + "hyper", + "jsonrpsee-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1705c65069729e3dccff6fd91ee431d5d31cabcf00ce68a62a2c6435ac713af9" +dependencies = [ + "async-trait", + "hyper", + "hyper-rustls", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6027ac0b197ce9543097d02a290f550ce1d9432bf301524b013053c0b75cc94" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 1.3.1", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5bf6c75ce2a4217421154adfc65a24d2b46e77286e59bba5d9fa6544ccc8f4" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", +] + [[package]] name = "keccak" version = "0.1.4" @@ -2645,6 +2908,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mpl-bubblegum" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a589ceb05c6277253960665ee3658da89f055629c428beb64c55ad24a41b13" +dependencies = [ + "anchor-lang", + "bytemuck", + "mpl-token-metadata", + "solana-program", + "spl-account-compression", + "spl-associated-token-account", + "spl-token", +] + [[package]] name = "mpl-token-auth-rules" version = "1.3.0" @@ -2689,7 +2967,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12989bc45715b0ee91944855130131479f9c772e198a910c3eb0ea327d5bffc3" dependencies = [ - "quote 1.0.28", + "quote 1.0.29", "syn 1.0.109", ] @@ -2813,8 +3091,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -2886,8 +3164,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate 1.3.1", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -2948,9 +3226,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -3005,8 +3283,8 @@ checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -3143,9 +3421,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -3217,7 +3495,7 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.60", + "proc-macro2 1.0.63", "syn 1.0.109", ] @@ -3247,8 +3525,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", "version_check", ] @@ -3259,8 +3537,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "version_check", ] @@ -3275,13 +3553,26 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] +[[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.63", + "quote 1.0.29", + "syn 1.0.109", + "version_check", + "yansi", +] + [[package]] name = "prost" version = "0.11.9" @@ -3322,8 +3613,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -3360,8 +3651,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -3438,11 +3729,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ - "proc-macro2 1.0.60", + "proc-macro2 1.0.63", ] [[package]] @@ -3751,8 +4042,8 @@ version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -4045,8 +4336,8 @@ checksum = "7216195de9c6b2474fd0efab486173dccd0eff21f28cc54aa4c0205d52fb3af0" dependencies = [ "bae", "heck 0.3.3", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -4058,8 +4349,8 @@ checksum = "28936f26d62234ff0be16f80115dbdeb3237fe9c25cf18fbcd1e3b3592360f20" dependencies = [ "bae", "heck 0.3.3", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -4147,8 +4438,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cdc022b4f606353fe5dc85b09713a04e433323b70163e81513b141c6ae6eb5" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", "thiserror", ] @@ -4160,8 +4451,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", "thiserror", ] @@ -4184,8 +4475,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", ] @@ -4205,8 +4496,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "rustversion", "syn 1.0.109", ] @@ -4270,9 +4561,9 @@ version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -4321,9 +4612,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling", - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -4421,8 +4712,8 @@ version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63927d22a1e8b74bda98cc6e151fcdf178b7abb0dc6c4f81e0bbf5ffe2fc4ec8" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "shank_macro_impl", "syn 1.0.109", ] @@ -4434,8 +4725,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce03403df682f80f4dc1efafa87a4d0cb89b03726d0565e6364bdca5b9a441" dependencies = [ "anyhow", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "serde", "syn 1.0.109", ] @@ -4529,9 +4820,9 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a799348a70a5885cf428f1921e851cec204b58de1aeb0ca4409b5973d6d59d" +checksum = "b04c1316932017ae5f947e83d77cc0356c4a395130a480cdc17ffb0570a0c115" dependencies = [ "Inflector", "base64 0.13.1", @@ -4554,9 +4845,9 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef5f98a080611a417da791853ca245ea7f58efefd4b5d9c5239e82693a65697" +checksum = "5be490ed850c99286a4e4ba169ce20695336fe666c56bd823bfd8db689d23a58" dependencies = [ "bincode", "bytemuck", @@ -4575,9 +4866,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43985f76c072db0412de3dcd8976bf6bc040ee10da8480fb5ba6b9455d733e6f" +checksum = "36228e03e14bc7d7707189b66f625981993f1a000b0b192d5b42367349901d91" dependencies = [ "chrono", "clap 2.34.0", @@ -4593,9 +4884,9 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6b277a2927981b7f4e437741a37fc457eed5da7de6317c2a89a6996fd573e1" +checksum = "6c43b08f24fd605eaeaafe0e834dc9b209137ac253bc874d32a5bdd791cbd318" dependencies = [ "dirs-next", "lazy_static", @@ -4609,9 +4900,9 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839f0ebb1c14a25da0e2118a96ed630a49e3aa524a459b7ab98b410f44abcc7" +checksum = "a3e270b1afd0b360c2aec42ae302ae7980ebb226017275b32a6156ab2ccbdad9" dependencies = [ "async-mutex", "async-trait", @@ -4663,9 +4954,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cd8f45fddff299b73121fbf751164b7e029b3324b274b50160d787c82673d2" +checksum = "fb275d80a482134f0f0c5439b0c40ba3f04bef70dbc46c0e47f6107f6ae482a8" dependencies = [ "bincode", "chrono", @@ -4677,9 +4968,9 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4b7f4d15cb628a7f89e570a18914551211a5f1da81352dffd93881b191986a" +checksum = "b3ef95ad1f87b8c011d0e4d85a46f4a703e9dd7e722459659b395ed70d6ba924" dependencies = [ "bincode", "byteorder", @@ -4701,9 +4992,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7245b88e5bcedc9096d7c7ffd8cf6769987b151e381e8a3561939898d9e495" +checksum = "f44a019070a6cec4d3ad8605c5caa65bdaa13f00b5f1849340f44ffea63b625b" dependencies = [ "ahash 0.7.6", "blake3", @@ -4735,12 +5026,12 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546f204604da1d6958e412f4d3bc8cad34de6a81dc379fac07e53a29e224bcf0" +checksum = "be23cc7a382f54dfe1348edb94610e5cc146b8eb21563cdd04062a403c75ba62" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "rustc_version", "syn 1.0.109", ] @@ -4768,9 +5059,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12090fa9b638f374492c86c62b79e0e82479e3632ced02a33ff560ffdce72e04" +checksum = "447d16a70a1b5383736ef44801050c0e1affd022303b22ed899352f958c2de4b" dependencies = [ "env_logger", "lazy_static", @@ -4779,9 +5070,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c98a872ae83f2e7c00846108f98a18f7f40265b2cd86b2d44369c33d9b7fb8f1" +checksum = "2400d2534a19f7605c5059060edea0499600a223f1a1f6a4b172666c04946a77" dependencies = [ "log", "solana-sdk", @@ -4789,9 +5080,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7557878951c7defed6e4daf34e9334d8ea85b82c9a1b2d5cc50d069fd9191db9" +checksum = "68aaa3d683945dc3b6ca38923ef952ca1f96a27b61f898a1ddf9f4cd79f2df92" dependencies = [ "crossbeam-channel", "gethostname", @@ -4803,9 +5094,9 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684c80c774275783bb754aa07e2e18f2064551a78cda5e54e12f44c85f3803d" +checksum = "d6d7093739e143d5e2edf3e81e523d47228adb802b847d66f4ab819be7ad6dc8" dependencies = [ "bincode", "clap 3.2.25", @@ -4825,9 +5116,9 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2db417718e96fd2473025d4f2afb975dd295e9f7bd479cd3a0bcea20da38088" +checksum = "cbc742f8d53f0a6e6f3a27ed11c1d0764b5486813c721d625c56094fcd14e984" dependencies = [ "ahash 0.7.6", "bincode", @@ -4852,9 +5143,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e7df881407995597cf32299df06cc8ac3d7fc4cd53e69e95964832beca48c3" +checksum = "d0937481f080f5dd495fae456c94718a7bacf30fb5fdabb02dcb8a9622e446d5" dependencies = [ "base64 0.13.1", "bincode", @@ -4901,9 +5192,9 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ad7f8f71d46673e2b68edb902856516795dd6b9d2322fa4e9edcd6cf0caac" +checksum = "b4d12047608bac77fca000e18f7a2df3c7fa90656d7c7d387b1cd7faf18b238c" dependencies = [ "base64 0.13.1", "bincode", @@ -4928,9 +5219,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccad09c8fbcb13ba7a1aca9d6337bf439593e1ea104d7ba9519d6d09c89da63c" +checksum = "b6eca67181e0381532db4bc69a625b1f96a047be461ff9050c451add0165424f" dependencies = [ "lazy_static", "num_cpus", @@ -4938,9 +5229,9 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a34ea5c66161b5f6a2085804bd0364bcdfdd584cfdb7237f88b6c3255199601" +checksum = "9b83d035ee90035ebcb07ec73672fdc0272e5b98899846dd29fcb31f856ac78c" dependencies = [ "console", "dialoguer", @@ -4957,9 +5248,9 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bdc5c047bf29730ad00e2c9ef92d396877c836633177089a00b7311e6eb3ead" +checksum = "390e7481c56dda2ceab2652beeda30a533e9667b34861a2eb4eec92fa1d826d7" dependencies = [ "assert_matches", "base64 0.13.1", @@ -5008,22 +5299,22 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cff60ba1f94594f1de7baf649bf781383e806e834e26607ff8857a9452cd3c" +checksum = "33d0acbad862093ea123f3a27364336dcb0c8373522cd6810496a34e932c56c1" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "rustversion", "syn 1.0.109", ] [[package]] name = "solana-streamer" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "753f510e0e145e70238fc597571e2bb5f2871dc022b04e869024b5dcf1fd82c3" +checksum = "853b0187fdf233c13e8b7ba76e61d0c7cb49ca92c5fdb3b7568ad5ca30e2cf88" dependencies = [ "crossbeam-channel", "futures-util", @@ -5050,9 +5341,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6a451415da98f850d5777df18d588d7504393106aea295b642cd243115ed77" +checksum = "3c5bbdaed99403e4a17763bee60c1e0e3418524503c72b514ebff62efbcc9d33" dependencies = [ "Inflector", "base64 0.13.1", @@ -5079,9 +5370,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3d9aa0a819a22f5befc7e8df5413259621b5d20be0a5a210d1fb41a083a09b" +checksum = "5a46c9ecb15ccd5388511cec0c5bfb989589425f8286ce432ff64b55dc7bf61e" dependencies = [ "log", "rustc_version", @@ -5095,9 +5386,9 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb0bb49cc1490ce16133368f4a04e0671f856d481f95416f03f08fede7d993ba" +checksum = "81ab9ff8928282cb42871a370435dd4713f700854801afb476cf63066f1337db" dependencies = [ "bincode", "log", @@ -5116,9 +5407,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.14.18" +version = "1.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a679b4dabee8d23a7bfa657440c892a88420191da11352313ab83f986826a7" +checksum = "cebca4083e982ae01583d1a590c4d679e6f648a4761364ddfb43026d2c433142" dependencies = [ "aes-gcm-siv", "arrayref", @@ -5161,11 +5452,23 @@ dependencies = [ "der", ] +[[package]] +name = "spl-account-compression" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a5417eae3c924553b872de5f1bca5945334a235f8d94841bd44c6dd7c6358c" +dependencies = [ + "anchor-lang", + "bytemuck", + "spl-concurrent-merkle-tree", + "spl-noop", +] + [[package]] name = "spl-associated-token-account" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" +checksum = "fbc000f0fdf1f12f99d77d398137c1751345b18c88258ce0f99b7872cf6c9bd6" dependencies = [ "assert_matches", "borsh 0.9.3", @@ -5177,6 +5480,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-concurrent-merkle-tree" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dd605d33bdc8d2522a9f55207c3eac06737b2e8310f602e252b510e3db1210" +dependencies = [ + "bytemuck", + "solana-program", + "thiserror", +] + [[package]] name = "spl-memo" version = "3.0.1" @@ -5186,6 +5500,15 @@ dependencies = [ "solana-program", ] +[[package]] +name = "spl-noop" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558536c75b5aed018113bfca39cddb414cd7ca77da7658d668e751d977830cda" +dependencies = [ + "solana-program", +] + [[package]] name = "spl-token" version = "3.5.0" @@ -5203,9 +5526,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.6.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" +checksum = "0edb869dbe159b018f17fb9bfa67118c30f232d7f54a73742bc96794dff77ed8" dependencies = [ "arrayref", "bytemuck", @@ -5309,8 +5632,8 @@ dependencies = [ "either", "heck 0.4.1", "once_cell", - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "serde_json", "sha2 0.10.6", "sqlx-core", @@ -5375,19 +5698,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.18" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "unicode-ident", ] @@ -5403,8 +5726,8 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", + "proc-macro2 1.0.63", + "quote 1.0.29", "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -5468,9 +5791,9 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -5590,9 +5913,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -5734,9 +6057,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" dependencies = [ "prettyplease", - "proc-macro2 1.0.60", + "proc-macro2 1.0.63", "prost-build", - "quote 1.0.28", + "quote 1.0.29", "syn 1.0.109", ] @@ -5804,9 +6127,9 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] @@ -6107,9 +6430,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", "wasm-bindgen-shared", ] @@ -6131,7 +6454,7 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ - "quote 1.0.28", + "quote 1.0.29", "wasm-bindgen-macro-support", ] @@ -6141,9 +6464,9 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6458,6 +6781,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yasna" version = "0.5.2" @@ -6508,9 +6837,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 2.0.18", + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.23", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b166a91..458a1ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,3 @@ [workspace] members = ["consumer", "core", "entity", "migration", "indexer"] resolver = "2" - -[workspace.dependencies] -holaplex-hub-nfts-solana-core = { path = "core" } -holaplex-hub-nfts-solana-entity = { path = "entity" } diff --git a/consumer/Cargo.toml b/consumer/Cargo.toml index f22bdd1..a8f8c0c 100644 --- a/consumer/Cargo.toml +++ b/consumer/Cargo.toml @@ -16,22 +16,28 @@ categories = ["cryptography::cryptocurrencies", "web-programming"] [lib] [dependencies] +anchor-lang = "0.26.0" bincode = "1.3.3" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" solana-program = "1.14.8" solana-client = "1.14.8" +spl-account-compression = "0.1.10" +spl-noop = "0.1.3" spl-token = "3.5.0" solana-sdk = "1.14.8" spl-associated-token-account = "1.1.2" +mpl-bubblegum = "0.8.0" mpl-token-metadata = "1.8.3" holaplex-hub-nfts-solana-core = { path = "../core" } holaplex-hub-nfts-solana-entity = { path = "../entity" } +jsonrpsee = { version = "0.18.2", features = ["macros", "http-client"] } +bs58 = "0.5.0" [dependencies.hub-core] package = "holaplex-hub-core" version = "0.2.0" git = "https://github.com/holaplex/hub-core" branch = "stable" -features = ["kafka"] \ No newline at end of file +features = ["kafka"] diff --git a/consumer/src/asset_api.rs b/consumer/src/asset_api.rs new file mode 100644 index 0000000..4d97e44 --- /dev/null +++ b/consumer/src/asset_api.rs @@ -0,0 +1,141 @@ +mod b58 { + use serde::{de::Visitor, Deserializer, Serializer}; + + pub fn serialize(bytes: &[u8], ser: S) -> Result { + ser.serialize_str(&bs58::encode(bytes).into_string()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result, D::Error> { + struct Vis; + + impl<'a> Visitor<'a> for Vis { + type Value = Vec; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("a base58-encoded string") + } + + fn visit_str(self, s: &str) -> Result { + bs58::decode(s).into_vec().map_err(E::custom) + } + } + + de.deserialize_str(Vis) + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Base58(#[serde(with = "b58")] pub Vec); + +impl From> for Base58 { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl From for Vec { + fn from(Base58(v): Base58) -> Self { + v + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Asset { + pub interface: String, + pub id: Base58, + pub content: serde_json::Value, + pub authorities: Vec, + pub compression: AssetCompression, + pub grouping: Vec, + pub royalty: AssetRoyalty, + pub creators: Vec, + pub ownership: AssetOwnership, + pub supply: Option, + pub mutable: bool, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetAuthority { + pub address: Base58, + pub scopes: Vec, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetCompression { + pub eligible: bool, + pub compressed: bool, + pub data_hash: Base58, + pub creator_hash: Base58, + pub asset_hash: Base58, + pub tree: Base58, + pub seq: u32, + pub leaf_id: u32, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetGrouping { + pub group_key: String, + pub group_value: Base58, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetRoyalty { + pub royalty_model: String, + pub target: Option, // TODO: what type is this + pub percent: serde_json::Number, // TODO: this is fractional, use BCD to avoid rounding error + pub basis_points: u32, + pub primary_sale_happened: bool, + pub locked: bool, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetCreator { + pub address: Base58, + pub share: u32, + pub verified: bool, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetOwnership { + pub frozen: bool, + pub delegated: bool, + pub delegate: Base58, + pub ownership_model: String, + pub owner: Base58, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct AssetProof { + pub root: Base58, + pub proof: Vec, + pub node_index: u32, + pub leaf: Base58, + pub tree_id: Base58, +} + +#[jsonrpsee::proc_macros::rpc(client)] +pub trait Rpc { + #[method(name = "getAsset", param_kind = map)] + fn get_asset(&self, id: &str) -> Result; + + #[method(name = "getAssetProof", param_kind = map)] + fn get_asset_proof(&self, id: &str) -> Result; + + // Supposedly Triton offers these but their docs were crashing my browser + // so I don't know what the signatures are. + + // #[method(name = "getAssetsByAuthority")] + // fn get_assets_by_authority(&self); + + // #[method(name = "getAssetsByOwner")] + // fn get_assets_by_owner(&self); + + // #[method(name = "getAssetsByGroup")] + // fn get_assets_by_group(&self); + + // #[method(name = "getAssetsByCreator")] + // fn get_assets_by_creator(&self); + + // #[method(name = "searchAssets")] + // fn search_assets(&self); +} diff --git a/consumer/src/backend.rs b/consumer/src/backend.rs new file mode 100644 index 0000000..bdff7dc --- /dev/null +++ b/consumer/src/backend.rs @@ -0,0 +1,110 @@ +use holaplex_hub_nfts_solana_core::proto::{ + MetaplexMasterEditionTransaction, SolanaPendingTransaction, TransferMetaplexAssetTransaction, +}; +use holaplex_hub_nfts_solana_entity::{collection_mints, collections}; +use hub_core::prelude::*; +use solana_program::pubkey::Pubkey; + +#[derive(Clone)] +pub struct MasterEditionAddresses { + pub metadata: Pubkey, + pub associated_token_account: Pubkey, + pub owner: Pubkey, + pub master_edition: Pubkey, + pub mint: Pubkey, + pub update_authority: Pubkey, +} + +#[derive(Clone)] +pub struct MintEditionAddresses { + pub edition: Pubkey, + pub mint: Pubkey, + pub metadata: Pubkey, + pub owner: Pubkey, + pub associated_token_account: Pubkey, + pub recipient: Pubkey, +} + +#[derive(Clone)] +pub struct MintMetaplexAddresses { + pub mint: Pubkey, + pub metadata: Pubkey, + pub owner: Pubkey, + pub associated_token_account: Pubkey, + pub recipient: Pubkey, + pub update_authority: Pubkey, +} + +#[derive(Clone)] +pub struct MintCompressedMintV1Addresses { + pub owner: Pubkey, + pub recipient: Pubkey, +} + +#[derive(Clone)] +pub struct UpdateMasterEditionAddresses { + pub metadata: Pubkey, + pub update_authority: Pubkey, +} + +#[derive(Clone)] +pub struct TransferAssetAddresses { + pub owner: Pubkey, + pub recipient: Pubkey, + pub recipient_associated_token_account: Pubkey, + pub owner_associated_token_account: Pubkey, +} + +/// Represents a response from a transaction on the blockchain. This struct +/// provides the serialized message and the signatures of the signed message. +pub struct TransactionResponse { + /// The serialized version of the message from the transaction. + pub serialized_message: Vec, + + /// The signatures of the signed message or the public keys of wallets that should sign the transaction. Order matters. + pub signatures_or_signers_public_keys: Vec, + + /// Addresses that are related to the transaction. + pub addresses: A, +} + +impl From> for SolanaPendingTransaction { + fn from( + TransactionResponse { + serialized_message, + signatures_or_signers_public_keys, + .. + }: TransactionResponse, + ) -> Self { + Self { + serialized_message, + signatures_or_signers_public_keys, + } + } +} + +pub trait CollectionBackend { + fn create( + &self, + txn: MetaplexMasterEditionTransaction, + ) -> Result>; + + fn update( + &self, + collection: &collections::Model, + txn: MetaplexMasterEditionTransaction, + ) -> Result>; +} + +pub trait MintBackend { + fn mint(&self, collection: &collections::Model, txn: T) -> Result>; +} + +#[async_trait] +pub trait TransferBackend { + async fn transfer( + &self, + collection_mint: &collection_mints::Model, + txn: TransferMetaplexAssetTransaction, + ) -> Result>; +} diff --git a/consumer/src/events.rs b/consumer/src/events.rs index 3f9e451..637c664 100644 --- a/consumer/src/events.rs +++ b/consumer/src/events.rs @@ -1,314 +1,454 @@ +use anchor_lang::Event; use holaplex_hub_nfts_solana_core::{ - db::Connection, + db, proto::{ - nft_events::Event::{ - SolanaCreateDrop, SolanaMintDrop, SolanaRetryDrop, SolanaRetryMintDrop, - SolanaTransferAsset, SolanaUpdateDrop, - }, - solana_nft_events::Event::{ - CreateDropFailed, CreateDropSigningRequested, CreateDropSubmitted, MintDropFailed, - MintDropSigningRequested, MintDropSubmitted, RetryCreateDropFailed, - RetryCreateDropSigningRequested, RetryCreateDropSubmitted, RetryMintDropFailed, - RetryMintDropSigningRequested, RetryMintDropSubmitted, TransferAssetFailed, - TransferAssetSigningRequested, TransferAssetSubmitted, UpdateDropFailed, - UpdateDropSigningRequested, UpdateDropSubmitted, - }, - treasury_events::{Event as TreasuryEvent, TransactionStatus}, + nft_events::Event as NftEvent, + solana_nft_events::Event as SolanaNftEvent, + treasury_events::{Event as TreasuryEvent, SolanaTransactionResult, TransactionStatus}, MetaplexMasterEditionTransaction, MintMetaplexEditionTransaction, - SolanaCompletedMintTransaction, SolanaCompletedTransferTransaction, - SolanaCompletedUpdateTransaction, SolanaFailedTransaction, SolanaNftEventKey, - SolanaNftEvents, SolanaPendingTransaction, SolanaTransactionFailureReason, - TransferMetaplexAssetTransaction, + MintMetaplexMetadataTransaction, SolanaCompletedMintTransaction, + SolanaCompletedTransferTransaction, SolanaCompletedUpdateTransaction, + SolanaFailedTransaction, SolanaNftEventKey, SolanaNftEvents, SolanaPendingTransaction, + SolanaTransactionFailureReason, TransferMetaplexAssetTransaction, }, - sea_orm::Set, + sea_orm::{DbErr, Set}, Collection, CollectionMint, Services, }; use holaplex_hub_nfts_solana_entity::{collection_mints, collections}; -use hub_core::{chrono::Utc, prelude::*, producer::Producer, thiserror::Error, uuid::Uuid}; +use hub_core::{ + chrono::Utc, + prelude::*, + producer::{Producer, SendError}, + thiserror, + util::DebugShim, + uuid, + uuid::Uuid, +}; -use crate::solana::{MasterEditionAddresses, Solana, TransactionResponse}; +use crate::{ + backend::{ + CollectionBackend, MasterEditionAddresses, MintBackend, MintEditionAddresses, + MintMetaplexAddresses, TransferBackend, + }, + solana::{EditionRef, Solana, UncompressedRef}, +}; -#[derive(Error, Debug)] -pub enum ProcessorError { - #[error("record not found")] +#[derive(Debug, thiserror::Error)] +pub enum ProcessorErrorKind { + #[error("Associated record not found in database")] RecordNotFound, - #[error("message not found")] - MessageNotFound, - #[error("transaction status not found")] + #[error("Transaction status not found in treasury event payload")] TransactionStatusNotFound, + + #[error("Error processing Solana operation")] + Solana(#[source] Error), + #[error("Error sending message")] + SendError(#[from] SendError), + #[error("Invalid UUID")] + InvalidUuid(#[from] uuid::Error), + #[error("Database error")] + DbError(#[from] DbErr), +} + +#[derive(Debug, thiserror::Error)] +#[error("Error handling {} of {}", src.name(), evt.name())] +pub struct ProcessorError { + #[source] + kind: ProcessorErrorKind, + evt: EventKind, + src: ErrorSource, +} + +impl ProcessorError { + fn new(kind: ProcessorErrorKind, evt: EventKind, src: ErrorSource) -> Self { + Self { kind, evt, src } + } +} + +type ProcessResult = std::result::Result; +type Result = std::result::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum ErrorSource { + NftFailure, + NftSignRequest, + TreasuryStatus, + TreasurySuccess, + TreasuryFailure, +} + +impl ErrorSource { + fn name(self) -> &'static str { + match self { + Self::NftFailure => "NFT failure response", + Self::NftSignRequest => "NFT transaction signature request", + Self::TreasuryStatus => "treasury status check", + Self::TreasurySuccess => "treasury success response", + Self::TreasuryFailure => "treasury success failure", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum EventKind { + CreateDrop, + MintDrop, + UpdateDrop, + TransferAsset, + RetryCreateDrop, + RetryMintDrop, + CreateCollection, + RetryCreateCollection, + UpdateCollection, + MintToCollection, + RetryMintToCollection, +} + +impl EventKind { + fn name(self) -> &'static str { + match self { + Self::CreateDrop => "drop creation", + Self::MintDrop => "drop mint", + Self::UpdateDrop => "drop update", + Self::TransferAsset => "drop asset transfer", + Self::RetryCreateDrop => "drop creation retry", + Self::RetryMintDrop => "drop mint retry", + Self::CreateCollection => "collection creation", + Self::RetryCreateCollection => "collection creation retry", + Self::UpdateCollection => "collection update", + Self::MintToCollection => "mint to collection", + Self::RetryMintToCollection => "mint to collection retry", + } + } + + fn into_sign_request(self, tx: SolanaPendingTransaction) -> SolanaNftEvent { + match self { + EventKind::CreateDrop => SolanaNftEvent::CreateDropSigningRequested(tx), + EventKind::MintDrop => SolanaNftEvent::MintDropSigningRequested(tx), + EventKind::UpdateDrop => SolanaNftEvent::UpdateDropSigningRequested(tx), + EventKind::TransferAsset => SolanaNftEvent::TransferAssetSigningRequested(tx), + EventKind::RetryCreateDrop => SolanaNftEvent::RetryCreateDropSigningRequested(tx), + EventKind::RetryMintDrop => SolanaNftEvent::RetryMintDropSigningRequested(tx), + EventKind::CreateCollection => SolanaNftEvent::CreateCollectionSigningRequested(tx), + EventKind::UpdateCollection => SolanaNftEvent::UpdateCollectionSigningRequested(tx), + EventKind::RetryCreateCollection => { + SolanaNftEvent::RetryCreateCollectionSigningRequested(tx) + }, + EventKind::MintToCollection => SolanaNftEvent::MintToCollectionSigningRequested(tx), + EventKind::RetryMintToCollection => { + SolanaNftEvent::RetryMintToCollectionSigningRequested(tx) + }, + } + } + + async fn into_success( + self, + db: &db::Connection, + key: &SolanaNftEventKey, + signature: String, + ) -> ProcessResult { + let id = || Uuid::parse_str(&key.id); + + Ok(match self { + Self::CreateDrop => { + let id = id()?; + let collection = Collection::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::CreateDropSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection.mint, + }) + }, + Self::CreateCollection => { + let id = id()?; + + let collection = Collection::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::CreateCollectionSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection.mint, + }) + }, + Self::RetryCreateCollection => { + let id = id()?; + + let collection = Collection::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::RetryCreateCollectionSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection.mint, + }) + }, + Self::UpdateCollection => { + SolanaNftEvent::UpdateCollectionSubmitted(SolanaCompletedUpdateTransaction { + signature, + }) + }, + Self::MintToCollection => { + let id = id()?; + let collection_mint = CollectionMint::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::MintToCollectionSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection_mint.mint.to_string(), + }) + }, + Self::MintDrop => { + let id = id()?; + let collection_mint = CollectionMint::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::MintDropSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection_mint.mint, + }) + }, + Self::UpdateDrop => { + SolanaNftEvent::UpdateDropSubmitted(SolanaCompletedUpdateTransaction { signature }) + }, + Self::TransferAsset => { + SolanaNftEvent::TransferAssetSubmitted(SolanaCompletedTransferTransaction { + signature, + }) + }, + Self::RetryCreateDrop => { + let id = id()?; + let collection = Collection::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::RetryCreateDropSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection.mint, + }) + }, + Self::RetryMintDrop => { + let id = id()?; + let collection_mint = CollectionMint::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::RetryMintDropSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection_mint.mint, + }) + }, + Self::RetryMintToCollection => { + let id = id()?; + let collection_mint = CollectionMint::find_by_id(db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; + + SolanaNftEvent::RetryMintToCollectionSubmitted(SolanaCompletedMintTransaction { + signature, + address: collection_mint.mint, + }) + }, + }) + } + + fn into_failure(self, tx: SolanaFailedTransaction) -> SolanaNftEvent { + match self { + Self::CreateDrop => SolanaNftEvent::CreateDropFailed(tx), + Self::MintDrop => SolanaNftEvent::MintDropFailed(tx), + Self::UpdateDrop => SolanaNftEvent::UpdateDropFailed(tx), + Self::TransferAsset => SolanaNftEvent::TransferAssetFailed(tx), + Self::RetryCreateDrop => SolanaNftEvent::RetryCreateDropFailed(tx), + Self::RetryMintDrop => SolanaNftEvent::RetryMintDropFailed(tx), + Self::CreateCollection => SolanaNftEvent::CreateCollectionFailed(tx), + Self::RetryCreateCollection => SolanaNftEvent::RetryCreateCollectionFailed(tx), + Self::UpdateCollection => SolanaNftEvent::UpdateCollectionFailed(tx), + Self::MintToCollection => SolanaNftEvent::MintToCollectionFailed(tx), + Self::RetryMintToCollection => SolanaNftEvent::RetryMintToCollectionFailed(tx), + } + } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct Processor { - solana: Solana, - db: Connection, + solana: DebugShim, + db: db::Connection, producer: Producer, } impl Processor { + #[inline] #[must_use] - pub fn new(solana: Solana, db: Connection, producer: Producer) -> Self { + pub fn new(solana: Solana, db: db::Connection, producer: Producer) -> Self { Self { - solana, + solana: DebugShim(solana), db, producer, } } - /// Process the given message for various services. - /// - /// # Errors - /// This function can return an error if it fails to process any event + #[inline] + fn solana(&self) -> &Solana { + &self.solana.0 + } + pub async fn process(&self, msg: Services) -> Result<()> { - // match topics match msg { - Services::Nfts(key, e) => { + Services::Nfts(key, msg) => { let key = SolanaNftEventKey::from(key); - match e.event { - Some(SolanaCreateDrop(payload)) => { - let create_drop_result = self.create_drop(key.clone(), payload).await; - - if create_drop_result.is_err() { - self.create_drop_failed(key, SolanaTransactionFailureReason::Assemble) - .await?; - } - - Ok(()) + match msg.event { + Some(NftEvent::SolanaCreateDrop(payload)) => { + self.process_nft( + EventKind::CreateDrop, + &key, + self.create_collection(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(SolanaMintDrop(payload)) => { - let mint_drop_result = self.mint_drop(key.clone(), payload).await; - - if mint_drop_result.is_err() { - self.mint_drop_failed(key, SolanaTransactionFailureReason::Assemble) - .await?; - } - - Ok(()) + Some(NftEvent::SolanaCreateCollection(payload)) => { + self.process_nft( + EventKind::CreateCollection, + &key, + self.create_collection(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(SolanaUpdateDrop(payload)) => { - let update_drop_result = self.update_drop(key.clone(), payload).await; - - if update_drop_result.is_err() { - self.update_drop_failed(key, SolanaTransactionFailureReason::Assemble) - .await?; - } - - Ok(()) + Some(NftEvent::SolanaMintDrop(payload)) => { + self.process_nft( + EventKind::MintDrop, + &key, + self.mint_drop(&EditionRef(self.solana()), &key, payload), + ) + .await + }, + Some(NftEvent::SolanaMintToCollection(payload)) => { + self.process_nft( + EventKind::MintToCollection, + &key, + self.mint_to_collection(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(SolanaTransferAsset(payload)) => { - let transfer_asset_result = self.transfer_asset(key.clone(), payload).await; - - if transfer_asset_result.is_err() { - self.transfer_asset_failed( - key, - SolanaTransactionFailureReason::Assemble, - ) - .await?; - } - - Ok(()) + Some(NftEvent::SolanaUpdateDrop(payload)) => { + self.process_nft( + EventKind::UpdateDrop, + &key, + self.update_collection(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(SolanaRetryDrop(payload)) => { - let retry_drop_result = self.retry_drop(key.clone(), payload).await; - - if retry_drop_result.is_err() { - self.retry_create_drop_failed( - key, - SolanaTransactionFailureReason::Assemble, - ) - .await?; - } - - Ok(()) + Some(NftEvent::SolanaUpdateCollection(payload)) => { + self.process_nft( + EventKind::UpdateCollection, + &key, + self.update_collection(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(SolanaRetryMintDrop(payload)) => { - let retry_mint_drop_result = - self.retry_mint_drop(key.clone(), payload).await; - - if retry_mint_drop_result.is_err() { - self.retry_mint_drop_failed( - key, - SolanaTransactionFailureReason::Assemble, - ) - .await?; - } - - Ok(()) + Some(NftEvent::SolanaTransferAsset(payload)) => { + self.process_nft( + EventKind::TransferAsset, + &key, + self.transfer_asset(&UncompressedRef(self.solana()), &key, payload), + ) + .await }, - Some(_) | None => Ok(()), + Some(NftEvent::SolanaRetryDrop(payload)) => { + self.process_nft( + EventKind::RetryCreateDrop, + &key, + self.retry_create_collection( + &UncompressedRef(self.solana()), + &key, + payload, + ), + ) + .await + }, + Some(NftEvent::SolanaRetryCreateCollection(payload)) => { + self.process_nft( + EventKind::RetryCreateCollection, + &key, + self.retry_create_collection( + &UncompressedRef(self.solana()), + &key, + payload, + ), + ) + .await + }, + Some(NftEvent::SolanaRetryMintDrop(payload)) => { + self.process_nft( + EventKind::RetryMintDrop, + &key, + self.retry_mint_drop(&EditionRef(self.solana()), &key, payload), + ) + .await + }, + Some(NftEvent::SolanaRetryMintToCollection(payload)) => { + self.process_nft( + EventKind::RetryMintToCollection, + &key, + self.retry_mint_to_collection( + &UncompressedRef(self.solana()), + &key, + payload, + ), + ) + .await + }, + _ => Ok(()), } }, - Services::Treasury(key, e) => { + Services::Treasury(key, msg) => { let key = SolanaNftEventKey::from(key); - match e.event { - Some(TreasuryEvent::SolanaCreateDropSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.create_drop_failed(key, SolanaTransactionFailureReason::Sign) - .await?; - - return Ok(()); - } - - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.create_drop_submitted(key, signature).await?; - }, - Err(_) => { - self.create_drop_failed( - key, - SolanaTransactionFailureReason::Submit, - ) - .await?; - }, - } - - Ok(()) + match msg.event { + Some(TreasuryEvent::SolanaCreateDropSigned(res)) => { + self.process_treasury(EventKind::CreateDrop, key, res).await + }, + Some(TreasuryEvent::SolanaMintDropSigned(res)) => { + self.process_treasury(EventKind::MintDrop, key, res).await }, - Some(TreasuryEvent::SolanaUpdateDropSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.update_drop_failed(key, SolanaTransactionFailureReason::Sign) - .await?; - - return Ok(()); - } - - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.update_drop_submitted(key, signature).await?; - }, - Err(_) => { - self.update_drop_failed( - key, - SolanaTransactionFailureReason::Submit, - ) - .await?; - }, - } - - Ok(()) + Some(TreasuryEvent::SolanaUpdateDropSigned(res)) => { + self.process_treasury(EventKind::UpdateDrop, key, res).await }, - Some(TreasuryEvent::SolanaMintDropSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.mint_drop_failed(key, SolanaTransactionFailureReason::Sign) - .await?; - - return Ok(()); - } - - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.mint_drop_submitted(key, signature).await?; - }, - Err(_) => { - self.mint_drop_failed(key, SolanaTransactionFailureReason::Submit) - .await?; - }, - } - - Ok(()) + Some(TreasuryEvent::SolanaTransferAssetSigned(res)) => { + self.process_treasury(EventKind::TransferAsset, key, res) + .await }, - Some(TreasuryEvent::SolanaTransferAssetSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.transfer_asset_failed(key, SolanaTransactionFailureReason::Sign) - .await?; - - return Ok(()); - } - - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.transfer_asset_submitted(key, signature).await?; - }, - Err(_) => { - self.transfer_asset_failed( - key, - SolanaTransactionFailureReason::Submit, - ) - .await?; - }, - } - - Ok(()) + Some(TreasuryEvent::SolanaRetryCreateDropSigned(res)) => { + self.process_treasury(EventKind::RetryCreateDrop, key, res) + .await }, - Some(TreasuryEvent::SolanaRetryCreateDropSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.retry_create_drop_failed( - key, - SolanaTransactionFailureReason::Sign, - ) - .await?; - - return Ok(()); - } - - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.retry_create_drop_submitted(key, signature).await?; - }, - Err(_) => { - self.retry_create_drop_failed( - key, - SolanaTransactionFailureReason::Submit, - ) - .await?; - }, - } - - Ok(()) + Some(TreasuryEvent::SolanaRetryMintDropSigned(res)) => { + self.process_treasury(EventKind::RetryMintDrop, key, res) + .await }, - Some(TreasuryEvent::SolanaRetryMintDropSigned(payload)) => { - let status = TransactionStatus::from_i32(payload.status) - .ok_or(ProcessorError::TransactionStatusNotFound)?; - - if status == TransactionStatus::Failed { - self.retry_mint_drop_failed(key, SolanaTransactionFailureReason::Sign) - .await?; - - return Ok(()); - } - let signature_result = self.solana.submit_transaction(&payload); - - match signature_result { - Ok(signature) => { - self.retry_mint_drop_submitted(key, signature).await?; - }, - Err(_) => { - self.retry_mint_drop_failed( - key, - SolanaTransactionFailureReason::Submit, - ) - .await?; - }, - } - - Ok(()) + Some(TreasuryEvent::SolanaMintToCollectionSigned(res)) => { + self.process_treasury(EventKind::MintToCollection, key, res) + .await + }, + Some(TreasuryEvent::SolanaRetryMintToCollectionSigned(res)) => { + self.process_treasury(EventKind::RetryMintToCollection, key, res) + .await + }, + Some(TreasuryEvent::SolanaCreateCollectionSigned(res)) => { + self.process_treasury(EventKind::CreateCollection, key, res) + .await + }, + Some(TreasuryEvent::SolanaUpdateCollectionSigned(res)) => { + self.process_treasury(EventKind::UpdateCollection, key, res) + .await + }, + Some(TreasuryEvent::SolanaRetryCreateCollectionSigned(res)) => { + self.process_treasury(EventKind::RetryCreateCollection, key, res) + .await }, _ => Ok(()), } @@ -316,73 +456,118 @@ impl Processor { } } - async fn create_drop( + async fn process_nft( &self, - key: SolanaNftEventKey, - payload: MetaplexMasterEditionTransaction, + kind: EventKind, + key: &SolanaNftEventKey, + fut: impl Future>, ) -> Result<()> { - let tx = self.solana.create(payload.clone())?; + match fut.await { + Ok(tx) => self + .producer + .send( + Some(&SolanaNftEvents { + event: Some(kind.into_sign_request(tx)), + }), + Some(key), + ) + .await + .map_err(|e| ProcessorError::new(e.into(), kind, ErrorSource::NftSignRequest)), + Err(e) => { + warn!( + "{:?}", + Error::new(e).context(format!("Error processing {}", kind.name())) + ); + self.event_failed(kind, key, SolanaTransactionFailureReason::Assemble) + .await + .map_err(|k| ProcessorError::new(k, kind, ErrorSource::NftFailure)) + }, + } + } - let MasterEditionAddresses { - metadata, - associated_token_account, - mint, - master_edition, - update_authority, - owner, - } = tx.addresses; - let id = key.id.parse()?; + async fn process_treasury( + &self, + kind: EventKind, + key: SolanaNftEventKey, + res: SolanaTransactionResult, + ) -> Result<()> { + let status = TransactionStatus::from_i32(res.status).ok_or_else(|| { + ProcessorError::new( + ProcessorErrorKind::TransactionStatusNotFound, + kind, + ErrorSource::TreasuryStatus, + ) + })?; - let collection = collections::Model { - id, - master_edition: master_edition.to_string(), - owner: owner.to_string(), - metadata: metadata.to_string(), - associated_token_account: associated_token_account.to_string(), - mint: mint.to_string(), - update_authority: update_authority.to_string(), - ..Default::default() - }; + if status == TransactionStatus::Failed { + return self + .event_failed(kind, &key, SolanaTransactionFailureReason::Sign) + .await + .map_err(|k| ProcessorError::new(k, kind, ErrorSource::TreasuryStatus)); + } - Collection::create(&self.db, collection).await?; + match self.solana().submit_transaction(&res) { + Ok(sig) => self + .event_submitted(kind, &key, sig) + .await + .map_err(|k| ProcessorError::new(k, kind, ErrorSource::TreasurySuccess)), + Err(e) => { + warn!( + "{:?}", + e.context(format!("Error submitting {}", kind.name())) + ); + self.event_failed(kind, &key, SolanaTransactionFailureReason::Submit) + .await + .map_err(|k| ProcessorError::new(k, kind, ErrorSource::TreasuryFailure)) + }, + } + } + async fn event_submitted( + &self, + kind: EventKind, + key: &SolanaNftEventKey, + sig: String, + ) -> ProcessResult<()> { self.producer .send( Some(&SolanaNftEvents { - event: Some(CreateDropSigningRequested(tx.into())), + event: Some(kind.into_success(&self.db, key, sig).await?), }), - Some(&key), + Some(key), ) - .await?; - - Ok(()) + .await + .map_err(Into::into) } - async fn create_drop_failed( + async fn event_failed( &self, - key: SolanaNftEventKey, + kind: EventKind, + key: &SolanaNftEventKey, reason: SolanaTransactionFailureReason, - ) -> Result<()> { + ) -> ProcessResult<()> { self.producer .send( Some(&SolanaNftEvents { - event: Some(CreateDropFailed(SolanaFailedTransaction { + event: Some(kind.into_failure(SolanaFailedTransaction { reason: reason as i32, })), }), - Some(&key), + Some(key), ) - .await?; - - Ok(()) + .await + .map_err(Into::into) } - async fn retry_drop( + async fn create_collection( &self, - key: SolanaNftEventKey, + backend: &B, + key: &SolanaNftEventKey, payload: MetaplexMasterEditionTransaction, - ) -> Result<()> { - let tx = self.solana.create(payload.clone())?; + ) -> ProcessResult { + let tx = backend + .create(payload.clone()) + .map_err(ProcessorErrorKind::Solana)?; let MasterEditionAddresses { metadata, @@ -392,67 +577,41 @@ impl Processor { update_authority, owner, } = tx.addresses; + let id = key.id.parse()?; - let collection_id = Uuid::parse_str(&key.id.clone())?; - let collection = Collection::find_by_id(&self.db, collection_id) - .await? - .ok_or(ProcessorError::RecordNotFound)?; - - let mut collection: collections::ActiveModel = collection.into(); - - collection.master_edition = Set(metadata.to_string()); - collection.associated_token_account = Set(associated_token_account.to_string()); - collection.mint = Set(mint.to_string()); - collection.master_edition = Set(master_edition.to_string()); - collection.update_authority = Set(update_authority.to_string()); - collection.owner = Set(owner.to_string()); - - Collection::update(&self.db, collection).await?; - - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryCreateDropSigningRequested(tx.into())), - }), - Some(&key), - ) - .await?; - - Ok(()) - } + let collection = collections::Model { + id, + master_edition: master_edition.to_string(), + owner: owner.to_string(), + metadata: metadata.to_string(), + associated_token_account: associated_token_account.to_string(), + mint: mint.to_string(), + update_authority: update_authority.to_string(), + ..Default::default() + }; - async fn retry_create_drop_failed( - &self, - key: SolanaNftEventKey, - reason: SolanaTransactionFailureReason, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryCreateDropFailed(SolanaFailedTransaction { - reason: reason as i32, - })), - }), - Some(&key), - ) - .await?; + Collection::create(&self.db, collection).await?; - Ok(()) + Ok(tx.into()) } - async fn mint_drop( + async fn mint_to_collection< + B: MintBackend, + >( &self, - key: SolanaNftEventKey, - payload: MintMetaplexEditionTransaction, - ) -> Result<()> { + backend: &B, + key: &SolanaNftEventKey, + payload: MintMetaplexMetadataTransaction, + ) -> ProcessResult { let id = Uuid::parse_str(&key.id.clone())?; let collection_id = Uuid::parse_str(&payload.collection_id)?; let collection = Collection::find_by_id(&self.db, collection_id) .await? - .ok_or(ProcessorError::RecordNotFound)?; + .ok_or(ProcessorErrorKind::RecordNotFound)?; - // TODO: the collection mint record may fail to be created if this fails. Need to handle upserting the record in retry mint. - let tx = self.solana.mint(&collection, payload)?; + let tx = backend + .mint(&collection, payload) + .map_err(ProcessorErrorKind::Solana)?; let collection_mint = collection_mints::Model { id, @@ -465,317 +624,189 @@ impl Processor { CollectionMint::create(&self.db, collection_mint).await?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(MintDropSigningRequested(tx.into())), - }), - Some(&key), - ) - .await?; - - Ok(()) - } - - async fn mint_drop_failed( - &self, - key: SolanaNftEventKey, - reason: SolanaTransactionFailureReason, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(MintDropFailed(SolanaFailedTransaction { - reason: reason as i32, - })), - }), - Some(&key), - ) - .await?; - - Ok(()) + Ok(tx.into()) } - async fn retry_mint_drop( + async fn mint_drop>( &self, - key: SolanaNftEventKey, + backend: &B, + key: &SolanaNftEventKey, payload: MintMetaplexEditionTransaction, - ) -> Result<()> { + ) -> ProcessResult { let id = Uuid::parse_str(&key.id.clone())?; + let collection_id = Uuid::parse_str(&payload.collection_id)?; + let collection = Collection::find_by_id(&self.db, collection_id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; - let (collection_mint, collection) = - CollectionMint::find_by_id_with_collection(&self.db, id) - .await? - .ok_or(ProcessorError::RecordNotFound)?; - - let collection = collection.ok_or(ProcessorError::RecordNotFound)?; - - let tx = self.solana.mint(&collection, payload)?; - - let mut collection_mint: collection_mints::ActiveModel = collection_mint.into(); - - collection_mint.mint = Set(tx.addresses.mint.to_string()); - collection_mint.owner = Set(tx.addresses.recipient.to_string()); - collection_mint.associated_token_account = - Set(Some(tx.addresses.associated_token_account.to_string())); - - CollectionMint::update(&self.db, collection_mint).await?; + let tx = backend + .mint(&collection, payload) + .map_err(ProcessorErrorKind::Solana)?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryMintDropSigningRequested(tx.into())), - }), - Some(&key), - ) - .await?; - - Ok(()) - } + let collection_mint = collection_mints::Model { + id, + collection_id: collection.id, + mint: tx.addresses.mint.to_string(), + owner: tx.addresses.recipient.to_string(), + associated_token_account: Some(tx.addresses.associated_token_account.to_string()), + created_at: Utc::now().naive_utc(), + }; - async fn retry_mint_drop_failed( - &self, - key: SolanaNftEventKey, - reason: SolanaTransactionFailureReason, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryMintDropFailed(SolanaFailedTransaction { - reason: reason as i32, - })), - }), - Some(&key), - ) - .await?; + CollectionMint::create(&self.db, collection_mint).await?; - Ok(()) + Ok(tx.into()) } - async fn update_drop( + async fn update_collection( &self, - key: SolanaNftEventKey, + backend: &B, + key: &SolanaNftEventKey, payload: MetaplexMasterEditionTransaction, - ) -> Result<()> { + ) -> ProcessResult { let collection_id = Uuid::parse_str(&key.id.clone())?; let collection = Collection::find_by_id(&self.db, collection_id) .await? - .ok_or(ProcessorError::RecordNotFound)?; - - let tx = self.solana.update(&collection, payload)?; - - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(UpdateDropSigningRequested(tx.into())), - }), - Some(&key), - ) - .await?; + .ok_or(ProcessorErrorKind::RecordNotFound)?; - Ok(()) - } + let tx = backend + .update(&collection, payload) + .map_err(ProcessorErrorKind::Solana)?; - async fn update_drop_failed( - &self, - key: SolanaNftEventKey, - reason: SolanaTransactionFailureReason, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(UpdateDropFailed(SolanaFailedTransaction { - reason: reason as i32, - })), - }), - Some(&key), - ) - .await?; - - Ok(()) + Ok(tx.into()) } - async fn transfer_asset( + async fn transfer_asset( &self, - key: SolanaNftEventKey, + backend: &B, + _key: &SolanaNftEventKey, payload: TransferMetaplexAssetTransaction, - ) -> Result<()> { + ) -> ProcessResult { let collection_mint_id = Uuid::parse_str(&payload.collection_mint_id.clone())?; let collection_mint = CollectionMint::find_by_id(&self.db, collection_mint_id) .await? - .ok_or(ProcessorError::RecordNotFound)?; + .ok_or(ProcessorErrorKind::RecordNotFound)?; - let tx = self.solana.transfer(&collection_mint, payload)?; + let tx = backend + .transfer(&collection_mint, payload) + .await + .map_err(ProcessorErrorKind::Solana)?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(TransferAssetSigningRequested(tx.into())), - }), - Some(&key), - ) - .await?; - - Ok(()) + Ok(tx.into()) } - async fn transfer_asset_failed( + async fn retry_create_collection( &self, - key: SolanaNftEventKey, - reason: SolanaTransactionFailureReason, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(TransferAssetFailed(SolanaFailedTransaction { - reason: reason as i32, - })), - }), - Some(&key), - ) - .await?; + backend: &B, + key: &SolanaNftEventKey, + payload: MetaplexMasterEditionTransaction, + ) -> ProcessResult { + let tx = backend + .create(payload.clone()) + .map_err(ProcessorErrorKind::Solana)?; - Ok(()) - } + let MasterEditionAddresses { + metadata, + associated_token_account, + mint, + master_edition, + update_authority, + owner, + } = tx.addresses; - async fn create_drop_submitted(&self, key: SolanaNftEventKey, signature: String) -> Result<()> { - let id = Uuid::parse_str(&key.id.clone())?; - let collection = Collection::find_by_id(&self.db, id) + let collection_id = Uuid::parse_str(&key.id.clone())?; + let collection = Collection::find_by_id(&self.db, collection_id) .await? - .ok_or(ProcessorError::RecordNotFound)?; + .ok_or(ProcessorErrorKind::RecordNotFound)?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(CreateDropSubmitted(SolanaCompletedMintTransaction { - signature, - address: collection.mint, - })), - }), - Some(&key), - ) - .await?; + let mut collection: collections::ActiveModel = collection.into(); - Ok(()) - } + collection.metadata = Set(metadata.to_string()); + collection.associated_token_account = Set(associated_token_account.to_string()); + collection.mint = Set(mint.to_string()); + collection.master_edition = Set(master_edition.to_string()); + collection.update_authority = Set(update_authority.to_string()); + collection.owner = Set(owner.to_string()); - async fn update_drop_submitted(&self, key: SolanaNftEventKey, signature: String) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(UpdateDropSubmitted(SolanaCompletedUpdateTransaction { - signature, - })), - }), - Some(&key), - ) - .await?; + Collection::update(&self.db, collection).await?; - Ok(()) + Ok(tx.into()) } - async fn mint_drop_submitted(&self, key: SolanaNftEventKey, signature: String) -> Result<()> { + async fn retry_mint_drop< + B: MintBackend, + >( + &self, + backend: &B, + key: &SolanaNftEventKey, + payload: MintMetaplexEditionTransaction, + ) -> ProcessResult { let id = Uuid::parse_str(&key.id.clone())?; - let collection_mint = CollectionMint::find_by_id(&self.db, id) - .await? - .ok_or(ProcessorError::RecordNotFound)?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(MintDropSubmitted(SolanaCompletedMintTransaction { - signature, - address: collection_mint.mint, - })), - }), - Some(&key), - ) - .await?; + let (collection_mint, collection) = + CollectionMint::find_by_id_with_collection(&self.db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; - Ok(()) - } + let collection = collection.ok_or(ProcessorErrorKind::RecordNotFound)?; - async fn transfer_asset_submitted( - &self, - key: SolanaNftEventKey, - signature: String, - ) -> Result<()> { - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(TransferAssetSubmitted(SolanaCompletedTransferTransaction { - signature, - })), - }), - Some(&key), - ) - .await?; + let tx = backend + .mint(&collection, payload) + .map_err(ProcessorErrorKind::Solana)?; - Ok(()) - } + let MintEditionAddresses { + mint, + recipient, + associated_token_account, + .. + } = tx.addresses; - async fn retry_create_drop_submitted( - &self, - key: SolanaNftEventKey, - signature: String, - ) -> Result<()> { - let id = Uuid::parse_str(&key.id.clone())?; - let collection = Collection::find_by_id(&self.db, id) - .await? - .ok_or(ProcessorError::RecordNotFound)?; + let mut collection_mint: collection_mints::ActiveModel = collection_mint.into(); - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryCreateDropSubmitted(SolanaCompletedMintTransaction { - signature, - address: collection.mint, - })), - }), - Some(&key), - ) - .await?; + collection_mint.mint = Set(mint.to_string()); + collection_mint.owner = Set(recipient.to_string()); + collection_mint.associated_token_account = Set(Some(associated_token_account.to_string())); + + CollectionMint::update(&self.db, collection_mint).await?; - Ok(()) + Ok(tx.into()) } - async fn retry_mint_drop_submitted( + async fn retry_mint_to_collection< + B: MintBackend, + >( &self, - key: SolanaNftEventKey, - signature: String, - ) -> Result<()> { + backend: &B, + key: &SolanaNftEventKey, + payload: MintMetaplexMetadataTransaction, + ) -> ProcessResult { let id = Uuid::parse_str(&key.id.clone())?; - let collection_mint = CollectionMint::find_by_id(&self.db, id) - .await? - .ok_or(ProcessorError::RecordNotFound)?; - self.producer - .send( - Some(&SolanaNftEvents { - event: Some(RetryMintDropSubmitted(SolanaCompletedMintTransaction { - signature, - address: collection_mint.mint, - })), - }), - Some(&key), - ) - .await?; + let (collection_mint, collection) = + CollectionMint::find_by_id_with_collection(&self.db, id) + .await? + .ok_or(ProcessorErrorKind::RecordNotFound)?; - Ok(()) - } -} + let collection = collection.ok_or(ProcessorErrorKind::RecordNotFound)?; + + let tx = backend + .mint(&collection, payload) + .map_err(ProcessorErrorKind::Solana)?; -impl From> for SolanaPendingTransaction { - fn from( - TransactionResponse { - serialized_message, - signatures_or_signers_public_keys, + let MintMetaplexAddresses { + mint, + recipient, + associated_token_account, .. - }: TransactionResponse, - ) -> Self { - Self { - serialized_message, - signatures_or_signers_public_keys, - } + } = tx.addresses; + + let mut collection_mint: collection_mints::ActiveModel = collection_mint.into(); + + collection_mint.mint = Set(mint.to_string()); + collection_mint.owner = Set(recipient.to_string()); + collection_mint.associated_token_account = Set(Some(associated_token_account.to_string())); + + CollectionMint::update(&self.db, collection_mint).await?; + + Ok(tx.into()) } } diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index 6a3983a..7dc73f0 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -2,6 +2,8 @@ #![warn(clippy::pedantic, clippy::cargo)] #![allow(clippy::module_name_repetitions)] +pub(crate) mod asset_api; +mod backend; pub mod events; pub mod solana; diff --git a/consumer/src/main.rs b/consumer/src/main.rs index b4cee6c..c84aed9 100644 --- a/consumer/src/main.rs +++ b/consumer/src/main.rs @@ -1,9 +1,6 @@ -use std::sync::Arc; - use holaplex_hub_nfts_solana::{events::Processor, solana::Solana, Args}; use holaplex_hub_nfts_solana_core::{db::Connection, proto::SolanaNftEvents, Services}; use hub_core::{prelude::*, tokio}; -use solana_client::rpc_client::RpcClient; pub fn main() { let opts = hub_core::StartConfig { @@ -20,8 +17,7 @@ pub fn main() { let producer = common.producer_cfg.build::().await?; - let solana_rpc = Arc::new(RpcClient::new(solana.solana_endpoint)); - let solana = Solana::new(solana_rpc, solana.solana_treasury_wallet_address); + let solana = Solana::new(solana)?; let cons = common.consumer_cfg.build::().await?; let event_processor = Processor::new(solana, connection, producer); diff --git a/consumer/src/solana.rs b/consumer/src/solana.rs index 26e7cd2..a1d93b0 100644 --- a/consumer/src/solana.rs +++ b/consumer/src/solana.rs @@ -1,17 +1,23 @@ -use std::vec; - +use anchor_lang::{prelude::AccountMeta, InstructionData}; use holaplex_hub_nfts_solana_core::proto::{ treasury_events::SolanaTransactionResult, MasterEdition, MetaplexMasterEditionTransaction, - MintMetaplexEditionTransaction, TransferMetaplexAssetTransaction, + MetaplexMetadata, MintMetaplexEditionTransaction, MintMetaplexMetadataTransaction, + TransferMetaplexAssetTransaction, }; use holaplex_hub_nfts_solana_entity::{collection_mints, collections}; use hub_core::{anyhow::Result, clap, prelude::*, thiserror::Error, uuid::Uuid}; +use mpl_bubblegum::state::metaplex_adapter::{ + Collection, Creator as BubblegumCreator, TokenProgramVersion, +}; use mpl_token_metadata::{ instruction::{mint_new_edition_from_master_edition_via_token, update_metadata_accounts_v2}, state::{Creator, DataV2, EDITION, PREFIX}, }; use solana_client::rpc_client::RpcClient; -use solana_program::{program_pack::Pack, pubkey::Pubkey, system_instruction::create_account}; +use solana_program::{ + instruction::Instruction, program_pack::Pack, pubkey::Pubkey, + system_instruction::create_account, system_program, +}; use solana_sdk::{ signature::Signature, signer::{keypair::Keypair, Signer}, @@ -25,15 +31,33 @@ use spl_token::{ state, }; +use crate::{ + asset_api::RpcClient as _, + backend::{ + CollectionBackend, MasterEditionAddresses, MintBackend, MintCompressedMintV1Addresses, + MintEditionAddresses, MintMetaplexAddresses, TransactionResponse, TransferAssetAddresses, + TransferBackend, UpdateMasterEditionAddresses, + }, +}; + const TOKEN_PROGRAM_PUBKEY: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; -#[derive(Debug, clap::Args, Clone)] +#[derive(Debug, clap::Args)] pub struct SolanaArgs { #[arg(long, env)] pub solana_endpoint: String, #[arg(long, env)] - pub solana_treasury_wallet_address: String, + pub solana_treasury_wallet_address: Pubkey, + + #[arg(long, env)] + pub digital_asset_api_endpoint: String, + + // TODO: remove these + #[arg(long, env)] + pub tree_authority: Pubkey, + #[arg(long, env)] + pub merkle_tree: Pubkey, } #[derive(Clone)] @@ -62,73 +86,53 @@ pub struct TransferAssetRequest { pub mint_address: String, } -#[derive(Clone)] -pub struct MasterEditionAddresses { - pub metadata: Pubkey, - pub associated_token_account: Pubkey, - pub owner: Pubkey, - pub master_edition: Pubkey, - pub mint: Pubkey, - pub update_authority: Pubkey, -} - -#[derive(Clone)] -pub struct MintEditionAddresses { - pub edition: Pubkey, - pub mint: Pubkey, - pub metadata: Pubkey, - pub owner: Pubkey, - pub associated_token_account: Pubkey, - pub recipient: Pubkey, -} - -#[derive(Clone)] -pub struct UpdateMasterEditionAddresses { - pub metadata: Pubkey, - pub update_authority: Pubkey, -} - -#[derive(Clone)] -pub struct TransferAssetAddresses { - pub owner: Pubkey, - pub recipient: Pubkey, - pub recipient_associated_token_account: Pubkey, - pub owner_associated_token_account: Pubkey, -} - -/// Represents a response from a transaction on the blockchain. This struct -/// provides the serialized message and the signatures of the signed message. -pub struct TransactionResponse { - /// The serialized version of the message from the transaction. - pub serialized_message: Vec, - - /// The signatures of the signed message or the public keys of wallets that should sign the transaction. Order matters. - pub signatures_or_signers_public_keys: Vec, - - /// Addresses that are related to the transaction. - pub addresses: A, -} - #[derive(Debug, Error)] -enum SolanaError { +enum SolanaErrorNotFoundMessage { #[error("master edition message not found")] - MasterEditionMessageNotFound, + MasterEdition, #[error("serialized message message not found")] - SerializedMessageNotFound, + Serialized, + #[error("metadata message not found")] + Metadata, } #[derive(Clone)] pub struct Solana { rpc_client: Arc, - treasury_wallet_address: String, + treasury_wallet_address: Pubkey, + bubblegum_tree_authority: Pubkey, + bubblegum_merkle_tree: Pubkey, + bubblegum_cpi_address: Pubkey, + asset_rpc_client: jsonrpsee::http_client::HttpClient, } impl Solana { - pub fn new(rpc_client: Arc, treasury_wallet_address: String) -> Self { - Self { + pub fn new(args: SolanaArgs) -> Result { + let SolanaArgs { + solana_endpoint, + solana_treasury_wallet_address, + digital_asset_api_endpoint, + tree_authority, + merkle_tree, + } = args; + let rpc_client = Arc::new(RpcClient::new(solana_endpoint)); + + let (bubblegum_cpi_address, _) = Pubkey::find_program_address( + &[mpl_bubblegum::state::COLLECTION_CPI_PREFIX.as_bytes()], + &mpl_bubblegum::ID, + ); + + Ok(Self { rpc_client, - treasury_wallet_address, - } + treasury_wallet_address: solana_treasury_wallet_address, + bubblegum_tree_authority: tree_authority, + bubblegum_merkle_tree: merkle_tree, + bubblegum_cpi_address, + asset_rpc_client: jsonrpsee::http_client::HttpClientBuilder::default() + .request_timeout(std::time::Duration::from_secs(5)) + .build(digital_asset_api_endpoint) + .context("Failed to initialize asset API client")?, + }) } #[must_use] @@ -154,7 +158,7 @@ impl Solana { &transaction .serialized_message .clone() - .ok_or(SolanaError::SerializedMessageNotFound)?, + .ok_or(SolanaErrorNotFoundMessage::Serialized)?, )?; let transaction = Transaction { @@ -166,41 +170,241 @@ impl Solana { Ok(signature.to_string()) } +} - /// Res - /// - /// # Errors - /// This function fails if unable to assemble or save solana drop - pub fn create( +#[repr(transparent)] +pub struct UncompressedRef<'a>(pub &'a Solana); +#[repr(transparent)] +pub struct CompressedRef<'a>(pub &'a Solana); +#[repr(transparent)] +pub struct EditionRef<'a>(pub &'a Solana); + +impl<'a> CollectionBackend for UncompressedRef<'a> { + fn create( &self, - payload: MetaplexMasterEditionTransaction, - ) -> Result> { - let MetaplexMasterEditionTransaction { master_edition, .. } = payload; - let master_edition = master_edition.ok_or(SolanaError::MasterEditionMessageNotFound)?; + txn: MetaplexMasterEditionTransaction, + ) -> hub_core::prelude::Result> { + let MetaplexMasterEditionTransaction { master_edition, .. } = txn; + let master_edition = master_edition.ok_or(SolanaErrorNotFoundMessage::MasterEdition)?; + let payer: Pubkey = self.0.treasury_wallet_address; + let rpc = &self.0.rpc_client; + let mint = Keypair::new(); + let MasterEdition { + name, + symbol, + seller_fee_basis_points, + metadata_uri, + creators, + supply, + owner_address, + } = master_edition; + let owner: Pubkey = owner_address.parse()?; + + let (metadata, _) = Pubkey::find_program_address( + &[ + b"metadata", + mpl_token_metadata::ID.as_ref(), + mint.pubkey().as_ref(), + ], + &mpl_token_metadata::ID, + ); + let associated_token_account = get_associated_token_address(&owner, &mint.pubkey()); + let (master_edition, _) = Pubkey::find_program_address( + &[ + b"metadata", + mpl_token_metadata::ID.as_ref(), + mint.pubkey().as_ref(), + b"edition", + ], + &mpl_token_metadata::ID, + ); + let len = spl_token::state::Mint::LEN; + let rent = rpc.get_minimum_balance_for_rent_exemption(len)?; + let blockhash = rpc.get_latest_blockhash()?; + + let create_account_ins = solana_program::system_instruction::create_account( + &payer, + &mint.pubkey(), + rent, + len.try_into()?, + &spl_token::ID, + ); + let initialize_mint_ins = spl_token::instruction::initialize_mint( + &spl_token::ID, + &mint.pubkey(), + &owner, + Some(&owner), + 0, + )?; + let ata_ins = spl_associated_token_account::instruction::create_associated_token_account( + &payer, + &owner, + &mint.pubkey(), + &spl_token::ID, + ); + let min_to_ins = spl_token::instruction::mint_to( + &spl_token::ID, + &mint.pubkey(), + &associated_token_account, + &owner, + &[], + 1, + )?; + let create_metadata_account_ins = + mpl_token_metadata::instruction::create_metadata_accounts_v3( + mpl_token_metadata::ID, + metadata, + mint.pubkey(), + owner, + payer, + owner, + name, + symbol, + metadata_uri, + Some( + creators + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + ), + seller_fee_basis_points.try_into()?, + true, + true, + None, + None, + None, + ); + let create_master_edition_ins = mpl_token_metadata::instruction::create_master_edition_v3( + mpl_token_metadata::ID, + master_edition, + mint.pubkey(), + owner, + owner, + metadata, + payer, + supply.map(TryInto::try_into).transpose()?, + ); + let instructions = vec![ + create_account_ins, + initialize_mint_ins, + ata_ins, + min_to_ins, + create_metadata_account_ins, + create_master_edition_ins, + ]; + + let message = solana_program::message::Message::new_with_blockhash( + &instructions, + Some(&payer), + &blockhash, + ); - let tx = self.master_edition_transaction(master_edition)?; + let serialized_message = message.serialize(); + let mint_signature = mint.try_sign_message(&message.serialize())?; - Ok(tx) + Ok(TransactionResponse { + serialized_message, + signatures_or_signers_public_keys: vec![ + payer.to_string(), + mint_signature.to_string(), + owner.to_string(), + ], + addresses: MasterEditionAddresses { + master_edition, + update_authority: owner, + associated_token_account, + mint: mint.pubkey(), + owner, + metadata, + }, + }) } - /// Res - /// - /// # Errors - /// This function fails if unable to assemble solana mint transaction - pub fn mint( + fn update( + &self, + collection: &collections::Model, + txn: MetaplexMasterEditionTransaction, + ) -> hub_core::prelude::Result> { + let rpc = &self.0.rpc_client; + + let MetaplexMasterEditionTransaction { master_edition, .. } = txn; + + let master_edition = master_edition.ok_or(SolanaErrorNotFoundMessage::MasterEdition)?; + + let MasterEdition { + name, + seller_fee_basis_points, + symbol, + creators, + metadata_uri, + .. + } = master_edition; + + let payer: Pubkey = self.0.treasury_wallet_address; + + let program_pubkey = mpl_token_metadata::id(); + let update_authority: Pubkey = master_edition.owner_address.parse()?; + let metadata: Pubkey = collection.metadata.parse()?; + + let ins = update_metadata_accounts_v2( + program_pubkey, + metadata, + update_authority, + None, + Some(DataV2 { + name, + symbol, + uri: metadata_uri, + seller_fee_basis_points: seller_fee_basis_points.try_into()?, + creators: Some( + creators + .into_iter() + .map(TryInto::try_into) + .collect::>>()?, + ), + collection: None, + uses: None, + }), + None, + None, + ); + + let blockhash = rpc.get_latest_blockhash()?; + + let message = + solana_program::message::Message::new_with_blockhash(&[ins], Some(&payer), &blockhash); + + let serialized_message = message.serialize(); + + Ok(TransactionResponse { + serialized_message, + signatures_or_signers_public_keys: vec![ + payer.to_string(), + update_authority.to_string(), + ], + addresses: UpdateMasterEditionAddresses { + metadata, + update_authority, + }, + }) + } +} + +impl<'a> MintBackend for EditionRef<'a> { + fn mint( &self, collection: &collections::Model, - payload: MintMetaplexEditionTransaction, - ) -> Result> { - let rpc = &self.rpc_client; + txn: MintMetaplexEditionTransaction, + ) -> hub_core::prelude::Result> { + let rpc = &self.0.rpc_client; let MintMetaplexEditionTransaction { recipient_address, owner_address, edition, .. - } = payload; + } = txn; - let payer: Pubkey = self.treasury_wallet_address.parse()?; + let payer: Pubkey = self.0.treasury_wallet_address; let owner = owner_address.parse()?; let program_pubkey = mpl_token_metadata::id(); @@ -296,96 +500,26 @@ impl Solana { }, }) } +} - /// Res - /// - /// # Errors - /// This function fails if unable to assemble or save solana update of a drop - pub fn update( - &self, - collection: &collections::Model, - payload: MetaplexMasterEditionTransaction, - ) -> Result> { - let rpc = &self.rpc_client; - - let MetaplexMasterEditionTransaction { master_edition, .. } = payload; - - let master_edition = master_edition.ok_or(SolanaError::MasterEditionMessageNotFound)?; - - let MasterEdition { - name, - seller_fee_basis_points, - symbol, - creators, - metadata_uri, - .. - } = master_edition; - - let payer: Pubkey = self.treasury_wallet_address.parse()?; - - let program_pubkey = mpl_token_metadata::id(); - let update_authority: Pubkey = master_edition.owner_address.parse()?; - let metadata: Pubkey = collection.metadata.parse()?; - - let ins = update_metadata_accounts_v2( - program_pubkey, - metadata, - update_authority, - None, - Some(DataV2 { - name, - symbol, - uri: metadata_uri, - seller_fee_basis_points: seller_fee_basis_points.try_into()?, - creators: Some( - creators - .into_iter() - .map(TryInto::try_into) - .collect::>>()?, - ), - collection: None, - uses: None, - }), - None, - None, - ); - - let blockhash = rpc.get_latest_blockhash()?; - - let message = - solana_program::message::Message::new_with_blockhash(&[ins], Some(&payer), &blockhash); - - let serialized_message = message.serialize(); - - Ok(TransactionResponse { - serialized_message, - signatures_or_signers_public_keys: vec![ - payer.to_string(), - update_authority.to_string(), - ], - addresses: UpdateMasterEditionAddresses { - metadata, - update_authority, - }, - }) - } - - pub fn transfer( +#[async_trait] +impl<'a> TransferBackend for UncompressedRef<'a> { + async fn transfer( &self, collection_mint: &collection_mints::Model, - payload: TransferMetaplexAssetTransaction, - ) -> Result> { - let rpc = &self.rpc_client; + txn: TransferMetaplexAssetTransaction, + ) -> hub_core::prelude::Result> { + let rpc = &self.0.rpc_client; let TransferMetaplexAssetTransaction { owner_address, recipient_address, .. - } = payload; + } = txn; let sender: Pubkey = owner_address.parse()?; let recipient: Pubkey = recipient_address.parse()?; let mint_address: Pubkey = collection_mint.mint.parse()?; - let payer: Pubkey = self.treasury_wallet_address.parse()?; + let payer: Pubkey = self.0.treasury_wallet_address; let blockhash = rpc.get_latest_blockhash()?; let source_ata = get_associated_token_address(&sender, &mint_address); let destination_ata = get_associated_token_address(&recipient, &mint_address); @@ -430,25 +564,217 @@ impl Solana { }, }) } +} + +#[async_trait] +impl<'a> TransferBackend for CompressedRef<'a> { + async fn transfer( + &self, + collection_mint: &collection_mints::Model, + txn: TransferMetaplexAssetTransaction, + ) -> hub_core::prelude::Result> { + let TransferMetaplexAssetTransaction { + recipient_address, + owner_address, + collection_mint_id, + .. + } = txn; + let payer = self.0.treasury_wallet_address; + let recipient = recipient_address.parse()?; + let owner = owner_address.parse()?; + + let asset_id = todo!("wait where's the asset address"); + let asset = self + .0 + .asset_rpc_client + .get_asset(asset_id) + .await + .context("Error getting asset data")?; + + let instructions = [Instruction { + program_id: mpl_bubblegum::ID, + accounts: [ + AccountMeta::new(self.0.bubblegum_tree_authority, false), + AccountMeta::new_readonly(owner, true), + AccountMeta::new_readonly(owner, false), + AccountMeta::new_readonly(recipient, false), + AccountMeta::new(self.0.bubblegum_merkle_tree, false), + AccountMeta::new_readonly(spl_noop::ID, false), + AccountMeta::new_readonly(spl_account_compression::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + ] + .into_iter() + .collect(), + data: mpl_bubblegum::instruction::Transfer { + root: todo!("how does DAA work"), + data_hash: todo!("how does DAA work"), + creator_hash: todo!("how does DAA work"), + nonce: todo!("how does DAA work"), + index: todo!("how does DAA work"), + } + .data(), + }]; + + let serialized_message = solana_program::message::Message::new_with_blockhash( + &instructions, + Some(&payer), + &self.0.rpc_client.get_latest_blockhash()?, + ) + .serialize(); + + Ok(TransactionResponse { + serialized_message, + signatures_or_signers_public_keys: vec![payer.to_string(), owner.to_string()], + addresses: TransferAssetAddresses { + owner, + recipient, + recipient_associated_token_account: todo!("what"), + owner_associated_token_account: todo!("what"), + }, + }) + } +} + +impl<'a> MintBackend + for CompressedRef<'a> +{ + fn mint( + &self, + collection: &collections::Model, + txn: MintMetaplexMetadataTransaction, + ) -> hub_core::prelude::Result> { + let MintMetaplexMetadataTransaction { + recipient_address, + metadata, + .. + } = txn; + + let MetaplexMetadata { + name, + seller_fee_basis_points, + symbol, + creators, + metadata_uri, + owner_address, + } = metadata.ok_or(SolanaErrorNotFoundMessage::Metadata)?; + let payer = self.0.treasury_wallet_address; + let recipient = recipient_address.parse()?; + let owner = owner_address.parse()?; + let treasury_wallet_address = self.0.treasury_wallet_address; + + let mut accounts = vec![ + // Tree authority + AccountMeta::new(self.0.bubblegum_tree_authority, false), + // TODO: can we make the project treasury the leaf owner while keeping the tree authority the holaplex treasury wallet + // Leaf owner + AccountMeta::new_readonly(recipient, false), + // Leaf delegate + AccountMeta::new_readonly(recipient, false), + // Merkle tree + AccountMeta::new(self.0.bubblegum_merkle_tree, false), + // Payer [signer] + AccountMeta::new_readonly(payer, true), + // Tree delegate [signer] + AccountMeta::new_readonly(treasury_wallet_address, true), + // Collection authority [signer] + AccountMeta::new_readonly(owner, true), + // Collection authority pda + AccountMeta::new_readonly(mpl_bubblegum::ID, false), + // Collection mint + AccountMeta::new_readonly(collection.mint.parse()?, false), + // collection metadata [mutable] + AccountMeta::new(collection.metadata.parse()?, false), + // Edition account + AccountMeta::new_readonly(collection.master_edition.parse()?, false), + // Bubblegum Signer + AccountMeta::new_readonly(self.0.bubblegum_cpi_address, false), + AccountMeta::new_readonly(spl_noop::ID, false), + AccountMeta::new_readonly(spl_account_compression::ID, false), + AccountMeta::new_readonly(mpl_token_metadata::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + ]; + + if creators + .iter() + .find(|&creator| creator.verified && creator.address == owner.to_string()) + .is_some() + { + accounts.push(AccountMeta::new_readonly(owner, true)); + } + + let instructions = [Instruction { + program_id: mpl_bubblegum::ID, + accounts: accounts.into_iter().collect(), + data: mpl_bubblegum::instruction::MintToCollectionV1 { + metadata_args: mpl_bubblegum::state::metaplex_adapter::MetadataArgs { + name, + symbol, + uri: metadata_uri, + seller_fee_basis_points: seller_fee_basis_points.try_into()?, + primary_sale_happened: false, + is_mutable: true, + edition_nonce: None, + token_standard: None, + collection: Some(Collection { + verified: true, + key: collection.mint.parse()?, + }), + uses: None, + token_program_version: TokenProgramVersion::Original, + creators: creators + .into_iter() + .map(TryInto::try_into) + .collect::>>()?, + }, + } + .data(), + }]; + + let serialized_message = solana_program::message::Message::new_with_blockhash( + &instructions, + Some(&payer), + &self.0.rpc_client.get_latest_blockhash()?, + ) + .serialize(); + + Ok(TransactionResponse { + serialized_message, + signatures_or_signers_public_keys: vec![payer.to_string(), owner.to_string()], + addresses: MintCompressedMintV1Addresses { owner, recipient }, + }) + } +} - #[allow(clippy::too_many_lines)] - fn master_edition_transaction( +impl<'a> MintBackend + for UncompressedRef<'a> +{ + fn mint( &self, - payload: MasterEdition, - ) -> Result> { - let payer: Pubkey = self.treasury_wallet_address.parse()?; - let rpc = &self.rpc_client; + collection: &collections::Model, + txn: MintMetaplexMetadataTransaction, + ) -> hub_core::prelude::Result> { + let MintMetaplexMetadataTransaction { + recipient_address, + metadata, + .. + } = txn; + let metadata = metadata.ok_or(SolanaErrorNotFoundMessage::Metadata)?; + let payer: Pubkey = self.0.treasury_wallet_address; + let rpc = &self.0.rpc_client; let mint = Keypair::new(); - let MasterEdition { + let MetaplexMetadata { name, symbol, seller_fee_basis_points, metadata_uri, creators, - supply, owner_address, - } = payload; + } = metadata; let owner: Pubkey = owner_address.parse()?; + let recipient: Pubkey = recipient_address.parse()?; + let collection_mint: Pubkey = collection.mint.parse()?; + let collection_metadata: Pubkey = collection.metadata.parse()?; + let collection_master_edition_account: Pubkey = collection.master_edition.parse()?; let (metadata, _) = Pubkey::find_program_address( &[ @@ -458,16 +784,7 @@ impl Solana { ], &mpl_token_metadata::ID, ); - let associated_token_account = get_associated_token_address(&owner, &mint.pubkey()); - let (master_edition, _) = Pubkey::find_program_address( - &[ - b"metadata", - mpl_token_metadata::ID.as_ref(), - mint.pubkey().as_ref(), - b"edition", - ], - &mpl_token_metadata::ID, - ); + let associated_token_account = get_associated_token_address(&recipient, &mint.pubkey()); let len = spl_token::state::Mint::LEN; let rent = rpc.get_minimum_balance_for_rent_exemption(len)?; let blockhash = rpc.get_latest_blockhash()?; @@ -488,7 +805,7 @@ impl Solana { )?; let ata_ins = spl_associated_token_account::instruction::create_associated_token_account( &payer, - &owner, + &recipient, &mint.pubkey(), &spl_token::ID, ); @@ -520,27 +837,32 @@ impl Solana { seller_fee_basis_points.try_into()?, true, true, - None, + Some(mpl_token_metadata::state::Collection { + verified: false, + key: collection_mint, + }), None, None, ); - let create_master_edition_ins = mpl_token_metadata::instruction::create_master_edition_v3( + + let verify_collection = mpl_token_metadata::instruction::verify_collection( mpl_token_metadata::ID, - master_edition, - mint.pubkey(), - owner, - owner, metadata, + owner, payer, - supply.map(TryInto::try_into).transpose()?, + collection_mint, + collection_metadata, + collection_master_edition_account, + None, ); + let instructions = vec![ create_account_ins, initialize_mint_ins, ata_ins, min_to_ins, create_metadata_account_ins, - create_master_edition_ins, + verify_collection, ]; let message = solana_program::message::Message::new_with_blockhash( @@ -556,16 +878,16 @@ impl Solana { serialized_message, signatures_or_signers_public_keys: vec![ payer.to_string(), - mint_signature.to_string(), owner.to_string(), + mint_signature.to_string(), ], - addresses: MasterEditionAddresses { - master_edition, + addresses: MintMetaplexAddresses { update_authority: owner, associated_token_account, mint: mint.pubkey(), owner, metadata, + recipient, }, }) } diff --git a/core/Cargo.toml b/core/Cargo.toml index 1598c15..cafb570 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -24,6 +24,7 @@ sea-orm = { version = "0.11.3", features = [ ] } prost = "0.11.9" mpl-token-metadata = "1.8.3" +mpl-bubblegum = "0.8.0" [dependencies.hub-core] package = "holaplex-hub-core" diff --git a/core/proto.lock b/core/proto.lock index 4755fe0..25d7214 100644 --- a/core/proto.lock +++ b/core/proto.lock @@ -1,14 +1,14 @@ [[schemas]] subject = "nfts" -version = 19 -sha512 = "94be29cc87e02f9622ba880302349b275262bc30546e6a6daacea541a6c1c740df9a185d0e18de782eda77ebf9c51c0e46c295d89abb9f7fb725b0ce9cfaf6f1" +version = 20 +sha512 = "893f6ec5b59d8af3c0db48b3cdf461104447bf818114d4f379a5aca55235da6a4b32385f52995963c6e0f62b706360c32a17d1b94975d4098f438669e3d0a66d" [[schemas]] subject = "solana_nfts" -version = 4 -sha512 = "272f1aed7d792a5fe5750cdca659091ca1a4a8dd4f36b35f5066ea6bb09cf4f1e905e7e5817dfa6c68d7ea3a8644192b4ff82e7ffcd85b2d9a58e48112a4a8bc" +version = 6 +sha512 = "106f013837b6efc1449778e7beb825d501214af42bd60153c8f09dfd25e3084b46782093ab6f1e5f194b99d9a5cb5eb9b72fec2ba953ae92c1e8ce9724264b40" [[schemas]] subject = "treasury" -version = 16 -sha512 = "bf8ad07bb11acefeaced6e5da417a9b49bad3770e4dd7f3d29b743fb943da973ab8587f7fef10a40a76b2e477d1c3956410de8403b7fb6efa80b95f2c3b8e8cf" +version = 19 +sha512 = "af9c5b6f8f6aef713a686b9253ef87a2332e8a69b4e0adbeab3a587ef2b74c09b510e3b017bf75bc2d1e083846073dcfd39cd104d95a4a76406a3a7fa0dbe2fb" diff --git a/core/proto.toml b/core/proto.toml index 5c63e6e..4186861 100644 --- a/core/proto.toml +++ b/core/proto.toml @@ -2,6 +2,6 @@ endpoint = "https://schemas.holaplex.tools" [schemas] -nfts = 19 -treasury = 16 -solana_nfts = 4 \ No newline at end of file +nfts = 20 +treasury = 19 +solana_nfts = 6 diff --git a/core/src/collection_mints.rs b/core/src/collection_mints.rs index aaf105b..f80e40d 100644 --- a/core/src/collection_mints.rs +++ b/core/src/collection_mints.rs @@ -53,8 +53,8 @@ impl CollectionMint { let conn = db.get(); Entity::find() - .find_also_related(collections::Entity) .join(JoinType::InnerJoin, Relation::Collections.def()) + .find_also_related(collections::Entity) .filter(Column::Id.eq(id)) .one(conn) .await diff --git a/core/src/lib.rs b/core/src/lib.rs index b5eac87..60868de 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -109,3 +109,23 @@ impl TryFrom for Creator { }) } } + +use mpl_bubblegum::state::metaplex_adapter::Creator as BubblegumCreator; + +impl TryFrom for BubblegumCreator { + type Error = Error; + + fn try_from( + ProtoCreator { + address, + verified, + share, + }: ProtoCreator, + ) -> Result { + Ok(Self { + address: address.parse()?, + verified, + share: share.try_into()?, + }) + } +} diff --git a/entity/src/collections.rs b/entity/src/collections.rs index f7ec25c..9ffb151 100644 --- a/entity/src/collections.rs +++ b/entity/src/collections.rs @@ -14,6 +14,7 @@ pub struct Model { pub mint: String, pub metadata: String, pub created_at: DateTime, + // TODO: add supply column to help denote mcc from editions } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/entity/src/mod.rs b/entity/src/mod.rs index 50effb6..da3ad48 100644 --- a/entity/src/mod.rs +++ b/entity/src/mod.rs @@ -3,4 +3,5 @@ pub mod prelude; pub mod collection_mints; -pub mod collections; +pub mod editions; +pub mod certified_collections; \ No newline at end of file diff --git a/indexer/Cargo.toml b/indexer/Cargo.toml index b2eb400..b53e132 100644 --- a/indexer/Cargo.toml +++ b/indexer/Cargo.toml @@ -20,13 +20,13 @@ sea-orm = { version = "^0.10.0", features = [ bs58 = "0.5.0" futures = "0.3.24" hex = "0.4.3" -solana-sdk = "=1.14.18" -solana-program = "=1.14.18" +solana-sdk = "1.14" +solana-program = "1.14" yellowstone-grpc-client = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v1.0.0+solana.1.16.1" } yellowstone-grpc-proto = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v1.0.0+solana.1.16.1" } dashmap = "5.4.0" spl-token = "=3.5.0" -solana-client = "=1.14.18" +solana-client = "1.14" backoff = { version = "0.4.0", features = ["tokio"] } diff --git a/indexer/src/handler.rs b/indexer/src/handler.rs index 4601c3a..fe0291a 100644 --- a/indexer/src/handler.rs +++ b/indexer/src/handler.rs @@ -111,7 +111,7 @@ impl MessageHandler { let keys = message.clone().account_keys; for (idx, key) in message.clone().account_keys.iter().enumerate() { - let k = Pubkey::try_from(key.clone()).map_err(|_| anyhow!("failed to parse pubkey"))?; + let k = Pubkey::new(key); if k == spl_token::ID { i = idx; break; @@ -147,8 +147,7 @@ impl MessageHandler { let source_account_index = account_indices[0]; let source_bytes = &keys[source_account_index as usize]; - let source = Pubkey::try_from(source_bytes.clone()) - .map_err(|_| anyhow!("failed to parse pubkey"))?; + let source = Pubkey::new(source_bytes); let collection_mint = CollectionMint::find_by_ata(&self.db, source.to_string()).await?; @@ -159,8 +158,7 @@ impl MessageHandler { let destination_account_index = account_indices[destination_ata_index]; let destination_bytes = &keys[destination_account_index as usize]; - let destination = Pubkey::try_from(destination_bytes.clone()) - .map_err(|_| anyhow!("failed to parse pubkey"))?; + let destination = Pubkey::new(destination_bytes); let acct = fetch_account(&self.rpc, &destination).await?; let destination_tkn_act = Account::unpack(&acct.data)?;