diff --git a/Cargo.lock b/Cargo.lock index de66418..b049fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,11 +29,11 @@ dependencies = [ [[package]] name = "aluvm" version = "0.11.0-beta.5" -source = "git+https://github.com/AluVM/rust-aluvm?branch=v0.11#d18ea84836a8a96f3dfe61c99b94f2eab1417819" +source = "git+https://github.com/AluVM/rust-aluvm?branch=develop#25be42a16519383daaa6ba50e65654ff0a1721aa" dependencies = [ "amplify", "ascii-armor", - "baid58", + "baid64", "blake3", "getrandom", "half", @@ -124,47 +124,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -193,12 +194,12 @@ dependencies = [ [[package]] name = "ascii-armor" -version = "0.2.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743d90b41a39d6e3920eef64a70f6411097cbb47141606a45b2a96533ec7111c" +checksum = "673486110323d50d9f33d99f0f526726b4721b1b596284351e75d3692225abe8" dependencies = [ "amplify", - "baid58", + "baid64", "base85", "sha2", "strict_encoding", @@ -226,28 +227,28 @@ dependencies = [ ] [[package]] -name = "baid58" -version = "0.4.4" +name = "baid64" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0585242d87ed976e05db6ae86a0f771f140104a4b6c91b4c3e43b9b2357486" +checksum = "7b8b80494235048845f856b267a4a1d97df59fd14ed7ca92652f834ce93becc6" dependencies = [ - "base58", - "blake3", + "amplify", + "base64 0.22.1", "mnemonic", "sha2", ] [[package]] -name = "base58" -version = "0.2.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base85" @@ -323,7 +324,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#ff275566b6546c39d6a37579861b918104fd859d" +source = "git+https://github.com/BP-WG/bp-core?branch=develop#289f1d31fd404b76b1ac04edb1024bdbe4386416" dependencies = [ "amplify", "chrono", @@ -337,7 +338,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#ff275566b6546c39d6a37579861b918104fd859d" +source = "git+https://github.com/BP-WG/bp-core?branch=develop#289f1d31fd404b76b1ac04edb1024bdbe4386416" dependencies = [ "amplify", "bp-consensus", @@ -355,7 +356,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#ff275566b6546c39d6a37579861b918104fd859d" +source = "git+https://github.com/BP-WG/bp-core?branch=develop#289f1d31fd404b76b1ac04edb1024bdbe4386416" dependencies = [ "amplify", "base85", @@ -369,7 +370,7 @@ dependencies = [ [[package]] name = "bp-derive" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#6741b11d42e92557ae884b7d5208013f0314807f" +source = "git+https://github.com/BP-WG/bp-std?branch=develop#3703d406e76d7ce1f85dd811a416569b4fabd889" dependencies = [ "amplify", "bitcoin_hashes", @@ -391,7 +392,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.21.11", + "rustls 0.21.12", "serde", "serde_json", "sha2", @@ -418,7 +419,7 @@ dependencies = [ [[package]] name = "bp-invoice" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#6741b11d42e92557ae884b7d5208013f0314807f" +source = "git+https://github.com/BP-WG/bp-std?branch=develop#3703d406e76d7ce1f85dd811a416569b4fabd889" dependencies = [ "amplify", "bech32", @@ -430,10 +431,10 @@ dependencies = [ [[package]] name = "bp-seals" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-core?branch=v0.11#ff275566b6546c39d6a37579861b918104fd859d" +source = "git+https://github.com/BP-WG/bp-core?branch=develop#289f1d31fd404b76b1ac04edb1024bdbe4386416" dependencies = [ "amplify", - "baid58", + "baid64", "bp-consensus", "bp-dbc", "commit_verify", @@ -446,7 +447,7 @@ dependencies = [ [[package]] name = "bp-std" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#6741b11d42e92557ae884b7d5208013f0314807f" +source = "git+https://github.com/BP-WG/bp-std?branch=develop#3703d406e76d7ce1f85dd811a416569b4fabd889" dependencies = [ "amplify", "bp-consensus", @@ -458,48 +459,28 @@ dependencies = [ ] [[package]] -name = "bp-util" +name = "bp-wallet" version = "0.11.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf9b988b0c9536f1b95268b592d5425584527006027c38ed172e85bc71bdd85" +source = "git+https://github.com/BP-WG/bp-wallet?branch=develop#03bb754618f99351d56f5f972a62f0580292a8e8" dependencies = [ "amplify", - "base64", + "base64 0.21.7", "bp-electrum", "bp-esplora", "bp-std", - "bp-wallet", "clap", "descriptors", "env_logger", "log", "psbt", "serde", + "serde_json", "serde_yaml", "shellexpand", "strict_encoding", "toml", ] -[[package]] -name = "bp-wallet" -version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.11#e05f6c75a1a150851a4b08fcec74f02230de6bb1" -dependencies = [ - "amplify", - "bp-electrum", - "bp-esplora", - "bp-std", - "cfg_eval", - "descriptors", - "psbt", - "serde", - "serde_json", - "serde_with", - "serde_yaml", - "toml", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -520,9 +501,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -530,17 +511,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_eval" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "chrono" version = "0.4.38" @@ -598,14 +568,14 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "commit_encoding_derive" version = "0.11.0-beta.5" -source = "git+https://github.com/LNP-BP/client_side_validation?branch=v0.11#f51e1e0d010b1531acc939d2443af552e8755bb7" +source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#5e50b251aa5a5c4d83c47dad609a15ef3d50800f" dependencies = [ "amplify", "amplify_syn", @@ -617,7 +587,7 @@ dependencies = [ [[package]] name = "commit_verify" version = "0.11.0-beta.5" -source = "git+https://github.com/LNP-BP/client_side_validation?branch=v0.11#f51e1e0d010b1531acc939d2443af552e8755bb7" +source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#5e50b251aa5a5c4d83c47dad609a15ef3d50800f" dependencies = [ "amplify", "commit_encoding_derive", @@ -744,7 +714,7 @@ dependencies = [ [[package]] name = "descriptors" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#6741b11d42e92557ae884b7d5208013f0314807f" +source = "git+https://github.com/BP-WG/bp-std?branch=develop#3703d406e76d7ce1f85dd811a416569b4fabd889" dependencies = [ "amplify", "bp-derive", @@ -829,15 +799,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -986,9 +956,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -1154,7 +1124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -1175,6 +1145,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -1198,9 +1174,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libredox" @@ -1414,10 +1390,10 @@ dependencies = [ [[package]] name = "psbt" version = "0.11.0-beta.5" -source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#6741b11d42e92557ae884b7d5208013f0314807f" +source = "git+https://github.com/BP-WG/bp-std?branch=develop#3703d406e76d7ce1f85dd811a416569b4fabd889" dependencies = [ "amplify", - "base64", + "base64 0.21.7", "bp-core", "bp-derive", "chrono", @@ -1513,7 +1489,7 @@ version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1551,12 +1527,11 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.5" -source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.11#e83f07bc2f64b9ee370f71aefacafbb59ce26c4e" +source = "git+https://github.com/RGB-WG/rgb-core?branch=develop#b34b2c7b96f669fb8bc7145ec7094cb65d8bbdf3" dependencies = [ "aluvm", "amplify", - "ascii-armor", - "baid58", + "baid64", "bp-core", "chrono", "commit_verify", @@ -1570,18 +1545,35 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "rgb-interfaces" +version = "0.11.0-beta.6" +source = "git+https://github.com/RGB-WG/rgb-interfaces?branch=develop#d688b6a98f547f24b7edc7f96b30b6c1e94893ef" +dependencies = [ + "aluvm", + "amplify", + "bp-core", + "chrono", + "rgb-std", + "serde_json", + "sha2", + "strict_encoding", + "strict_types", +] + [[package]] name = "rgb-invoice" version = "0.11.0-beta.5" -source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#76b6da4c1af821b159a32c76a0c02afd97a23e66" +source = "git+https://github.com/RGB-WG/rgb-std?branch=develop#47413dfdda70dcf7963a9b4b03f406e374e15fae" dependencies = [ "amplify", - "baid58", + "baid64", "bp-core", "bp-invoice", "fluent-uri", "indexmap 2.2.6", "percent-encoding", + "rand", "rgb-core", "serde", "strict_encoding", @@ -1593,7 +1585,7 @@ name = "rgb-psbt" version = "0.11.0-beta.5" dependencies = [ "amplify", - "baid58", + "baid64", "bp-core", "bp-std", "commit_verify", @@ -1611,7 +1603,7 @@ name = "rgb-runtime" version = "0.11.0-beta.5" dependencies = [ "amplify", - "baid58", + "baid64", "bp-core", "bp-electrum", "bp-esplora", @@ -1625,7 +1617,6 @@ dependencies = [ "rgb-psbt", "rgb-std", "serde", - "serde_with", "serde_yaml", "strict_types", ] @@ -1633,12 +1624,12 @@ dependencies = [ [[package]] name = "rgb-std" version = "0.11.0-beta.5" -source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#76b6da4c1af821b159a32c76a0c02afd97a23e66" +source = "git+https://github.com/RGB-WG/rgb-std?branch=develop#47413dfdda70dcf7963a9b4b03f406e374e15fae" dependencies = [ "aluvm", "amplify", "ascii-armor", - "baid58", + "baid64", "base85", "bp-core", "chrono", @@ -1659,16 +1650,16 @@ name = "rgb-wallet" version = "0.11.0-beta.5" dependencies = [ "amplify", - "baid58", + "baid64", "bp-seals", "bp-std", - "bp-util", "bp-wallet", "clap", "commit_verify", "env_logger", "log", "psbt", + "rgb-interfaces", "rgb-runtime", "rgb-std", "serde", @@ -1724,9 +1715,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -1754,14 +1745,14 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" @@ -1903,18 +1894,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -1965,11 +1956,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -1983,9 +1974,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", @@ -2029,7 +2020,7 @@ dependencies = [ [[package]] name = "single_use_seals" version = "0.11.0-beta.5" -source = "git+https://github.com/LNP-BP/client_side_validation?branch=v0.11#f51e1e0d010b1531acc939d2443af552e8755bb7" +source = "git+https://github.com/LNP-BP/client_side_validation?branch=develop#5e50b251aa5a5c4d83c47dad609a15ef3d50800f" dependencies = [ "amplify_derive", ] @@ -2045,9 +2036,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2073,8 +2064,7 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strict_encoding" version = "2.7.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c463f8ea993e323740d78544473e791adb91ac659f5bf2c1a59db64a34f99fc" +source = "git+https://github.com/strict-types/strict-encoding?branch=develop#901e26ee8b01b3bcff4d7ac76acff2fcc633634b" dependencies = [ "amplify", "half", @@ -2085,8 +2075,7 @@ dependencies = [ [[package]] name = "strict_encoding_derive" version = "2.7.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475fa6f1fdde6e0555422b5111ad34bde30a1459af3599f920c3af9829772c0e" +source = "git+https://github.com/strict-types/strict-encoding?branch=develop#901e26ee8b01b3bcff4d7ac76acff2fcc633634b" dependencies = [ "amplify_syn", "heck 0.4.1", @@ -2098,12 +2087,11 @@ dependencies = [ [[package]] name = "strict_types" version = "2.7.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152bd04284e9093f23a911d0d89b7dd950a461af1ed5e243f6215fbcd45e9445" +source = "git+https://github.com/strict-types/strict-types?branch=develop#86486d299bf068c734c406e529d35f0c25805293" dependencies = [ "amplify", "ascii-armor", - "baid58", + "baid64", "half", "indexmap 2.2.6", "serde", @@ -2436,11 +2424,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.6" +version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64", + "base64 0.22.1", "flate2", "log", "once_cell", @@ -2642,11 +2630,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -2805,9 +2793,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index e1d2638..6c4dd43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,25 +22,24 @@ license = "Apache-2.0" [workspace.dependencies] amplify = "4.6.0" -baid58 = "0.4.4" +baid64 = "0.1.0" commit_verify = "0.11.0-beta.5" strict_encoding = "2.7.0-beta.3" strict_types = "2.7.0-beta.3" bp-core = "0.11.0-beta.5" bp-seals = "0.11.0-beta.5" bp-std = "0.11.0-beta.5" -bp-wallet = "0.11.0-beta.5" -bp-util = "0.11.0-beta.5" bp-electrum = "0.11.0-beta.5" bp-esplora = "0.11.0-beta.5" descriptors = "0.11.0-beta.5" psbt = { version = "0.11.0-beta.5", features = ["client-side-validation"] } +bp-wallet = { version = "0.11.0-beta.5" } rgb-std = { version = "0.11.0-beta.5", features = ["fs"] } rgb-psbt = { version = "0.11.0-beta.5", path = "psbt" } +rgb-interfaces = "0.11.0-beta.5" indexmap = "2.0.2" chrono = "0.4.31" serde_crate = { package = "serde", version = "1", features = ["derive"] } -serde_with = "3.4.0" serde_yaml = "0.9.19" log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] } @@ -59,52 +58,56 @@ edition.workspace = true license.workspace = true [lib] -name = "rgb_rt" +name = "rgb" crate-type = ["cdylib", "rlib"] [dependencies] amplify = { workspace = true } -baid58 = { workspace = true } +baid64 = { workspace = true } bp-electrum = { workspace = true, optional = 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 = { workspace = true } +bp-wallet = { workspace = true } rgb-std = { workspace = true } rgb-psbt = { workspace = true } indexmap = { workspace = true } chrono = { workspace = true } serde_crate = { workspace = true, optional = true } serde_yaml = { workspace = true, optional = true } -serde_with = { workspace = true, optional = true } log = { workspace = true, optional = true } [features] default = ["esplora_blocking"] -all = ["esplora_blocking", "electrum", "serde", "log"] -esplora_blocking = ["bp-esplora", "bp-wallet/esplora"] -electrum = ["bp-electrum", "bp-wallet/electrum"] -serde = ["serde_crate", "serde_with", "serde_yaml", "bp-std/serde", "bp-wallet/serde", "descriptors/serde", "rgb-psbt/serde"] +all = ["esplora_blocking", "electrum_blocking", "serde", "log", "fs"] +fs = ["serde", "bp-wallet/fs"] +esplora_blocking = ["bp-esplora"] +electrum_blocking = ["bp-electrum"] +serde = ["serde_crate", "serde_yaml", "bp-std/serde", "descriptors/serde", "rgb-psbt/serde"] [package.metadata.docs.rs] features = ["all"] [patch.crates-io] -commit_verify = { git = "https://github.com/LNP-BP/client_side_validation", branch = "v0.11" } -single_use_seals = { git = "https://github.com/LNP-BP/client_side_validation", branch = "v0.11" } -bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "v0.11" } -bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } -bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } -bp-wallet = { git = "https://github.com/BP-WG/bp-wallet", branch = "v0.11" } -psbt = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } -descriptors = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } -aluvm = { git = "https://github.com/AluVM/rust-aluvm", branch = "v0.11" } -rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" } -rgb-std = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.11" } -rgb-invoice = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.11" } +strict_encoding = { git = "https://github.com/strict-types/strict-encoding", branch = "develop" } +strict_types = { git = "https://github.com/strict-types/strict-types", branch = "develop" } +commit_verify = { git = "https://github.com/LNP-BP/client_side_validation", branch = "develop" } +single_use_seals = { git = "https://github.com/LNP-BP/client_side_validation", branch = "develop" } +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "develop" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "develop" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "develop" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "develop" } +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "develop" } +bp-derive = { git = "https://github.com/BP-WG/bp-std", branch = "develop" } +bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "develop" } +bp-wallet = { git = "https://github.com/BP-WG/bp-wallet", branch = "develop" } +psbt = { git = "https://github.com/BP-WG/bp-std", branch = "develop" } +descriptors = { git = "https://github.com/BP-WG/bp-std", branch = "develop" } +aluvm = { git = "https://github.com/AluVM/rust-aluvm", branch = "develop" } +rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "develop" } +rgb-std = { git = "https://github.com/RGB-WG/rgb-std", branch = "develop" } +rgb-invoice = { git = "https://github.com/RGB-WG/rgb-std", branch = "develop" } +rgb-interfaces = { git = "https://github.com/RGB-WG/rgb-interfaces", branch = "develop" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8f8fa82..4f0740b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,16 +18,16 @@ path = "src/main.rs" [dependencies] amplify = { workspace = true } -baid58 = { workspace = true } +baid64 = { workspace = true } strict_types = { workspace = true, features = ["serde"] } commit_verify = { workspace = true } bp-seals = { workspace = true } bp-std = { workspace = true, features = ["serde"] } -bp-wallet = { workspace = true } -bp-util = { workspace = true } +bp-wallet = { workspace = true, features = ["cli"] } psbt = { workspace = true } rgb-std = { workspace = true, features = ["serde"] } -rgb-runtime = { version = "0.11.0-beta.5", path = "..", features = ["electrum", "esplora_blocking", "log", "serde"] } +rgb-interfaces = { workspace = true } +rgb-runtime = { version = "0.11.0-beta.5", path = "..", features = ["electrum_blocking", "esplora_blocking", "log", "serde", "fs"] } 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 edc6a83..b4c5166 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -19,15 +19,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(clippy::needless_update)] // Caused by the From derivation macro +#![allow(clippy::needless_update)] // Required by From derive macro + +use std::fs; +use std::io::ErrorKind; +use std::path::Path; -use bp_util::{Config, DescriptorOpts}; use bpstd::{Wpkh, XpubDerivable}; -use rgb_rt::{ - electrum, esplora_blocking, AnyResolver, AnyResolverError, RgbDescr, Runtime, RuntimeError, - TapretKey, -}; +use bpwallet::cli::{Args as BpArgs, Config, DescriptorOpts}; +use bpwallet::Wallet; +use rgb::{AnyResolver, RgbDescr, StoredStock, StoredWallet, TapretKey, WalletError}; +use rgbstd::persistence::fs::{LoadFs, StoreFs}; use rgbstd::persistence::Stock; +use strict_types::encoding::{DecodeError, DeserializeError}; use crate::Command; @@ -65,7 +69,7 @@ impl DescriptorOpts for DescrRgbOpts { #[command(author, version, about)] pub struct RgbArgs { #[clap(flatten)] - pub inner: bp_util::Args, + pub inner: BpArgs, } impl Default for RgbArgs { @@ -73,35 +77,56 @@ impl Default for RgbArgs { } impl RgbArgs { - pub fn rgb_stock(&self) -> Result { - eprint!("Loading stock ... "); - let runtime = Runtime::::load_walletless(&self.general.base_dir())?; - eprintln!("success"); + fn load_stock(&self, stock_path: &Path) -> Result { + if self.verbose > 1 { + eprint!("Loading stock ... "); + } - Ok(runtime) + Stock::load(&stock_path).map_err(WalletError::from).or_else(|err| { + if matches!(err, WalletError::Deserialize(DeserializeError::Decode(DecodeError::Io(ref err))) if err.kind() == ErrorKind::NotFound) { + if self.verbose > 1 { + eprint!("stock file is absent, creating a new one ... "); + } + let stock = Stock::default(); + fs::create_dir_all(&stock_path)?; + stock.store(&stock_path)?; + if self.verbose > 1 { + eprintln!("success"); + } + return Ok(stock) + } + eprintln!("stock file is damaged, failing"); + Err(err) + }) } - pub fn rgb_runtime(&self, config: &Config) -> Result { - let bprt = self.inner.bp_runtime::(config)?; - eprint!("Loading stock ... "); - let runtime = Runtime::::load_attach(self.general.base_dir(), bprt)?; - eprintln!("success"); + pub fn rgb_stock(&self) -> Result { + let stock_path = self.general.base_dir(); + let stock = self.load_stock(&stock_path)?; + Ok(StoredStock::attach(stock_path, stock)) + } - Ok(runtime) + pub fn rgb_wallet( + &self, + config: &Config, + ) -> Result>, WalletError> { + let stock_path = self.general.base_dir(); + let stock = self.load_stock(&stock_path)?; + let wallet = self.inner.bp_runtime::(config)?; + let wallet_path = wallet.path().clone(); + let wallet = StoredWallet::attach(stock_path, wallet_path, stock, wallet.detach()); + + Ok(wallet) } - #[allow(clippy::result_large_err)] - pub fn resolver(&self) -> Result { - if self.resolver.electrum != bp_util::DEFAULT_ELECTRUM { - match electrum::Resolver::new(&self.resolver.electrum) { - Ok(c) => Ok(AnyResolver::Electrum(Box::new(c))), - Err(e) => Err(AnyResolverError::Electrum(e)), - } - } else { - match esplora_blocking::Resolver::new(&self.resolver.esplora) { - Ok(c) => Ok(AnyResolver::Esplora(Box::new(c))), - Err(e) => Err(AnyResolverError::Esplora(e)), - } + pub fn resolver(&self) -> Result { + let resolver = match (&self.resolver.esplora, &self.resolver.electrum) { + (None, Some(url)) => AnyResolver::electrum_blocking(url), + (Some(url), None) => AnyResolver::esplora_blocking(url), + _ => unreachable!("clap is broken"), } + .map_err(WalletError::Resolver)?; + resolver.check(self.general.network)?; + Ok(resolver) } } diff --git a/cli/src/command.rs b/cli/src/command.rs index 7a176c2..cc0c129 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -25,25 +25,25 @@ use std::path::PathBuf; use std::str::FromStr; use amplify::confinement::{SmallOrdMap, TinyOrdMap, TinyOrdSet, U16 as MAX16}; -use baid58::ToBaid58; -use bp_util::{BpCommand, Config, Exec}; +use baid64::DisplayBaid64; use bpstd::Sats; +use bpwallet::cli::{BpCommand, Config, Exec}; +use ifaces::{IfaceStandard, Rgb20, Rgb21, Rgb25}; use psbt::{Psbt, PsbtVer}; -use rgb_rt::{DescriptorRgb, RgbKeychain, RuntimeError, TransferParams}; -use rgbstd::containers::{ - BuilderSeal, ContainerVer, ContentId, ContentSigs, Contract, FileContent, Terminal, Transfer, - UniversalFile, +use rgb::containers::{ + BuilderSeal, ContainerVer, ContentId, ContentSigs, Contract, FileContent, Supplement, Terminal, + Transfer, UniversalFile, +}; +use rgb::interface::{AmountChange, FilterExclude, IfaceId}; +use rgb::invoice::{Beneficiary, RgbInvoice, RgbInvoiceBuilder, XChainNet}; +use rgb::persistence::StashReadProvider; +use rgb::schema::SchemaId; +use rgb::validation::Validity; +use rgb::vm::RgbIsa; +use rgb::{ + BundleId, ContractId, DescriptorRgb, GenesisSeal, GraphSeal, Identity, OutputSeal, RgbKeychain, + StateType, TransferParams, WalletError, WalletProvider, XChain, XOutputSeal, }; -use rgbstd::contract::{ContractId, GenesisSeal, GraphSeal, StateType}; -use rgbstd::interface::{AmountChange, ContractSuppl, FilterExclude, IfaceId}; -use rgbstd::invoice::{Beneficiary, RgbInvoice, RgbInvoiceBuilder, XChainNet}; -use rgbstd::persistence::fs::StoreFs; -use rgbstd::persistence::{SchemaIfaces, StashReadProvider}; -use rgbstd::schema::SchemaId; -use rgbstd::validation::Validity; -use rgbstd::vm::RgbIsa; -use rgbstd::{BundleId, OutputSeal, XChain, XOutputSeal}; -use seals::txout::CloseMethod; use serde_crate::{Deserialize, Serialize}; use strict_types::encoding::{FieldName, TypeName}; use strict_types::StrictVal; @@ -56,7 +56,7 @@ use crate::RgbArgs; pub enum Command { #[clap(flatten)] #[display(inner)] - General(bp_util::Command), + General(bpwallet::cli::Command), #[clap(flatten)] #[display(inner)] @@ -66,8 +66,13 @@ pub enum Command { Schemata, /// Prints out list of known RGB interfaces Interfaces, + /// Prints out list of known RGB contracts - Contracts, + #[display("contracts")] + Contracts { + /// Select only contracts using specific interface standard + standard: Option, + }, /// Imports RGB data into the stash: contracts, schema, interfaces, etc #[display("import")] @@ -141,6 +146,9 @@ pub enum Command { /// Schema name to use for the contract schema: SchemaId, //String, + /// Issuer identity string + issuer: Identity, + /// File containing contract genesis description in YAML format contract: PathBuf, }, @@ -170,10 +178,6 @@ pub enum Command { #[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")] @@ -189,8 +193,9 @@ pub enum Command { psbt: Option, }, - /// Prepare consignment for transferring RGB assets. In the most of cases - /// you need to use `transfer` command instead of `prepare` and `consign`. + /// Prepare consignment for transferring RGB assets. In the most of the + /// cases you need to use `transfer` command instead of `prepare` and + /// `consign`. #[display("prepare")] Consign { /// Invoice data @@ -210,10 +215,6 @@ pub enum Command { #[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")] @@ -297,63 +298,76 @@ pub enum DebugCommand { } impl Exec for RgbArgs { - type Error = RuntimeError; + type Error = WalletError; const CONF_FILE_NAME: &'static str = "rgb.toml"; - fn exec(self, config: Config, _name: &'static str) -> Result<(), RuntimeError> { - if let Some(stock) = match &self.command { + fn exec(self, config: Config, _name: &'static str) -> Result<(), WalletError> { + match &self.command { Command::General(cmd) => { self.inner.translate(cmd).exec(config, "rgb")?; - None } + Command::Utxos => { + self.inner + .translate(&BpCommand::Balance { + addr: true, + utxo: true, + }) + .exec(config, "rgb")?; + } + Command::Debug(DebugCommand::Taprets) => { let stock = self.rgb_stock()?; for (witness_id, tapret) in stock.as_stash_provider().taprets()? { println!("{witness_id}\t{tapret}"); } - None } Command::Schemata => { let stock = self.rgb_stock()?; - for schema_iface in stock.schemata()? { - print!("{} ", schema_iface.schema.schema_id()); - for iimpl in schema_iface.iimpls.values() { - let iface = stock.iface(iimpl.iface_id)?; - print!("{} ", iface.name); - } - println!(); + for info in stock.schemata()? { + print!("{info}"); } - None } Command::Interfaces => { let stock = self.rgb_stock()?; - for (id, name) in stock.ifaces()? { - println!("{} {id}", name); + for info in stock.ifaces()? { + print!("{info}"); } - None } - Command::Contracts => { + Command::Contracts { standard: None } => { let stock = self.rgb_stock()?; - for id in stock.contract_ids()? { - println!("{id}"); + for info in stock.contracts()? { + print!("{info}"); } - None } - - Command::Utxos => { - self.inner - .translate(&BpCommand::Balance { - addr: true, - utxo: true, - }) - .exec(config, "rgb")?; - None + Command::Contracts { + standard: Some(IfaceStandard::Rgb20), + } => { + let stock = self.rgb_stock()?; + for info in stock.contracts_by::()? { + print!("{info}"); + } + } + Command::Contracts { + standard: Some(IfaceStandard::Rgb21), + } => { + let stock = self.rgb_stock()?; + for info in stock.contracts_by::()? { + print!("{info}"); + } + } + Command::Contracts { + standard: Some(IfaceStandard::Rgb25), + } => { + let stock = self.rgb_stock()?; + for info in stock.contracts_by::()? { + print!("{info}"); + } } Command::HistoryFungible { contract_id, iface } => { - let runtime = self.rgb_runtime(&config)?; + let wallet = self.rgb_wallet(&config)?; let iface: TypeName = tn!(iface.clone()); - let history = runtime.fungible_history(*contract_id, iface)?; + let history = wallet.fungible_history(*contract_id, iface)?; println!("Amount\tCounterparty\tWitness Id"); for (id, op) in history { let (cparty, more) = match op.state_change { @@ -375,7 +389,6 @@ impl Exec for RgbArgs { .unwrap_or_else(|| s!("none")); println!("{}\t{}{}\t{}", op.state_change, cparty, more, id); } - None } Command::Import { armored, file } => { @@ -386,18 +399,18 @@ impl Exec for RgbArgs { match content { UniversalFile::Kit(kit) => { let id = kit.kit_id(); - eprintln!("Importing kit {id}"); + eprintln!("Importing kit {id}:"); let mut iface_names = map![]; let mut schema_names = map![]; for iface in &kit.ifaces { let iface_id = iface.iface_id(); iface_names.insert(iface_id, &iface.name); - eprintln!("- Interface {} {}", iface.name, iface_id); + eprintln!("- interface {} {:-}", iface.name, iface_id); } for schema in &kit.schemata { let schema_id = schema.schema_id(); schema_names.insert(schema_id, &schema.name); - eprintln!("- Schema {} {}", schema.name, schema_id); + eprintln!("- schema {} {:-}", schema.name, schema_id); } for iimpl in &kit.iimpls { let iface = iface_names @@ -408,24 +421,30 @@ impl Exec for RgbArgs { .get(&iimpl.schema_id) .map(|name| name.to_string()) .unwrap_or_else(|| iimpl.schema_id.to_string()); - eprintln!("- Implementation of {iface} for {schema}",); + eprintln!("- implementation of {iface} for {schema}",); } for lib in &kit.scripts { - eprintln!("- AluVM library {}", lib.id()); + eprintln!("- script library {}", lib.id()); } - eprintln!("- Strict types: {} definitions", kit.types.len()); + eprintln!("- strict types: {} definitions", kit.types.len()); let kit = kit.validate().map_err(|(status, _)| status.to_string())?; stock.import_kit(kit)?; eprintln!("Kit is imported"); } UniversalFile::Contract(contract) => { - let mut resolver = self.resolver()?; let id = contract.consignment_id(); + eprintln!("Importing consignment {id}:"); + let mut resolver = self.resolver()?; + eprint!("- validating the contract {} ... ", contract.contract_id()); let contract = contract .validate(&mut resolver, self.general.network.is_testnet()) - .map_err(|(status, _)| status.to_string())?; + .map_err(|(status, _)| { + eprintln!("failure"); + status.to_string() + })?; + eprintln!("success"); stock.import_contract(contract, &mut resolver)?; - eprintln!("Contract {id} is imported"); + eprintln!("Consignment is imported"); } UniversalFile::Transfer(_) => { return Err(s!("use `validate` and `accept` commands to work with \ @@ -433,7 +452,6 @@ impl Exec for RgbArgs { .into()); } } - Some(stock) } Command::Export { armored: _, @@ -451,13 +469,11 @@ impl Exec for RgbArgs { } else { println!("{contract}"); } - None } Command::Armor { file } => { let content = UniversalFile::load_file(file)?; println!("{content}"); - None } Command::State { @@ -465,10 +481,11 @@ impl Exec for RgbArgs { iface, all, } => { - let runtime = self.rgb_runtime(&config)?; + let wallet = self.rgb_wallet(&config)?; - let iface = runtime.iface(tn!(iface.to_owned()))?.clone(); - let contract = runtime.contract_iface(*contract_id, iface.iface_id())?; + let contract = wallet + .stock() + .contract_iface(*contract_id, tn!(iface.to_owned()))?; println!("Global:"); for global in &contract.iface.global_state { @@ -482,7 +499,9 @@ impl Exec for RgbArgs { println!("\nOwned:"); for owned in &contract.iface.assignments { println!(" {}:", owned.name); - if let Ok(allocations) = contract.fungible(owned.name.clone(), &runtime) { + if let Ok(allocations) = + contract.fungible(owned.name.clone(), wallet.wallet().filter()) + { for allocation in allocations { println!( " amount={}, utxo={}, witness={} # owned by the wallet", @@ -491,8 +510,8 @@ impl Exec for RgbArgs { } } if *all { - if let Ok(allocations) = - contract.fungible(owned.name.clone(), &FilterExclude(&runtime)) + if let Ok(allocations) = contract + .fungible(owned.name.clone(), &FilterExclude(wallet.wallet().filter())) { for allocation in allocations { println!( @@ -504,10 +523,10 @@ impl Exec for RgbArgs { } // TODO: Print out other types of state } - None } Command::Issue { schema: schema_id, + issuer, contract, } => { let mut stock = self.rgb_stock()?; @@ -525,26 +544,23 @@ impl Exec for RgbArgs { .expect("contract must specify interface under which it is constructed") .as_str() .expect("interface name must be a string"); - let SchemaIfaces { - ref schema, - ref iimpls, - } = stock.schema(*schema_id)?; + let schema_ifaces = stock.schema(*schema_id)?; let iface_name = tn!(iface_name.to_owned()); let iface = stock .iface(iface_name.clone()) .or_else(|_| { let id = IfaceId::from_str(iface_name.as_str())?; - stock.iface(id).map_err(RuntimeError::from) + stock.iface(id).map_err(WalletError::from) })? .clone(); let iface_id = iface.iface_id(); - let iface_impl = iimpls.get(&iface_id).ok_or_else(|| { - RuntimeError::Custom(format!( + let iface_impl = schema_ifaces.get(iface_id).ok_or_else(|| { + WalletError::Custom(format!( "no known interface implementation for {iface_name}" )) })?; - let mut builder = stock.contract_builder(*schema_id, iface_id)?; + let mut builder = stock.contract_builder(issuer.clone(), *schema_id, iface_id)?; let types = builder.type_system().clone(); if let Some(globals) = code.get("globals") { @@ -561,7 +577,8 @@ impl Exec for RgbArgs { .find(|info| info.name.as_str() == name) .unwrap_or_else(|| panic!("unknown type name '{name}'")) .id; - let sem_id = schema + let sem_id = schema_ifaces + .schema .global_types .get(&state_type) .expect("invalid schema implementation") @@ -597,7 +614,8 @@ impl Exec for RgbArgs { .find(|info| info.name.as_str() == name) .expect("unknown type name") .id; - let state_schema = schema + let state_schema = schema_ifaces + .schema .owned_types .get(&state_type) .expect("invalid schema implementation"); @@ -641,7 +659,6 @@ impl Exec for RgbArgs { "A new contract {id} is issued and added to the stash.\nUse `export` command \ to export the contract." ); - Some(stock) } Command::Invoice { address_based, @@ -649,24 +666,24 @@ impl Exec for RgbArgs { iface, value, } => { - let mut runtime = self.rgb_runtime(&config)?; + let mut wallet = self.rgb_wallet(&config)?; let iface = TypeName::try_from(iface.to_owned()).expect("invalid interface name"); - let outpoint = runtime + let outpoint = wallet .wallet() .coinselect(Sats::ZERO, |utxo| { RgbKeychain::contains_rgb(utxo.terminal.keychain) }) .next(); - let network = runtime.wallet().network(); + let network = wallet.wallet().network(); let beneficiary = match (address_based, outpoint) { (false, None) => { - return Err(RuntimeError::Custom(s!( + return Err(WalletError::Custom(s!( "blinded invoice requested but no suitable outpoint is available" ))); } (true, _) => { - let addr = runtime + let addr = wallet .wallet() .addresses(RgbKeychain::Rgb) .next() @@ -676,11 +693,11 @@ impl Exec for RgbArgs { } (_, Some(outpoint)) => { let seal = XChain::Bitcoin(GraphSeal::new_random( - runtime.wallet().seal_close_method(), + wallet.wallet().seal_close_method(), outpoint.txid, outpoint.vout, )); - runtime.store_secret_seal(seal)?; + wallet.stock_mut().store_secret_seal(seal)?; Beneficiary::BlindedSeal(*seal.to_secret_seal().as_reduced_unsafe()) } }; @@ -690,22 +707,20 @@ impl Exec for RgbArgs { .set_amount_raw(*value) .finish(); println!("{invoice}"); - Some(runtime.into_stock()) } Command::Prepare { v2, - method, invoice, fee, sats, psbt: psbt_file, } => { - let mut runtime = self.rgb_runtime(&config)?; + let mut wallet = self.rgb_wallet(&config)?; // TODO: Support lock time and RBFs let params = TransferParams::with(*fee, *sats); - let (psbt, _) = runtime - .construct_psbt(invoice, *method, params) + let (psbt, _) = wallet + .construct_psbt(invoice, params) .map_err(|err| err.to_string())?; let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; @@ -719,40 +734,36 @@ impl Exec for RgbArgs { PsbtVer::V2 => println!("{psbt:#}"), }, } - Some(runtime.into_stock()) } Command::Consign { invoice, psbt: psbt_name, consignment: out_file, } => { - let mut runtime = self.rgb_runtime(&config)?; + let mut wallet = self.rgb_wallet(&config)?; let mut psbt_file = File::open(psbt_name)?; let mut psbt = Psbt::decode(&mut psbt_file)?; - let transfer = runtime + let transfer = wallet .transfer(invoice, &mut psbt) .map_err(|err| err.to_string())?; let mut psbt_file = File::create(psbt_name)?; psbt.encode(psbt.version, &mut psbt_file)?; transfer.save_file(out_file)?; - Some(runtime.into_stock()) } Command::Transfer { v2, - method, invoice, fee, sats, psbt: psbt_file, consignment: out_file, } => { - let mut runtime = self.rgb_runtime(&config)?; + let mut wallet = self.rgb_wallet(&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())?; + let (psbt, _, transfer) = + wallet.pay(invoice, params).map_err(|err| err.to_string())?; transfer.save_file(out_file)?; @@ -767,7 +778,6 @@ impl Exec for RgbArgs { PsbtVer::V2 => println!("{psbt:#}"), }, } - Some(runtime.into_stock()) } Command::Inspect { file, dir, path } => { #[derive(Clone, Debug)] @@ -777,7 +787,7 @@ impl Exec for RgbArgs { version: ContainerVer, transfer: bool, terminals: SmallOrdMap, - supplements: TinyOrdSet, + supplements: TinyOrdSet, signatures: TinyOrdMap, } @@ -805,7 +815,7 @@ impl Exec for RgbArgs { for lib in consignment.scripts { let mut buf = Vec::new(); lib.print_disassemble::(&mut buf)?; - map.insert(format!("{}.aluasm", lib.id().to_baid58().mnemonic()), unsafe { + map.insert(format!("{}.aluasm", lib.id().to_baid64_mnemonic()), unsafe { String::from_utf8_unchecked(buf) }); } @@ -833,7 +843,6 @@ impl Exec for RgbArgs { fs::write(format!("{}/{file}", path.display()), value)?; } } - None } Command::Reconstruct { contract: false, @@ -848,7 +857,6 @@ impl Exec for RgbArgs { transfer.save_file(dst)?; } } - None } Command::Reconstruct { contract: true, @@ -863,7 +871,6 @@ impl Exec for RgbArgs { contract.save_file(dst)?; } } - None } Command::Dump { root_dir } => { let stock = self.rgb_stock()?; @@ -875,28 +882,29 @@ impl Exec for RgbArgs { fs::create_dir_all(format!("{root_dir}/stash/bundles"))?; fs::create_dir_all(format!("{root_dir}/stash/witnesses"))?; fs::create_dir_all(format!("{root_dir}/stash/extensions"))?; + fs::create_dir_all(format!("{root_dir}/stash/supplements"))?; fs::create_dir_all(format!("{root_dir}/state"))?; fs::create_dir_all(format!("{root_dir}/index"))?; // Stash - for schema_ifaces in stock.schemata()? { + for (id, schema_ifaces) in stock.as_stash_provider().debug_schemata() { fs::write( format!( - "{root_dir}/stash/schemata/{}.yaml", - schema_ifaces.schema.schema_id() + "{root_dir}/stash/schemata/{}.{id:-#}.yaml", + schema_ifaces.schema.name ), serde_yaml::to_string(&schema_ifaces)?, )?; } - for (id, name) in stock.ifaces()? { + for (id, iface) in stock.as_stash_provider().debug_ifaces() { fs::write( - format!("{root_dir}/stash/ifaces/{id}.{name}.yaml"), - serde_yaml::to_string(stock.iface(id)?)?, + format!("{root_dir}/stash/ifaces/{}.{id:-#}.yaml", iface.name), + serde_yaml::to_string(stock.iface(*id)?)?, )?; } for (id, genesis) in stock.as_stash_provider().debug_geneses() { fs::write( - format!("{root_dir}/stash/geneses/{id}.yaml"), + format!("{root_dir}/stash/geneses/{id:-}.yaml"), serde_yaml::to_string(genesis)?, )?; } @@ -904,7 +912,7 @@ impl Exec for RgbArgs { for suppl in list { fs::write( format!( - "{root_dir}/stash/geneses/{id}.suppl.{}.yaml", + "{root_dir}/stash/geneses/{id:-}.suppl.{}.yaml", suppl.suppl_id() ), serde_yaml::to_string(suppl)?, @@ -929,8 +937,14 @@ impl Exec for RgbArgs { serde_yaml::to_string(extension)?, )?; } + for (id, suppl) in stock.as_stash_provider().debug_suppl() { + fs::write( + format!("{root_dir}/stash/supplements/{id:#}.yaml"), + serde_yaml::to_string(suppl)?, + )?; + } fs::write( - format!("{root_dir}/seal-secret.yaml"), + format!("{root_dir}/stash/seal-secret.yaml"), serde_yaml::to_string(stock.as_stash_provider().debug_secret_seals())?, )?; // TODO: Add sigs debugging @@ -938,7 +952,7 @@ impl Exec for RgbArgs { // State for (id, history) in stock.as_state_provider().debug_history() { fs::write( - format!("{root_dir}/state/{id}.yaml"), + format!("{root_dir}/state/{id:-}.yaml"), serde_yaml::to_string(history)?, )?; } @@ -965,7 +979,6 @@ impl Exec for RgbArgs { serde_yaml::to_string(stock.as_index_provider().debug_terminal_index())?, )?; eprintln!("Dump is successfully generated and saved to '{root_dir}'"); - None } Command::Validate { file } => { let mut resolver = self.resolver()?; @@ -981,7 +994,6 @@ impl Exec for RgbArgs { } else { eprintln!("{status}"); } - None } Command::Accept { force: _, file } => { // TODO: Ensure we properly handle unmined terminal transactions @@ -994,14 +1006,8 @@ impl Exec for RgbArgs { .map_err(|(status, _)| status)?; stock.accept_transfer(valid, &mut resolver)?; eprintln!("Transfer accepted into the stash"); - Some(stock) } - } { - stock - .store(self.general.base_dir()) - .expect("unable to save stock"); } - println!(); Ok(()) diff --git a/cli/src/main.rs b/cli/src/main.rs index 47030a2..8475d3a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -34,9 +34,9 @@ mod args; use std::process::ExitCode; -use bp_util::{Config, Exec, LogLevel}; +use bpwallet::cli::{Config, Exec, LogLevel}; use clap::Parser; -use rgb_rt::RuntimeError; +use rgb::WalletError; pub use crate::args::RgbArgs; pub use crate::command::Command; @@ -50,14 +50,16 @@ fn main() -> ExitCode { } } -fn run() -> Result<(), RuntimeError> { +fn run() -> Result<(), WalletError> { let mut args = RgbArgs::parse(); args.process(); LogLevel::from_verbosity_flag_count(args.verbose).apply(); trace!("Command-line arguments: {:#?}", &args); - eprintln!("RGB: command-line wallet for RGB smart contracts"); - eprintln!(" by LNP/BP Standards Association\n"); + if args.verbose > 3 { + eprintln!("RGB: command-line wallet for RGB smart contracts"); + eprintln!(" by LNP/BP Standards Association\n"); + } let conf = Config::load(&args.conf_path("rgb")); debug!("Executing command: {:?}", args.command); diff --git a/examples/rgb20-demo.con b/examples/rgb20-demo.con index 2ab5893..a6abe6f 100644 --- a/examples/rgb20-demo.con +++ b/examples/rgb20-demo.con @@ -1,31 +1,25 @@ -contract Test implements RGB20 - spec = ( - naming = ( - ticker = "TEST", - name = "Test Asset" - ), - precision = centiMicro - ) - data = ( - terms = - """ - SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER - EXPRESSLY SET FORTH IN THIS AGREEMENT OR ANY OTHER EXPRESS OBLIGATION OF SELLERS PURSUANT TO THE - TERMS HEREOF, AND ACKNOWLEDGING THE PRIOR USE OF THE PROPERTY AND PURCHASER’S OPPORTUNITY - TO INSPECT THE PROPERTY, PURCHASER AGREES TO PURCHASE THE PROPERTY “AS IS”, “WHERE IS”, - WITH ALL FAULTS AND CONDITIONS THEREON. ANY WRITTEN OR ORAL INFORMATION, REPORTS, STATEMENTS, - DOCUMENTS OR RECORDS CONCERNING THE PROPERTY PROVIDED OR MADE AVAILABLE TO PURCHASER, ITS AGENTS - OR CONSTITUENTS BY ANY SELLER, ANY SELLER’S AGENTS, EMPLOYEES OR THIRD PARTIES REPRESENTING OR - PURPORTING TO REPRESENT ANY SELLER, SHALL NOT BE REPRESENTATIONS OR WARRANTIES, UNLESS - SPECIFICALLY SET FORTH HEREIN. IN PURCHASING THE PROPERTY OR TAKING OTHER ACTION HEREUNDER, - PURCHASER HAS NOT AND SHALL NOT RELY ON ANY SUCH DISCLOSURES, BUT RATHER, PURCHASER SHALL RELY - ONLY ON PURCHASER’S OWN INSPECTION OF THE PROPERTY AND THE REPRESENTATIONS AND WARRANTIES - HEREIN. PURCHASER ACKNOWLEDGES THAT THE PURCHASE PRICE REFLECTS AND TAKES INTO ACCOUNT THAT THE - PROPERTY IS BEING SOLD “AS IS”. - """, - media = ~ - ) - issuedSupply = 1_000_000__000_000_00 - created = 1687969158 +contract Test: RGB20 + spec: + "TEST" "Test Asset" centiMicro + terms: + """ + SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER + EXPRESSLY SET FORTH IN THIS AGREEMENT OR ANY OTHER EXPRESS OBLIGATION OF SELLERS PURSUANT TO THE + TERMS HEREOF, AND ACKNOWLEDGING THE PRIOR USE OF THE PROPERTY AND PURCHASER’S OPPORTUNITY + TO INSPECT THE PROPERTY, PURCHASER AGREES TO PURCHASE THE PROPERTY “AS IS”, “WHERE IS”, + WITH ALL FAULTS AND CONDITIONS THEREON. ANY WRITTEN OR ORAL INFORMATION, REPORTS, STATEMENTS, + DOCUMENTS OR RECORDS CONCERNING THE PROPERTY PROVIDED OR MADE AVAILABLE TO PURCHASER, ITS AGENTS + OR CONSTITUENTS BY ANY SELLER, ANY SELLER’S AGENTS, EMPLOYEES OR THIRD PARTIES REPRESENTING OR + PURPORTING TO REPRESENT ANY SELLER, SHALL NOT BE REPRESENTATIONS OR WARRANTIES, UNLESS + SPECIFICALLY SET FORTH HEREIN. IN PURCHASING THE PROPERTY OR TAKING OTHER ACTION HEREUNDER, + PURCHASER HAS NOT AND SHALL NOT RELY ON ANY SUCH DISCLOSURES, BUT RATHER, PURCHASER SHALL RELY + ONLY ON PURCHASER’S OWN INSPECTION OF THE PROPERTY AND THE REPRESENTATIONS AND WARRANTIES + HEREIN. PURCHASER ACKNOWLEDGES THAT THE PURCHASE PRICE REFLECTS AND TAKES INTO ACCOUNT THAT THE + PROPERTY IS BEING SOLD “AS IS”. + """ - assetOwner = 1_000_000__000_000_00 @ tapret1st:01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42:0 + issuedSupply: + 1_000_000__000_000_00 + + assetOwner: + 1_000_000__000_000_00 tapret1st:01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42:0 diff --git a/examples/rgb20-demo.json b/examples/rgb20-demo.json deleted file mode 100644 index a5cec78..0000000 --- a/examples/rgb20-demo.json +++ /dev/null @@ -1,24 +0,0 @@ -{"interface": "RGB20", - "globals": [ - { - "spec": { - "naming": { - "ticker": "TEST", - "name": "Test asset" - }, - "precision": "centiMicro" - }, - "data": { - "terms": "SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER EXPRESSLY SET FORTH IN THIS AGREEMENT OR ANY OTHER EXPRESS OBLIGATION OF SELLERS PURSUANT TO THE TERMS HEREOF, AND ACKNOWLEDGING THE PRIOR USE OF THE PROPERTY AND PURCHASER’S OPPORTUNITY TO INSPECT THE PROPERTY, PURCHASER AGREES TO PURCHASE THE PROPERTY “AS IS”, “WHERE IS”, WITH ALL FAULTS AND CONDITIONS THEREON. ANY WRITTEN OR ORAL INFORMATION, REPORTS, STATEMENTS, DOCUMENTS OR RECORDS CONCERNING THE PROPERTY PROVIDED OR MADE AVAILABLE TO PURCHASER, ITS AGENTS OR CONSTITUENTS BY ANY SELLER, ANY SELLER’S AGENTS, EMPLOYEES OR THIRD PARTIES REPRESENTING OR PURPORTING TO REPRESENT ANY SELLER, SHALL NOT BE REPRESENTATIONS OR WARRANTIES, UNLESS SPECIFICALLY SET FORTH HEREIN. IN PURCHASING THE PROPERTY OR TAKING OTHER ACTION HEREUNDER, PURCHASER HAS NOT AND SHALL NOT RELY ON ANY SUCH DISCLOSURES, BUT RATHER, PURCHASER SHALL RELY ONLY ON PURCHASER’S OWN INSPECTION OF THE PROPERTY AND THE REPRESENTATIONS AND WARRANTIES HEREIN. PURCHASER ACKNOWLEDGES THAT THE PURCHASE PRICE REFLECTS AND TAKES INTO ACCOUNT THAT THE PROPERTY IS BEING SOLD “AS IS”." - }, - "issuedSupply": 100000000000000, - "created": 1687969158 - } - ], -"assignments": [ - { - "assetOwner": { - "tapret1st:01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42:0": 100000000000000 - } - } -]} diff --git a/examples/rgb20-demo.toml b/examples/rgb20-demo.toml deleted file mode 100644 index 8191282..0000000 --- a/examples/rgb20-demo.toml +++ /dev/null @@ -1,24 +0,0 @@ -interface = "RGB20" - -[global] -spec = { naming = { ticker = "TEST", name = "Test asset" }, precision = "centiMicro" } -data = { terms = """ -SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER -EXPRESSLY SET FORTH IN THIS AGREEMENT OR ANY OTHER EXPRESS OBLIGATION OF SELLERS PURSUANT TO THE -TERMS HEREOF, AND ACKNOWLEDGING THE PRIOR USE OF THE PROPERTY AND PURCHASER’S OPPORTUNITY -TO INSPECT THE PROPERTY, PURCHASER AGREES TO PURCHASE THE PROPERTY “AS IS”, “WHERE IS”, -WITH ALL FAULTS AND CONDITIONS THEREON. ANY WRITTEN OR ORAL INFORMATION, REPORTS, STATEMENTS, -DOCUMENTS OR RECORDS CONCERNING THE PROPERTY PROVIDED OR MADE AVAILABLE TO PURCHASER, ITS AGENTS -OR CONSTITUENTS BY ANY SELLER, ANY SELLER’S AGENTS, EMPLOYEES OR THIRD PARTIES REPRESENTING OR -PURPORTING TO REPRESENT ANY SELLER, SHALL NOT BE REPRESENTATIONS OR WARRANTIES, UNLESS -SPECIFICALLY SET FORTH HEREIN. IN PURCHASING THE PROPERTY OR TAKING OTHER ACTION HEREUNDER, -PURCHASER HAS NOT AND SHALL NOT RELY ON ANY SUCH DISCLOSURES, BUT RATHER, PURCHASER SHALL RELY -ONLY ON PURCHASER’S OWN INSPECTION OF THE PROPERTY AND THE REPRESENTATIONS AND WARRANTIES -HEREIN. PURCHASER ACKNOWLEDGES THAT THE PURCHASE PRICE REFLECTS AND TAKES INTO ACCOUNT THAT THE -PROPERTY IS BEING SOLD “AS IS”. -""" } -issuedSupply = 1_000_000_000_000_00 -created = 1687969158 - -[assignment.assetOwner] -"tapret1st:01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42:0" = 1_000_000_000_000_00 diff --git a/examples/rgb20-demo.yaml b/examples/rgb20-demo.yaml index 89c828a..6938c47 100644 --- a/examples/rgb20-demo.yaml +++ b/examples/rgb20-demo.yaml @@ -1,14 +1,13 @@ -interface: RGB20 +interface: RGB20Fixed globals: spec: - naming: - ticker: DBG - name: Debug asset - details: "Pay attention: the asset has no value" + ticker: DBG + name: Debug asset + details: "Pay attention: the asset has no value" precision: 2 - data: - terms: > + terms: + text: > SUBJECT TO, AND WITHOUT IN ANY WAY LIMITING, THE REPRESENTATIONS AND WARRANTIES OF ANY SELLER EXPRESSLY SET FORTH IN THIS AGREEMENT OR ANY OTHER EXPRESS OBLIGATION OF SELLERS PURSUANT TO THE TERMS HEREOF, AND ACKNOWLEDGING THE PRIOR USE OF THE PROPERTY AND PURCHASER’S OPPORTUNITY @@ -24,7 +23,6 @@ globals: PROPERTY IS BEING SOLD “AS IS”. media: ~ issuedSupply: 100000000 - created: 1687969158 assignments: assetOwner: diff --git a/psbt/Cargo.toml b/psbt/Cargo.toml index a2fbf1b..90b7712 100644 --- a/psbt/Cargo.toml +++ b/psbt/Cargo.toml @@ -13,12 +13,12 @@ rust-version = { workspace = true } readme = "../README.md" [lib] -name = "psbt" +name = "psrgbt" crate-type = ["cdylib", "rlib"] # We need this for WASM [dependencies] amplify = { workspace = true } -baid58 = { workspace = true } +baid64 = { workspace = true } commit_verify = { workspace = true } strict_encoding = { workspace = true } bp-core = { workspace = true } @@ -40,4 +40,4 @@ all = ["serde"] serde = ["bp-core/serde", "bp-std/serde", "psbt/serde", "rgb-std/serde"] [package.metadata.docs.rs] -features = [ "all" ] +features = ["all"] diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index 9806976..d2f543b 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -28,8 +28,8 @@ use bp::dbc::opret::OpretProof; use bp::dbc::tapret::TapretProof; pub use psbt::*; pub use rgb::*; -use rgbstd::containers::{AnchorSet, Batch, CloseMethodSet, Fascia}; -use rgbstd::{XChain, XWitnessId}; +use rgbstd::containers::{AnchorSet, Batch, CloseMethodSet, Fascia, PubWitness, XPubWitness}; +use rgbstd::XChain; pub use self::rgb::{ ProprietaryKeyRgb, RgbExt, RgbInExt, RgbOutExt, RgbPsbtError, PSBT_GLOBAL_RGB_TRANSITION, @@ -77,10 +77,11 @@ impl RgbPsbt for Psbt { let contract_id = info.transition.contract_id; let mut inputs = info.inputs.into_inner(); for input in self.inputs_mut() { - inputs.remove(&XChain::Bitcoin(input.prevout().outpoint())); - input - .set_rgb_consumer(contract_id, info.id) - .map_err(|_| EmbedError::PsbtRepeatedInputs)?; + if inputs.remove(&XChain::Bitcoin(input.prevout().outpoint())) { + input + .set_rgb_consumer(contract_id, info.id) + .map_err(|_| EmbedError::PsbtRepeatedInputs)?; + } } if !inputs.is_empty() { return Err(EmbedError::AbsentInputs); @@ -115,8 +116,10 @@ impl RgbPsbt for Psbt { (None, Some(opret)) => AnchorSet::Opret(opret), (Some(tapret), Some(opret)) => AnchorSet::Double { tapret, opret }, }; + // TODO: Use signed transaction here! + let witness = PubWitness::with(self.to_unsigned_tx().finalize()); Ok(Fascia { - witness_id: XWitnessId::Bitcoin(self.txid()), + witness: XPubWitness::Bitcoin(witness), anchor, bundles, }) diff --git a/psbt/src/rgb.rs b/psbt/src/rgb.rs index 49ef4d2..3029acb 100644 --- a/psbt/src/rgb.rs +++ b/psbt/src/rgb.rs @@ -27,10 +27,11 @@ use bp::dbc::Method; use bp::seals::txout::CloseMethod; use commit_verify::mpc; use psbt::{KeyAlreadyPresent, KeyMap, MpcPsbtError, PropKey, Psbt}; -use rgbstd::accessors::{MergeReveal, MergeRevealError}; -use rgbstd::containers::BundleDichotomy; -use rgbstd::interface::VelocityHint; -use rgbstd::{ContractId, InputMap, OpId, Operation, Transition, TransitionBundle, Vin}; +use rgbstd::containers::{BundleDichotomy, VelocityHint}; +use rgbstd::{ + ContractId, InputMap, MergeReveal, MergeRevealError, OpId, Operation, Transition, + TransitionBundle, Vin, +}; use strict_encoding::{DeserializeError, StrictDeserialize, StrictSerialize}; // TODO: Instead of storing whole RGB contract in PSBT create a shortened diff --git a/src/descriptor.rs b/src/descriptor.rs index 2c1c02f..a101354 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -20,8 +20,8 @@ // limitations under the License. use std::collections::{BTreeSet, HashMap}; +use std::iter; use std::str::FromStr; -use std::{iter, vec}; use amplify::Wrapper; use bp::dbc::tapret::TapretCommitment; @@ -109,7 +109,6 @@ impl From for Keychain { fn from(keychain: RgbKeychain) -> Self { Keychain::from(keychain as u8) } } -#[cfg_attr(feature = "serde", serde_as)] #[derive(Clone, Eq, PartialEq, Debug)] #[cfg_attr( feature = "serde", @@ -119,11 +118,6 @@ impl From for Keychain { pub struct TapretKey { pub internal_key: K, // TODO: Allow multiple tweaks per index by introducing derivation using new Terminal trait - // TODO: Change serde implementation for both Terminal and TapretCommitment - #[cfg_attr( - feature = "serde", - serde_as(as = "HashMap") - )] pub tweaks: HashMap, } @@ -188,15 +182,17 @@ impl From> for TapretKey { } impl Descriptor for TapretKey { - type KeyIter<'k> = iter::Once<&'k K> where Self: 'k, K: 'k; - 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()) } + fn keys<'a>(&'a self) -> impl Iterator + where K: 'a { + iter::once(&self.internal_key) + } + fn vars<'a>(&'a self) -> impl Iterator + where (): 'a { + iter::empty() + } + fn xpubs(&self) -> impl Iterator { iter::once(self.internal_key.xpub_spec()) } fn compr_keyset(&self, _terminal: Terminal) -> IndexMap { IndexMap::new() @@ -279,10 +275,6 @@ impl Derive for RgbDescr { impl + DeriveCompr + DeriveXOnly> Descriptor for RgbDescr where Self: Derive { - type KeyIter<'k> = vec::IntoIter<&'k K> where Self: 'k, K: 'k; - 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::Wpkh(d) => d.class(), @@ -290,7 +282,8 @@ where Self: Derive } } - fn keys(&self) -> Self::KeyIter<'_> { + fn keys<'a>(&'a self) -> impl Iterator + where K: 'a { match self { RgbDescr::Wpkh(d) => d.keys().collect::>(), RgbDescr::TapretKey(d) => d.keys().collect::>(), @@ -298,14 +291,12 @@ where Self: Derive .into_iter() } - fn vars(&self) -> Self::VarIter<'_> { - match self { - RgbDescr::Wpkh(d) => d.vars(), - RgbDescr::TapretKey(d) => d.vars(), - } + fn vars<'a>(&'a self) -> impl Iterator + where (): 'a { + iter::empty() } - fn xpubs(&self) -> Self::XpubIter<'_> { + fn xpubs(&self) -> impl Iterator { match self { RgbDescr::Wpkh(d) => d.xpubs().collect::>(), RgbDescr::TapretKey(d) => d.xpubs().collect::>(), diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..0632e29 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,214 @@ +// 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. + +#![allow(clippy::result_large_err)] + +use std::convert::Infallible; +use std::io; + +use amplify::IoError; +use psrgbt::{CommitError, ConstructionError, EmbedError, TapretKeyError}; +use rgbstd::containers::LoadError; +use rgbstd::interface::{BuilderError, ContractError}; +use rgbstd::persistence::{ + ComposeError, ConsignError, ContractIfaceError, FasciaError, StockError, StockErrorAll, + StockErrorMem, +}; +use strict_types::encoding::{DeserializeError, Ident, SerializeError}; + +use crate::{validation, TapTweakAlreadyAssigned}; + +#[derive(Debug, Display, Error, From)] +#[display(inner)] +pub enum WalletError { + #[from] + #[from(io::Error)] + Io(IoError), + + #[from] + Serialize(SerializeError), + + #[from] + Deserialize(DeserializeError), + + #[from] + Load(LoadError), + + #[from] + Builder(BuilderError), + + #[from] + History(HistoryError), + + #[from] + Contract(ContractError), + + #[from] + PsbtDecode(psrgbt::DecodeError), + + /// wallet with id '{0}' is not known to the system. + #[display(doc_comments)] + WalletUnknown(Ident), + + #[from] + InvalidConsignment(validation::Status), + + /// invalid identifier. + #[from] + #[display(doc_comments)] + InvalidId(baid64::Baid64ParseError), + + /// the contract source doesn't fit requirements imposed by the used schema. + /// + /// {0} + #[display(doc_comments)] + IncompleteContract(validation::Status), + + #[cfg(feature = "fs")] + #[from] + #[from(bpwallet::LoadError)] + Bp(bpwallet::RuntimeError), + + /// resolver error: {0} + #[display(doc_comments)] + Resolver(String), + + #[from(StockError)] + #[from(StockErrorAll)] + #[from(StockErrorMem)] + #[display(inner)] + Stock(String), + + #[cfg(feature = "serde_yaml")] + #[from] + Yaml(serde_yaml::Error), + + #[from] + Custom(String), +} + +impl From for WalletError { + fn from(_: Infallible) -> Self { unreachable!() } +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum HistoryError { + /// interface doesn't define default operation + NoDefaultOp, + /// default operation defined by the interface is not a state transition + DefaultOpNotTransition, + /// interface doesn't define default fungible state + NoDefaultAssignment, +} + +#[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)] + Embed(EmbedError), + + #[from(String)] + #[from(StockError)] + #[from(StockErrorMem)] + #[from(StockErrorMem)] + #[display(inner)] + Stock(String), +} + +#[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)] + Commit(CommitError), + + #[from(String)] + #[from(StockErrorMem)] + #[from(StockErrorMem)] + #[display(inner)] + Stock(String), +} diff --git a/src/lib.rs b/src/lib.rs index 5ff46a6..0602dd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,17 +24,22 @@ extern crate amplify; #[cfg(feature = "serde")] #[macro_use] extern crate serde_crate as serde; -#[cfg(feature = "serde")] -#[macro_use] -extern crate serde_with; -mod runtime; mod descriptor; -mod pay; +#[allow(hidden_glob_reexports)] mod resolvers; +mod wallet; +pub mod pay; +mod errors; +#[cfg(feature = "fs")] +mod store; pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapTweakAlreadyAssigned, TapretKey}; -pub use pay::{CompletionError, CompositionError, PayError, TransferParams}; -#[cfg(any(feature = "electrum", feature = "esplora_blocking"))] +pub use errors::{CompletionError, CompositionError, HistoryError, PayError, WalletError}; +pub use pay::{TransferParams, WalletProvider}; +#[cfg(any(feature = "electrum_blocking", feature = "esplora_blocking"))] pub use resolvers::*; -pub use runtime::{ContractOutpointsFilter, Runtime, RuntimeError}; +pub use rgbstd::*; +#[cfg(feature = "fs")] +pub use store::{StoredStock, StoredWallet}; +pub use wallet::{WalletStock, WalletWrapper}; diff --git a/src/pay.rs b/src/pay.rs index 788eed1..7126bcb 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -20,117 +20,26 @@ // limitations under the License. use std::collections::{BTreeMap, BTreeSet}; +use std::marker::PhantomData; +use std::ops::DerefMut; use bp::dbc::tapret::TapretProof; -use bp::seals::txout::{CloseMethod, ExplicitSeal}; +use bp::seals::txout::ExplicitSeal; use bp::{Outpoint, Sats, ScriptPubkey, Vout}; -use bpstd::Address; -use bpwallet::{Beneficiary as BpBeneficiary, ConstructionError, PsbtMeta, TxParams}; -use psbt::{CommitError, EmbedError, Psbt, RgbPsbt, TapretKeyError}; +use bpstd::{psbt, Address}; +use bpwallet::Wallet; +use psrgbt::{ + Beneficiary as BpBeneficiary, Psbt, PsbtConstructor, PsbtMeta, RgbPsbt, TapretKeyError, + TxParams, +}; use rgbstd::containers::Transfer; -use rgbstd::interface::ContractError; +use rgbstd::interface::{OutpointFilter, WitnessFilter}; use rgbstd::invoice::{Amount, Beneficiary, InvoiceState, RgbInvoice}; -use rgbstd::persistence::{ - ComposeError, ConsignError, ContractIfaceError, FasciaError, StockError, StockErrorAll, - StockErrorMem, -}; -use rgbstd::XChain; - -use crate::{ - ContractOutpointsFilter, 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)] - Embed(EmbedError), +use rgbstd::persistence::{IndexProvider, StashProvider, StateProvider, Stock}; +use rgbstd::{ContractId, XChain, XOutpoint}; - #[from] - #[from(StockError)] - #[from(StockErrorMem)] - #[from(StockErrorMem)] - #[display(inner)] - Stock(StockErrorAll), -} - -#[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)] - Commit(CommitError), - - #[from(StockErrorMem)] - #[from(StockErrorMem)] - #[display(inner)] - Stock(StockErrorAll), -} +use crate::wallet::WalletWrapper; +use crate::{CompletionError, CompositionError, DescriptorRgb, PayError, RgbKeychain, Txid}; #[derive(Clone, PartialEq, Debug)] pub struct TransferParams { @@ -147,32 +56,80 @@ impl TransferParams { } } -impl Runtime { +struct ContractOutpointsFilter< + 'stock, + 'wallet, + W: WalletProvider + ?Sized, + K, + S: StashProvider, + H: StateProvider, + P: IndexProvider, +> where W::Descr: DescriptorRgb +{ + contract_id: ContractId, + stock: &'stock Stock, + wallet: &'wallet W, + _phantom: PhantomData, +} + +impl< + 'stock, + 'wallet, + W: WalletProvider + ?Sized, + K, + S: StashProvider, + H: StateProvider, + P: IndexProvider, +> OutpointFilter for ContractOutpointsFilter<'stock, 'wallet, W, K, S, H, P> +where W::Descr: DescriptorRgb +{ + fn include_outpoint(&self, output: impl Into) -> bool { + let output = output.into(); + if !self.wallet.filter().include_outpoint(output) { + return false; + } + matches!(self.stock.contract_assignments_for(self.contract_id, [output]), Ok(list) if !list.is_empty()) + } +} + +pub trait WalletProvider: PsbtConstructor +where Self::Descr: DescriptorRgb +{ + type Filter<'a>: Copy + WitnessFilter + OutpointFilter + where Self: 'a; + fn filter(&self) -> Self::Filter<'_>; + fn descriptor_mut(&mut self) -> &mut Self::Descr; + fn outpoints(&self) -> impl Iterator; + fn txids(&self) -> impl Iterator; + #[allow(clippy::result_large_err)] - pub fn pay( + fn pay( &mut self, + stock: &mut Stock, invoice: &RgbInvoice, - method: CloseMethod, params: TransferParams, ) -> Result<(Psbt, PsbtMeta, Transfer), PayError> { - let (mut psbt, meta) = self.construct_psbt(invoice, method, params)?; + let (mut psbt, meta) = self.construct_psbt_rgb(stock, invoice, params)?; // ... here we pass PSBT around signers, if necessary - let transfer = self.transfer(invoice, &mut psbt)?; + let transfer = self.transfer(stock, invoice, &mut psbt)?; Ok((psbt, meta, transfer)) } #[allow(clippy::result_large_err)] - pub fn construct_psbt( + fn construct_psbt_rgb( &mut self, + stock: &Stock, invoice: &RgbInvoice, - method: CloseMethod, mut params: TransferParams, ) -> Result<(Psbt, PsbtMeta), CompositionError> { let contract_id = invoice.contract.ok_or(CompositionError::NoContract)?; + let method = self.descriptor().seal_close_method(); let iface_name = invoice.iface.clone().ok_or(CompositionError::NoIface)?; - let iface = self.stock().iface(iface_name.clone())?; - let contract = self.contract_iface(contract_id, iface_name)?; + let iface = stock.iface(iface_name.clone()).map_err(|e| e.to_string())?; + let contract = stock + .contract_iface(contract_id, iface_name) + .map_err(|e| e.to_string())?; let operation = invoice .operation .as_ref() @@ -195,7 +152,9 @@ impl Runtime { InvoiceState::Amount(amount) => { let filter = ContractOutpointsFilter { contract_id, - filter: self, + stock, + wallet: self, + _phantom: PhantomData, }; let state: BTreeMap<_, Vec> = contract .fungible(assignment_name, &filter)? @@ -241,8 +200,7 @@ impl Runtime { .map(|o| Outpoint::new(o.txid, o.vout)); params.tx.change_keychain = RgbKeychain::for_method(method).into(); let (mut psbt, mut meta) = - self.wallet_mut() - .construct_psbt(prev_outpoints, &beneficiaries, params.tx)?; + self.construct_psbt(prev_outpoints, &beneficiaries, params.tx)?; let beneficiary_script = if let Beneficiary::WitnessVout(addr) = invoice.beneficiary.into_inner() { @@ -283,8 +241,9 @@ impl Runtime { } Beneficiary::BlindedSeal(_) => None, }; - let batch = self - .compose(invoice, prev_outputs, method, beneficiary_vout, |_, _, _| meta.change_vout)?; + let batch = stock + .compose(invoice, prev_outputs, method, beneficiary_vout, |_, _, _| meta.change_vout) + .map_err(|e| e.to_string())?; let methods = batch.close_method_set(); if methods.has_opret_first() { @@ -298,8 +257,9 @@ impl Runtime { } #[allow(clippy::result_large_err)] - pub fn transfer( + fn transfer( &mut self, + stock: &mut Stock, invoice: &RgbInvoice, psbt: &mut Psbt, ) -> Result { @@ -314,7 +274,7 @@ impl Runtime { .terminal_derivation() .ok_or(CompletionError::InconclusiveDerivation)?; let tapret_commitment = output.tapret_commitment()?; - self.wallet_mut() + self.descriptor_mut() .add_tapret_tweak(terminal, tapret_commitment)?; } @@ -327,7 +287,7 @@ impl Runtime { .position(|output| output.script == s) .ok_or(CompletionError::NoBeneficiaryOutput)?; let vout = Vout::from_u32(vout as u32); - let method = self.wallet().seal_close_method(); + let method = self.descriptor().seal_close_method(); let seal = XChain::Bitcoin(ExplicitSeal::new(method, Outpoint::new(witness_txid, vout))); (vec![], vec![seal]) @@ -335,11 +295,19 @@ impl Runtime { Beneficiary::BlindedSeal(seal) => (vec![XChain::Bitcoin(seal)], vec![]), }; - self.stock_mut().consume_fascia(fascia)?; - let transfer = self - .stock() - .transfer(contract_id, beneficiary2, beneficiary1)?; + stock.consume_fascia(fascia).map_err(|e| e.to_string())?; + let transfer = stock + .transfer(contract_id, beneficiary2, beneficiary1) + .map_err(|e| e.to_string())?; Ok(transfer) } } + +impl> WalletProvider for Wallet { + type Filter<'a> = WalletWrapper<'a, K, D> where Self: 'a; + fn filter(&self) -> Self::Filter<'_> { WalletWrapper(self) } + fn descriptor_mut(&mut self) -> &mut Self::Descr { self.deref_mut() } + fn outpoints(&self) -> impl Iterator { self.coins().map(|coin| coin.outpoint) } + fn txids(&self) -> impl Iterator { self.transactions().keys().copied() } +} diff --git a/src/resolvers/any.rs b/src/resolvers/any.rs index e3da7a8..b85376e 100644 --- a/src/resolvers/any.rs +++ b/src/resolvers/any.rs @@ -4,6 +4,8 @@ // // Written in 2024 by // Zoe Faltibà +// Rewritten in 2024 by +// Dr Maxim Orlovsky // // Copyright (C) 2024 LNP/BP Standards Association. All rights reserved. // @@ -19,77 +21,92 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; + +use bp::Tx; +use bpstd::Network; use rgbstd::containers::Consignment; use rgbstd::resolvers::ResolveHeight; use rgbstd::validation::{ResolveWitness, WitnessResolverError}; use rgbstd::{WitnessAnchor, XWitnessId, XWitnessTx}; -#[cfg(feature = "electrum")] -use crate::electrum; -#[cfg(feature = "esplora_blocking")] -use crate::esplora_blocking; +use crate::{Txid, WitnessOrd, XChain}; + +pub trait RgbResolver { + fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String>; + fn resolve_height(&mut self, txid: Txid) -> Result; + fn resolve_pub_witness(&self, txid: Txid) -> Result>; +} /// Type that contains any of the [`Resolver`] types defined by the library #[derive(From)] #[non_exhaustive] -pub enum AnyResolver { - #[cfg(feature = "electrum")] - #[from] - /// Electrum resolver - Electrum(Box), - #[cfg(feature = "esplora_blocking")] - #[from] - /// Esplora resolver - Esplora(Box), +pub struct AnyResolver { + inner: Box, + terminal_txes: HashMap, } -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum AnyResolverError { - #[cfg(feature = "electrum")] - #[display(inner)] - Electrum(::electrum::Error), - #[cfg(feature = "esplora_blocking")] - #[display(inner)] - Esplora(esplora::Error), -} +impl AnyResolver { + #[cfg(feature = "electrum_blocking")] + pub fn electrum_blocking(url: &str) -> Result { + Ok(AnyResolver { + inner: Box::new(electrum::Client::new(url).map_err(|e| e.to_string())?), + terminal_txes: Default::default(), + }) + } -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum AnyAnchorResolverError { - #[cfg(feature = "electrum")] - #[from] - #[display(inner)] - Electrum(electrum::AnchorResolverError), #[cfg(feature = "esplora_blocking")] - #[from] - #[display(inner)] - Esplora(esplora_blocking::AnchorResolverError), -} + pub fn esplora_blocking(url: &str) -> Result { + Ok(AnyResolver { + inner: Box::new( + esplora::Builder::new(url) + .build_blocking() + .map_err(|e| e.to_string())?, + ), + terminal_txes: Default::default(), + }) + } -impl AnyResolver { - pub fn add_terminals(&mut self, consignment: &Consignment) { - match self { - #[cfg(feature = "electrum")] - AnyResolver::Electrum(inner) => inner.add_witnesses(consignment), - #[cfg(feature = "esplora_blocking")] - AnyResolver::Esplora(inner) => inner.add_witnesses(consignment), + pub fn check(&self, network: Network) -> Result<(), String> { + let expected_block_hash = match network { + Network::Mainnet => "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + Network::Testnet3 => "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", + Network::Signet => "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", + Network::Regtest => "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", } + .to_string(); + self.inner.check(network, expected_block_hash) + } + + pub fn add_terminals(&mut self, consignment: &Consignment) { + self.terminal_txes.extend( + consignment + .bundles + .iter() + .filter_map(|bw| bw.pub_witness.maybe_map_ref(|w| w.tx.clone())) + .filter_map(|tx| match tx { + XChain::Bitcoin(tx) => Some(tx), + XChain::Liquid(_) | XChain::Other(_) => None, + }) + .map(|tx| (tx.txid(), tx)), + ); } } impl ResolveHeight for AnyResolver { - type Error = AnyAnchorResolverError; + fn resolve_height(&mut self, witness_id: XWitnessId) -> Result { + let XWitnessId::Bitcoin(txid) = witness_id else { + return Err(format!("{} is not supported as layer 1 network", witness_id.layer1())); + }; - fn resolve_height(&mut self, witness_id: XWitnessId) -> Result { - match self { - #[cfg(feature = "electrum")] - AnyResolver::Electrum(inner) => inner.resolve_height(witness_id).map_err(|e| e.into()), - #[cfg(feature = "esplora_blocking")] - AnyResolver::Esplora(inner) => inner.resolve_height(witness_id).map_err(|e| e.into()), + if self.terminal_txes.contains_key(&txid) { + return Ok(WitnessAnchor { + witness_ord: WitnessOrd::OffChain, + witness_id, + }); } + + self.inner.resolve_height(txid) } } @@ -98,11 +115,23 @@ impl ResolveWitness for AnyResolver { &self, witness_id: XWitnessId, ) -> Result { - match self { - #[cfg(feature = "electrum")] - AnyResolver::Electrum(inner) => inner.resolve_pub_witness(witness_id), - #[cfg(feature = "esplora_blocking")] - AnyResolver::Esplora(inner) => inner.resolve_pub_witness(witness_id), + let XWitnessId::Bitcoin(txid) = witness_id else { + return Err(WitnessResolverError::Other( + witness_id, + format!("{} is not supported as layer 1 network", witness_id.layer1()), + )); + }; + + if let Some(tx) = self.terminal_txes.get(&txid) { + return Ok(XWitnessTx::Bitcoin(tx.clone())); } + + self.inner + .resolve_pub_witness(txid) + .map(XWitnessTx::Bitcoin) + .map_err(|e| match e { + None => WitnessResolverError::Unknown(witness_id), + Some(e) => WitnessResolverError::Other(witness_id, e), + }) } } diff --git a/src/resolvers/electrum.rs b/src/resolvers/electrum.rs deleted file mode 100644 index af72748..0000000 --- a/src/resolvers/electrum.rs +++ /dev/null @@ -1,192 +0,0 @@ -// RGB smart contracts for Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2024 by -// Zoe Faltibà -// -// Copyright (C) 2024 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 bp::ConsensusDecode; -use bpstd::{Tx, Txid}; -use electrum::{Client, ElectrumApi, Error, Param}; -use rgbstd::containers::Consignment; -use rgbstd::resolvers::ResolveHeight; -use rgbstd::validation::{ResolveWitness, WitnessResolverError}; -use rgbstd::{Layer1, WitnessAnchor, WitnessOrd, WitnessPos, XChain, XWitnessId, XWitnessTx}; - -pub struct Resolver { - electrum_client: Client, - terminal_txes: HashMap, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum AnchorResolverError { - #[from] - #[display(inner)] - Error(Error), - - /// impossible conversion - ImpossibleConversion, - - /// unsupported layer 1 {0} - UnsupportedLayer1(Layer1), -} - -impl Resolver { - #[allow(clippy::result_large_err)] - pub fn new(url: &str) -> Result { - let electrum_client = Client::new(url)?; - Ok(Self { - electrum_client, - terminal_txes: none!(), - }) - } - - pub fn add_witnesses(&mut self, consignment: &Consignment) { - self.terminal_txes.extend( - consignment - .bundles - .iter() - .filter_map(|bw| bw.pub_witness.maybe_map_ref(|w| w.tx.clone())) - .filter_map(|tx| match tx { - XChain::Bitcoin(tx) => Some(tx), - XChain::Liquid(_) | XChain::Other(_) => None, - }) - .map(|tx| (tx.txid(), tx)), - ); - } -} - -impl ResolveHeight for Resolver { - type Error = AnchorResolverError; - - fn resolve_height(&mut self, witness_id: XWitnessId) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(AnchorResolverError::UnsupportedLayer1(witness_id.layer1())); - }; - - if self.terminal_txes.contains_key(&txid) { - return Ok(WitnessAnchor { - witness_ord: WitnessOrd::OffChain, - witness_id, - }); - } - - fn get_block_height(electrum_client: &Client) -> Result { - electrum_client - .block_headers_subscribe()? - .height - .try_into() - .map_err(|_| AnchorResolverError::ImpossibleConversion) - } - - let last_block_height_min = get_block_height(&self.electrum_client)?; - let witness_ord = match self - .electrum_client - .raw_call("blockchain.transaction.get", vec![ - Param::String(txid.to_string()), - Param::Bool(true), - ]) { - Ok(tx_details) => { - if let Some(confirmations) = tx_details.get("confirmations") { - let confirmations = confirmations - .as_u64() - .ok_or(Error::InvalidResponse(tx_details.clone()))?; - let last_block_height_max = get_block_height(&self.electrum_client)?; - let skew = confirmations - 1; - let mut tx_height: u32 = 0; - for height in (last_block_height_min - skew)..=(last_block_height_max - skew) { - if let Ok(get_merkle_res) = self.electrum_client.transaction_get_merkle( - &txid, - height - .try_into() - .map_err(|_| AnchorResolverError::ImpossibleConversion)?, - ) { - tx_height = get_merkle_res - .block_height - .try_into() - .map_err(|_| AnchorResolverError::ImpossibleConversion)?; - break; - } else { - continue; - } - } - let block_time = tx_details - .get("blocktime") - .ok_or(Error::InvalidResponse(tx_details.clone()))? - .as_i64() - .ok_or(Error::InvalidResponse(tx_details.clone()))?; - WitnessOrd::OnChain( - WitnessPos::new(tx_height, block_time) - .ok_or(Error::InvalidResponse(tx_details.clone()))?, - ) - } else { - WitnessOrd::OffChain - } - } - Err(e) - if e.to_string() - .contains("No such mempool or blockchain transaction") => - { - WitnessOrd::OffChain - } - Err(e) => return Err(e.into()), - }; - - Ok(WitnessAnchor { - witness_ord, - witness_id, - }) - } -} - -impl ResolveWitness for Resolver { - fn resolve_pub_witness( - &self, - witness_id: XWitnessId, - ) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(WitnessResolverError::Other( - witness_id, - AnchorResolverError::UnsupportedLayer1(witness_id.layer1()).to_string(), - )); - }; - - if let Some(tx) = self.terminal_txes.get(&txid) { - return Ok(XWitnessTx::Bitcoin(tx.clone())); - } - - match self.electrum_client.transaction_get_raw(&txid) { - Ok(raw_tx) => { - let tx = Tx::consensus_deserialize(raw_tx).map_err(|_| { - WitnessResolverError::Other(witness_id, s!("cannot deserialize raw TX")) - })?; - Ok(XWitnessTx::Bitcoin(tx)) - } - Err(e) - if e.to_string() - .contains("No such mempool or blockchain transaction") => - { - Err(WitnessResolverError::Unknown(witness_id)) - } - Err(e) => Err(WitnessResolverError::Other(witness_id, e.to_string())), - } - } -} diff --git a/src/resolvers/electrum_blocking.rs b/src/resolvers/electrum_blocking.rs new file mode 100644 index 0000000..4918f76 --- /dev/null +++ b/src/resolvers/electrum_blocking.rs @@ -0,0 +1,137 @@ +// RGB smart contracts for Bitcoin & Lightning +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2024 by +// Zoe Faltibà +// Rewritten in 2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2024 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::iter; + +use bp::ConsensusDecode; +use bpstd::{Network, Tx, Txid}; +use electrum::{Client, ElectrumApi, Error, Param}; +use rgbstd::{WitnessAnchor, WitnessOrd, WitnessPos, XWitnessId}; + +use super::RgbResolver; + +macro_rules! check { + ($e:expr) => { + $e.map_err(|e| e.to_string())? + }; +} + +impl RgbResolver for Client { + fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String> { + // check the electrum server is for the correct network + let block_hash = check!(self.block_header(0)).block_hash().to_string(); + if expected_block_hash != block_hash { + return Err(s!("resolver is for a network different from the wallet's one")); + } + // check the electrum server has the required functionality (verbose + // transactions) + let txid = match network { + Network::Mainnet => "33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036", + Network::Testnet3 => "5e6560fd518aadbed67ee4a55bdc09f19e619544f5511e9343ebba66d2f62653", + Network::Signet => "8153034f45e695453250a8fb7225a5e545144071d8ed7b0d3211efa1f3c92ad8", + Network::Regtest => "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + }; + if let Err(e) = self.raw_call("blockchain.transaction.get", vec![ + Param::String(txid.to_string()), + Param::Bool(true), + ]) { + if !e + .to_string() + .contains("genesis block coinbase is not considered an ordinary transaction") + { + return Err(s!( + "verbose transactions are unsupported by the provided electrum service" + )); + } + } + Ok(()) + } + + fn resolve_height(&mut self, txid: Txid) -> Result { + let mut witness_anchor = WitnessAnchor { + witness_ord: WitnessOrd::OffChain, + witness_id: XWitnessId::Bitcoin(txid), + }; + + // We get the height of the tip of blockchain + let header = check!(self.block_headers_subscribe()); + + // Now we get and parse transaction information to get the number of + // confirmations + let tx_details = check!(self.raw_call("blockchain.transaction.get", vec![ + Param::String(txid.to_string()), + Param::Bool(true), + ])); + let forward = iter::from_fn(|| self.block_headers_pop().ok().flatten()).count() as isize; + + let Some(confirmations) = tx_details.get("confirmations") else { + return Ok(witness_anchor); + }; + let confirmations = check!( + confirmations + .as_u64() + .and_then(|x| u32::try_from(x).ok()) + .ok_or(Error::InvalidResponse(tx_details.clone())) + ); + let block_time = check!( + tx_details + .get("blocktime") + .and_then(|v| v.as_i64()) + .ok_or(Error::InvalidResponse(tx_details.clone())) + ); + + let tip_height = u32::try_from(header.height).map_err(|_| s!("impossible height value"))?; + let height: isize = (tip_height - confirmations) as isize; + const SAFETY_MARGIN: isize = 1; + // first check from expected min to max height + let get_merkle_res = (1..=forward + 1) + // we need this under assumption that electrum was lying due to "DB desynchronization" + // since this have a very low probability we do that after everything else + .chain((1..=SAFETY_MARGIN).flat_map(|i| [i + forward + 1, 1 - i])) + .find_map(|offset| self.transaction_get_merkle(&txid, (height + offset) as usize).ok()) + .ok_or_else(|| s!("transaction can't be located in the blockchain"))?; + + let tx_height = u32::try_from(get_merkle_res.block_height) + .map_err(|_| s!("impossible height value"))?; + + let pos = check!( + WitnessPos::new(tx_height, block_time) + .ok_or(Error::InvalidResponse(tx_details.clone())) + ); + witness_anchor.witness_ord = WitnessOrd::OnChain(pos); + + Ok(witness_anchor) + } + + fn resolve_pub_witness(&self, txid: Txid) -> Result> { + let raw_tx = self.transaction_get_raw(&txid).map_err(|e| { + let e = e.to_string(); + if e.contains("No such mempool or blockchain transaction") { + return None; + } + Some(e) + })?; + Tx::consensus_deserialize(raw_tx) + .map_err(|e| Some(format!("cannot deserialize raw TX - {e}"))) + } +} diff --git a/src/resolvers/esplora_blocking.rs b/src/resolvers/esplora_blocking.rs index b167751..374c3dd 100644 --- a/src/resolvers/esplora_blocking.rs +++ b/src/resolvers/esplora_blocking.rs @@ -19,109 +19,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use bp::Tx; +use bpstd::{Network, Txid}; +use esplora::{BlockingClient, Error}; +use rgbstd::{WitnessAnchor, WitnessOrd, WitnessPos}; -use bpstd::{Tx, Txid}; -pub use esplora::Error as ResolverError; -use rgbstd::containers::Consignment; -use rgbstd::resolvers::ResolveHeight; -use rgbstd::validation::{ResolveWitness, WitnessResolverError}; -use rgbstd::{Layer1, WitnessAnchor, WitnessOrd, WitnessPos, XChain, XWitnessId, XWitnessTx}; +use super::RgbResolver; +use crate::XWitnessId; -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), - - /// unsupported layer 1 {0} - UnsupportedLayer1(Layer1), -} - -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_witnesses(&mut self, consignment: &Consignment) { - self.terminal_txes.extend( - consignment - .bundles - .iter() - .filter_map(|bw| bw.pub_witness.maybe_map_ref(|w| w.tx.clone())) - .filter_map(|tx| match tx { - XChain::Bitcoin(tx) => Some(tx), - XChain::Liquid(_) | XChain::Other(_) => None, - }) - .map(|tx| (tx.txid(), tx)), - ); - } -} - -impl ResolveHeight for Resolver { - type Error = AnchorResolverError; - - fn resolve_height(&mut self, witness_id: XWitnessId) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(AnchorResolverError::UnsupportedLayer1(witness_id.layer1())); - }; - - if self.terminal_txes.contains_key(&txid) { - return Ok(WitnessAnchor { - witness_ord: WitnessOrd::OffChain, - witness_id, - }); +impl RgbResolver for BlockingClient { + fn check(&self, _network: Network, expected_block_hash: String) -> Result<(), String> { + // check the esplora server is for the correct network + let block_hash = self.block_hash(0)?.to_string(); + if expected_block_hash != block_hash { + return Err(s!("resolver is for a network different from the wallet's one")); } + Ok(()) + } - let status = self.esplora_client.tx_status(&txid)?; + fn resolve_height(&mut self, txid: Txid) -> Result { + let status = self.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)?, - ), + Some((h, t)) => { + WitnessOrd::OnChain(WitnessPos::new(h, t as i64).ok_or(Error::InvalidServerData)?) + } None => WitnessOrd::OffChain, }; Ok(WitnessAnchor { witness_ord: ord, - witness_id, + witness_id: XWitnessId::Bitcoin(txid), }) } -} - -impl ResolveWitness for Resolver { - fn resolve_pub_witness( - &self, - witness_id: XWitnessId, - ) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(WitnessResolverError::Other( - witness_id, - AnchorResolverError::UnsupportedLayer1(witness_id.layer1()).to_string(), - )); - }; - - if let Some(tx) = self.terminal_txes.get(&txid) { - return Ok(XWitnessTx::Bitcoin(tx.clone())); - } - self.esplora_client - .tx(&txid) - .map_err(|err| WitnessResolverError::Other(witness_id, err.to_string()))? - .ok_or(WitnessResolverError::Unknown(witness_id)) - .map(XChain::Bitcoin) + fn resolve_pub_witness(&self, txid: Txid) -> Result> { + self.tx(&txid) + .map_err(|e| match e { + Error::TransactionNotFound(_) => None, + e => Some(e.to_string()), + })? + .ok_or(None) } } diff --git a/src/resolvers/mod.rs b/src/resolvers/mod.rs index 3452915..3ef5322 100644 --- a/src/resolvers/mod.rs +++ b/src/resolvers/mod.rs @@ -19,12 +19,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(any(feature = "electrum", feature = "esplora_blocking"))] mod any; #[cfg(feature = "esplora_blocking")] pub mod esplora_blocking; -#[cfg(feature = "electrum")] -pub mod electrum; +#[cfg(feature = "electrum_blocking")] +pub mod electrum_blocking; -#[cfg(any(feature = "electrum", feature = "esplora_blocking"))] -pub use any::{AnyResolver, AnyResolverError}; +pub use any::{AnyResolver, RgbResolver}; diff --git a/src/runtime.rs b/src/runtime.rs deleted file mode 100644 index c4b3092..0000000 --- a/src/runtime.rs +++ /dev/null @@ -1,264 +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. - -#![allow(clippy::result_large_err)] - -use std::collections::HashMap; -use std::convert::Infallible; -use std::io; -use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; - -use amplify::IoError; -use bpstd::{Network, XpubDerivable}; -use bpwallet::Wallet; -use rgbstd::containers::LoadError; -use rgbstd::interface::{ - AmountChange, BuilderError, ContractError, IfaceOp, IfaceRef, OutpointFilter, WitnessFilter, -}; -use rgbstd::persistence::fs::{LoadFs, StoreFs}; -use rgbstd::persistence::{ContractIfaceError, Stock, StockError, StockErrorAll, StockErrorMem}; -use rgbstd::validation::{self}; -use rgbstd::{AssignmentWitness, ContractId, XChain, XOutpoint, XWitnessId}; -use strict_types::encoding::{DeserializeError, Ident, SerializeError}; - -use crate::{DescriptorRgb, RgbDescr}; - -#[derive(Debug, Display, Error, From)] -#[display(inner)] -pub enum RuntimeError { - #[from] - #[from(io::Error)] - Io(IoError), - - #[from] - Serialize(SerializeError), - - #[from] - Deserialize(DeserializeError), - - #[from] - Load(LoadError), - - #[from] - Builder(BuilderError), - - #[from] - History(HistoryError), - - #[from] - Contract(ContractError), - - #[from] - PsbtDecode(psbt::DecodeError), - - /// wallet with id '{0}' is not known to the system. - #[display(doc_comments)] - WalletUnknown(Ident), - - #[from] - InvalidConsignment(validation::Status), - - /// invalid identifier. - #[from] - #[display(doc_comments)] - InvalidId(baid58::Baid58ParseError), - - /// the contract source doesn't fit requirements imposed by the used schema. - /// - /// {0} - #[display(doc_comments)] - IncompleteContract(validation::Status), - - #[from] - #[from(bpwallet::LoadError)] - Bp(bpwallet::RuntimeError), - - /// resolver error: {0} - #[cfg(any(feature = "electrum", feature = "esplora_blocking"))] - #[from] - #[display(doc_comments)] - ResolverError(crate::AnyResolverError), - - #[from] - #[from(StockError)] - #[from(StockErrorMem)] - #[display(inner)] - Stock(StockErrorAll), - - #[cfg(feature = "serde_yaml")] - #[from] - Yaml(serde_yaml::Error), - - #[from] - Custom(String), -} - -impl From for RuntimeError { - fn from(_: Infallible) -> Self { unreachable!() } -} - -#[derive(Getters)] -pub struct Runtime = RgbDescr, K = XpubDerivable> { - stock_path: PathBuf, - #[getter(as_mut)] - // TODO: Parametrize by the stock - stock: Stock, - bprt: bpwallet::Runtime, -} - -impl, K> Deref for Runtime { - type Target = Stock; - - fn deref(&self) -> &Self::Target { &self.stock } -} - -impl, K> DerefMut for Runtime { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stock } -} - -impl, K> OutpointFilter for Runtime { - fn include_outpoint(&self, output: impl Into) -> bool { - let output = output.into(); - self.wallet() - .coins() - .any(|utxo| XChain::Bitcoin(utxo.outpoint) == *output) - } -} - -impl, K> WitnessFilter for Runtime { - fn include_witness(&self, witness: impl Into) -> bool { - let witness = witness.into(); - self.wallet() - .transactions() - .keys() - .any(|txid| AssignmentWitness::Present(XWitnessId::Bitcoin(*txid)) == witness) - } -} - -pub struct ContractOutpointsFilter<'runtime, D: DescriptorRgb, K> { - pub contract_id: ContractId, - pub filter: &'runtime Runtime, -} - -impl<'runtime, D: DescriptorRgb, K> OutpointFilter for ContractOutpointsFilter<'runtime, D, K> { - fn include_outpoint(&self, output: impl Into) -> bool { - let output = output.into(); - if !self.filter.include_outpoint(output) { - return false; - } - matches!(self.filter.stock.contract_assignments_for(self.contract_id, [output]), Ok(list) if !list.is_empty()) - } -} - -#[cfg(feature = "serde")] -impl, K> Runtime -where - for<'de> D: serde::Serialize + serde::Deserialize<'de>, - for<'de> bpwallet::WalletDescr: serde::Serialize + serde::Deserialize<'de>, -{ - pub fn load_walletless(stock_path: &PathBuf) -> Result { - use std::io::ErrorKind; - - use strict_types::encoding::DecodeError; - - Stock::load(stock_path).map_err(RuntimeError::from).or_else(|err| { - if matches!(err, RuntimeError::Deserialize(DeserializeError::Decode(DecodeError::Io(ref err))) if err.kind() == ErrorKind::NotFound) { - #[cfg(feature = "log")] - eprint!("stock file is absent, creating a new one ... "); - let stock = Stock::default(); - std::fs::create_dir_all(stock_path)?; - stock.store(stock_path)?; - return Ok(stock) - } - eprintln!("stock file is damaged"); - Err(err) - }) - } - - pub fn load_attach( - stock_path: PathBuf, - bprt: bpwallet::Runtime, - ) -> Result { - let stock = Self::load_walletless(&stock_path)?; - Ok(Self { - stock_path, - stock, - bprt, - }) - } - - pub fn store(&mut self) { - self.stock - .store(&self.stock_path) - .expect("unable to save stock"); - self.bprt.try_store().expect("unable to save wallet data"); - } - - pub fn into_stock(self) -> Stock { self.stock } -} - -impl, K> Runtime { - pub fn wallet(&self) -> &Wallet { self.bprt.wallet() } - - pub fn wallet_mut(&mut self) -> &mut Wallet { self.bprt.wallet_mut() } - - pub fn attach(&mut self, bprt: bpwallet::Runtime) { self.bprt = bprt } - - pub fn network(&self) -> Network { self.bprt.network() } - - // TODO: Integrate into BP Wallet `TxRow` as L2 and provide transactional info - pub fn fungible_history( - &self, - contract_id: ContractId, - iface: impl Into, - ) -> Result>, RuntimeError> { - let iref = iface.into(); - let iface = self.stock.iface(iref.clone())?; - let default_op = iface - .default_operation - .as_ref() - .ok_or(HistoryError::NoDefaultOp)?; - let state_name = iface - .transitions - .get(default_op) - .ok_or(HistoryError::DefaultOpNotTransition)? - .default_assignment - .as_ref() - .ok_or(HistoryError::NoDefaultAssignment)? - .clone(); - let contract = self.stock.contract_iface(contract_id, iref)?; - contract - .fungible_ops::(state_name, self, self) - .map_err(RuntimeError::from) - } -} - -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum HistoryError { - /// interface doesn't define default operation - NoDefaultOp, - /// default operation defined by the interface is not a state transition - DefaultOpNotTransition, - /// interface doesn't define default fungible state - NoDefaultAssignment, -} diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..2285255 --- /dev/null +++ b/src/store.rs @@ -0,0 +1,264 @@ +// 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::collections::HashMap; +use std::error::Error; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::path::{Path, PathBuf}; + +use bpstd::XpubDerivable; +use bpwallet::{StoreError, Wallet, WalletDescr}; +use psrgbt::{Psbt, PsbtMeta}; +use rgbstd::containers::Transfer; +use rgbstd::interface::{AmountChange, IfaceOp, IfaceRef}; +use rgbstd::persistence::fs::StoreFs; +use rgbstd::persistence::{ + IndexProvider, MemIndex, MemStash, MemState, StashProvider, StateProvider, Stock, +}; + +use super::{ + CompletionError, CompositionError, ContractId, DescriptorRgb, PayError, TransferParams, + WalletError, WalletProvider, WalletStock, XWitnessId, +}; +use crate::invoice::RgbInvoice; + +pub trait Store { + type Err: Error; + fn store(&self, path: impl AsRef) -> Result<(), Self::Err>; +} + +impl> Store for Wallet +where + for<'de> WalletDescr: serde::Serialize + serde::Deserialize<'de>, + for<'de> D: serde::Serialize + serde::Deserialize<'de>, +{ + type Err = StoreError; + fn store(&self, path: impl AsRef) -> Result<(), Self::Err> { self.store(path.as_ref()) } +} + +#[derive(Getters)] +pub struct StoredStock< + S: StashProvider = MemStash, + H: StateProvider = MemState, + P: IndexProvider = MemIndex, +> where + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + stock_path: PathBuf, + stock: Stock, + #[getter(prefix = "is_")] + dirty: bool, +} + +impl Deref for StoredStock +where + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + type Target = Stock; + + fn deref(&self) -> &Self::Target { &self.stock } +} + +impl DerefMut for StoredStock +where + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty = true; + &mut self.stock + } +} + +impl StoredStock +where + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + pub fn attach(path: PathBuf, stock: Stock) -> Self { + Self { + stock_path: path, + stock, + dirty: false, + } + } + + pub fn store(&self) { + if self.dirty { + self.stock + .store(&self.stock_path) + .expect("error saving data"); + } + } +} + +impl Drop for StoredStock +where + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + fn drop(&mut self) { self.store() } +} + +#[derive(Getters)] +pub struct StoredWallet< + W: WalletProvider, + K = XpubDerivable, + S: StashProvider = MemStash, + H: StateProvider = MemState, + P: IndexProvider = MemIndex, +> where + W::Descr: DescriptorRgb, + W: Store, + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + stock_path: PathBuf, + wallet_path: Option, + stock: Stock, + wallet: W, + #[getter(prefix = "is_")] + stock_dirty: bool, + #[getter(prefix = "is_")] + wallet_dirty: bool, + #[getter(skip)] + _phantom: PhantomData, +} + +impl, S: StashProvider, H: StateProvider, P: IndexProvider> + StoredWallet +where + W::Descr: DescriptorRgb, + W: Store, + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + pub fn attach( + stock_path: PathBuf, + wallet_path: Option, + stock: Stock, + wallet: W, + ) -> Self { + Self { + stock_path, + wallet_path, + stock, + wallet, + stock_dirty: false, + wallet_dirty: false, + _phantom: PhantomData, + } + } + + pub fn stock_mut(&mut self) -> &mut Stock { + self.stock_dirty = true; + &mut self.stock + } + + pub fn wallet_mut(&mut self) -> &mut W { + self.wallet_dirty = true; + &mut self.wallet + } + + #[allow(clippy::result_large_err)] + pub fn fungible_history( + &self, + contract_id: ContractId, + iface: impl Into, + ) -> Result>, WalletError> { + self.stock + .fungible_history(&self.wallet, contract_id, iface) + } + + #[allow(clippy::result_large_err)] + pub fn pay( + &mut self, + invoice: &RgbInvoice, + params: TransferParams, + ) -> Result<(Psbt, PsbtMeta, Transfer), PayError> { + self.stock_dirty = true; + self.wallet_dirty = true; + self.wallet.pay(&mut self.stock, invoice, params) + } + + #[allow(clippy::result_large_err)] + pub fn construct_psbt( + &mut self, + invoice: &RgbInvoice, + params: TransferParams, + ) -> Result<(Psbt, PsbtMeta), CompositionError> { + self.wallet_dirty = true; + self.wallet.construct_psbt_rgb(&self.stock, invoice, params) + } + + #[allow(clippy::result_large_err)] + pub fn transfer( + &mut self, + invoice: &RgbInvoice, + psbt: &mut Psbt, + ) -> Result { + self.stock_dirty = true; + self.wallet_dirty = true; + self.wallet.transfer(&mut self.stock, invoice, psbt) + } + + pub fn store(&self) { + let r1 = if self.stock_dirty { + self.stock + .store(&self.stock_path) + .map_err(|e| e.to_string()) + } else { + Ok(()) + }; + let r2 = if self.wallet_dirty { + if let Some(path) = self.wallet_path.as_ref() { + self.wallet.store(path).map_err(|e| e.to_string()) + } else { + Ok(()) + } + } else { + Ok(()) + }; + r1.and(r2).expect("error saving data"); + } +} + +impl, S: StashProvider, H: StateProvider, P: IndexProvider> Drop + for StoredWallet +where + W::Descr: DescriptorRgb, + W: Store, + S: StoreFs, + H: StoreFs, + P: StoreFs, +{ + fn drop(&mut self) { self.store() } +} diff --git a/src/wallet.rs b/src/wallet.rs new file mode 100644 index 0000000..bba7f3f --- /dev/null +++ b/src/wallet.rs @@ -0,0 +1,102 @@ +// 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::collections::HashMap; + +use bpwallet::Wallet; +use rgbstd::interface::{AmountChange, IfaceOp, IfaceRef, OutpointFilter, WitnessFilter}; +use rgbstd::persistence::{IndexProvider, StashProvider, StateProvider, Stock}; + +use crate::{ + AssignmentWitness, ContractId, DescriptorRgb, HistoryError, WalletError, WalletProvider, + XChain, XOutpoint, XWitnessId, +}; + +pub struct WalletWrapper<'a, K, D: DescriptorRgb>(pub &'a Wallet); + +impl<'a, K, D: DescriptorRgb> Copy for WalletWrapper<'a, K, D> {} +impl<'a, K, D: DescriptorRgb> Clone for WalletWrapper<'a, K, D> { + fn clone(&self) -> Self { *self } +} + +impl<'a, K, D: DescriptorRgb> OutpointFilter for WalletWrapper<'a, K, D> { + fn include_outpoint(&self, output: impl Into) -> bool { + let output = output.into(); + self.0 + .outpoints() + .any(|outpoint| XChain::Bitcoin(outpoint) == *output) + } +} + +impl<'a, K, D: DescriptorRgb> WitnessFilter for WalletWrapper<'a, K, D> { + fn include_witness(&self, witness: impl Into) -> bool { + let witness = witness.into(); + self.0 + .txids() + .any(|txid| AssignmentWitness::Present(XWitnessId::Bitcoin(txid)) == witness) + } +} + +pub trait WalletStock, K> +where W::Descr: DescriptorRgb +{ + #[allow(clippy::result_large_err)] + fn fungible_history( + &self, + wallet: &W, + contract_id: ContractId, + iface: impl Into, + ) -> Result>, WalletError>; +} + +impl, K, S: StashProvider, H: StateProvider, P: IndexProvider> + WalletStock for Stock +where W::Descr: DescriptorRgb +{ + // TODO: Integrate into BP Wallet `TxRow` as L2 and provide transactional info + fn fungible_history( + &self, + wallet: &W, + contract_id: ContractId, + iface: impl Into, + ) -> Result>, WalletError> { + let iref = iface.into(); + let iface = self.iface(iref.clone()).map_err(|e| e.to_string())?; + let default_op = iface + .default_operation + .as_ref() + .ok_or(HistoryError::NoDefaultOp)?; + let state_name = iface + .transitions + .get(default_op) + .ok_or(HistoryError::DefaultOpNotTransition)? + .default_assignment + .as_ref() + .ok_or(HistoryError::NoDefaultAssignment)? + .clone(); + let contract = self + .contract_iface(contract_id, iref) + .map_err(|e| e.to_string())?; + Ok(contract + .fungible_ops::(state_name, wallet.filter(), wallet.filter()) + .map_err(|e| e.to_string())?) + } +}