diff --git a/Cargo.lock b/Cargo.lock index 712a83a..acf143d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "aluvm" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134a93e142c6aabca39c69c501c7d34ec99b04a5cb25738e1c0d1382f14b02a1" +checksum = "e10be187b383247e1902aa5a415f76ffc9a04f197829c46b9ccb6da3582e394f" dependencies = [ "amplify", "baid58", @@ -89,11 +89,12 @@ dependencies = [ [[package]] name = "amplify_num" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddce3bc63e807ea02065e8d8b702695f3d302ae4158baddff8b0ce5c73947251" +checksum = "9681187211554ab98f138ba159e90861b136c20afc680dcff2ba82d020721e27" dependencies = [ "serde", + "wasm-bindgen", ] [[package]] @@ -124,9 +125,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -144,30 +145,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -309,9 +310,9 @@ dependencies = [ [[package]] name = "bp-consensus" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b50dbcec1e75163cdd1ba1cc265f1bb67ae4319b00469c36b3972a2eb49b879" +checksum = "190ac89a2a3c79d5bfb677f48e5393691832c540973341831572edaffdce0881" dependencies = [ "amplify", "chrono", @@ -324,9 +325,9 @@ dependencies = [ [[package]] name = "bp-core" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece2f2e6f91bdbf5684f26a105c078e50394f98e37dc173c20b23eba53e7be09" +checksum = "f0143f6c7399cb6d0003407e6de197a03b3b42b34380fab02c8fecaf4431b061" dependencies = [ "amplify", "bp-consensus", @@ -341,9 +342,9 @@ dependencies = [ [[package]] name = "bp-dbc" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d9d7bbc4dc2debd9c30469752963410b44adee55af807378589e88c5fd913a" +checksum = "3e3e04649c77079cfd1466ba14a62c83585053bb61d294578a39fcfe38f402db" dependencies = [ "amplify", "base85", @@ -354,11 +355,26 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "bp-derive" +version = "0.11.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497e2b7324e0c7a65bdaabccd0fc7d05b003cca4ae3f3a1520e023aab3a9a24f" +dependencies = [ + "amplify", + "bitcoin_hashes", + "bp-consensus", + "bp-invoice", + "commit_verify", + "indexmap 2.1.0", + "serde", +] + [[package]] name = "bp-esplora" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae41858fe075fed93276e1399be6db314775adbf4470f7f9b056d78d37ebc56" +checksum = "7d642e2e6d699edd884e0359668750df1590d209f2ff956d3c5fbf58b5eba730" dependencies = [ "amplify", "bp-std", @@ -366,14 +382,28 @@ dependencies = [ "reqwest", "serde", "serde_with", + "sha2", "ureq", ] +[[package]] +name = "bp-invoice" +version = "0.11.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a40b7fb87b58b2371b7b3b010568baa9ba463007707f9af9bb9b77a63d77e8f" +dependencies = [ + "amplify", + "bech32", + "bitcoin_hashes", + "bp-consensus", + "serde", +] + [[package]] name = "bp-seals" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3661c2c7bddda0b6fd37dafa1a56c863ba0ff9787ebc06d05eb8ac676f1d469e" +checksum = "a1e4955315fad858472320ee5a7fba8a35c61c3928bdacb581931e23d4f25c7b" dependencies = [ "amplify", "baid58", @@ -388,25 +418,24 @@ dependencies = [ [[package]] name = "bp-std" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6674cfa814dee185eb49d791065448ae8a8dcf0ad20f23cdec49ba7341bce9ab" +checksum = "6d6847d1e7359ed49947092fd319433c82b9cced1a2d780a9415d212770cf93c" dependencies = [ "amplify", - "bech32", - "bitcoin_hashes", "bp-consensus", - "bp-core", - "commit_verify", - "indexmap 2.1.0", + "bp-derive", + "bp-invoice", + "descriptors", + "psbt", "serde", ] [[package]] name = "bp-util" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abdb986e5bf767576c8533563d7d4daa0667e3dc4acc12ddd7354ada5473b436" +checksum = "26f19a4670648894ce6e3dc46d1c2fe31c6ac168fe83c66575b1f2bcbf24b49f" dependencies = [ "amplify", "base64", @@ -427,9 +456,9 @@ dependencies = [ [[package]] name = "bp-wallet" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9dbf251e691b0d45420e86116deafc4fefc7833c571c32e7fd318e2f4ea3f16" +checksum = "54a947aa0c73c5097e30e6be62939e8a6f230aa076710817000fb1cd15b408e8" dependencies = [ "amplify", "bp-esplora", @@ -486,14 +515,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "clap" -version = "4.4.8" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", @@ -501,9 +530,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", @@ -520,7 +549,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] @@ -550,9 +579,9 @@ dependencies = [ [[package]] name = "commit_verify" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c1a7d13181451d927abcc477d4565f23eb06ab36820599cc75ed3161147daa" +checksum = "5598661b1d90b149f0b944faef8c1d1d131f847678a6c450b9540b629ac11291" dependencies = [ "amplify", "commit_encoding_derive", @@ -564,6 +593,16 @@ dependencies = [ "strict_types", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -572,9 +611,9 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -582,9 +621,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -641,7 +680,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] @@ -652,14 +691,14 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", "serde", @@ -667,12 +706,12 @@ dependencies = [ [[package]] name = "descriptors" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b5433142e6b89ff9603f654e6725b9e97a1ce5f3783bb11dbedc93b3839296" +checksum = "58cfb07301f4982a5470167155c186586871d21693a791078cfa15c173657626" dependencies = [ "amplify", - "bp-std", + "bp-derive", "indexmap 2.1.0", "serde", ] @@ -705,7 +744,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -744,12 +783,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -800,45 +839,45 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-task", @@ -871,9 +910,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" @@ -911,9 +950,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -952,9 +991,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -981,9 +1020,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -996,7 +1035,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -1047,9 +1086,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1073,7 +1112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "serde", ] @@ -1091,20 +1130,20 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1117,9 +1156,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libredox" @@ -1134,9 +1173,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" @@ -1167,13 +1206,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1215,24 +1254,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1251,7 +1290,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] @@ -1262,9 +1301,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -1286,9 +1325,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -1304,9 +1343,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "powerfmt" @@ -1322,26 +1361,29 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] [[package]] name = "psbt" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2b08f79bc8546b6ad7d2b7e0da71ce84e7e690b33703fa4e5e3b1806012868" +checksum = "010207105ec57c07146a30363074b71fa204c7515b3b413280d0e5986c658359" dependencies = [ "amplify", "base64", - "bp-std", + "bp-core", + "bp-derive", "chrono", + "commit_verify", "descriptors", "indexmap 2.1.0", "serde", + "strict_encoding", ] [[package]] @@ -1434,9 +1476,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", @@ -1473,9 +1515,9 @@ dependencies = [ [[package]] name = "rgb-core" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f1484191ad41598bc3c9b2a5aa3ed0a323ce1a56fc3916c8c4f7fa81d8eb" +checksum = "bbd6b8a6d616997c670ea40a5a9badc9744f3e1fa29213affabdd2d2c192ef3b" dependencies = [ "aluvm", "amplify", @@ -1494,17 +1536,21 @@ dependencies = [ [[package]] name = "rgb-invoice" -version = "0.11.0-alpha.2" +version = "0.11.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62ce12f4677d821ed5877bcc4125248cc3b82c8aeb0bd01a3924ae3c477284" dependencies = [ "amplify", "baid58", - "bp-seals", - "bp-std", + "bp-core", + "bp-invoice", "fluent-uri", "indexmap 2.1.0", "percent-encoding", - "rgb-std", + "rgb-core", + "serde", "strict_encoding", + "strict_types", ] [[package]] @@ -1516,9 +1562,27 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "rgb-psbt" +version = "0.11.0-beta.1" +dependencies = [ + "amplify", + "baid58", + "bp-core", + "bp-std", + "commit_verify", + "getrandom", + "psbt", + "rand", + "rgb-std", + "strict_encoding", + "wasm-bindgen", + "wasm-bindgen-test", +] + [[package]] name = "rgb-runtime" -version = "0.11.0-alpha.2" +version = "0.11.0-beta.1" dependencies = [ "amplify", "baid58", @@ -1526,10 +1590,13 @@ dependencies = [ "bp-esplora", "bp-std", "bp-wallet", + "chrono", + "commit_verify", "descriptors", "indexmap 2.1.0", "log", "rgb-persist-fs", + "rgb-psbt", "rgb-std", "serde", "serde_yaml", @@ -1538,9 +1605,9 @@ dependencies = [ [[package]] name = "rgb-std" -version = "0.11.0-beta.2" +version = "0.11.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322467d948a5695007dfb2e25b6a33bd05cb28339a61b4cca6ba6b55d6513d2" +checksum = "138a9d7eea58593bb756e2effd991fcf42fe55503e311abc9b1d22c5088dd71f" dependencies = [ "amplify", "baid58", @@ -1551,6 +1618,7 @@ dependencies = [ "getrandom", "indexmap 2.1.0", "rgb-core", + "rgb-invoice", "serde", "strict_encoding", "strict_types", @@ -1559,11 +1627,10 @@ dependencies = [ [[package]] name = "rgb-wallet" -version = "0.11.0-alpha.2" +version = "0.11.0-beta.1" dependencies = [ "amplify", "baid58", - "bp-esplora", "bp-seals", "bp-std", "bp-util", @@ -1572,7 +1639,7 @@ dependencies = [ "commit_verify", "env_logger", "log", - "rgb-invoice", + "psbt", "rgb-runtime", "rgb-std", "serde", @@ -1585,16 +1652,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1614,22 +1681,22 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", @@ -1649,9 +1716,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" @@ -1659,9 +1726,15 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "sct" version = "0.7.1" @@ -1690,7 +1763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "rand", - "secp256k1-sys 0.9.0", + "secp256k1-sys 0.9.1", "serde", ] @@ -1705,9 +1778,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" +checksum = "4dd97a086ec737e30053fd5c46f097465d25bb81dd3608825f65298c4c98be83" dependencies = [ "cc", ] @@ -1760,22 +1833,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] @@ -1791,9 +1864,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -1846,14 +1919,14 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] name = "serde_yaml" -version = "0.9.27" +version = "0.9.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" dependencies = [ "indexmap 2.1.0", "itoa", @@ -1884,9 +1957,9 @@ dependencies = [ [[package]] name = "single_use_seals" -version = "0.11.0-beta.1" +version = "0.11.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a46b0f289450dc06e7b94f048a55ac437177532f2c6c9bed12c1de7ac948f5a5" +checksum = "c30647a1342641c45ca7c1dcd5ae7db16533b86744e827c84cfed875db2de3fe" dependencies = [ "amplify_derive", ] @@ -1900,16 +1973,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -1917,7 +1980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2010,9 +2073,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -2050,7 +2113,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2064,29 +2127,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -2104,9 +2167,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -2128,17 +2191,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", - "socket2 0.5.5", - "windows-sys", + "socket2", + "windows-sys 0.48.0", ] [[package]] @@ -2238,9 +2301,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -2250,9 +2313,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -2271,9 +2334,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "untrusted" @@ -2283,9 +2346,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" dependencies = [ "base64", "flate2", @@ -2302,9 +2365,9 @@ dependencies = [ [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -2346,9 +2409,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2356,24 +2419,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -2383,9 +2446,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2393,28 +2456,53 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -2422,9 +2510,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "winapi" @@ -2463,7 +2551,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2472,7 +2560,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -2481,13 +2578,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -2496,47 +2608,89 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" dependencies = [ "memchr", ] @@ -2548,5 +2702,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] diff --git a/Cargo.toml b/Cargo.toml index cdbdfd1..806fcf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ [workspace] members = [ - "invoice", + "psbt", "cli", "fs", "." ] default-members = [ - "invoice", + "psbt", "cli", "fs", "." ] [workspace.package] -version = "0.11.0-alpha.2" +version = "0.11.0-beta.1" keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] categories = ["cryptography::cryptocurrencies"] authors = ["Dr Maxim Orlovsky "] @@ -26,16 +26,21 @@ license = "Apache-2.0" [workspace.dependencies] amplify = "4.5.0" baid58 = "0.4.4" +commit_verify = "0.11.0-beta.2" strict_encoding = "2.6.1" strict_types = "1.6.3" -bp-core = "0.11.0-beta.1" -bp-seals = "0.11.0-beta.1" -bp-std = "0.11.0-beta.2" -bp-wallet = "0.11.0-beta.2" -bp-utils = "0.11.0-beta.2" -bp-esplora = "0.11.0-beta.1" -rgb-std = { version = "0.11.0-beta.2", features = ["fs"] } +bp-core = "0.11.0-beta.2" +bp-seals = "0.11.0-beta.2" +bp-std = "0.11.0-beta.3" +bp-wallet = "0.11.0-beta.3" +bp-util = "0.11.0-beta.3" +bp-esplora = "0.11.0-beta.2" +descriptors = "0.11.0-beta.2" +psbt = { version = "0.11.0-beta.2", features = ["client-side-validation"] } +rgb-std = { version = "0.11.0-beta.3", features = ["fs"] } +rgb-psbt = { version = "0.11.0-beta.1", path = "psbt" } indexmap = "2.0.2" +chrono = "0.4.31" serde_crate = { package = "serde", version = "1", features = ["derive"] } serde_yaml = "0.9.19" log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] } @@ -60,21 +65,24 @@ name = "rgb_rt" [dependencies] amplify = { workspace = true } baid58 = { workspace = true } +commit_verify = { workspace = true } strict_types = { workspace = true } bp-core = { workspace = true } bp-std = { workspace = true } bp-wallet = { workspace = true, features = ["fs"] } bp-esplora = { workspace = true, optional = true } -descriptors = "0.11.0-beta.2" +descriptors = { workspace = true } rgb-std = { workspace = true } +rgb-psbt = { workspace = true } rgb-persist-fs = { version = "0.11.0-alpha", path = "fs" } indexmap = { workspace = true } +chrono = { workspace = true } serde_crate = { workspace = true, optional = true } serde_yaml = { workspace = true, optional = true } log = { workspace = true, optional = true } [features] -default = [] +default = ["esplora"] all = ["esplora", "serde", "log"] esplora = ["bp-esplora", "bp-wallet/esplora"] serde = ["serde_crate", "serde_yaml", "bp-std/serde", "bp-wallet/serde",] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 84a5c9d..2354bc1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -20,15 +20,14 @@ path = "src/main.rs" amplify = { workspace = true } baid58 = { workspace = true } strict_types = { workspace = true, features = ["serde"] } -commit_verify = "0.11.0-beta.1" +commit_verify = { workspace = true } bp-seals = { workspace = true } bp-std = { workspace = true, features = ["serde"] } bp-wallet = { workspace = true } -bp-esplora = { workspace = true } -bp-util = "0.11.0-beta.2" +bp-util = { workspace = true } +psbt = { workspace = true } rgb-std = { workspace = true, features = ["serde"] } -rgb-invoice = { version = "0.11.0-alpha.2", path = "../invoice" } -rgb-runtime = { version = "0.11.0-alpha.2", path = "..", features = ["log", "serde"] } +rgb-runtime = { version = "0.11.0-beta.1", path = "..", features = ["esplora", "log", "serde"] } log = { workspace = true } env_logger = "0.10.1" clap = { version = "4.4.8", features = ["derive", "env"] } diff --git a/cli/src/args.rs b/cli/src/args.rs index 1e841de..370bd1b 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -23,7 +23,7 @@ use bp_util::{Config, DescriptorOpts}; use bpstd::XpubDerivable; -use rgb_rt::{RgbDescr, Runtime, RuntimeError, TapretKey}; +use rgb_rt::{Resolver, ResolverError, RgbDescr, Runtime, RuntimeError, TapretKey}; use crate::Command; @@ -75,4 +75,9 @@ impl RgbArgs { Ok(runtime) } + + #[allow(clippy::result_large_err)] + pub fn resolver(&self) -> Result { + Resolver::new(&self.resolver.esplora) + } } diff --git a/cli/src/command.rs b/cli/src/command.rs index cf06f7d..b7584e0 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -20,25 +20,27 @@ // limitations under the License. use std::fs; +use std::fs::File; use std::path::PathBuf; use std::str::FromStr; use amplify::confinement::U16; use bp_util::{Config, Exec}; use bpstd::{Sats, Txid}; -use rgb_rt::{DescriptorRgb, RgbDescr, RgbKeychain, RuntimeError}; -use rgbinvoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; +use psbt::{Psbt, PsbtVer}; +use rgb_rt::{DescriptorRgb, RgbDescr, RgbKeychain, RuntimeError, TransferParams}; use rgbstd::containers::{Bindle, Transfer, UniversalBindle}; use rgbstd::contract::{ContractId, GenesisSeal, GraphSeal, StateType}; use rgbstd::interface::{ContractBuilder, FilterExclude, IfaceId, SchemaIfaces}; +use rgbstd::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; use rgbstd::persistence::{Inventory, Stash}; use rgbstd::schema::SchemaId; -use rgbstd::SealDefinition; +use rgbstd::validation::Validity; +use rgbstd::XSeal; use seals::txout::{CloseMethod, ExplicitSeal}; use strict_types::encoding::{FieldName, TypeName}; use strict_types::StrictVal; -use crate::resolver::PanickingResolver; use crate::RgbArgs; // TODO: For now, serde implementation doesn't work for consignments due to @@ -63,140 +65,198 @@ pub enum Command { #[display(inner)] Bp(bp_util::Command), - /// Prints out list of known RGB schemata. + /// Prints out list of known RGB schemata Schemata, - /// Prints out list of known RGB interfaces. + /// Prints out list of known RGB interfaces Interfaces, - /// Prints out list of known RGB contracts. + /// Prints out list of known RGB contracts Contracts, - /// Imports RGB data into the stash: contracts, schema, interfaces, etc. + /// Imports RGB data into the stash: contracts, schema, interfaces, etc #[display("import")] Import { - /// Use BASE64 ASCII armoring for binary data. + /// Use BASE64 ASCII armoring for binary data #[arg(short)] armored: bool, - /// File with RGB data. If not provided, assumes `-a` and prints out - /// data to STDOUT. + /// File with RGB data + /// + /// If not provided, assumes `-a` and prints out data to STDOUT file: PathBuf, }, - /// Exports existing RGB contract. + /// Exports existing RGB contract #[display("export")] Export { - /// Use BASE64 ASCII armoring for binary data. + /// Use BASE64 ASCII armoring for binary data #[arg(short)] armored: bool, - /// Contract to export. + /// Contract to export contract: ContractId, - /// File with RGB data. If not provided, assumes `-a` and reads the data - /// from STDIN. + /// File with RGB data + /// + /// If not provided, assumes `-a` and reads the data from STDIN file: Option, }, - /// Reports information about state of a contract. + /// Convert binary RGB file into a text armored version + #[display("convert")] + Armor { + /// File with RGB data + /// + /// If not provided, assumes `-a` and reads the data from STDIN + file: PathBuf, + }, + + /// Reports information about state of a contract #[display("state")] State { - /// Contract identifier. + /// Show all state - not just the one owned by the wallet + #[clap(short, long)] + all: bool, + + /// Contract identifier contract_id: ContractId, - /// Interface to interpret the state data. + + /// Interface to interpret the state data iface: String, }, - /// Issues new contract. + /// Issues new contract #[display("issue")] Issue { - /// Schema name to use for the contract. + /// Schema name to use for the contract schema: SchemaId, //String, - /// File containing contract genesis description in YAML format. + /// File containing contract genesis description in YAML format contract: PathBuf, }, - /// Create new invoice. + /// Create new invoice #[display("invoice")] Invoice { - /// Force address-based invoice. + /// Force address-based invoice #[clap(short, long)] address_based: bool, - /// Contract identifier. + /// Contract identifier contract_id: ContractId, - /// Interface to interpret the state data. + /// Interface to interpret the state data iface: String, - /// Value to transfer. + /// Value to transfer value: u64, }, - /// Create new transfer. + /// Prepare PSBT file for transferring RGB assets. In the most of cases you + /// need to use `transfer` command instead of `prepare` and `consign`. + #[display("prepare")] + Prepare { + /// Encode PSBT as V2 + #[clap(short = '2')] + v2: bool, + + /// Method for single-use-seals + #[clap(long, default_value = "tapret1st")] + method: CloseMethod, + + /// Amount of satoshis which should be paid to the address-based + /// beneficiary + #[clap(long, default_value = "2000")] + sats: Sats, + + /// Invoice data + invoice: RgbInvoice, + + /// Fee + fee: Sats, + + /// Name of PSBT file to save. If not given, prints PSBT to STDOUT + psbt: Option, + }, + + /// Prepare consignment for transferring RGB assets. In the most of cases + /// you need to use `transfer` command instead of `prepare` and `consign`. + #[display("prepare")] + Consign { + /// Invoice data + invoice: RgbInvoice, + + /// Name of PSBT file containing prepared transfer data + psbt: PathBuf, + + /// File for generated transfer consignment + consignment: PathBuf, + }, + + /// Transfer RGB assets #[display("transfer")] Transfer { + /// Encode PSBT as V2 + #[clap(short = '2')] + v2: bool, + + /// Method for single-use-seals #[clap(long, default_value = "tapret1st")] - /// Method for single-use-seals. method: CloseMethod, - /// PSBT file. - psbt_file: PathBuf, + /// Amount of satoshis which should be paid to the address-based + /// beneficiary + #[clap(long, default_value = "2000")] + sats: Sats, - /// Invoice data. + /// Invoice data invoice: RgbInvoice, - /// Filename to save transfer consignment. - out_file: PathBuf, + /// Fee + fee: Sats, + + /// File for generated transfer consignment + consignment: PathBuf, + + /// Name of PSBT file to save. If not given, prints PSBT to STDOUT + psbt: Option, }, - /// Inspects any RGB data file. + /// Inspects any RGB data file #[display("inspect")] Inspect { - #[clap(short, long, default_value = "yaml")] /// Format used for data inspection + #[clap(short, long, default_value = "yaml")] format: InspectFormat, - /// RGB file to inspect. + /// RGB file to inspect file: PathBuf, }, - /// Debug-dump all stash and inventory data. + /// Debug-dump all stash and inventory data #[display("dump")] Dump { - /// Directory to put the dump into. + /// Directory to put the dump into #[arg(default_value = "./rgb-dump")] root_dir: String, }, - /// Validate transfer consignment. + /// Validate transfer consignment #[display("validate")] Validate { - /// File with the transfer consignment. + /// File with the transfer consignment file: PathBuf, }, - /// Validate transfer consignment & accept to the stash. + /// Validate transfer consignment & accept to the stash #[display("accept")] Accept { - /// Force accepting consignments with non-mined terminal witness. + /// Force accepting consignments with non-mined terminal witness #[arg(short, long)] force: bool, - /// File with the transfer consignment. + /// File with the transfer consignment file: PathBuf, }, - - /// Set first opret/tapret output to host a commitment - #[display("set-host")] - SetHost { - #[arg(long, default_value = "tapret1st")] - /// Method for single-use-seals. - method: CloseMethod, - - /// PSBT file. - psbt_file: PathBuf, - }, } impl Exec for RgbArgs { @@ -265,7 +325,7 @@ impl Exec for RgbArgs { ); } UniversalBindle::Contract(bindle) => { - let mut resolver = self.resolver(); + let mut resolver = self.resolver()?; let id = bindle.id(); let contract = bindle .unbindle() @@ -289,7 +349,7 @@ impl Exec for RgbArgs { contract, file, } => { - let mut runtime = self.rgb_runtime(&config)?; + let runtime = self.rgb_runtime(&config)?; let bindle = runtime .export_contract(*contract) .map_err(|err| err.to_string())?; @@ -302,7 +362,16 @@ impl Exec for RgbArgs { } } - Command::State { contract_id, iface } => { + Command::Armor { file } => { + let bindle = UniversalBindle::load_file(file)?; + println!("{bindle}"); + } + + Command::State { + contract_id, + iface, + all, + } => { let mut runtime = self.rgb_runtime(&config)?; let bp_runtime = self.bp_runtime::(&config)?; runtime.attach(bp_runtime.detach()); @@ -324,20 +393,22 @@ impl Exec for RgbArgs { println!(" {}:", owned.name); if let Ok(allocations) = contract.fungible(owned.name.clone(), &runtime) { for allocation in allocations { - print!( + println!( " amount={}, utxo={}, witness={} # owned by the wallet", allocation.value, allocation.owner, allocation.witness ); } } - if let Ok(allocations) = - contract.fungible(owned.name.clone(), &FilterExclude(&runtime)) - { - for allocation in allocations { - print!( - " amount={}, utxo={}, witness={} # owner unknown", - allocation.value, allocation.owner, allocation.witness - ); + if *all { + if let Ok(allocations) = + contract.fungible(owned.name.clone(), &FilterExclude(&runtime)) + { + for allocation in allocations { + println!( + " amount={}, utxo={}, witness={} # owner unknown", + allocation.value, allocation.owner, allocation.witness + ); + } } } // TODO: Print out other types of state @@ -465,7 +536,7 @@ impl Exec for RgbArgs { .expect("seal must be a string"); let seal = ExplicitSeal::::from_str(seal).expect("invalid seal definition"); - let seal = GenesisSeal::from(seal); + let seal = XSeal::Bitcoin(GenesisSeal::from(seal)); // Workaround for borrow checker: let field_name = @@ -490,7 +561,7 @@ impl Exec for RgbArgs { let contract = builder.issue_contract().expect("failure issuing contract"); let id = contract.contract_id(); - let mut resolver = PanickingResolver; + let mut resolver = self.resolver()?; let validated_contract = contract .validate(&mut resolver, self.general.network.is_testnet()) .map_err(|consignment| { @@ -531,7 +602,7 @@ impl Exec for RgbArgs { .next() .expect("no addresses left") .addr; - Beneficiary::WitnessUtxo(addr) + Beneficiary::WitnessVoutBitcoin(addr) } (_, Some(outpoint)) => { let seal = GraphSeal::new( @@ -539,7 +610,7 @@ impl Exec for RgbArgs { outpoint.txid, outpoint.vout, ); - runtime.store_seal_secret(SealDefinition::Bitcoin(seal))?; + runtime.store_seal_secret(XSeal::Bitcoin(seal))?; Beneficiary::BlindedSeal(seal.to_concealed_seal()) } }; @@ -557,32 +628,79 @@ impl Exec for RgbArgs { }; println!("{invoice}"); } - #[allow(unused_variables)] - Command::Transfer { + Command::Prepare { + v2, method, - psbt_file, invoice, - out_file, + fee, + sats, + psbt: psbt_file, + } => { + let mut runtime = self.rgb_runtime(&config)?; + // TODO: Support lock time and RBFs + let params = TransferParams::with(*fee, *sats); + + let (psbt, _) = runtime + .construct_psbt(invoice, *method, params) + .map_err(|err| err.to_string())?; + + let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; + match psbt_file { + Some(file_name) => { + let mut psbt_file = File::create(file_name)?; + psbt.encode(ver, &mut psbt_file)?; + } + None => match ver { + PsbtVer::V0 => println!("{psbt}"), + PsbtVer::V2 => println!("{psbt:#}"), + }, + } + } + Command::Consign { + invoice, + psbt: psbt_name, + consignment: out_file, } => { - todo!() - /* - // TODO: Check PSBT format - let psbt_data = fs::read(&psbt_file)?; - let mut psbt = Psbt::deserialize(&psbt_data)?; + let mut runtime = self.rgb_runtime(&config)?; + let mut psbt_file = File::open(psbt_name)?; + let mut psbt = Psbt::decode(&mut psbt_file)?; let transfer = runtime - .pay(invoice, &mut psbt, method) + .transfer(invoice, &mut psbt) .map_err(|err| err.to_string())?; - fs::write(&psbt_file, psbt.serialize())?; - // TODO: Print PSBT as Base64 - transfer.save(&out_file)?; - eprintln!("Transfer is created and saved into '{}'.", out_file.display()); - eprintln!( - "PSBT file '{}' is updated with all required commitments and ready to be \ - signed.", - psbt_file.display() - ); - eprintln!("Stash data are updated."); - */ + let mut psbt_file = File::create(psbt_name)?; + psbt.encode(psbt.version, &mut psbt_file)?; + transfer.save(out_file)?; + } + Command::Transfer { + v2, + method, + invoice, + fee, + sats, + psbt: psbt_file, + consignment: out_file, + } => { + let mut runtime = self.rgb_runtime(&config)?; + // TODO: Support lock time and RBFs + let params = TransferParams::with(*fee, *sats); + + let (psbt, _, transfer) = runtime + .pay(invoice, *method, params) + .map_err(|err| err.to_string())?; + + transfer.save(out_file)?; + + let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; + match psbt_file { + Some(file_name) => { + let mut psbt_file = File::create(file_name)?; + psbt.encode(ver, &mut psbt_file)?; + } + None => match ver { + PsbtVer::V0 => println!("{psbt}"), + PsbtVer::V2 => println!("{psbt:#}"), + }, + } } Command::Inspect { file, format } => { let bindle = UniversalBindle::load_file(file)?; @@ -635,13 +753,22 @@ impl Exec for RgbArgs { format!("{root_dir}/stash/geneses/{id}.yaml"), serde_yaml::to_string(runtime.genesis(id)?)?, )?; - for (no, suppl) in runtime.contract_suppl(id).into_iter().flatten().enumerate() + for (no, suppl) in runtime + .contract_suppl_all(id) + .into_iter() + .flatten() + .enumerate() { fs::write( format!("{root_dir}/stash/geneses/{id}.suppl.{no:03}.yaml"), serde_yaml::to_string(suppl)?, )?; } + let tags = runtime.contract_asset_tags(id)?; + fs::write( + format!("{root_dir}/stash/geneses/{id}.tags.yaml"), + serde_yaml::to_string(tags)?, + )?; } for id in runtime.bundle_ids()? { fs::write( @@ -649,10 +776,10 @@ impl Exec for RgbArgs { serde_yaml::to_string(runtime.bundle(id)?)?, )?; } - for id in runtime.anchor_ids()? { + for id in runtime.witness_ids()? { fs::write( - format!("{root_dir}/stash/anchors/{id}.yaml"), - serde_yaml::to_string(runtime.anchor(id)?)?, + format!("{root_dir}/stash/anchors/{id}.debug"), + format!("{:#?}", runtime.anchor(id)?), )?; } for id in runtime.extension_ids()? { @@ -695,75 +822,35 @@ impl Exec for RgbArgs { eprintln!("Dump is successfully generated and saved to '{root_dir}'"); } Command::Validate { file } => { - let mut resolver = self.resolver(); + let mut resolver = self.resolver()?; let bindle = Bindle::::load_file(file)?; - let status = match bindle - .unbindle() - .validate(&mut resolver, self.general.network.is_testnet()) - { - Ok(consignment) => consignment.into_validation_status(), - Err(consignment) => consignment.into_validation_status(), + let consignment = bindle.unbindle(); + resolver.add_terminals(&consignment); + let status = + match consignment.validate(&mut resolver, self.general.network.is_testnet()) { + Ok(consignment) => consignment.into_validation_status(), + Err(consignment) => consignment.into_validation_status(), + } + .expect("just validated"); + if status.validity() == Validity::Valid { + eprintln!("The provided consignment is valid") + } else { + eprintln!("{status}"); } - .expect("just validated"); - eprintln!("{status}"); } Command::Accept { force, file } => { let mut runtime = self.rgb_runtime(&config)?; - let mut resolver = self.resolver(); + let mut resolver = self.resolver()?; let bindle = Bindle::::load_file(file)?; - let transfer = bindle - .unbindle() + let consignment = bindle.unbindle(); + resolver.add_terminals(&consignment); + let transfer = consignment .validate(&mut resolver, self.general.network.is_testnet()) .unwrap_or_else(|c| c); eprintln!("{}", transfer.validation_status().expect("just validated")); runtime.accept_transfer(transfer, &mut resolver, *force)?; eprintln!("Transfer accepted into the stash"); } - #[allow(unused_variables)] - Command::SetHost { method, psbt_file } => { - todo!(); - /* - let psbt_data = fs::read(&psbt_file)?; - let mut psbt = Psbt::deserialize(&psbt_data)?; - let mut psbt_modified = false; - match method { - CloseMethod::OpretFirst => { - psbt.unsigned_tx - .output - .iter() - .zip(&mut psbt.outputs) - .find(|(o, outp)| { - o.script_pubkey.is_op_return() && !outp.is_opret_host() - }) - .and_then(|(_, outp)| { - psbt_modified = true; - outp.set_opret_host().ok() - }); - } - CloseMethod::TapretFirst => { - psbt.unsigned_tx - .output - .iter() - .zip(&mut psbt.outputs) - .find(|(o, outp)| { - o.script_pubkey.is_v1_p2tr() && !outp.is_tapret_host() - }) - .and_then(|(_, outp)| { - psbt_modified = true; - outp.set_tapret_host().ok() - }); - } - _ => {} - }; - fs::write(&psbt_file, psbt.serialize())?; - if psbt_modified { - eprintln!( - "PSBT file '{}' is updated with {method} host now set.", - psbt_file.display() - ); - } - */ - } } println!(); diff --git a/cli/src/main.rs b/cli/src/main.rs index fc13ac6..47030a2 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -31,7 +31,6 @@ extern crate serde_crate as serde; mod command; mod args; -mod resolver; use std::process::ExitCode; @@ -41,7 +40,6 @@ use rgb_rt::RuntimeError; pub use crate::args::RgbArgs; pub use crate::command::Command; -pub use crate::resolver::PanickingResolver; fn main() -> ExitCode { if let Err(err) = run() { diff --git a/cli/src/resolver.rs b/cli/src/resolver.rs deleted file mode 100644 index 6634d77..0000000 --- a/cli/src/resolver.rs +++ /dev/null @@ -1,60 +0,0 @@ -// RGB smart contract wallet runtime -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::convert::Infallible; - -use bpstd::{Tx, Txid}; -use rgbstd::resolvers::ResolveHeight; -use rgbstd::validation::{ResolveTx, TxResolverError}; -use rgbstd::{Anchor, Layer1, WitnessAnchor}; - -use crate::RgbArgs; - -// TODO: Embed in contract issuance builder -pub struct PanickingResolver; -impl ResolveHeight for PanickingResolver { - type Error = Infallible; - fn resolve_anchor(&mut self, _: &Anchor) -> Result { - unreachable!("PanickingResolver must be used only for newly issued contract validation") - } -} -impl ResolveTx for PanickingResolver { - fn resolve_tx(&self, _: Layer1, _: Txid) -> Result { - unreachable!("PanickingResolver must be used only for newly issued contract validation") - } -} - -impl RgbArgs { - pub fn resolver(&self) -> impl ResolveTx + ResolveHeight { - #[derive(Default)] - struct DumbResolver(); - impl ResolveHeight for DumbResolver { - type Error = Infallible; - fn resolve_anchor(&mut self, _: &Anchor) -> Result { - todo!() - } - } - impl ResolveTx for DumbResolver { - fn resolve_tx(&self, _: Layer1, _: Txid) -> Result { todo!() } - } - DumbResolver::default() - } -} diff --git a/examples/rgb20-demo.yaml b/examples/rgb20-demo.yaml index dddc475..3c9fbe4 100644 --- a/examples/rgb20-demo.yaml +++ b/examples/rgb20-demo.yaml @@ -3,10 +3,10 @@ interface: RGB20 globals: spec: naming: - ticker: TST - name: Test asset by RGB command-line + ticker: DBG + name: Debug asset details: ~ - precision: 8 + precision: 2 data: terms: > SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER @@ -23,10 +23,10 @@ globals: HEREIN. PURCHASER ACKNOWLEDGES THAT THE PURCHASE PRICE REFLECTS AND TAKES INTO ACCOUNT THAT THE PROPERTY IS BEING SOLD “AS IS”. media: ~ - issuedSupply: 100000000000000 + issuedSupply: 100000000 created: 1687969158 assignments: assetOwner: - seal: tapret1st:ca43e9a01782343c78fa67cc20d75d81545a8d38a031d361180c36c088639fed:0 - amount: 100000000000000 + seal: tapret1st:4729190e129e4eb0b95b1e2b6050ade623e9b82a740f2d61c22488e0b3cc145a:1 + amount: 100000000 diff --git a/invoice/Cargo.toml b/invoice/Cargo.toml deleted file mode 100644 index e5301c8..0000000 --- a/invoice/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "rgb-invoice" -version = { workspace = true } -description = "Invoicing library for RGB smart contracts" -keywords = { workspace = true } -categories = { workspace = true } -readme = "../README.md" -authors = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -rust-version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } - -[lib] -name = "rgbinvoice" - -[dependencies] -amplify = { workspace = true } -baid58 = { workspace = true } -strict_encoding = { workspace = true } -bp-seals = { workspace = true } -bp-std = { workspace = true } -rgb-std = { workspace = true } -indexmap = { workspace = true } -fluent-uri = "0.1.4" -percent-encoding = "2.3.0" - -[features] -default = [] diff --git a/invoice/src/builder.rs b/invoice/src/builder.rs deleted file mode 100644 index 6ecb38a..0000000 --- a/invoice/src/builder.rs +++ /dev/null @@ -1,170 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::str::FromStr; - -use bpstd::Network; -use rgbstd::stl::Precision; -use rgbstd::ContractId; - -use super::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport, TransportParseError}; - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct RgbInvoiceBuilder(RgbInvoice); - -#[allow(clippy::result_large_err)] -impl RgbInvoiceBuilder { - pub fn new(beneficiary: impl Into) -> Self { - Self(RgbInvoice { - transports: vec![RgbTransport::UnspecifiedMeans], - contract: None, - iface: None, - operation: None, - assignment: None, - beneficiary: beneficiary.into(), - owned_state: InvoiceState::Void, - network: None, - expiry: None, - unknown_query: none!(), - }) - } - - pub fn with(contract_id: ContractId, beneficiary: impl Into) -> Self { - Self::new(beneficiary).set_contract(contract_id) - } - - pub fn rgb20(contract_id: ContractId, beneficiary: impl Into) -> Self { - Self::with(contract_id, beneficiary).set_interface("RGB20") - } - - pub fn rgb20_anything(beneficiary: impl Into) -> Self { - Self::new(beneficiary).set_interface("RGB20") - } - - pub fn set_contract(mut self, contract_id: ContractId) -> Self { - self.0.contract = Some(contract_id); - self - } - - pub fn set_interface(mut self, name: &'static str) -> Self { - self.0.iface = Some(tn!(name)); - self - } - - pub fn set_operation(mut self, name: &'static str) -> Self { - self.0.operation = Some(tn!(name)); - self - } - - pub fn set_assignment(mut self, name: &'static str) -> Self { - self.0.assignment = Some(fname!(name)); - self - } - - pub fn set_amount_raw(mut self, amount: u64) -> Self { - self.0.owned_state = InvoiceState::Amount(amount); - self - } - - pub fn set_amount( - self, - integer: u64, - decimals: u64, - precision: Precision, - ) -> Result { - // 2^64 ~ 10^19 < 10^18 (18 is max value for Precision enum) - let pow = 10u64.pow(precision as u32); - // number of decimals can't be larger than the smallest possible integer - if decimals >= pow { - return Err(self); - } - let Some(mut amount) = integer.checked_mul(pow) else { - return Err(self); - }; - amount = amount.checked_add(decimals).expect( - "integer has at least the same number of zeros in the lowest digits as much as \ - decimals has digits at most, so overflow is not possible", - ); - Ok(self.set_amount_raw(amount)) - } - - /// # Safety - /// - /// The function may cause the loss of the information about the precise - /// amout of the asset, since f64 type doesn't provide full precision - /// required for that. - pub unsafe fn set_amount_approx(self, amount: f64, precision: Precision) -> Result { - if amount <= 0.0 { - return Err(self); - } - let coins = amount.floor(); - let cents = amount - coins; - self.set_amount(coins as u64, cents as u64, precision) - } - - pub fn set_network(mut self, network: impl Into) -> Self { - self.0.network = Some(network.into()); - self - } - - pub fn set_expiry_timestamp(mut self, expiry: i64) -> Self { - self.0.expiry = Some(expiry); - self - } - - pub fn add_transport(self, transport: &str) -> Result { - let transport = match RgbTransport::from_str(transport) { - Err(err) => return Err((self, err)), - Ok(transport) => transport, - }; - Ok(self.add_transport_raw(transport)) - } - - pub fn add_transport_raw(mut self, transport: RgbTransport) -> Self { - self.0.transports.push(transport); - self - } - - pub fn add_transports<'a>( - self, - transports: impl IntoIterator, - ) -> Result { - let res = transports - .into_iter() - .map(RgbTransport::from_str) - .collect::, TransportParseError>>(); - let transports = match res { - Err(err) => return Err((self, err)), - Ok(transports) => transports, - }; - Ok(self.add_transports_raw(transports)) - } - - pub fn add_transports_raw( - mut self, - transports: impl IntoIterator, - ) -> Self { - self.0.transports.extend(transports); - self - } - - pub fn finish(self) -> RgbInvoice { self.0 } -} diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs deleted file mode 100644 index d7c5dc8..0000000 --- a/invoice/src/invoice.rs +++ /dev/null @@ -1,70 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use bpstd::{Address, Network}; -use indexmap::IndexMap; -use rgbstd::{AttachId, ContractId, SecretSeal}; -use strict_encoding::{FieldName, TypeName}; - -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub enum RgbTransport { - JsonRpc { tls: bool, host: String }, - RestHttp { tls: bool, host: String }, - WebSockets { tls: bool, host: String }, - Storm {/* todo */}, - UnspecifiedMeans, -} - -#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] -pub enum InvoiceState { - #[display("")] - Void, - #[display("{0}")] - Amount(u64), - #[display("...")] // TODO - Data(Vec /* StrictVal */), - #[display(inner)] - Attach(AttachId), -} - -#[derive(Clone, Eq, PartialEq, Hash, Debug, Display, From)] -#[display(inner)] -pub enum Beneficiary { - #[from] - BlindedSeal(SecretSeal), - #[from] - WitnessUtxo(Address), -} - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct RgbInvoice { - pub transports: Vec, - pub contract: Option, - pub iface: Option, - pub operation: Option, - pub assignment: Option, - pub beneficiary: Beneficiary, - pub owned_state: InvoiceState, - pub network: Option, - /// UTC unix timestamp - pub expiry: Option, - pub unknown_query: IndexMap, -} diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs deleted file mode 100644 index c9f1db2..0000000 --- a/invoice/src/lib.rs +++ /dev/null @@ -1,34 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[macro_use] -extern crate amplify; -#[macro_use] -extern crate strict_encoding; - -#[allow(clippy::module_inception)] -mod invoice; -mod parse; -mod builder; - -pub use builder::RgbInvoiceBuilder; -pub use invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; -pub use parse::{InvoiceParseError, TransportParseError}; diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs deleted file mode 100644 index 72c70cb..0000000 --- a/invoice/src/parse.rs +++ /dev/null @@ -1,654 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::fmt::{self, Debug, Display, Formatter}; -use std::num::ParseIntError; -use std::str::FromStr; - -use bpstd::{Address, AddressNetwork, Network, UnknownNetwork}; -use fluent_uri::enc::EStr; -use fluent_uri::Uri; -use indexmap::IndexMap; -use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; -use rgbstd::{ContractId, SecretSeal}; -use strict_encoding::{InvalidIdent, TypeName}; - -use super::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; - -const OMITTED: char = '~'; -const EXPIRY: &str = "expiry"; -const NETWORK: &str = "network"; -const ENDPOINTS: &str = "endpoints"; -const TRANSPORT_SEP: char = ','; -const TRANSPORT_HOST_SEP: &str = "://"; -const QUERY_ENCODE: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'[') - .add(b']') - .add(b'&') - .add(b'='); - -#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] -#[display(inner)] -pub enum TransportParseError { - #[display(doc_comments)] - /// invalid transport {0}. - InvalidTransport(String), - - #[display(doc_comments)] - /// invalid transport host {0}. - InvalidTransportHost(String), -} - -#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] -#[display(inner)] -pub enum InvoiceParseError { - #[from] - Uri(fluent_uri::ParseError), - - #[display(doc_comments)] - /// invalid invoice. - Invalid, - - #[display(doc_comments)] - /// invalid invoice scheme {0}. - InvalidScheme(String), - - #[display(doc_comments)] - /// no invoice transport has been provided. - NoTransport, - - #[display(doc_comments)] - /// invalid invoice: contract ID present but no contract interface provided. - ContractIdNoIface, - - #[display(doc_comments)] - /// invalid contract ID. - InvalidContractId(String), - - #[display(doc_comments)] - /// invalid interface {0}. - InvalidIface(String), - - #[display(doc_comments)] - /// invalid expiration timestamp {0}. - InvalidExpiration(String), - - #[display(inner)] - #[from] - InvalidNetwork(UnknownNetwork), - - #[display(doc_comments)] - /// address network `{0:#?}` doesn't match network `{1}` specified in the - /// invoice. - NetworkMismatch(AddressNetwork, Network), - - #[display(doc_comments)] - /// invalid query parameter {0}. - InvalidQueryParam(String), - - #[from] - Id(baid58::Baid58ParseError), - - #[display(doc_comments)] - /// can't recognize beneficiary "": it should be either a bitcoin address or - /// a blinded UTXO seal. - Beneficiary(String), - - #[from] - Num(ParseIntError), - - #[from] - #[display(doc_comments)] - /// invalid interface name. - IfaceName(InvalidIdent), -} - -impl RgbInvoice { - fn has_params(&self) -> bool { - self.expiry.is_some() || - self.transports != vec![RgbTransport::UnspecifiedMeans] || - !self.unknown_query.is_empty() - } - - fn query_params(&self) -> IndexMap { - let mut query_params: IndexMap = IndexMap::new(); - if let Some(expiry) = self.expiry { - query_params.insert(EXPIRY.to_string(), expiry.to_string()); - } - if self.transports != vec![RgbTransport::UnspecifiedMeans] { - let mut transports: Vec = vec![]; - for transport in self.transports.clone() { - transports.push(transport.to_string()); - } - query_params.insert(ENDPOINTS.to_string(), transports.join(&TRANSPORT_SEP.to_string())); - } - query_params.extend(self.unknown_query.clone()); - query_params - } -} - -impl Display for RgbTransport { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RgbTransport::JsonRpc { tls, host } => { - let s = if *tls { "s" } else { "" }; - write!(f, "rpc{s}{TRANSPORT_HOST_SEP}{}", host)?; - } - RgbTransport::RestHttp { tls, host } => { - let s = if *tls { "s" } else { "" }; - write!(f, "http{s}{TRANSPORT_HOST_SEP}{}", host)?; - } - RgbTransport::WebSockets { tls, host } => { - let s = if *tls { "s" } else { "" }; - write!(f, "ws{s}{TRANSPORT_HOST_SEP}{}", host)?; - } - RgbTransport::Storm {} => { - write!(f, "storm{TRANSPORT_HOST_SEP}_/")?; - } - RgbTransport::UnspecifiedMeans => {} - }; - Ok(()) - } -} - -impl FromStr for RgbTransport { - type Err = TransportParseError; - - fn from_str(s: &str) -> Result { - let tokens = s.split_once(TRANSPORT_HOST_SEP); - if tokens.is_none() { - return Err(TransportParseError::InvalidTransport(s.to_string())); - } - let (trans_type, host) = tokens.unwrap(); - if host.is_empty() { - return Err(TransportParseError::InvalidTransportHost(host.to_string())); - } - let host = host.to_string(); - let transport = match trans_type { - "rpc" => RgbTransport::JsonRpc { tls: false, host }, - "rpcs" => RgbTransport::JsonRpc { tls: true, host }, - "http" => RgbTransport::RestHttp { tls: false, host }, - "https" => RgbTransport::RestHttp { tls: true, host }, - "ws" => RgbTransport::WebSockets { tls: false, host }, - "wss" => RgbTransport::WebSockets { tls: true, host }, - "storm" => RgbTransport::Storm {}, - _ => return Err(TransportParseError::InvalidTransport(s.to_string())), - }; - Ok(transport) - } -} - -impl Display for RgbInvoice { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let amt = self.owned_state.to_string(); - if let Some(contract) = self.contract { - Display::fmt(&contract, f)?; - f.write_str("/")?; - } else { - write!(f, "rgb:{OMITTED}/")?; - } - if let Some(iface) = self.iface.clone() { - write!(f, "{iface}/")?; - } else { - write!(f, "{OMITTED}/")?; - } - if let Some(ref op) = self.operation { - write!(f, "{op}/")?; - } - if let Some(ref assignment_name) = self.assignment { - write!(f, "{assignment_name}/")?; - } - if !amt.is_empty() { - write!(f, "{amt}+")?; - } - Display::fmt(&self.beneficiary, f)?; - if self.has_params() { - f.write_str("?")?; - } - let query_params = self.query_params(); - for (key, val) in query_params.iter().take(1) { - write!( - f, - "{}={}", - utf8_percent_encode(key, QUERY_ENCODE), - utf8_percent_encode(val, QUERY_ENCODE) - )?; - } - for (key, val) in query_params.iter().skip(1) { - write!( - f, - "&{}={}", - utf8_percent_encode(key, QUERY_ENCODE), - utf8_percent_encode(val, QUERY_ENCODE) - )?; - } - Ok(()) - } -} - -impl FromStr for RgbInvoice { - type Err = InvoiceParseError; - - fn from_str(s: &str) -> Result { - let uri = Uri::parse(s)?; - - let scheme = uri.scheme().ok_or(InvoiceParseError::Invalid)?.to_string(); - if scheme != "rgb" { - return Err(InvoiceParseError::InvalidScheme(scheme)); - } - - let path = uri - .path() - .segments() - .map(|e| e.to_string()) - .collect::>(); - - let mut network = None; - let mut address_network = None; - - let mut next_path_index = 0; - - let contract_id_str = &path[next_path_index]; - let contract = match ContractId::from_str(contract_id_str) { - Ok(cid) => Some(cid), - Err(_) if contract_id_str == &OMITTED.to_string() => None, - Err(_) => return Err(InvoiceParseError::InvalidContractId(contract_id_str.clone())), - }; - next_path_index += 1; - - let iface_str = &path[next_path_index]; - let iface = match TypeName::try_from(iface_str.clone()) { - Ok(i) => Some(i), - Err(_) if iface_str == &OMITTED.to_string() => None, - Err(_) => return Err(InvoiceParseError::InvalidIface(iface_str.clone())), - }; - next_path_index += 1; - if contract.is_some() && iface.is_none() { - return Err(InvoiceParseError::ContractIdNoIface); - } - - let mut assignment = path[next_path_index].split('+'); - // TODO: support other state types - let (beneficiary_str, value) = match (assignment.next(), assignment.next()) { - (Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::()?)), - (Some(b), None) => (b, InvoiceState::Void), - _ => return Err(InvoiceParseError::Invalid), - }; - - let beneficiary = - match (SecretSeal::from_str(beneficiary_str), Address::from_str(beneficiary_str)) { - (Ok(seal), Err(_)) => Beneficiary::BlindedSeal(seal), - (Err(_), Ok(addr)) => { - address_network = Some(addr.network); - Beneficiary::WitnessUtxo(addr) - } - (Err(_), Err(_)) => { - return Err(InvoiceParseError::Beneficiary(beneficiary_str.to_owned())); - } - (Ok(_), Ok(_)) => { - panic!("found a string which is both valid bitcoin address and UTXO blind seal") - } - }; - - let mut query_params = map_query_params(&uri)?; - - let transports = if let Some(endpoints) = query_params.remove(ENDPOINTS) { - let tokens: Vec<&str> = endpoints.split(TRANSPORT_SEP).collect(); - let mut transport_vec: Vec = vec![]; - for token in tokens { - transport_vec.push( - RgbTransport::from_str(token) - .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))?, - ); - } - transport_vec - } else { - vec![RgbTransport::UnspecifiedMeans] - }; - - let mut expiry = None; - if let Some(exp) = query_params.remove(EXPIRY) { - let timestamp = exp - .parse::() - .map_err(|e| InvoiceParseError::InvalidExpiration(e.to_string()))?; - expiry = Some(timestamp); - } - - if let Some(nw) = query_params.remove(NETWORK) { - let nw = Network::from_str(&nw)?; - if let Some(an) = address_network { - if an.is_testnet() != nw.is_testnet() { - return Err(InvoiceParseError::NetworkMismatch(an, nw)); - } - } - } else if let Some(an) = address_network { - network = Some(match an { - AddressNetwork::Mainnet => Network::Mainnet, - AddressNetwork::Testnet => Network::Testnet3, - AddressNetwork::Regtest => Network::Regtest, - }) - } - - Ok(RgbInvoice { - transports, - contract, - iface, - operation: None, - assignment: None, - beneficiary, - owned_state: value, - network, - expiry, - unknown_query: query_params, - }) - } -} - -fn percent_decode(estr: &EStr) -> Result { - Ok(estr - .decode() - .into_string() - .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))? - .to_string()) -} - -fn map_query_params(uri: &Uri<&str>) -> Result, InvoiceParseError> { - let mut map: IndexMap = IndexMap::new(); - if let Some(q) = uri.query() { - let params = q.split('&'); - for p in params { - if let Some((k, v)) = p.split_once('=') { - map.insert(percent_decode(k)?, percent_decode(v)?); - } else { - return Err(InvoiceParseError::InvalidQueryParam(p.to_string())); - } - } - } - Ok(map) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse() { - // all path parameters - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', "")); - - // no amount - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // no contract ID - let invoice_str = - "rgb:~/RGB20/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // no contract ID nor iface - let invoice_str = "rgb:~/~/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // contract ID provided but no iface - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ - egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::ContractIdNoIface))); - - // invalid contract ID - let invalid_contract_id = "invalid"; - let invoice_str = format!( - "rgb:{invalid_contract_id}/RGB20/utxob:\ - egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb" - ); - let result = RgbInvoice::from_str(&invoice_str); - assert!(matches!(result, - Err(InvoiceParseError::InvalidContractId(c)) if c == invalid_contract_id)); - - // with expiration - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - expiry=1682086371"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // bad expiration - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - expiry=six"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidExpiration(_)))); - - // with bad query parameter - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?expiry"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // with an unknown query parameter - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - unknown=new"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // with two unknown query parameters - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - unknown=new&another=new"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // with expiration and an unknown query parameter - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - expiry=1682086371&unknown=new"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.to_string(), invoice_str); - - // with an unknown query parameter containing percent-encoded text - let invoice_base = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?"; - let query_key_encoded = ":@-%20%23"; - let query_key_decoded = ":@- #"; - let query_val_encoded = "?/.%26%3D"; - let query_val_decoded = "?/.&="; - let invoice = - RgbInvoice::from_str(&format!("{invoice_base}{query_key_encoded}={query_val_encoded}")) - .unwrap(); - let query_params = invoice.query_params(); - assert_eq!(query_params[query_key_decoded], query_val_decoded); - assert_eq!( - invoice.to_string(), - format!("{invoice_base}{query_key_encoded}={query_val_encoded}") - ); - - // no scheme - let invoice_str = "2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ - egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::Invalid))); - - // invalid scheme - let invoice_str = "bad:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ - egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidScheme(_)))); - - // empty transport endpoint specification - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints="; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // invalid transport endpoint specification - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=bad"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // invalid transport variant - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpca://host.example.com"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // rgb-rpc variant - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpc://host.example.com"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { - tls: false, - host: "host.example.com".to_string() - }]); - assert_eq!(invoice.to_string(), invoice_str); - - // rgb-rpc variant, host containing authentication, "-" characters and port - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpcs://user:pass@host-1.ex-ample.com:1234"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { - tls: true, - host: "user:pass@host-1.ex-ample.com:1234".to_string() - }]); - assert_eq!(invoice.to_string(), invoice_str); - - // rgb-rpc variant, IPv6 host - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpcs://%5B2001:db8::1%5D:1234"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { - tls: true, - host: "[2001:db8::1]:1234".to_string() - }]); - assert_eq!(invoice.to_string(), invoice_str); - - // rgb-rpc variant with missing host - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpc://"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // rgb-rpc variant with invalid separator - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpc/host.example.com"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); - - // rgb-rpc variant with invalid transport host specification - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=rpc://ho]t"; - let result = RgbInvoice::from_str(invoice_str); - assert!(matches!(result, Err(InvoiceParseError::Uri(_)))); - - // rgb+http variant - let invoice_str = "rgb:\ - 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=https://\ - host.example.com"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - let transports = vec![RgbTransport::RestHttp { - tls: true, - host: "host.example.com".to_string(), - }]; - assert_eq!(invoice.transports, transports); - assert_eq!(invoice.to_string(), invoice_str); - - // rgb+ws variant - let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ - endpoints=wss://host.example.com"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - let transports = vec![RgbTransport::WebSockets { - tls: true, - host: "host.example.com".to_string(), - }]; - assert_eq!(invoice.transports, transports); - assert_eq!(invoice.to_string(), invoice_str); - - // TODO: rgb+storm variant - - // multiple transports - let invoice_str = "rgb:\ - 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ - 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=rpcs://\ - host1.example.com,http://host2.example.com,ws://host3.example.com"; - let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - let transports = vec![ - RgbTransport::JsonRpc { - tls: true, - host: "host1.example.com".to_string(), - }, - RgbTransport::RestHttp { - tls: false, - host: "host2.example.com".to_string(), - }, - RgbTransport::WebSockets { - tls: false, - host: "host3.example.com".to_string(), - }, - ]; - assert_eq!(invoice.transports, transports); - assert_eq!(invoice.to_string(), invoice_str); - - // empty transport parse error - let result = RgbTransport::from_str(""); - assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); - - // invalid transport parse error - let result = RgbTransport::from_str("bad"); - assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); - - // invalid transport variant parse error - let result = RgbTransport::from_str("rpca://host.example.com"); - assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); - - // rgb-rpc variant with missing host parse error - let result = RgbTransport::from_str("rpc://"); - assert!(matches!(result, Err(TransportParseError::InvalidTransportHost(_)))); - - // rgb-rpc variant with invalid separator parse error - let result = RgbTransport::from_str("rpc/host.example.com"); - assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); - } -} diff --git a/psbt/Cargo.toml b/psbt/Cargo.toml index 921b443..6cee557 100644 --- a/psbt/Cargo.toml +++ b/psbt/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "psbt-" -version = "0.10.10" -description = "RGB smart contract invoicing library" -keywords = ["bitcoin", "invoices", "rgb", "smart-contracts", "lnp-bp"] +name = "rgb-psbt" +version = { workspace = true } +description = "Partially signed bitcoin transaction RGB extensions" +keywords = ["bitcoin", "invoices", "rgb", "smart-contracts", "psbt"] categories = ["cryptography::cryptocurrencies"] authors = { workspace = true } repository = { workspace = true } @@ -13,15 +13,18 @@ rust-version = { workspace = true } readme = "../README.md" [lib] -name = "rgbpsbt" +name = "psbt" crate-type = ["cdylib", "rlib"] # We need this for WASM [dependencies] amplify = { workspace = true } baid58 = { workspace = true } +commit_verify = { workspace = true } +strict_encoding = { workspace = true } +bp-core = { workspace = true } bp-std = { workspace = true } -psbt = "=0.10.0-BP-beta.1" -rgb-std = { version = "0.10.10", path = "../std" } +psbt = { workspace = true } +rgb-std = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index 7d12d9a..6643f72 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -1,14 +1,122 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +// Partially signed bitcoin transaction RGB extensions +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2020-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate amplify; + +mod rgb; + +use bp::dbc::opret::OpretProof; +use bp::dbc::tapret::TapretProof; +pub use psbt::*; +use rgbstd::containers::{Batch, Fascia, XchainOutpoint}; +use rgbstd::{AnchorSet, XAnchor}; + +pub use self::rgb::{ + ProprietaryKeyRgb, RgbExt, RgbInExt, RgbOutExt, RgbPsbtError, PSBT_GLOBAL_RGB_TRANSITION, + PSBT_IN_RGB_CONSUMED_BY, PSBT_OUT_RGB_VELOCITY_HINT, PSBT_RGB_PREFIX, +}; + +#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum EmbedError { + /// provided transaction batch references inputs which are absent from the + /// PSBT. Possible it was created for a different PSBT. + AbsentInputs, + + /// the provided PSBT is invalid since it doublespends on some of its + /// inputs. + PsbtRepeatedInputs, +} + +#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] +#[display(inner)] +pub enum CommitError { + #[from] + Rgb(RgbPsbtError), + + #[from] + Dbc(DbcPsbtError), +} + +#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum ExtractError {} + +// TODO: Batch must be homomorphic by the outpoint type (chain) + +pub trait RgbPsbt { + fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError>; + #[allow(clippy::result_large_err)] + fn rgb_commit(&mut self) -> Result; + fn rgb_extract(&self) -> Result; } -#[cfg(test)] -mod tests { - use super::*; +impl RgbPsbt for Psbt { + fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError> { + for info in batch { + let contract_id = info.transition.contract_id; + let mut inputs = info.inputs.into_inner(); + for input in self.inputs_mut() { + let outpoint = input.prevout().outpoint(); + if let Some(pos) = inputs + .iter() + .position(|i| i == &XchainOutpoint::Bitcoin(outpoint)) + { + inputs.remove(pos); + input + .set_rgb_consumer(contract_id, info.id) + .map_err(|_| EmbedError::PsbtRepeatedInputs)?; + } + } + if !inputs.is_empty() { + return Err(EmbedError::AbsentInputs); + } + self.push_rgb_transition(info.transition, info.methods) + .expect("transitions are unique since they are in BTreeMap indexed by opid"); + } + Ok(()) + } + + fn rgb_commit(&mut self) -> Result { + // Convert RGB data to MPCs? Or should we do it at the moment we add them... No, + // since we may require more DBC methods with each additional state transition + let (bundles, methods) = self.rgb_bundles_to_mpc()?; + // DBC commitment for the required methods + let (mut tapret_anchor, mut opret_anchor) = (None, None); + if methods.has_tapret_first() { + tapret_anchor = Some(self.dbc_commit::()?); + } + if methods.has_opret_first() { + opret_anchor = Some(self.dbc_commit::()?); + } + let anchor = AnchorSet::from_split(tapret_anchor, opret_anchor) + .expect("at least one of DBC are present due to CloseMethodSet type guarantees"); + Ok(Fascia { + anchor: XAnchor::Bitcoin(anchor), + bundles, + }) + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn rgb_extract(&self) -> Result { + todo!("implement RGB PSBT fascia extraction for multi-party protocols") } } diff --git a/psbt/src/pay.rs b/psbt/src/pay.rs deleted file mode 100644 index 74df140..0000000 --- a/psbt/src/pay.rs +++ /dev/null @@ -1,304 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap}; -use std::error::Error; -use std::iter; - -use amplify::RawArray; -use bitcoin::hashes::Hash; -use bitcoin::psbt::Psbt; -use bp::seals::txout::CloseMethod; -use bp::{Outpoint, Txid}; -use chrono::Utc; -use rgb::{AssignmentType, ContractId, GraphSeal, Operation, Opout}; -use rgbstd::containers::{Bindle, BuilderSeal, Transfer}; -use rgbstd::interface::{BuilderError, ContractSuppl, TypedState, VelocityHint}; -use rgbstd::persistence::{ConsignerError, Inventory, InventoryError, Stash}; - -use crate::invoice::Beneficiary; -use crate::psbt::{DbcPsbtError, PsbtDbc, RgbExt, RgbInExt, RgbOutExt, RgbPsbtError}; -use crate::{RgbInvoice, RGB_NATIVE_DERIVATION_INDEX, RGB_TAPRET_DERIVATION_INDEX}; - -#[derive(Debug, Display, Error, From)] -#[display(inner)] -pub enum PayError -where E1: From -{ - /// not enough PSBT output found to put all required state (can't add - /// assignment type {1} for {0}-velocity state). - #[display(doc_comments)] - NoBlankOrChange(VelocityHint, AssignmentType), - - /// PSBT lacks beneficiary output matching the invoice. - #[display(doc_comments)] - NoBeneficiaryOutput, - - /// unspecified contract - #[display(doc_comments)] - NoContract, - - /// unspecified interface - #[display(doc_comments)] - NoIface, - - /// state provided via PSBT inputs is not sufficient to cover invoice state - /// requirements. - #[display(doc_comments)] - InsufficientState, - - /// the invoice has expired - #[display(doc_comments)] - InvoiceExpired, - - #[from] - Inventory(InventoryError), - - #[from] - Builder(BuilderError), - - #[from] - Consigner(ConsignerError), - - #[from] - RgbPsbt(RgbPsbtError), - - #[from] - DbcPsbt(DbcPsbtError), -} - -pub trait InventoryWallet: Inventory { - /// # Assumptions - /// - /// 1. If PSBT output has BIP32 derivation information it belongs to our - /// wallet - except when it matches address from the invoice. - #[allow(clippy::result_large_err, clippy::type_complexity)] - fn pay( - &mut self, - invoice: RgbInvoice, - psbt: &mut Psbt, - method: CloseMethod, - ) -> Result, PayError::Error>> - where - Self::Error: From<::Error>, - { - // 1. Prepare the data - if let Some(expiry) = invoice.expiry { - if expiry < Utc::now().timestamp() { - return Err(PayError::InvoiceExpired); - } - } - let contract_id = invoice.contract.ok_or(PayError::NoContract)?; - let iface = invoice.iface.ok_or(PayError::NoIface)?; - let mut main_builder = - self.transition_builder(contract_id, iface.clone(), invoice.operation)?; - - let (beneficiary_output, beneficiary) = match invoice.beneficiary { - Beneficiary::BlindedSeal(seal) => { - let seal = BuilderSeal::Concealed(seal); - (None, seal) - } - Beneficiary::WitnessUtxo(addr) => { - let vout = psbt - .unsigned_tx - .output - .iter() - .enumerate() - .find(|(_, txout)| txout.script_pubkey == addr.script_pubkey()) - .map(|(no, _)| no as u32) - .ok_or(PayError::NoBeneficiaryOutput)?; - let seal = BuilderSeal::Revealed(GraphSeal::new_vout(method, vout)); - (Some(vout), seal) - } - }; - let prev_outputs = psbt - .unsigned_tx - .input - .iter() - .map(|txin| txin.previous_output) - .map(|outpoint| Outpoint::new(outpoint.txid.to_byte_array().into(), outpoint.vout)) - .collect::>(); - - // Classify PSBT outputs which can be used for assignments - let mut out_classes = HashMap::>::new(); - for (no, outp) in psbt.outputs.iter().enumerate() { - if beneficiary_output == Some(no as u32) { - continue; - } - if outp - // NB: Here we assume that if output has derivation information it belongs to our wallet. - .bip32_derivation - .first_key_value() - .map(|(_, src)| src) - .or_else(|| outp.tap_key_origins.first_key_value().map(|(_, (_, src))| src)) - .and_then(|(_, src)| src.into_iter().rev().nth(1)) - .copied() - .map(u32::from) - .filter(|index| *index == RGB_NATIVE_DERIVATION_INDEX || *index == RGB_TAPRET_DERIVATION_INDEX) - .is_some() - { - let class = outp.rgb_velocity_hint().unwrap_or_default(); - out_classes.entry(class).or_default().push(no as u32); - } - } - let mut out_classes = out_classes - .into_iter() - .map(|(class, indexes)| (class, indexes.into_iter().cycle())) - .collect::>(); - let mut output_for_assignment = |suppl: Option<&ContractSuppl>, - assignment_type: AssignmentType| - -> Result, PayError<_, _>> { - let velocity = suppl - .and_then(|suppl| suppl.owned_state.get(&assignment_type)) - .map(|s| s.velocity) - .unwrap_or_default(); - let vout = out_classes - .get_mut(&velocity) - .and_then(iter::Cycle::next) - .or_else(|| { - out_classes - .get_mut(&VelocityHint::default()) - .and_then(iter::Cycle::next) - }) - .ok_or(PayError::NoBlankOrChange(velocity, assignment_type))?; - let seal = GraphSeal::new_vout(method, vout); - Ok(BuilderSeal::Revealed(seal)) - }; - - // 2. Prepare and self-consume transition - let assignment_name = invoice - .assignment - .as_ref() - .or_else(|| main_builder.default_assignment().ok()) - .ok_or(BuilderError::NoDefaultAssignment)?; - let assignment_id = main_builder - .assignments_type(assignment_name) - .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; - // TODO: select supplement basing on the signer trust level - let suppl = self - .contract_suppl(contract_id) - .and_then(|set| set.first()) - .cloned(); - let mut sum_inputs = 0u64; - for (opout, state) in self.state_for_outpoints(contract_id, prev_outputs.iter().copied())? { - main_builder = main_builder.add_input(opout)?; - if opout.ty != assignment_id { - let seal = output_for_assignment(suppl.as_ref(), opout.ty)?; - main_builder = main_builder.add_raw_state(opout.ty, seal, state)?; - } else if let TypedState::Amount(value) = state { - sum_inputs += value; - } - } - // Add change - let transition = match invoice.owned_state { - TypedState::Amount(amt) => { - match sum_inputs.cmp(&amt) { - Ordering::Greater => { - let seal = output_for_assignment(suppl.as_ref(), assignment_id)?; - let change = TypedState::Amount(sum_inputs - amt); - main_builder = main_builder.add_raw_state(assignment_id, seal, change)?; - } - Ordering::Less => return Err(PayError::InsufficientState), - Ordering::Equal => {} - } - main_builder - .add_raw_state(assignment_id, beneficiary, TypedState::Amount(amt))? - .complete_transition(contract_id)? - } - _ => { - todo!("only TypedState::Amount is currently supported") - } - }; - - // 3. Prepare and self-consume other transitions - let mut contract_inputs = HashMap::>::new(); - let mut spent_state = HashMap::>::new(); - for outpoint in prev_outputs { - for id in self.contracts_by_outpoints([outpoint])? { - contract_inputs.entry(id).or_default().push(outpoint); - if id == contract_id { - continue; - } - spent_state - .entry(id) - .or_default() - .extend(self.state_for_outpoints(id, [outpoint])?); - } - } - // Construct blank transitions, self-consume them - let mut other_transitions = HashMap::with_capacity(spent_state.len()); - for (id, opouts) in spent_state { - let mut blank_builder = self.blank_builder(id, iface.clone())?; - // TODO: select supplement basing on the signer trust level - let suppl = self.contract_suppl(id).and_then(|set| set.first()); - - for (opout, state) in opouts { - let seal = output_for_assignment(suppl, opout.ty)?; - blank_builder = blank_builder - .add_input(opout)? - .add_raw_state(opout.ty, seal, state)?; - } - - other_transitions.insert(id, blank_builder.complete_transition(contract_id)?); - } - - // 4. Add transitions to PSBT - other_transitions.insert(contract_id, transition); - for (id, transition) in other_transitions { - let inputs = contract_inputs.remove(&id).unwrap_or_default(); - for (input, txin) in psbt.inputs.iter_mut().zip(&psbt.unsigned_tx.input) { - let prevout = txin.previous_output; - let outpoint = Outpoint::new(prevout.txid.to_byte_array().into(), prevout.vout); - if inputs.contains(&outpoint) { - input.set_rgb_consumer(id, transition.id())?; - } - } - psbt.push_rgb_transition(transition)?; - } - // Here we assume the provided PSBT is final: its inputs and outputs will not be - // modified after calling this method. - let bundles = psbt.rgb_bundles()?; - // TODO: Make it two-staged, such that PSBT editing will be allowed by other - // participants as required for multiparty protocols like coinjoin. - psbt.rgb_bundle_to_lnpbp4()?; - let anchor = psbt.dbc_conclude(method)?; - // TODO: Ensure that with PSBTv2 we remove flag allowing PSBT modification. - - // 4. Prepare transfer - let witness_txid = psbt.unsigned_tx.txid(); - self.consume_anchor(anchor)?; - for (id, bundle) in bundles { - self.consume_bundle(id, bundle, witness_txid.to_byte_array().into())?; - } - let beneficiary = match beneficiary { - BuilderSeal::Revealed(seal) => BuilderSeal::Revealed( - seal.resolve(Txid::from_raw_array(witness_txid.to_byte_array())), - ), - BuilderSeal::Concealed(seal) => BuilderSeal::Concealed(seal), - }; - let transfer = self.transfer(contract_id, [beneficiary])?; - - Ok(transfer) - } -} - -impl InventoryWallet for I where I: Inventory {} diff --git a/psbt/src/rgb.rs b/psbt/src/rgb.rs index 8e6800d..2b67da1 100644 --- a/psbt/src/rgb.rs +++ b/psbt/src/rgb.rs @@ -19,24 +19,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO: Implement state transition ops for PSBT - -use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet}; -use amplify::confinement::{Confined, U24}; -use amplify::{confinement, Wrapper}; -use bitcoin::psbt::raw::ProprietaryKey; -use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt}; +use amplify::confinement::{Confined, MediumOrdMap, SmallOrdMap, U24}; +use amplify::{confinement, FromSliceError}; +use bp::dbc::Method; use commit_verify::mpc; -use rgb::{BundleItem, ContractId, OpId, Operation, Transition, TransitionBundle}; +use psbt::{KeyAlreadyPresent, KeyMap, MpcPsbtError, PropKey, Psbt}; use rgbstd::accessors::{MergeReveal, MergeRevealError}; +use rgbstd::containers::CloseMethodSet; use rgbstd::interface::VelocityHint; -use strict_encoding::{SerializeError, StrictDeserialize, StrictSerialize}; - -use super::lnpbp4::OutputLnpbp4; -use super::opret::OutputOpret; -use super::tapret::OutputTapret; +use rgbstd::{ContractId, OpId, Operation, Transition, TransitionBundle, Vin}; +use strict_encoding::{DeserializeError, StrictDeserialize, StrictSerialize}; // TODO: Instead of storing whole RGB contract in PSBT create a shortened // contract version which skips all info not important for hardware @@ -46,47 +40,59 @@ use super::tapret::OutputTapret; // pub const PSBT_GLOBAL_RGB_CONTRACT: u8 = 0x00; /// PSBT proprietary key prefix used for RGB. -pub const PSBT_RGB_PREFIX: &[u8] = b"RGB"; +pub const PSBT_RGB_PREFIX: &str = "RGB"; /// Proprietary key subtype for storing RGB state transition in global map. -pub const PSBT_GLOBAL_RGB_TRANSITION: u8 = 0x01; +pub const PSBT_GLOBAL_RGB_TRANSITION: u64 = 0x01; +/// Proprietary key subtype for storing information on which closed methods +/// should be used for each of RGB state transitions. +pub const PSBT_GLOBAL_RGB_CLOSE_METHODS: u64 = 0x02; /// Proprietary key subtype for storing RGB state transition operation id which /// consumes this input. -pub const PSBT_IN_RGB_CONSUMED_BY: u8 = 0x03; +pub const PSBT_IN_RGB_CONSUMED_BY: u64 = 0x01; /// Proprietary key subtype for storing hint for the velocity of the state /// which can be assigned to the provided output. -pub const PSBT_OUT_RGB_VELOCITY_HINT: u8 = 0x10; +pub const PSBT_OUT_RGB_VELOCITY_HINT: u64 = 0x01; /// Extension trait for static functions returning RGB-related proprietary keys. pub trait ProprietaryKeyRgb { /// Constructs [`PSBT_GLOBAL_RGB_TRANSITION`] proprietary key. - fn rgb_transition(opid: OpId) -> ProprietaryKey { - ProprietaryKey { - prefix: PSBT_RGB_PREFIX.to_vec(), + fn rgb_transition(opid: OpId) -> PropKey { + PropKey { + identifier: PSBT_RGB_PREFIX.to_owned(), subtype: PSBT_GLOBAL_RGB_TRANSITION, - key: opid.to_vec(), + data: opid.to_vec().into(), + } + } + /// Constructs [`PSBT_GLOBAL_RGB_CLOSE_METHODS`] proprietary key. + fn rgb_closing_methods(opid: OpId) -> PropKey { + PropKey { + identifier: PSBT_RGB_PREFIX.to_owned(), + subtype: PSBT_GLOBAL_RGB_CLOSE_METHODS, + data: opid.to_vec().into(), } } /// Constructs [`PSBT_IN_RGB_CONSUMED_BY`] proprietary key. - fn rgb_in_consumed_by(contract_id: ContractId) -> ProprietaryKey { - ProprietaryKey { - prefix: PSBT_RGB_PREFIX.to_vec(), + fn rgb_in_consumed_by(contract_id: ContractId) -> PropKey { + PropKey { + identifier: PSBT_RGB_PREFIX.to_owned(), subtype: PSBT_IN_RGB_CONSUMED_BY, - key: contract_id.to_vec(), + data: contract_id.to_vec().into(), } } - fn rgb_out_velocity_hint() -> ProprietaryKey { - ProprietaryKey { - prefix: PSBT_RGB_PREFIX.to_vec(), + /// Constructs [`PSBT_OUT_RGB_VELOCITY_HINT`] proprietary key. + fn rgb_out_velocity_hint() -> PropKey { + PropKey { + identifier: PSBT_RGB_PREFIX.to_owned(), subtype: PSBT_OUT_RGB_VELOCITY_HINT, - key: vec![], + data: none!(), } } } -impl ProprietaryKeyRgb for ProprietaryKey {} +impl ProprietaryKeyRgb for PropKey {} /// Errors processing RGB-related proprietary PSBT keys and their values. #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -99,86 +105,118 @@ pub enum RgbPsbtError { /// transition {1} which has to be added to RGB UnrelatedTransitions(OpId, OpId, MergeRevealError), - /// PSBT doesn't specify an output which can host tapret or opret - /// commitment. - NoHostOutput, + /// PSBT contains no contract information + NoContracts, + + /// contract {0} listed in the PSBT has zero known transition information. + NoTransitions(ContractId), + + /// invalid contract id data. + #[from(FromSliceError)] + InvalidContractId, + + /// state transition {0} doesn't provide information about seal closing + /// methods used by its inputs. + NoCloseMethod(OpId), + + /// invalid close method data for opid {0} + InvalidCloseMethod(OpId), + + /// PSBT doesn't specify an output which can host {0} commitment. + NoHostOutput(Method), + + /// PSBT contains too many contracts (more than 16 million). + TooManyContracts, /// PSBT contains too many state transitions for a bundle. #[from(confinement::Error)] TooManyTransitionsInBundle, + /// the size of transition {0} exceeds 16 MB. + TransitionTooBig(OpId), + /// state transition data in PSBT are invalid. Details: {0} #[from] - InvalidTransition(SerializeError), + InvalidTransition(DeserializeError), + + #[from] + #[display(inner)] + Mpc(MpcPsbtError), } #[allow(clippy::result_large_err)] pub trait RgbExt { - fn rgb_contract_ids(&self) -> BTreeSet; + fn rgb_contract_ids(&self) -> Result, FromSliceError>; fn rgb_contract_consumers( &self, contract_id: ContractId, - ) -> Result, RgbPsbtError>; + ) -> Result, FromSliceError>; - fn rgb_op_ids(&self, contract_id: ContractId) -> BTreeSet; + fn rgb_op_ids(&self, contract_id: ContractId) -> Result, FromSliceError>; - fn rgb_transitions(&self, contract_id: ContractId) -> BTreeMap { - self.rgb_op_ids(contract_id) - .into_iter() - .filter_map(|opid| self.rgb_transition(opid).map(|ts| (opid, ts))) - .collect() - } + fn rgb_transition(&self, opid: OpId) -> Result, RgbPsbtError>; - fn rgb_transition(&self, opid: OpId) -> Option; + fn rgb_close_methods(&self, opid: OpId) -> Result, RgbPsbtError>; - fn push_rgb_transition(&mut self, transition: Transition) -> Result; + fn push_rgb_transition( + &mut self, + transition: Transition, + methods: CloseMethodSet, + ) -> Result; - fn rgb_bundles(&self) -> Result, RgbPsbtError> { + fn rgb_bundles( + &self, + ) -> Result, RgbPsbtError> { let mut map = BTreeMap::new(); - for contract_id in self.rgb_contract_ids() { - let mut items = BTreeMap::new(); - for (opid, no) in self.rgb_contract_consumers(contract_id)? { - let transition = self.rgb_transition(opid); - match items.entry(opid) { - Entry::Vacant(entry) => { - entry.insert(BundleItem { - inputs: tiny_bset!(no), - transition, - }); - } - Entry::Occupied(entry) => { - let item = entry.into_mut(); - if item.transition.is_none() { - item.transition = transition; - } - item.inputs.push(no)?; - } + for contract_id in self.rgb_contract_ids()? { + let mut input_map = SmallOrdMap::::new(); + let mut known_transitions = SmallOrdMap::::new(); + let mut method_set = None::; + for (opid, vin) in self.rgb_contract_consumers(contract_id)? { + let (transition, methods) = ( + self.rgb_transition(opid)?, + self.rgb_close_methods(opid)? + .ok_or(RgbPsbtError::NoCloseMethod(opid))?, + ); + method_set |= methods; + input_map.insert(vin, opid)?; + if let Some(transition) = transition { + known_transitions.insert(opid, transition)?; } } - let bundle = Confined::try_from(items).map(TransitionBundle::from_inner)?; - map.insert(contract_id, bundle); + let bundle = TransitionBundle { + input_map: Confined::try_from(input_map.into_inner()) + .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, + known_transitions: Confined::try_from(known_transitions.into_inner()) + .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, + }; + map.insert(contract_id, (bundle, method_set.expect("type guarantees"))); } Ok(map) } - fn rgb_bundle_to_lnpbp4(&mut self) -> Result; + fn rgb_bundles_to_mpc( + &mut self, + ) -> Result< + (Confined, 1, U24>, CloseMethodSet), + RgbPsbtError, + >; } impl RgbExt for Psbt { - fn rgb_contract_ids(&self) -> BTreeSet { - self.inputs - .iter() + fn rgb_contract_ids(&self) -> Result, FromSliceError> { + self.inputs() .flat_map(|input| { input .proprietary .keys() .filter(|prop_key| { - prop_key.prefix == PSBT_RGB_PREFIX && + prop_key.identifier == PSBT_RGB_PREFIX && prop_key.subtype == PSBT_IN_RGB_CONSUMED_BY }) - .map(|prop_key| &prop_key.key) - .filter_map(ContractId::from_slice) + .map(|prop_key| prop_key.data.as_slice()) + .map(ContractId::copy_from_slice) }) .collect() } @@ -186,34 +224,51 @@ impl RgbExt for Psbt { fn rgb_contract_consumers( &self, contract_id: ContractId, - ) -> Result, RgbPsbtError> { - let mut consumers: BTreeSet<(OpId, u16)> = bset! {}; - for (no, input) in self.inputs.iter().enumerate() { - if let Some(opid) = input.rgb_consumer(contract_id) { - consumers.insert((opid, no as u16)); + ) -> Result, FromSliceError> { + let mut consumers: BTreeSet<(OpId, Vin)> = bset! {}; + for (no, input) in self.inputs().enumerate() { + if let Some(opid) = input.rgb_consumer(contract_id)? { + consumers.insert((opid, Vin::from_u32(no as u32))); } } Ok(consumers) } - fn rgb_op_ids(&self, contract_id: ContractId) -> BTreeSet { - self.inputs - .iter() - .filter_map(|input| input.rgb_consumer(contract_id)) + fn rgb_op_ids(&self, contract_id: ContractId) -> Result, FromSliceError> { + self.inputs() + .filter_map(|input| input.rgb_consumer(contract_id).transpose()) .collect() } - fn rgb_transition(&self, opid: OpId) -> Option { - let data = self - .proprietary - .get(&ProprietaryKey::rgb_transition(opid))?; - let data = Confined::try_from_iter(data.iter().copied()).ok()?; - Transition::from_strict_serialized::(data).ok() + fn rgb_transition(&self, opid: OpId) -> Result, RgbPsbtError> { + let Some(data) = self.proprietary(&PropKey::rgb_transition(opid)) else { + return Ok(None); + }; + let data = Confined::try_from_iter(data.iter().copied())?; + let transition = Transition::from_strict_serialized::(data)?; + Ok(Some(transition)) } - fn push_rgb_transition(&mut self, mut transition: Transition) -> Result { + fn rgb_close_methods(&self, opid: OpId) -> Result, RgbPsbtError> { + let Some(m) = self.proprietary(&PropKey::rgb_closing_methods(opid)) else { + return Ok(None); + }; + if m.len() == 1 { + if let Ok(method) = CloseMethodSet::try_from(m[0]) { + return Ok(Some(method)); + } + } + Err(RgbPsbtError::InvalidCloseMethod(opid)) + } + + fn push_rgb_transition( + &mut self, + mut transition: Transition, + mut methods: CloseMethodSet, + ) -> Result { let opid = transition.id(); - let prev_transition = self.rgb_transition(opid); + let prev_methods = self.rgb_close_methods(opid)?; + let prev_transition = self.rgb_transition(opid)?; if let Some(ref prev_transition) = prev_transition { transition = transition .merge_reveal(prev_transition.clone()) @@ -221,29 +276,59 @@ impl RgbExt for Psbt { RgbPsbtError::UnrelatedTransitions(prev_transition.id(), opid, err) })?; } - let serialized_transition = transition.to_strict_serialized::()?; - self.proprietary - .insert(ProprietaryKey::rgb_transition(opid), serialized_transition.into_inner()); + let serialized_transition = transition + .to_strict_serialized::() + .map_err(|_| RgbPsbtError::TransitionTooBig(opid))?; + // Since we update transition it's ok to ignore the fact that it previously + // existed + let _ = self + .push_proprietary(PropKey::rgb_transition(opid), serialized_transition.into_inner()); + methods |= prev_methods; + let _ = self.push_proprietary(PropKey::rgb_closing_methods(opid), vec![methods as u8]); Ok(prev_transition.is_none()) } - fn rgb_bundle_to_lnpbp4(&mut self) -> Result { + fn rgb_bundles_to_mpc( + &mut self, + ) -> Result< + (Confined, 1, U24>, CloseMethodSet), + RgbPsbtError, + > { let bundles = self.rgb_bundles()?; - let output = self - .outputs - .iter_mut() - .find(|output| output.is_tapret_host() | output.is_opret_host()) - .ok_or(RgbPsbtError::NoHostOutput)?; - - let len = bundles.len(); - for (contract_id, bundle) in bundles { - output - .set_lnpbp4_message(mpc::ProtocolId::from(contract_id), bundle.bundle_id().into()) - .map_err(|_| RgbPsbtError::AlreadySet)?; + let mut map = MediumOrdMap::new(); + let mut close_methods = None::; + for (contract_id, (bundle, methods)) in bundles { + let protocol_id = mpc::ProtocolId::from(contract_id); + let message = mpc::Message::from(bundle.bundle_id()); + if methods.has_tapret_first() { + // We need to do it each time due to Rust borrow checker + let tapret_host = self + .outputs_mut() + .find(|output| output.is_tapret_host()) + .ok_or(RgbPsbtError::NoHostOutput(Method::TapretFirst))?; + tapret_host.set_mpc_message(protocol_id, message)?; + } + if methods.has_opret_first() { + // We need to do it each time due to Rust borrow checker + let opret_host = self + .outputs_mut() + .find(|output| output.is_opret_host()) + .ok_or(RgbPsbtError::NoHostOutput(Method::OpretFirst))?; + opret_host.set_mpc_message(protocol_id, message)?; + } + map.insert(contract_id, bundle) + .map_err(|_| RgbPsbtError::TooManyContracts)?; + close_methods |= methods; } - Ok(len) + let Some(close_methods) = close_methods else { + return Err(RgbPsbtError::NoContracts); + }; + + let map = Confined::try_from(map.into_inner()).map_err(|_| RgbPsbtError::NoContracts)?; + + Ok((map, close_methods)) } } @@ -254,7 +339,7 @@ pub trait RgbInExt { /// this proprietary key to a standard one. In this case, the invalid /// data will be filtered at the moment of PSBT deserialization and this /// function will return `None` only in situations when the key is absent. - fn rgb_consumer(&self, contract_id: ContractId) -> Option; + fn rgb_consumer(&self, contract_id: ContractId) -> Result, FromSliceError>; /// Adds information about state transition consuming this PSBT input. /// @@ -273,30 +358,33 @@ pub trait RgbInExt { &mut self, contract_id: ContractId, opid: OpId, - ) -> Result; + ) -> Result; } impl RgbInExt for psbt::Input { - fn rgb_consumer(&self, contract_id: ContractId) -> Option { - let data = self + fn rgb_consumer(&self, contract_id: ContractId) -> Result, FromSliceError> { + let Some(data) = self .proprietary - .get(&ProprietaryKey::rgb_in_consumed_by(contract_id))?; - OpId::from_slice(data) + .get(&PropKey::rgb_in_consumed_by(contract_id)) + else { + return Ok(None); + }; + Ok(Some(OpId::copy_from_slice(data)?)) } fn set_rgb_consumer( &mut self, contract_id: ContractId, opid: OpId, - ) -> Result { + ) -> Result { + let key = PropKey::rgb_in_consumed_by(contract_id); match self.rgb_consumer(contract_id) { - None => { - self.proprietary - .insert(ProprietaryKey::rgb_in_consumed_by(contract_id), opid.to_vec()); + Ok(None) | Err(_) => { + let _ = self.push_proprietary(key, opid.to_vec()); Ok(true) } - Some(id) if id == opid => Ok(false), - Some(_) => Err(RgbPsbtError::AlreadySet), + Ok(Some(id)) if id == opid => Ok(false), + Ok(Some(_)) => Err(KeyAlreadyPresent(key)), } } } @@ -323,9 +411,7 @@ pub trait RgbOutExt { impl RgbOutExt for psbt::Output { fn rgb_velocity_hint(&self) -> Option { - let data = self - .proprietary - .get(&ProprietaryKey::rgb_out_velocity_hint())?; + let data = self.proprietary.get(&PropKey::rgb_out_velocity_hint())?; if data.len() != 1 { None } else { @@ -335,8 +421,8 @@ impl RgbOutExt for psbt::Output { fn set_rgb_velocity_hint(&mut self, hint: VelocityHint) -> bool { let prev = self.rgb_velocity_hint(); - self.proprietary - .insert(ProprietaryKey::rgb_out_velocity_hint(), vec![hint as u8]); + self.push_proprietary(PropKey::rgb_out_velocity_hint(), vec![hint as u8]) + .ok(); Some(hint) == prev } } diff --git a/src/descriptor.rs b/src/descriptor.rs index 318714c..dc0e8cd 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -1,4 +1,4 @@ -// RGB standard library for working with smart contracts on Bitcoin & Lightning +// RGB wallet library for smart contracts on Bitcoin & Lightning network // // SPDX-License-Identifier: Apache-2.0 // @@ -19,23 +19,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeSet, HashMap}; use std::str::FromStr; use std::{iter, vec}; use amplify::Wrapper; use bp::dbc::tapret::TapretCommitment; +use bp::dbc::Method; use bp::seals::txout::CloseMethod; use bpstd::{ CompressedPk, Derive, DeriveCompr, DeriveSet, DeriveXOnly, DerivedScript, Idx, IdxBase, - IndexError, IndexParseError, KeyOrigin, Keychain, NormalIndex, TapDerivation, Terminal, - XOnlyPk, XpubDerivable, XpubSpec, + IndexError, IndexParseError, KeyOrigin, Keychain, NormalIndex, TapDerivation, TapScript, + TapTree, Terminal, XOnlyPk, XpubDerivable, XpubSpec, }; -use descriptors::{Descriptor, StdDescr, TrKey}; +use commit_verify::CommitVerify; +use descriptors::{Descriptor, SpkClass, StdDescr, TrKey}; use indexmap::IndexMap; +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display("terminal derivation /10/{0} already has a taptweak assigned")] +pub struct TapTweakAlreadyAssigned(pub NormalIndex); + pub trait DescriptorRgb: Descriptor { fn seal_close_method(&self) -> CloseMethod; + fn add_tapret_tweak( + &mut self, + index: NormalIndex, + tweak: TapretCommitment, + ) -> Result<(), TapTweakAlreadyAssigned>; } #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] @@ -64,6 +75,13 @@ impl RgbKeychain { k == Self::Rgb as u8 || k == Self::Tapret as u8 } pub fn is_seal(self) -> bool { self == Self::Rgb || self == Self::Tapret } + + pub const fn for_method(method: Method) -> Self { + match method { + Method::OpretFirst => Self::Rgb, + Method::TapretFirst => Self::Tapret, + } + } } impl FromStr for RgbKeychain { @@ -88,12 +106,13 @@ impl From for Keychain { fn from(keychain: RgbKeychain) -> Self { Keychain::from(keychain as u8) } } -#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Serialize, Deserialize)] #[serde(crate = "serde_crate", rename_all = "camelCase")] pub struct TapretKey { pub internal_key: K, - pub tweaks: BTreeMap, + // TODO: Allow multiple tweaks per index by introducing derivation using new Terminal trait + pub tweaks: HashMap, } impl TapretKey { @@ -119,8 +138,16 @@ impl Derive for TapretKey { } fn derive(&self, change: impl Into, index: impl Into) -> DerivedScript { - // TODO: Apply tweaks + let change = change.into(); + let index = index.into(); let internal_key = self.internal_key.derive(change, index); + if change.into_inner() == RgbKeychain::Tapret as u8 { + if let Some(tweak) = self.tweaks.get(&index) { + let script_commitment = TapScript::commit(tweak); + let tap_tree = TapTree::with_single_leaf(script_commitment); + return DerivedScript::TaprootScript(internal_key.into(), tap_tree); + } + } DerivedScript::TaprootKeyOnly(internal_key.into()) } } @@ -148,6 +175,8 @@ impl Descriptor for TapretKey { type VarIter<'v> = iter::Empty<&'v ()> where Self: 'v, (): 'v; type XpubIter<'x> = iter::Once<&'x XpubSpec> where Self: 'x; + fn class(&self) -> SpkClass { SpkClass::P2tr } + fn keys(&self) -> Self::KeyIter<'_> { iter::once(&self.internal_key) } fn vars(&self) -> Self::VarIter<'_> { iter::empty() } fn xpubs(&self) -> Self::XpubIter<'_> { iter::once(self.internal_key.xpub_spec()) } @@ -172,9 +201,21 @@ impl Descriptor for TapretKey { impl DescriptorRgb for TapretKey { fn seal_close_method(&self) -> CloseMethod { CloseMethod::TapretFirst } + + fn add_tapret_tweak( + &mut self, + index: NormalIndex, + tweak: TapretCommitment, + ) -> Result<(), TapTweakAlreadyAssigned> { + if self.tweaks.contains_key(&index) { + return Err(TapTweakAlreadyAssigned(index)); + } + self.tweaks.insert(index, tweak); + Ok(()) + } } -#[derive(Clone, Eq, PartialEq, Hash, Debug, From)] +#[derive(Clone, Eq, PartialEq, Debug, From)] #[derive(Serialize, Deserialize)] #[serde( crate = "serde_crate", @@ -216,6 +257,12 @@ where Self: Derive type VarIter<'v> = iter::Empty<&'v ()> where Self: 'v, (): 'v; type XpubIter<'x> = vec::IntoIter<&'x XpubSpec> where Self: 'x; + fn class(&self) -> SpkClass { + match self { + RgbDescr::TapretKey(d) => d.class(), + } + } + fn keys(&self) -> Self::KeyIter<'_> { match self { RgbDescr::TapretKey(d) => d.keys().collect::>(), @@ -258,6 +305,16 @@ where Self: Derive RgbDescr::TapretKey(d) => d.seal_close_method(), } } + + fn add_tapret_tweak( + &mut self, + index: NormalIndex, + tweak: TapretCommitment, + ) -> Result<(), TapTweakAlreadyAssigned> { + match self { + RgbDescr::TapretKey(d) => d.add_tapret_tweak(index, tweak), + } + } } impl From for RgbDescr { diff --git a/src/lib.rs b/src/lib.rs index bc471d8..7c22480 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// RGB smart contract wallet runtime +// RGB wallet library for smart contracts on Bitcoin & Lightning network // // SPDX-License-Identifier: Apache-2.0 // @@ -30,6 +30,12 @@ extern crate serde_crate as serde; mod runtime; mod descriptor; +mod pay; +#[cfg(feature = "esplora")] +mod resolver; -pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapretKey}; +pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapTweakAlreadyAssigned, TapretKey}; +pub use pay::{CompletionError, CompositionError, PayError, TransferParams}; +#[cfg(feature = "esplora")] +pub use resolver::{AnchorResolverError, Resolver, ResolverError}; pub use runtime::{Runtime, RuntimeError}; diff --git a/src/pay.rs b/src/pay.rs new file mode 100644 index 0000000..19fae13 --- /dev/null +++ b/src/pay.rs @@ -0,0 +1,326 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::convert::Infallible; + +use amplify::confinement::Confined; +use bp::dbc::tapret::TapretProof; +use bp::seals::txout::CloseMethod; +use bp::{Outpoint, Sats, ScriptPubkey, Vout}; +use bpwallet::{Beneficiary as BpBeneficiary, ConstructionError, PsbtMeta, TxParams}; +use psbt::{CommitError, EmbedError, Psbt, RgbPsbt, TapretKeyError}; +use rgbstd::containers::{Bindle, BuilderSeal, TerminalSeal, Transfer, VoutSeal}; +use rgbstd::interface::{ContractError, FilterIncludeAll}; +use rgbstd::invoice::{Beneficiary, InvoiceState, RgbInvoice}; +use rgbstd::persistence::{ + ComposeError, ConsignerError, Inventory, InventoryError, Stash, StashError, +}; +use rgbstd::XSeal; + +use crate::{DescriptorRgb, RgbKeychain, Runtime, TapTweakAlreadyAssigned}; + +#[derive(Debug, Display, Error, From)] +#[display(inner)] +pub enum PayError { + #[from] + Composition(CompositionError), + + #[from] + Completion(CompletionError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum CompositionError { + /// unspecified contract. + NoContract, + + /// unspecified interface. + NoIface, + + /// invoice doesn't provide information about the operation, and the used + /// interface do not define default operation. + NoOperation, + + /// invoice doesn't provide information about the assignment type, and the + /// used interface do not define default assignment type. + NoAssignment, + + /// state provided via PSBT inputs is not sufficient to cover invoice state + /// requirements. + InsufficientState, + + /// the invoice has expired. + InvoiceExpired, + + /// one of the RGB assignments spent require presence of tapret output - + /// even this is not a taproot wallet. Unable to create a valid PSBT, manual + /// work is needed. + TapretRequired, + + /// non-fungible state is not yet supported by the invoices. + Unsupported, + + #[from] + #[display(inner)] + Construction(ConstructionError), + + #[from] + #[display(inner)] + Interface(ContractError), + + #[from] + #[display(inner)] + Inventory(InventoryError), + + #[from] + #[display(inner)] + Stash(StashError), + + #[from] + #[display(inner)] + Compose(ComposeError), + + #[from] + #[display(inner)] + Embed(EmbedError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum CompletionError { + /// unspecified contract. + NoContract, + + /// the provided PSBT doesn't pay any sats to the RGB beneficiary address. + NoBeneficiaryOutput, + + /// the provided PSBT has conflicting descriptor in the taptweak output. + InconclusiveDerivation, + + #[from] + #[display(inner)] + MultipleTweaks(TapTweakAlreadyAssigned), + + #[from] + #[display(inner)] + TapretKey(TapretKeyError), + + #[from] + #[display(inner)] + Inventory(InventoryError), + + #[from] + #[display(inner)] + Consigner(ConsignerError), + + #[from] + #[display(inner)] + Commit(CommitError), +} + +#[derive(Clone, PartialEq, Debug)] +pub struct TransferParams { + pub tx: TxParams, + pub min_amount: Sats, +} + +impl TransferParams { + pub fn with(fee: Sats, min_amount: Sats) -> Self { + TransferParams { + tx: TxParams::with(fee), + min_amount, + } + } +} + +impl Runtime { + #[allow(clippy::result_large_err)] + pub fn pay( + &mut self, + invoice: &RgbInvoice, + method: CloseMethod, + params: TransferParams, + ) -> Result<(Psbt, PsbtMeta, Bindle), PayError> { + let (mut psbt, meta) = self.construct_psbt(invoice, method, params)?; + // ... here we pass PSBT around signers, if necessary + let transfer = self.transfer(invoice, &mut psbt)?; + Ok((psbt, meta, transfer)) + } + + #[allow(clippy::result_large_err)] + pub fn construct_psbt( + &mut self, + invoice: &RgbInvoice, + method: CloseMethod, + mut params: TransferParams, + ) -> Result<(Psbt, PsbtMeta), CompositionError> { + let contract_id = invoice.contract.ok_or(CompositionError::NoContract)?; + + let iface_name = invoice.iface.clone().ok_or(CompositionError::NoIface)?; + let iface = self.stock().iface_by_name(&iface_name)?; + let contract = self.contract_iface_named(contract_id, iface_name)?; + let operation = invoice + .operation + .as_ref() + .or(iface.default_operation.as_ref()) + .ok_or(CompositionError::NoOperation)?; + + let assignment_name = invoice + .assignment + .as_ref() + .or_else(|| { + iface.transitions.get(operation).and_then(|t| { + t.default_assignment + .as_ref() + .and_then(|f| t.assignments.get(f).and_then(|arg| (&arg.name).into())) + }) + }) + .cloned() + .ok_or(CompositionError::NoAssignment)?; + + let outputs = match invoice.owned_state { + InvoiceState::Amount(amount) => { + let mut state = contract + .fungible(assignment_name, &FilterIncludeAll)? + .into_inner(); + state.sort_by_key(|a| a.value); + let mut sum = 0u64; + state + .iter() + .rev() + .take_while(|a| { + if sum >= amount { + false + } else { + sum += a.value; + true + } + }) + .map(|a| a.owner) + .collect::>() + } + _ => return Err(CompositionError::Unsupported), + }; + let beneficiaries = match invoice.beneficiary { + Beneficiary::BlindedSeal(_) => vec![], + Beneficiary::WitnessVoutBitcoin(addr) => { + vec![BpBeneficiary::new(addr, params.min_amount)] + } + }; + let outpoints = outputs + .iter() + .filter_map(|o| o.reduce_to_bp()) + .map(|o| Outpoint::new(o.txid, o.vout)); + params.tx.change_keychain = RgbKeychain::for_method(method).into(); + let (mut psbt, meta) = + self.wallet() + .construct_psbt(outpoints, &beneficiaries, params.tx)?; + // TODO: Increase change index + + let (beneficiary_vout, beneficiary_script) = match invoice.beneficiary { + Beneficiary::WitnessVoutBitcoin(addr) => { + let s = addr.script_pubkey(); + let vout = psbt + .outputs() + .position(|output| output.script == s) + .map(|vout| Vout::from_u32(vout as u32)); + (vout, s) + } + Beneficiary::BlindedSeal(_) => (None, none!()), + }; + let batch = + self.compose(invoice, outputs, method, beneficiary_vout, |_, _, _| meta.change_vout)?; + + let methods = batch.close_method_set(); + if methods.has_tapret_first() { + let output = psbt + .outputs_mut() + .find(|o| o.script.is_p2tr() && o.script != beneficiary_script) + .ok_or(CompositionError::TapretRequired)?; + // TODO: Add descriptor id to the tapret host data + output.set_tapret_host().expect("just created"); + } + if methods.has_opret_first() { + let output = psbt.construct_output_expect(ScriptPubkey::op_return(&[]), Sats::ZERO); + output.set_opret_host().expect("just created"); + } + + psbt.sort_outputs_by(|output| !output.is_tapret_host() && !output.is_opret_host()) + .expect("psbt must be modifiable at this stage"); + psbt.complete_construction(); + psbt.rgb_embed(batch)?; + Ok((psbt, meta)) + } + + #[allow(clippy::result_large_err)] + pub fn transfer( + &mut self, + invoice: &RgbInvoice, + psbt: &mut Psbt, + ) -> Result, CompletionError> { + let contract_id = invoice.contract.ok_or(CompletionError::NoContract)?; + + let fascia = psbt.rgb_commit()?; + if let Some(output) = psbt.dbc_output::() { + let terminal = output + .terminal_derivation() + .ok_or(CompletionError::InconclusiveDerivation)?; + let tapret_commitment = output.tapret_commitment()?; + self.wallet_mut() + .add_tapret_tweak(terminal.index, tapret_commitment)?; + } + + let (beneficiary, terminal_seal) = match invoice.beneficiary { + Beneficiary::WitnessVoutBitcoin(addr) => { + let s = addr.script_pubkey(); + let vout = psbt + .outputs() + .position(|output| output.script == s) + .ok_or(CompletionError::NoBeneficiaryOutput)?; + let vout = Vout::from_u32(vout as u32); + let witness_txid = psbt.txid(); + let method = self.wallet().seal_close_method(); + let seal = XSeal::Bitcoin(Outpoint::new(witness_txid, vout).into()); + ( + BuilderSeal::Revealed(seal), + TerminalSeal::BitcoinWitnessVout(VoutSeal::new(method, vout)), + ) + } + Beneficiary::BlindedSeal(seal) => { + (BuilderSeal::Concealed(seal), TerminalSeal::ConcealedUtxo(seal)) + } + }; + + self.stock_mut().consume(fascia)?; + let mut transfer = self.stock().transfer(contract_id, [beneficiary])?; + let mut terminals = transfer.terminals.to_inner(); + for terminal in terminals.values_mut() { + if terminal.seals.contains(&terminal_seal) { + // TODO: Store unsigned tx + terminal.tx = Some(psbt.to_unsigned_tx().into()) + } + } + transfer.terminals = Confined::from_collection_unsafe(terminals); + + Ok(transfer) + } +} diff --git a/src/resolver.rs b/src/resolver.rs new file mode 100644 index 0000000..16dcf4b --- /dev/null +++ b/src/resolver.rs @@ -0,0 +1,117 @@ +// RGB smart contracts for Bitcoin & Lightning +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; + +use bpstd::{Tx, Txid}; +pub use esplora::Error as ResolverError; +use rgbstd::containers::Consignment; +use rgbstd::resolvers::ResolveHeight; +use rgbstd::validation::{ResolveTx, TxResolverError}; +use rgbstd::{Layer1, WitnessAnchor, WitnessId, WitnessOrd, WitnessPos, XAnchor}; + +pub struct Resolver { + esplora_client: esplora::BlockingClient, + terminal_txes: HashMap, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum AnchorResolverError { + #[from] + #[display(inner)] + Error(esplora::Error), + + /// invalid anchor {0} + InvalidAnchor(String), +} + +impl Resolver { + #[allow(clippy::result_large_err)] + pub fn new(url: &str) -> Result { + let esplora_client = esplora::Builder::new(url).build_blocking()?; + Ok(Self { + esplora_client, + terminal_txes: none!(), + }) + } + + pub fn add_terminals(&mut self, consignment: &Consignment) { + self.terminal_txes.extend( + consignment + .terminals + .values() + .filter_map(|t| t.tx.as_ref()) + .map(|tx| (tx.txid(), tx.clone())), + ); + } +} + +impl ResolveHeight for Resolver { + type Error = AnchorResolverError; + + fn resolve_anchor(&mut self, anchor: &XAnchor) -> Result { + let XAnchor::Bitcoin(anchor) = anchor else { + panic!("Liquid is not yet supported") + }; + let txid = anchor + .txid() + .ok_or(AnchorResolverError::InvalidAnchor(format!("{:#?}", anchor)))?; + + if self.terminal_txes.contains_key(&txid) { + return Ok(WitnessAnchor { + witness_ord: WitnessOrd::OffChain, + witness_id: WitnessId::Bitcoin(txid), + }); + } + + let status = self.esplora_client.tx_status(&txid)?; + let ord = match status + .block_height + .and_then(|h| status.block_time.map(|t| (h, t))) + { + Some((h, t)) => WitnessOrd::OnChain( + WitnessPos::new(h, t as i64).ok_or(esplora::Error::InvalidServerData)?, + ), + None => WitnessOrd::OffChain, + }; + Ok(WitnessAnchor { + witness_ord: ord, + witness_id: WitnessId::Bitcoin(txid), + }) + } +} + +impl ResolveTx for Resolver { + fn resolve_bp_tx(&self, layer1: Layer1, txid: Txid) -> Result { + assert_eq!(layer1, Layer1::Bitcoin, "Liquid is not yet supported"); + + if let Some(tx) = self.terminal_txes.get(&txid) { + return Ok(tx.clone()); + } + + self.esplora_client + .tx(&txid) + .map_err(|err| TxResolverError::Other(txid, err.to_string()))? + .ok_or(TxResolverError::Unknown(txid)) + } +} diff --git a/src/runtime.rs b/src/runtime.rs index 350ad6c..da90033 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,4 +1,4 @@ -// RGB smart contract wallet runtime +// RGB wallet library for smart contracts on Bitcoin & Lightning network // // SPDX-License-Identifier: Apache-2.0 // @@ -26,15 +26,15 @@ use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::{fs, io}; +use amplify::IoError; use bpstd::{AddressNetwork, Network, XpubDerivable}; use bpwallet::Wallet; use rgbfs::StockFs; -use rgbstd::containers::{Contract, LoadError, Transfer}; +use rgbstd::containers::{Contract, LoadError, Transfer, XchainOutpoint}; use rgbstd::interface::{BuilderError, OutpointFilter}; use rgbstd::persistence::{Inventory, InventoryDataError, InventoryError, StashError, Stock}; use rgbstd::resolvers::ResolveHeight; use rgbstd::validation::{self, ResolveTx}; -use rgbstd::Output; use strict_types::encoding::{DeserializeError, Ident, SerializeError}; use crate::{DescriptorRgb, RgbDescr}; @@ -43,7 +43,8 @@ use crate::{DescriptorRgb, RgbDescr}; #[display(inner)] pub enum RuntimeError { #[from] - Io(io::Error), + #[from(io::Error)] + Io(IoError), #[from] Serialize(SerializeError), @@ -64,6 +65,9 @@ pub enum RuntimeError { #[from] Builder(BuilderError), + #[from] + PsbtDecode(psbt::DecodeError), + /// wallet with id '{0}' is not known to the system. #[display(doc_comments)] WalletUnknown(Ident), @@ -86,6 +90,10 @@ pub enum RuntimeError { #[from(bpwallet::LoadError)] Bp(bpwallet::RuntimeError), + #[cfg(feature = "esplora")] + #[from] + Esplora(esplora::Error), + #[from] Yaml(serde_yaml::Error), @@ -100,6 +108,7 @@ impl From for RuntimeError { #[derive(Getters)] pub struct Runtime = RgbDescr, K = XpubDerivable> { stock_path: PathBuf, + #[getter(as_mut)] stock: Stock, #[getter(as_mut)] wallet: Wallet, @@ -118,10 +127,11 @@ impl, K> DerefMut for Runtime { } impl, K> OutpointFilter for Runtime { - fn include_output(&self, output: Output) -> bool { + fn include_output(&self, output: impl Into) -> bool { + let output = output.into(); self.wallet .coins() - .any(|utxo| Output::Bitcoin(utxo.outpoint) == output) + .any(|utxo| XchainOutpoint::Bitcoin(utxo.outpoint) == output) } } @@ -223,38 +233,10 @@ impl, K> Runtime { pub fn attach(&mut self, wallet: Wallet) { self.wallet = wallet } - pub fn descriptor(&self) -> &D { self.wallet.deref() } - pub fn unload(self) {} pub fn address_network(&self) -> AddressNetwork { self.network.into() } - /* - pub fn create_wallet( - &mut self, - name: &Ident, - xpub: ExtendedPubKey, - ) -> Result<&RgbDescr, RuntimeError> { - let descr = RgbDescr::Tapret(Tapret { - xpub, - taprets: empty!(), - }); - let entry = match self.wallets.entry(name.clone()) { - Entry::Occupied(_) => return Err(format!("wallet named {name} already exists").into()), - Entry::Vacant(entry) => entry.insert(descr), - }; - Ok(entry) - } - - pub fn wallet(&mut self, name: &Ident) -> Result { - let descr = self - .wallets - .get(name) - .ok_or(RuntimeError::WalletUnknown(name.clone()))?; - Ok(RgbWallet::new(descr.clone())) - } - */ - pub fn import_contract( &mut self, contract: Contract,