diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5708a9d..1eb248c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always @@ -35,11 +35,6 @@ jobs: matrix: feature: - fs - - cli - - log - - esplora_blocking - - electrum_blocking - - mempool_blocking - serde steps: - uses: actions/checkout@v4 @@ -65,7 +60,7 @@ jobs: strategy: fail-fast: false matrix: - toolchain: [ nightly, beta, stable, 1.77.0 ] + toolchain: [ nightly, beta, stable, 1.82.0 ] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 3711d7a..9ba863f 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ab5249d..91f36b6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,7 @@ on: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81ede7e..d9c899f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always @@ -34,6 +34,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: jetli/wasm-pack-action@v0.4.0 + with: + version: 'latest' - name: Add wasm32 target run: rustup target add wasm32-unknown-unknown - name: Test in headless Chrome diff --git a/.rustfmt.toml b/.rustfmt.toml index 6d14899..29c789a 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -5,8 +5,10 @@ max_width = 100 array_width = 100 attr_fn_like_width = 100 comment_width = 100 +chain_width = 60 fn_call_width = 100 single_line_if_else_max_width = 100 +struct_lit_width = 60 fn_single_line = true format_code_in_doc_comments = true diff --git a/Cargo.lock b/Cargo.lock index 9b874c1..3d95235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,53 +19,35 @@ dependencies = [ [[package]] name = "aluvm" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a6767842958f458dc7010a2a1005db96dfaceadd366d07532c5045bbc81f24" +checksum = "ecadfc638661bf95a89674ab269895ff0ef2e23e265f4e9bacb4b00aeb4ed574" dependencies = [ "amplify", - "ascii-armor", "baid64", - "blake3", + "commit_verify", "getrandom", - "half", "paste", - "ripemd", "serde", - "sha2", "strict_encoding", - "strict_types", "wasm-bindgen", ] [[package]] name = "amplify" -version = "4.7.0" +version = "4.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7147b742325842988dd6c793d55f58df3ae36bccf7d9b6e07db10ab035be343d" +checksum = "448cf0c3afc71439b5f837aac5399a1ef2b223f5f38324dbfb4343deec3b80cc" dependencies = [ - "amplify_apfloat", "amplify_derive", "amplify_num", "amplify_syn", "ascii", - "rand", "serde", "stringly_conversions", "wasm-bindgen", ] -[[package]] -name = "amplify_apfloat" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695e433882668b55b3d7fb0ba22bf9be66a91abe30d7ca1f1a774f8b90b4db4c" -dependencies = [ - "amplify_num", - "bitflags 2.6.0", - "wasm-bindgen", -] - [[package]] name = "amplify_derive" version = "4.0.1" @@ -116,9 +98,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -131,9 +113,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -155,19 +137,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] -name = "arrayref" -version = "0.3.9" +name = "anyhow" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arrayvec" @@ -184,19 +167,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ascii-armor" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4966ac403dc4a666d8131dfe4df684f45acc68d4c7e768db89c463aa5617910" -dependencies = [ - "amplify", - "baid64", - "base85", - "sha2", - "strict_encoding", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -205,36 +175,34 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.10.0" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" +checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" dependencies = [ "aws-lc-sys", - "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libc", "paste", ] [[package]] name = "baid64" -version = "0.2.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95dabc2759e01e2c382968639868a701f384a18890934f9e75d4feb4d6623794" +checksum = "6cb4a8b2f1afee4ef00a190b260ad871842b93206177b59631fecd325d48d538" dependencies = [ "amplify", "base64", @@ -269,7 +237,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cexpr", "clang-sys", "itertools", @@ -282,21 +250,15 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.85", + "syn 2.0.98", "which", ] [[package]] name = "bitcoin-io" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" - -[[package]] -name = "bitcoin-private" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" [[package]] name = "bitcoin_hashes" @@ -310,28 +272,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "blake3" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -343,25 +286,28 @@ dependencies = [ ] [[package]] -name = "bp-consensus" -version = "0.11.0-beta.9" +name = "borrow-or-share" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54db63118d55e32ea78f8775e98871d442a33e3bdef6419c7964d71b308316c0" +checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" + +[[package]] +name = "bp-consensus" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.12#6fbc7f16d028d12d6d1336a9aa7752ea8ae06e15" dependencies = [ "amplify", "chrono", "commit_verify", - "secp256k1 0.30.0", + "secp256k1", "serde", "strict_encoding", - "strict_types", ] [[package]] name = "bp-core" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e51a329150531b12243adf51d978490c796a6a20ec76c506b41c8e1226022bc" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.12#6fbc7f16d028d12d6d1336a9aa7752ea8ae06e15" dependencies = [ "amplify", "bp-consensus", @@ -372,46 +318,43 @@ dependencies = [ "serde", "single_use_seals", "strict_encoding", - "strict_types", "wasm-bindgen", ] [[package]] name = "bp-dbc" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9286fb448160672148262317f4647ebdcdd4699ed2bd34401f9799d0920cc376" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.12#6fbc7f16d028d12d6d1336a9aa7752ea8ae06e15" dependencies = [ "amplify", "base85", "bp-consensus", "commit_verify", - "secp256k1 0.30.0", + "secp256k1", "serde", "strict_encoding", ] [[package]] name = "bp-derive" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394f8a2c3f4df405aff7269d1727b463e2cf74ec35a077fe7425084cdbdd0448" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.12#91aaf51498126f6a514f0d9af80888465e5dbfee" dependencies = [ "amplify", "bp-consensus", "bp-invoice", "commit_verify", "hmac", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "sha2", ] [[package]] name = "bp-electrum" -version = "0.11.0-beta.9.2" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc58108a622b07755bf4a9468325b76e74f47042b1b493ff30a9059a20c2c34a" +checksum = "200dd90936c240f274adafda20a52a11565d44f5da6933425be841ad342a10d0" dependencies = [ "amplify", "bp-std", @@ -428,37 +371,36 @@ dependencies = [ [[package]] name = "bp-esplora" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b002e82289784e579f130ebdc22aa0b19a7b873620ea1dab16628999ba404d" +checksum = "a21e228cb0153265cb3f79a541da335f3c22ec73d1afbbff396438aa35dd7b18" dependencies = [ "amplify", "bp-std", "log", "serde", - "serde_with", + "serde_with 3.12.0", "sha2", "ureq", ] [[package]] name = "bp-invoice" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfe7be7b2d740de57e895f2ac93b305bbd67a2a0914dac424f8569bb9981679" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.12#91aaf51498126f6a514f0d9af80888465e5dbfee" dependencies = [ "amplify", "bech32", "bp-consensus", "commit_verify", "serde", + "strict_encoding", ] [[package]] name = "bp-seals" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9873cfe420f4ce5cc539c394c75df0669cdbe2c23eed1930dffe024cb0f13a57" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-core?branch=v0.12#6fbc7f16d028d12d6d1336a9aa7752ea8ae06e15" dependencies = [ "amplify", "baid64", @@ -473,9 +415,8 @@ dependencies = [ [[package]] name = "bp-std" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e23c585b49b6d3112b0ea1a2d2e143f493fd0ecbb3bb11bbbe41c69b63a4411c" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.12#91aaf51498126f6a514f0d9af80888465e5dbfee" dependencies = [ "amplify", "bp-consensus", @@ -485,15 +426,15 @@ dependencies = [ "descriptors", "getrandom", "psbt", - "secp256k1 0.30.0", + "secp256k1", "serde", "wasm-bindgen", ] [[package]] name = "bp-wallet" -version = "0.11.0-beta.9" -source = "git+https://github.com/BP-WG/bp-wallet?branch=master#784ff20685693324e76380a703ecc4cc6d613145" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.12#6b77c581bf514e0b4ce809e395da278855434e19" dependencies = [ "amplify", "base64", @@ -518,9 +459,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -530,9 +471,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.31" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" dependencies = [ "jobserver", "libc", @@ -556,9 +497,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -582,9 +523,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -592,39 +533,39 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" -version = "0.1.51" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" dependencies = [ "cc", ] @@ -637,19 +578,19 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "commit_encoding_derive" -version = "0.11.0-beta.8" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea07c5ad73a637276dc4f8a957f8285764018d45bdefef35eb9137f32d0e3c81" +checksum = "9613582af45e1564c09d813dae82e7c7bca95678b9c42dae955a8499ecc6ae95" dependencies = [ "amplify", "amplify_syn", @@ -660,9 +601,9 @@ dependencies = [ [[package]] name = "commit_verify" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf08c4941e147937551f6a3d370552d67f98cf72c9eb18948142596beadd31e" +checksum = "6b822f3253ddad821c2a08b88d6534cc322e57dfe3aa8a5bbe88cce8beecc965" dependencies = [ "amplify", "commit_encoding_derive", @@ -675,22 +616,6 @@ dependencies = [ "vesper-lang", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -699,9 +624,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -715,12 +640,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -731,14 +650,38 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -751,8 +694,19 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.85", + "strsim 0.11.1", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", ] [[package]] @@ -761,9 +715,9 @@ version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] @@ -778,13 +732,12 @@ dependencies = [ [[package]] name = "descriptors" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49798157ca640745a3c898714b5805c28482295de8819cb7b2c530b8f9246df1" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.12#91aaf51498126f6a514f0d9af80888465e5dbfee" dependencies = [ "amplify", "bp-derive", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", ] @@ -820,6 +773,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "dunce" version = "1.0.5" @@ -834,9 +798,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -844,9 +808,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -863,25 +827,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] -[[package]] -name = "fast32" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35a73237400bde66c82e38387343f90d7182a2f2f22729e096a2abd57d75db9" - [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -889,11 +847,12 @@ dependencies = [ [[package]] name = "fluent-uri" -version = "0.1.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" dependencies = [ - "bitflags 1.3.2", + "borrow-or-share", + "ref-cast", ] [[package]] @@ -942,19 +901,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "hashbrown" @@ -964,9 +913,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -1000,11 +949,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1013,6 +962,24 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hypersonic" +version = "0.12.0-beta.4" +source = "git+https://github.com/AluVM/sonic?branch=master#51069101a8b2794f9d14cc50c014c13cc18ff1a1" +dependencies = [ + "aluvm", + "amplify", + "commit_verify", + "getrandom", + "serde", + "sonic-api", + "sonic-callreq", + "strict_encoding", + "strict_types", + "ultrasonic", + "wasm-bindgen", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1036,6 +1003,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1044,12 +1129,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1065,12 +1161,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] @@ -1091,9 +1187,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -1106,10 +1202,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1127,15 +1224,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -1147,21 +1244,27 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags", "libc", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -1169,17 +1272,11 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minicov" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def6d99771d7c499c26ad4d40eb6645eafd3a1553b35fc26ea5a489a45e82d9a" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -1193,19 +1290,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] -[[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - [[package]] name = "mnemonic" version = "1.1.1" @@ -1224,12 +1315,11 @@ dependencies = [ [[package]] name = "nonasync" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1005555d351f593bf72ffc3a89a0d42e243df004d2c4ded17699f10b562b98" +checksum = "d81f7335a3fa37124e24461d6164485760d4c056dd92a81a70c23fc28c2b63c8" dependencies = [ "amplify", - "log", ] [[package]] @@ -1288,46 +1378,44 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "psbt" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d36b72ab550141a04a368c25611bf04c87178b65d3be5165529e0b010ee551b" +version = "0.12.0-beta.4" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.12#91aaf51498126f6a514f0d9af80888465e5dbfee" dependencies = [ "amplify", "base64", "bp-core", "bp-derive", - "chrono", "commit_verify", "descriptors", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "strict_encoding", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1373,6 +1461,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "regex" version = "1.11.1" @@ -1387,9 +1495,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1404,78 +1512,57 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rgb-core" -version = "0.11.0-beta.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf48c4e395882c5228cd788a78e04674c94f7c177d82afdd87def8619f5dff8" +version = "0.12.0-beta.4" +source = "git+https://github.com/RGB-WG/rgb-core?branch=v0.12#4a020e09dcf4e30edc41432e3c9f92f3c13ad0ac" dependencies = [ - "aluvm", "amplify", - "baid64", "bp-core", - "chrono", "commit_verify", "getrandom", - "mime", - "secp256k1-zkp", "serde", "single_use_seals", "strict_encoding", - "strict_types", + "ultrasonic", "wasm-bindgen", ] [[package]] name = "rgb-invoice" -version = "0.11.0-beta.9" -source = "git+https://github.com/RGB-WG/rgb-std?branch=master#ddee8898e4d6a2f156587feabb6f19fca08946c0" +version = "0.12.0-beta.4" +source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.12#01d6dc4a38e08456176fb469516342f0ca38041e" dependencies = [ "amplify", "baid64", "bp-core", "bp-invoice", - "fast32", - "fluent-uri", - "indexmap 2.6.0", - "percent-encoding", - "rand", + "commit_verify", + "hypersonic", "rgb-core", "serde", + "sonic-callreq", "strict_encoding", - "strict_types", ] [[package]] name = "rgb-psbt" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" dependencies = [ "amplify", - "baid64", "bp-core", "bp-std", - "commit_verify", - "getrandom", - "rand", "rgb-std", - "strict_encoding", - "wasm-bindgen", - "wasm-bindgen-test", ] [[package]] name = "rgb-runtime" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" dependencies = [ "amplify", - "baid64", - "bp-core", - "bp-electrum", - "bp-esplora", "bp-std", "bp-wallet", - "chrono", "commit_verify", "getrandom", - "indexmap 2.6.0", + "indexmap 2.7.1", "log", "nonasync", "rand", @@ -1490,24 +1577,21 @@ dependencies = [ [[package]] name = "rgb-std" -version = "0.11.0-beta.9" -source = "git+https://github.com/RGB-WG/rgb-std?branch=master#ddee8898e4d6a2f156587feabb6f19fca08946c0" +version = "0.12.0-beta.4" +source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.12#01d6dc4a38e08456176fb469516342f0ca38041e" dependencies = [ - "aluvm", "amplify", - "ascii-armor", - "baid64", - "base85", "bp-core", + "bp-invoice", "chrono", "commit_verify", "getrandom", - "indexmap 2.6.0", - "nonasync", - "rand", + "hypersonic", "rgb-core", "rgb-invoice", "serde", + "serde_with 1.14.0", + "single_use_seals", "strict_encoding", "strict_types", "wasm-bindgen", @@ -1515,24 +1599,21 @@ dependencies = [ [[package]] name = "rgb-wallet" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" dependencies = [ "amplify", - "baid64", - "bp-std", + "anyhow", + "bp-electrum", + "bp-esplora", "bp-wallet", "clap", - "commit_verify", - "env_logger", - "log", + "rgb-psbt", "rgb-runtime", "rgb-std", "serde", - "serde_json", "serde_yaml", - "shellexpand", + "strict_encoding", "strict_types", - "toml", ] [[package]] @@ -1567,22 +1648,22 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "aws-lc-rs", "log", @@ -1596,9 +1677,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -1612,11 +1693,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -1627,23 +1714,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "secp256k1" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" -dependencies = [ - "rand", - "secp256k1-sys", - "serde", -] - [[package]] name = "secp256k1" version = "0.30.0" @@ -1665,54 +1735,31 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-zkp" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" -dependencies = [ - "bitcoin-private", - "rand", - "secp256k1 0.29.1", - "secp256k1-zkp-sys", - "serde", -] - -[[package]] -name = "secp256k1-zkp-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f08b2d0b143a22e07f798ae4f0ab20d5590d7c68e0d090f2088a48a21d1654" -dependencies = [ - "cc", - "secp256k1-sys", -] - [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -1741,32 +1788,54 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros 1.5.2", +] + +[[package]] +name = "serde_with" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", - "serde_with_macros", + "serde_with_macros 3.12.0", "time", ] [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", - "syn 2.0.85", + "syn 1.0.109", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -1775,7 +1844,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "itoa", "ryu", "serde", @@ -1810,13 +1879,20 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "single_use_seals" -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec071f3b3153217f1cb2bca5ba7ac87eeafc446cb35a5c0643dec33495a37244" +checksum = "84e1ecf8923d062bff162583e1f71f517e535974196512a13ab47d8816b38998" dependencies = [ - "amplify_derive", + "serde", + "strict_encoding", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "socks" version = "0.3.4" @@ -1828,20 +1904,61 @@ dependencies = [ "winapi", ] +[[package]] +name = "sonic-api" +version = "0.12.0-beta.4" +source = "git+https://github.com/AluVM/sonic?branch=master#51069101a8b2794f9d14cc50c014c13cc18ff1a1" +dependencies = [ + "aluvm", + "amplify", + "baid64", + "chrono", + "commit_verify", + "getrandom", + "serde", + "sonic-callreq", + "strict_encoding", + "strict_types", + "ultrasonic", + "wasm-bindgen", +] + +[[package]] +name = "sonic-callreq" +version = "0.12.0-beta.4" +source = "git+https://github.com/AluVM/sonic?branch=master#51069101a8b2794f9d14cc50c014c13cc18ff1a1" +dependencies = [ + "amplify", + "baid64", + "chrono", + "fluent-uri", + "indexmap 2.7.1", + "percent-encoding", + "serde", + "strict_encoding", + "strict_types", + "ultrasonic", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strict_encoding" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69b4893cf054e129d5288a565102124520d7b94eb9589d1e78202abc7e2092d" +checksum = "8fd36b71bb44ca146be0b2185ed6c6deb3684cc0d5c3a94284e97fe7fa6a642f" dependencies = [ "amplify", - "half", "serde", "strict_encoding_derive", "wasm-bindgen", @@ -1849,9 +1966,9 @@ dependencies = [ [[package]] name = "strict_encoding_derive" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4f9b678862372f8e439bcaafc27df7610ea93b06d2deb6244dec0af4259ce6" +checksum = "34e3bc6e4a2450420b4dbfb6929d9ce005e8c36cf73bf215db99f0d09c9fa79f" dependencies = [ "amplify_syn", "heck", @@ -1862,15 +1979,12 @@ dependencies = [ [[package]] name = "strict_types" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bae7475fc901144d8a35d25e36d76aa020b840f233d60532d6d52318718781b" +version = "2.8.1" +source = "git+https://github.com/strict-types/strict-types?branch=develop#7a34f5691749c1355d14c608619460ebd8fd9ad2" dependencies = [ "amplify", - "ascii-armor", "baid64", - "half", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_json", "serde_yaml", @@ -1891,6 +2005,12 @@ dependencies = [ "serde_str_helpers", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -1916,40 +2036,51 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -1968,29 +2099,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "toml" version = "0.8.19" @@ -2014,11 +2140,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -2032,25 +2158,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +name = "ultrasonic" +version = "0.12.0-beta.4" +source = "git+https://github.com/AluVM/ultrasonic?branch=master#2bb7be96e316746363ff3e4f1f2ccb1c8496a94a" +dependencies = [ + "amplify", + "baid64", + "commit_verify", + "getrandom", + "serde", + "strict_encoding", + "wasm-bindgen", + "zk-aluvm", +] [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unsafe-libyaml" @@ -2066,9 +2192,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.10.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64", "flate2", @@ -2085,15 +2211,27 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2108,9 +2246,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vesper-lang" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72ebd3b32f16ee8ace2bd3058c2bfa0f4820992bd4ea86e73ba228bb13dd2b0" +checksum = "cd2b7e3e27aeb0524204e58042f6e4531a720745d1b1a3978d3a084f1885f63d" dependencies = [ "amplify", "strict_encoding", @@ -2134,47 +2272,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2182,33 +2321,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -2216,20 +2356,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2237,9 +2377,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -2446,13 +2586,49 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2471,7 +2647,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", ] [[package]] @@ -2479,3 +2676,39 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "zk-aluvm" +version = "0.12.0-beta.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc204be52b2d144d3961a17676d191477cc468a2a82779f4502db83d58a083" +dependencies = [ + "aluvm", + "amplify", + "getrandom", + "serde", + "strict_encoding", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml index 77e3ec7..73937de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,79 +1,66 @@ [workspace] -members = [ - "psbt", - "cli", - "." -] -default-members = [ - "psbt", - "." -] +members = [".", "psbt", "cli"] +default-members = [".", "psbt"] [workspace.package] -version = "0.11.0-beta.9" +version = "0.12.0-beta.4" keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] categories = ["cryptography::cryptocurrencies"] authors = ["Dr Maxim Orlovsky "] homepage = "https://rgb.tech" repository = "https://github.com/RGB-WG/rgb" -rust-version = "1.77.0" +rust-version = "1.82.0" edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.7.0" -nonasync = { version = "0.1.2", features = ["log"] } -baid64 = "0.2.2" -strict_encoding = "2.7.0" -strict_types = "2.7.2" -commit_verify = "0.11.0-beta.9" -bp-core = "0.11.0-beta.9" -bp-std = { version = "0.11.0-beta.9", features = ["client-side-validation"] } -bp-electrum = "0.11.0-beta.9" -bp-esplora = { version = "0.11.0-beta.9", default-features = false } -bp-wallet = { version = "0.11.0-beta.9" } -rgb-std = { version = "0.11.0-beta.9" } -rgb-psbt = { version = "0.11.0-beta.9", path = "psbt" } -indexmap = "2.4.0" -chrono = "0.4.38" -serde_crate = { package = "serde", version = "1", features = ["derive"] } +amplify = "4.8.0" +nonasync = "0.1.2" +strict_encoding = "2.8.1" +strict_types = "2.8.1" +commit_verify = "=0.12.0-beta.4" +bp-core = "=0.12.0-beta.4" +bp-std = { version = "=0.12.0-beta.4", features = ["client-side-validation"] } +bp-electrum = "=0.12.0-beta.4" +bp-esplora = { version = "=0.12.0-beta.4", default-features = false } +bp-wallet = { version = "=0.12.0-beta.4" } +rgb-std = { version = "=0.12.0-beta.4" } +rgb-runtime = { version = "=0.12.0-beta.4", path = "." } +rgb-psbt = { version = "=0.12.0-beta.4", path = "psbt" } +indexmap = "2.7.0" +serde = { version = "1", features = ["derive"] } serde_yaml = "0.9.19" log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] } [package] name = "rgb-runtime" -version.workspace = true description = "RGB smart contracts wallet runtime" +version.workspace = true keywords.workspace = true categories.workspace = true -readme = "README.md" authors.workspace = true homepage.workspace = true repository.workspace = true rust-version.workspace = true edition.workspace = true license.workspace = true +readme = "README.md" [lib] -name = "rgb" +name = "rgbp" crate-type = ["cdylib", "rlib"] [dependencies] amplify = { workspace = true } -nonasync = { workspace = true } -baid64 = { workspace = true } -bp-electrum = { workspace = true, optional = true } commit_verify = { workspace = true } strict_types = { workspace = true } -bp-core = { workspace = true } +nonasync = { workspace = true } bp-std = { workspace = true } -bp-esplora = { workspace = true, optional = true } bp-wallet = { workspace = true } -rgb-std = { workspace = true } -rgb-psbt = { workspace = true } +rgb-std = { workspace = true, features = ["bitcoin"] } +rgb-psbt = { workspace = true, features = ["bp"] } indexmap = { workspace = true } -chrono = { workspace = true } -serde_crate = { workspace = true, optional = true } +serde = { workspace = true, optional = true } serde_yaml = { workspace = true, optional = true } log = { workspace = true, optional = true } @@ -86,20 +73,42 @@ getrandom = { version = "0.2", features = ["js"] } wasm-bindgen-test = "0.3" [features] -default = [] -all = ["esplora_blocking", "electrum_blocking", "mempool_blocking", "serde", "log", "fs", "cli"] -fs = ["serde", "bp-wallet/fs", "rgb-std/fs"] -cli = ["fs", "bp-wallet/cli"] -esplora_blocking = ["bp-esplora", "bp-esplora/blocking"] -esplora_blocking-wasm = ["bp-esplora", "bp-esplora/blocking-wasm"] -electrum_blocking = ["bp-electrum"] -mempool_blocking = ["esplora_blocking"] -serde = ["serde_crate", "serde_yaml", "bp-std/serde", "rgb-psbt/serde"] +default = ["std"] +all = [ + "std", + "serde", "log", "fs", +] +std = [] +fs = [ + "std", + "serde", + "rgb-std/fs", + "bp-wallet/fs", +] +serde = [ + "dep:serde", "serde_yaml", + "bp-std/serde", "rgb-std/serde" +] [package.metadata.docs.rs] features = ["all"] [patch.crates-io] -bp-wallet = { git = "https://github.com/BP-WG/bp-wallet", branch = "master" } -rgb-invoice = { git = "https://github.com/RGB-WG/rgb-std", branch = "master" } -rgb-std = { git = "https://github.com/RGB-WG/rgb-std", branch = "master" } +strict_types = { git = "https://github.com/strict-types/strict-types", branch = "develop" } +ultrasonic = { git = "https://github.com/AluVM/ultrasonic", branch = "master" } +hypersonic = { git = "https://github.com/AluVM/sonic", branch = "master" } +sonic-api = { git = "https://github.com/AluVM/sonic", branch = "master" } +sonic-callreq = { git = "https://github.com/AluVM/sonic", branch = "master" } +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "v0.12" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "v0.12" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "v0.12" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "v0.12" } +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.12" } +bp-derive = { git = "https://github.com/BP-WG/bp-std", branch = "v0.12" } +descriptors = { git = "https://github.com/BP-WG/bp-std", branch = "v0.12" } +psbt = { git = "https://github.com/BP-WG/bp-std", branch = "v0.12" } +bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "v0.12" } +bp-wallet = { git = "https://github.com/BP-WG/bp-wallet", branch = "v0.12" } +rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.12" } +rgb-std = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.12" } +rgb-invoice = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.12" } diff --git a/MANIFEST.yml b/MANIFEST.yml deleted file mode 100644 index 5258264..0000000 --- a/MANIFEST.yml +++ /dev/null @@ -1,14 +0,0 @@ -Name: rgb -Type: Binary -Kind: Free software -License: Apache-2.0 -Language: Rust -Compiler: 1.77 -Author: Maxim Orlovsky -Maintained: LNP/BP Standards Association, Switzerland -Maintainers: - Maxim Orlovsky: - GitHub: @dr-orlovsky - GPG: EAE730CEC0C663763F028A5860094BAF18A26EC9 - SSH: BoSGFzbyOKC7Jm28MJElFboGepihCpHop60nS8OoG/A - EMail: dr@orlovsky.ch diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b408432..803ce17 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,38 +1,31 @@ [package] name = "rgb-wallet" -version = { workspace = true } -description = "Command-line wallet for RGB smart contracts on Bitcoin" -keywords = { workspace = true } -categories = { workspace = true } -readme = "README.md" -authors = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -rust-version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } +description = "RGB smart contracts command-line wallet" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +readme.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true [[bin]] -name = "rgb" path = "src/main.rs" +name = "rgb" [dependencies] -amplify = { workspace = true } -baid64 = { workspace = true } -strict_types = { workspace = true, features = ["serde"] } -commit_verify = { workspace = true } -bp-std = { workspace = true, features = ["serde"] } -bp-wallet = { workspace = true, features = ["cli"] } -rgb-std = { workspace = true, features = ["serde"] } -rgb-runtime = { version = "0.11.0-beta.8", path = "..", features = ["electrum_blocking", "esplora_blocking", "mempool_blocking", "log", "serde", "fs", "cli"] } -log = { workspace = true } -env_logger = "0.11.5" -clap = { version = "4.5.17", features = ["derive", "env"] } -shellexpand = "3.1.0" -serde_crate = { workspace = true } -serde_yaml = { workspace = true } -serde_json = "1.0" -toml = "0.8.19" - -[features] -default = [] +amplify.workspace = true +strict_encoding.workspace = true +strict_types.workspace = true +bp-wallet = { workspace = true, features = ["fs", "serde", "cli"] } +bp-electrum = { workspace = true } +bp-esplora = { workspace = true } +rgb-std = { workspace = true, features = ["fs", "uri", "serde"] } +rgb-runtime = { workspace = true, features = ["fs", "serde"] } +rgb-psbt = { workspace = true, features = ["bp"] } +serde.workspace = true +serde_yaml = "0.9.34" +anyhow = "1.0.93" +clap = { version = "4.5.21", features = ["derive", "env"] } diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index af0b599..0000000 --- a/cli/README.md +++ /dev/null @@ -1,450 +0,0 @@ -## Basic concepts - -In order to understand the following content, you need to grasp these prerequisite concepts. - -### Schema - -The RGB protocol uses *Schemas*, akin to classes in OOP, **defines the contract business logic**, i.e. how the contract -works. Each RGB contract is an instance of a schema created via the schema's genesis operation, separating roles for -contract developers and issuers for easier operation. - -### State - -The **global state** applies to the entire contract (for example the name of token, is not belong to any party of the -contract), while the **ownable state** is associated with specific single-use seals. Contracts also use special syntax -structures like braces, brackets, and question marks to denote sets or arrays of data types involved in state operations -and their optionality. - -The **state extension** allows the public to participate in specific logical parts of the contract, such as declaring a -Burn. State extension operations allow anyone to create state extensions without on-chain commitments, similar to -Bitcoin transactions not yet packaged into a block. - -### Interface - -In RGB, contract interfaces are similar to Ethereum’s ERC standards. Generic interfaces are called “RGBxx” and are -defined as independent LNP/BP standards. - -**Interface Definition**: Defines global states (like Ticker and Name) and ownable states (like Inflation and Asset), -along with operations (like Issue and Transfer). - -**Interface Implementation**: When implementing an interface, states and operations of a specific schema are bound to -the interface. For example, the FungibleToken interface implements global and ownable state bindings for the -DecentralizedIdentity schema. - -## Install - -from source - -``` -$ git clone -$ cd rgb/cli -$ cargo install --path --all-features . -``` - -## Data Directory - -The RGB wallet stores its data in a directory specified by the `DATA_DIR` constant. - -The `DATA_DIR_ENV` environment variable be used to override the default data directory location. If not set, the -default data directory locations are: - -- Linux and BSD-based systems: `~/.lnp-bp` -- macOS: `~/Library/Application Support/LNP-BP Suite` -- Windows: `%LOCALAPPDATA%\\LNP-BP Suite` -- iOS: `~/Documents` -- Android: `.` (the current working directory) - -The wallet will create the data directory if it does not already exist. The data directory is used to store the wallet's -configuration, transaction history, and other persistent data. - -The base directory of the wallet will be `$data_dir/$network`. - -## Configuration File - -The default configuration file is `rgb.toml`. - -Currently, the only supported configuration key is `default_wallet`, and the default value is `default`. - -## Overview - -Here is the command line help for rgb-cli. - -``` -Command-line wallet for RGB smart contracts on Bitcoin - -Usage: rgb [OPTIONS] - -Commands: - list List known wallets - default Get or set default wallet - create Create a wallet - address Generate a new wallet address(es) - taprets - schemata Prints out list of known RGB schemata - interfaces Prints out list of known RGB interfaces - contracts Prints out list of known RGB contracts - import Imports RGB data into the stash: contracts, schema, interfaces, etc - export Exports existing RGB contract - armor Convert binary RGB file into a text armored version - state Reports information about state of a contract - history-fungible Print operation history for a default fungible token under a given interface - utxos Display all known UTXOs belonging to this wallet - issue Issues new contract - invoice Create new invoice - prepare Prepare PSBT file for transferring RGB assets. In the most of cases you need to use `transfer` command instead of `prepare` and `consign` - consign Prepare consignment for transferring RGB assets. In the most of cases you need to use `transfer` command instead of `prepare` and `consign` - transfer Transfer RGB assets - inspect Inspects any RGB data file - dump Debug-dump all stash and inventory data - validate Validate transfer consignment - accept Validate transfer consignment & accept to the stash - help Print this message or the help of the given subcommand(s) - -Options: - -v, --verbose... - Set verbosity level - -w, --wallet - - -W, --wallet-path - Path to wallet directory - --tapret-key-only - Use tapret(KEY) descriptor as wallet - --wpkh - Use wpkh(KEY) descriptor as wallet - -e, --esplora - Esplora server to use [env: ESPLORA_SERVER=] [default: ] - --sync - - -d, --data-dir - Data directory path [env: LNPBP_DATA_DIR=] [default: ~/.lnp-bp] - -n, --network - Network to use [env: LNPBP_NETWORK=] [default: testnet] - -h, --help - Print help (see more with '--help') - -V, --version - Print version - -``` - -## Preparation - -### Create a wallet - -To create a wallet, you need to prepare a wallet descriptor. You can create a wallet via `bdk-cli` or other similar -utilities. - -Here is an example descriptor: - -```shell -[1f09c6b9/86h/1h/0h]tpubDCrfSMscBA93FWm8qounj6kcBjnw6LxmVeKSi6VoYS327VCpoLHARWjdqeVtDt2ujDRznB9m1uXpHkDpDXyXM5gsvg2bMMmFcSHrtWUA4Py/<0;1;9;10>/* -``` - -``` -$ rgb create my_wallet --wpkh "[1f09c6b9/86h/1h/0h]tpubDCrfSMscBA93FWm8qounj6kcBjnw6LxmVeKSi6VoYS327VCpoLHARWjdqeVtDt2ujDRznB9m1uXpHkDpDXyXM5gsvg2bMMmFcSHrtWUA4Py/<0;1;9;10>/*" -``` - -Now we can find the related files created in the wallet runtime directory: - -```shell -$ ls ~/.lnp-bp/testnet3/my_wallet - -cache.yaml data.toml descriptor.toml -``` - -### List wallets - -Usage: - -```shell -$ rgb list -``` - -Example output: - -```shell -Known wallets: -my_wallet -``` - -### Set default wallet - -Now let’s set our default wallet to `my_wallet` - -```shell -$ rgb default my_wallet -``` - -## Assets - -### Import schemata - -The schemata file’s name ends with `.rgba`, and the standard schemata can be found -in [`https://github.com/RGB-WG/rgb-schemata`](https://github.com/RGB-WG/rgb-schemata) repository. - -You can take a look -at [https://github.com/RGB-WG/rgb-schemata/blob/master/schemata/NonInflatableAssets.rgba](https://github.com/RGB-WG/rgb-schemata/blob/master/schemata/NonInflatableAssets.rgba) -which is the NIA schema. - -Example: - -```shell -$ rgb import rgb-schemata/schemata/NonInflatableAssets.rgb -``` - -### List schemata - -```shell -$ rgb schemata -``` - -Example Output: - -```shell -urn:lnp-bp:sc:9ZKGvK-tGs6nJvr-HQVRDDyV-zPnJYE5U-J2mb6yDi-PgBrby#frog-order-costume -``` - -### Import interface - -Now we need to import the interface definition and interface implementation, otherwise you may encounter an error: - -```shell -Error: no known interface implementation for XXX -``` - -Execute: - -```shell -$ rgb import ../rgb-schemata/interfaces/RGB20.rgb -$ rgb import ../rgb-schemata/schemata/NonInflatableAssets-RGB20.rgb -``` - -### List interface - -```shell -$ rgb interfaces -``` - -```shell -RGB21 urn:lnp-bp:if:KtMq1E-bFRhMzn5-sc9NezhQ-kn2JzeJn-VxjDCqru-sieYa#portal-ecology-hostel -RGB25 urn:lnp-bp:if:75swax-yN5mDaKB-B3peGeLu-tLctU3Ef-rAjFySp7-RMLTVF#cable-kayak-david -RGB20 urn:lnp-bp:if:9UMsvx-HkLVK5VT-GkSy7yNU-ihAUBo7a-hxQvLCFq-U4aouK#object-spring-silk -``` - -### Issue a contract - -Usage: - -``` -$ rgb issue [OPTIONS] - -``` - -Tutorial: - -Write a contract declaration. (YAML in this example) - -```yaml -interface: RGB20 - -globals: - spec: - naming: - ticker: DBG - name: Debug asset - details: "Pay attention: the asset has no value" - precision: 2 - 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: 100000000 - created: 1687969158 - -assignments: - assetOwner: - seal: tapret1st:fb9ae7ae4b70a27e7fdfdefac91b37967b549d65007dbf25470b0817a2ae810a:1 - amount: 100000000 # this is 1 million (we have two digits for cents) - -``` - -Here, we observe a seal value in the form of `closing_method:txid:vout` and here closing method is `tapret1st` (can also -be `opret1st`). This hash, in reality, represents the txid of the previously created PSBT. And `txid:vout` is the -outpoint of a valid UTXO. - -Compile the contract: - -``` -$ rgb issue urn:lnp-bp:sc:9ZKGvK-tGs6nJvr-HQVRDDyV-zPnJYE5U-J2mb6yDi-PgBrby#frog-order-costume ./examples/rgb20-demo.yaml - -``` - -A contract (which also serves as a consignment) will be generated and imported into the current runtime's stock. - -Output: - -```shell -A new contract rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB is issued and added to the stash. -``` - -### Export contract - -Next, we export the contract that was just created. - -```shell -$ rgb export rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB -RGB: command-line wallet for RGB smart contracts - by LNP/BP Standards Association - -Loading descriptor from wallet my_wallet ... success -Loading stock ... success ------BEGIN RGB CONSIGNMENT----- -Id: urn:lnp-bp:consignment:Ctc1wq-Xrqm78uM-nNaDsoHj-TJESKydn-4GLgtYmr-G9AdQE#smoke-oxford-burger -Version: v2 -Type: contract -Contract-Id: rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB -Checksum-SHA256: 50468d33da7aab15c8c2b467126b721c4c3c6cf31d00c8964fb12e23fbc64777 - -0ssM^4-D2iQYiE=(kro^uy=TL1t60DmODi%$$wo#Ma -... - ------END RGB CONSIGNMENT----- -``` - -The consignment encoded in base64 format will be output to the `stdout`. - -Alternatively, you can specify a file name to obtain the binary consignment: - -```shell -$ rgb export rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB demo.rgb - -Contract rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB exported to 'demo.rgb' -``` - -### Import contract (or other kind of consignment) - -Consignments can be imported using the import subcommand, but the RGB CLI already automatically imports the contract, so -there is no need to execute it. - -```shell -$ rgb import demo.rgb -``` - -### Read the contract state - -```shell -$ rgb state rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB RGB20 - -Global: - spec := (naming=(ticker=("DBG"), name=("Debug asset"), details=1(("Pay attention: the asset has no value"))), precision=2) - data := (terms=("..."), media=~) - issuedSupply := (100000000) - created := (1687969158) - -Owned: - assetOwner: - -``` - -### List contract - -Execute: - -```shell -$ rgb contracts -``` - -Example output: - -```shell -rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB -``` - -### Take an address - -```shell -$ rgb address -Term. Address -&0/0 tb1qeyu926l47099vtp7wewvhwt03vc5sn5c6t604p -``` - -Run multiple times to generate more addresses at different indexes. To view an address at given index, for example `0`, -execute: - -```shell -$ rgb address --index 0 -``` - -### Create an address based invoice - -```shell -$ rgb invoice --address-based rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB RGB20 100 - -``` - -Created invoice: - -```shell -rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB/RGB20/100+tb:q0q6u0urtzn59cg9qacm7c5aq7ud3wmgms7stew -``` - -Here's a breakdown of the different parts of the invoice string: - -1. `rgb:DF4vyV9-i85ZzUqbq-QLxvKtgtp-AJk9NvpL3-k4AHmcRrf-vyHksB`: This is the contract ID, which is a unique identifier - for the contract associated with this invoice. -2. `RGB20`: This is the interface (or protocol) used for the transaction. -3. `100`: This is the amount of the transaction, which is 100 units. -4. `tb:q0q6u0urtzn59cg9qacm7c5aq7ud3wmgms7stew`: This is the beneficiary of the transaction - -The invoice string could also includes some additional parameters that are encoded as query parameters, which are -separated by the `?` character. These parameters are used to provide additional information about the transaction, such -as the operation being performed or the assignment associated with the transaction. - -### Validate the consignment - -```shell -$ rgb validate demo.rgb -``` - -Example output: - -```shell -Consignment has non-mined terminal(s) -Non-mined terminals: -- f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 -Validation warnings: -- terminal witness transaction f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 is not yet mined. -``` - -### Sign and broadcast the transaction - -Create transfer: - -```shell -$ rgb transfer [PSBT] -$ rgb transfer \ - rgb:2bLwMXo-deVgzKq97-GUVy6wXea-G1nE84nxw-v5CX3WSJN-mbhsMn7/RGB20/1000+bcrt:p9yjaffzhuh9p7d9gnwfunxssngesk25tz7rudu4v69dl6e7w7qhq5x43k5 \ - transfer.consignment \ - alice.psbt -``` - -Now you can use bdk-cli or any other wallet to sign and broadcast the transaction. - -### Wait for confirmation and accept transfer - -As receiver: - -```shell -$ rgb accept -f CONSIGNMENT_FILE -``` diff --git a/cli/src/args.rs b/cli/src/args.rs index 4614f5d..0c08798 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,195 +1,164 @@ -// RGB smart contracts for Bitcoin & Lightning +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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::needless_update)] // Required by From derive macro +// 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::fs; -use std::io::ErrorKind; -use std::ops::{Deref, DerefMut}; use std::path::PathBuf; +use std::process::exit; + +use bpwallet::cli::ResolverOpt; +use bpwallet::fs::FsTextStore; +use bpwallet::{AnyIndexer, Network}; +use clap::ValueHint; +use rgb::popls::bp::file::{BpDirMound, DirBarrow}; +use rgb::Consensus; +use rgbp::{RgbDirRuntime, RgbWallet}; + +use crate::cmd::Cmd; +use crate::opts::WalletOpts; + +pub const RGB_NETWORK_ENV: &str = "RGB_NETWORK"; +pub const RGB_NO_NETWORK_PREFIX_ENV: &str = "RGB_NO_NETWORK_PREFIX"; + +pub const RGB_DATA_DIR_ENV: &str = "RGB_DATA_DIR"; +#[cfg(target_os = "linux")] +pub const RGB_DATA_DIR: &str = "~/.rgb"; +#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))] +pub const RGB_DATA_DIR: &str = "~/.rgb"; +#[cfg(target_os = "macos")] +pub const RGB_DATA_DIR: &str = "~/Library/Application Support/RGB Smart Contracts"; +#[cfg(target_os = "windows")] +pub const RGB_DATA_DIR: &str = "~\\AppData\\Local\\RGB Smart Contracts"; +#[cfg(target_os = "ios")] +pub const RGB_DATA_DIR: &str = "~/Documents"; +#[cfg(target_os = "android")] +pub const RGB_DATA_DIR: &str = "."; -use bpstd::{Wpkh, XpubDerivable}; -use bpwallet::cli::{Args as BpArgs, Config, DescriptorOpts}; -use bpwallet::Wallet; -use rgb::persistence::Stock; -use rgb::resolvers::AnyResolver; -use rgb::{RgbDescr, RgbWallet, TapretKey, WalletError}; -use rgbstd::persistence::fs::FsBinStore; -use strict_types::encoding::{DecodeError, DeserializeError}; - -use crate::Command; - -#[derive(Args, Clone, PartialEq, Eq, Debug)] -#[group()] -pub struct DescrRgbOpts { - /// Use tapret(KEY) descriptor as wallet. - #[arg(long, global = true)] - pub tapret_key_only: Option, - - /// Use wpkh(KEY) descriptor as wallet. - #[arg(long, global = true)] - pub wpkh: Option, -} - -impl DescriptorOpts for DescrRgbOpts { - type Descr = RgbDescr; - - fn is_some(&self) -> bool { self.tapret_key_only.is_some() || self.wpkh.is_some() } - - fn descriptor(&self) -> Option { - self.tapret_key_only - .clone() - .map(TapretKey::from) - .map(TapretKey::into) - .or(self.wpkh.clone().map(Wpkh::from).map(Wpkh::into)) - } -} - -/// Command-line arguments #[derive(Parser)] -#[derive(Clone, Eq, PartialEq, Debug)] -#[command(author, version, about)] -pub struct RgbArgs { - #[clap(flatten)] - pub inner: BpArgs, - - /// Specify blockchain height starting from which witness transactions - /// should be checked for re-orgs - #[clap(short = 'H', long, requires = "sync")] - pub from_height: Option, -} - -impl Deref for RgbArgs { - type Target = BpArgs; - #[inline] - fn deref(&self) -> &Self::Target { &self.inner } -} - -impl DerefMut for RgbArgs { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } +pub struct Args { + /// Location of the data directory + #[clap( + short, + long, + global = true, + default_value = RGB_DATA_DIR, + env = RGB_DATA_DIR_ENV, + value_hint = ValueHint::DirPath + )] + pub data_dir: PathBuf, + + /// Initialize data directory if it doesn't exit + #[clap(long, global = true)] + pub init: bool, + + /// Bitcoin network + #[arg(short, long, global = true, default_value = "testnet4", env = RGB_NETWORK_ENV)] + pub network: Network, + + /// Do not add network name as a prefix to the data directory + #[arg(long, global = true, env = RGB_NO_NETWORK_PREFIX_ENV)] + pub no_network_prefix: bool, + + /// Command to execute + #[clap(subcommand)] + pub command: Cmd, } -impl Default for RgbArgs { - fn default() -> Self { unreachable!() } -} - -impl RgbArgs { - pub(crate) fn load_stock( - &self, - stock_path: impl ToOwned, - ) -> Result { - let stock_path = stock_path.to_owned(); - - if self.verbose > 1 { - eprint!("Loading stock from `{}` ... ", stock_path.display()); - } - - let provider = FsBinStore::new(stock_path.clone())?; - let mut stock = Stock::load(provider, true).or_else(|err| { - if err - .0 - .downcast_ref::() - .map(|e| matches!(e, DeserializeError::Decode(DecodeError::Io(ref e)) if e.kind() == ErrorKind::NotFound)) - .unwrap_or_default() - { - if self.verbose > 1 { - eprint!("stock file is absent, creating a new one ... "); - } - fs::create_dir_all(&stock_path)?; - let provider = FsBinStore::new(stock_path)?; - let mut stock = Stock::in_memory(); - stock - .make_persistent(provider, true) - .map_err(WalletError::StockPersist)?; - return Ok(stock); - } - eprintln!("stock file is damaged, failing"); - error!("Unable to load stock data: {err:?}"); - Err(WalletError::StockPersist(err)) - })?; - - if self.sync { - let resolver = self.resolver()?; - let from_height = self.from_height.unwrap_or(1); - eprint!("Updating witness information starting from height {from_height} ... "); - let res = stock.update_witnesses(resolver, from_height)?; - eprint!("{} transactions were checked and updated", res.succeeded); - if res.failed.is_empty() { - eprintln!(); - } else { - eprintln!(", {} resolution failures:", res.failed.len()); - for (witness_id, failure) in res.failed { - eprintln!(" - {witness_id}: {failure}"); - } +impl Args { + pub fn data_dir(&self) -> PathBuf { + if self.no_network_prefix { + self.data_dir.clone() + } else { + let mut dir = self.data_dir.join("bitcoin"); + if self.network.is_testnet() { + dir.set_extension("testnet"); } + dir } + } - if self.verbose > 1 { - eprintln!("success"); + pub fn mound(&self) -> BpDirMound { + if self.init { + let _ = fs::create_dir_all(self.data_dir()); } - - Ok(stock) + if !self.network.is_testnet() { + panic!("Non-testnet networks are not yet supported"); + } + BpDirMound::load_testnet(Consensus::Bitcoin, &self.data_dir, self.no_network_prefix) } - pub fn rgb_stock(&self) -> Result { - let stock_path = self.general.base_dir(); - let stock = self.load_stock(stock_path)?; - Ok(stock) + fn wallet_dir(&self, name: Option<&str>) -> PathBuf { + self.data_dir() + .join(name.unwrap_or("default")) + .with_extension("wallet") } - pub fn rgb_wallet( - &self, - config: &Config, - ) -> Result>, WalletError> { - let stock = self.rgb_stock()?; - self.rgb_wallet_from_stock(config, stock) - .map_err(|(_, err)| err) + pub fn wallet_provider(&self, name: Option<&str>) -> FsTextStore { + FsTextStore::new(self.wallet_dir(name)).expect("Broken directory structure") } - pub fn rgb_wallet_from_stock( - &self, - config: &Config, - stock: Stock, - ) -> Result>, (Stock, WalletError)> { - let wallet = match self.inner.bp_wallet::(config) { - Ok(wallet) => wallet, - Err(e) => return Err((stock, e.into())), - }; - let wallet = RgbWallet::new(stock, wallet); - - Ok(wallet) + pub fn runtime(&self, opts: &WalletOpts) -> RgbDirRuntime { + let provider = self.wallet_provider(opts.wallet.as_deref()); + let wallet = RgbWallet::load(provider, true).unwrap_or_else(|_| { + panic!( + "Error: unable to load wallet from path `{}`", + self.wallet_dir(opts.wallet.as_deref()).display() + ) + }); + let mut runtime = RgbDirRuntime::from(DirBarrow::with(wallet, self.mound())); + if opts.sync { + eprintln!("Synchronizing wallet"); + runtime.wallet.update(&self.indexer(&opts.resolver), false); + } + runtime } - pub fn resolver(&self) -> Result { - let resolver = - match (&self.resolver.esplora, &self.resolver.electrum, &self.resolver.mempool) { - (None, Some(url), None) => AnyResolver::electrum_blocking(url, None), - (Some(url), None, None) => AnyResolver::esplora_blocking(url, None), - (None, None, Some(url)) => AnyResolver::mempool_blocking(url, None), - _ => Err(s!(" - error: no transaction resolver is specified; use either \ - --esplora --mempool or --electrum argument")), + pub fn indexer(&self, resolver: &ResolverOpt) -> AnyIndexer { + let network = self.network.to_string(); + match (&resolver.esplora, &resolver.electrum, &resolver.mempool) { + (None, Some(url), None) => AnyIndexer::Electrum(Box::new( + // TODO: Check network match + electrum::Client::new(url).expect("Unable to initialize indexer"), + )), + (Some(url), None, None) => AnyIndexer::Esplora(Box::new( + bpwallet::indexers::esplora::Client::new_esplora( + &url.replace("{network}", &network), + ) + .expect("Unable to initialize indexer"), + )), + (None, None, Some(url)) => AnyIndexer::Mempool(Box::new( + bpwallet::indexers::esplora::Client::new_mempool( + &url.replace("{network}", &network), + ) + .expect("Unable to initialize indexer"), + )), + _ => { + eprintln!( + "Error: no blockchain indexer specified; use either --esplora --mempool or \ + --electrum argument" + ); + exit(1); } - .map_err(WalletError::Resolver)?; - resolver.check(self.general.network)?; - Ok(resolver) + } } } diff --git a/cli/src/cmd.rs b/cli/src/cmd.rs new file mode 100644 index 0000000..9b5fbfa --- /dev/null +++ b/cli/src/cmd.rs @@ -0,0 +1,337 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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::path::PathBuf; + +use bpwallet::cli::ResolverOpt; +use bpwallet::Sats; +use clap::ValueHint; +use rgb::invoice::RgbInvoice; +use rgb::{AuthToken, ContractId, ContractRef, MethodName, StateName}; +use rgbp::CoinselectStrategy; +use strict_encoding::TypeName; + +use crate::opts::WalletOpts; + +pub const RGB_COINSELECT_STRATEGY_ENV: &str = "RGB_COINSELECT_STRATEGY"; +pub const RGB_WALLET_ENV: &str = "RGB_WALLET"; +pub const RGB_PSBT_VER: &str = "RGB_PSBT_VER2"; + +#[derive(Parser)] +pub enum Cmd { + // ===================================================================================== + // I. Wallet management + /// List known wallets + Wallets, + + /// Create a new wallet + Create { + #[clap(long, conflicts_with = "wpkh")] + tapret_key_only: bool, + + #[clap(long)] + wpkh: bool, + + /// Wallet name + name: String, + + /// Extended pubkey descriptor + descriptor: String, + }, + + /// Synchronize wallet with blockchain data + Sync { + #[clap(flatten)] + resolver: ResolverOpt, + + /// Wallet to use + #[clap(env = RGB_WALLET_ENV)] + wallet: Option, + }, + + /// Receiving a wallet address for gas funding + Fund { + /// Wallet to use + #[clap(env = RGB_WALLET_ENV)] + wallet: Option, + }, + + /// List available wallet seals + Seals { + /// Wallet to use + #[clap(env = RGB_WALLET_ENV)] + wallet: Option, + }, + + // ===================================================================================== + // II. Contract management + /// List contracts + Contracts, + + /// Issue a new RGB contract + Issue { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + wallet: Option, + + /// Parameters and data for the contract + #[clap(value_hint = ValueHint::FilePath)] + params: Option, + }, + + /// Remove contract + Purge { + /// Force removal of a contract with a known state + #[clap(short, long)] + force: bool, + + /// Contract id to remove + contract: ContractRef, + }, + + /// Import contract articles + Import { + /// Contract articles to process + #[clap(value_hint = ValueHint::FilePath)] + articles: PathBuf, + }, + + /// Export contract articles + Export { + /// Contract id to export + contract: ContractRef, + + /// Path to export articles to + #[clap(value_hint = ValueHint::FilePath)] + file: Option, + }, + + Backup { + /// Path for saving backup tar file + #[clap(default_value = "rgb-backup.tar", value_hint = ValueHint::FilePath)] + file: PathBuf, + }, + + // ===================================================================================== + // III. Combined contract/wallet operations + /// Print out a contract state + #[clap(alias = "s")] + State { + #[clap(flatten)] + wallet: WalletOpts, + + /// Present all the state, not just the one owned by the wallet + #[clap(short, long)] + all: bool, + + /// Display global state entries + #[clap(short, long, required_unless_present = "owned")] + global: bool, + + /// Display owned state entries + #[clap(short, long)] + owned: bool, + + /// Print out just a single contract state + contract: Option, + }, + + /// Generate an invoice + Invoice { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + wallet: Option, + + /// Just generate a single-use seal, and not an entire invoice + #[clap(long)] + seal_only: bool, + + /// Use witness output-based seal + #[clap(long)] + wout: bool, + + /// Nonce number to use + #[clap(long, global = true)] + nonce: Option, + + /// Contract to use + contract: Option, + + /// API name to interface the contract + /// + /// If skipped, a default contract API will be used. + #[clap(short, long, global = true)] + api: Option, + + /// Method name to call the contract with + /// + /// If skipped, a default API method will be used. + #[clap(short, long, global = true)] + method: Option, + + /// State name used for the invoice + /// + /// If skipped, a default API state for the default method will be used. + #[clap(short, long, global = true)] + state: Option, + + /// Invoiced state value + value: Option, + }, + + /// Pay an invoice, creating ready-to-be signed PSBT and a consignment + #[clap(alias = "p")] + Pay { + #[clap(flatten)] + wallet: WalletOpts, + + /// Coinselect strategy to use + #[clap(short, long, default_value = "aggregate", env = RGB_COINSELECT_STRATEGY_ENV)] + strategy: CoinselectStrategy, + + /// Amount of sats to send to pay-to-address invoice + #[clap(long, global = true)] + sats: Option, + + /// Fees for PSBT + #[clap(long, global = true, default_value = "1000")] + fee: Sats, + + /// Use PSBT version 2 + #[clap(short = '2', long, global = true, env = "RGB_PSBT_VER2")] + psbt2: bool, + + /// Print PSBT to STDOUT + #[clap(short, long, global = true)] + print: bool, + + /// Invoice to fulfill + invoice: RgbInvoice, + + /// Location to save the consignment file to + #[clap(value_hint = ValueHint::FilePath)] + consignment: PathBuf, + + /// File to save the produced PSBT + /// + /// If not provided, uses the same path and filename as for the consignment, adding *.psbt + /// extension. + #[clap(value_hint = ValueHint::FilePath)] + psbt: Option, + }, + + /// Create a payment script out from invoice + Script { + #[clap(flatten)] + wallet: WalletOpts, + + /// Amount of sats to send to pay-to-address invoice + #[clap(long, global = true)] + sats: Option, + + /// Coinselect strategy to use + #[clap(short, long, default_value = "aggregate", env = RGB_COINSELECT_STRATEGY_ENV)] + strategy: CoinselectStrategy, + + /// Invoice to fulfill + invoice: RgbInvoice, + + /// Location to save the payment script to + #[clap(value_hint = ValueHint::FilePath)] + output: PathBuf, + }, + + /// Execute a script, producing prefabricated operation bundle and PSBT + #[clap(alias = "x")] + Exec { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + wallet: Option, + + /// YAML file with a script to execute + #[clap(value_hint = ValueHint::FilePath)] + script: PathBuf, + + /// File to save the produced prefabricated operation bundle + #[clap(value_hint = ValueHint::FilePath)] + bundle: PathBuf, + + /// Fees for PSBT + fee: Sats, + + /// Use PSBT version 2 + #[clap(short = '2', long, global = true, env = "RGB_PSBT_VER2")] + psbt2: bool, + + /// Print PSBT to STDOUT + #[clap(short, long, global = true)] + print: bool, + + /// File to save the produced PSBT + /// + /// If not provided, uses the same filename as for the bundle, replacing the extension with + /// 'psbt'. + #[clap(value_hint = ValueHint::FilePath)] + psbt: Option, + }, + + /// Complete finalizes PSBT and adds information about witness to the contracts mound + Complete { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + wallet: Option, + + /// Prefabricated operation bundle, used in PSBT construction + bundle: PathBuf, + + /// Signed PSBT + psbt: PathBuf, + }, + + /// Create a consignment transferring part of a contract state to another peer + Consign { + /// Contract to use for the consignment + contract: ContractRef, + + /// List of tokens of authority which should serve as a contract terminals + #[clap(short, long)] + terminals: Vec, + + /// Location to save the consignment file to + #[clap(value_hint = ValueHint::FilePath)] + output: PathBuf, + }, + + /// Verify and accept a consignment + #[clap(alias = "a")] + Accept { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + wallet: Option, + + /// File with consignment to accept + #[clap(value_hint = ValueHint::FilePath)] + input: PathBuf, + }, +} diff --git a/cli/src/command.rs b/cli/src/command.rs deleted file mode 100644 index f14b5c2..0000000 --- a/cli/src/command.rs +++ /dev/null @@ -1,1316 +0,0 @@ -// RGB smart contracts for Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::fs; -use std::fs::File; -use std::ops::ControlFlow; -use std::path::PathBuf; -use std::str::FromStr; - -use amplify::confinement::{SmallOrdMap, TinyOrdMap, TinyOrdSet, U16 as MAX16}; -use baid64::DisplayBaid64; -use bpstd::psbt::{Psbt, PsbtVer}; -use bpstd::seals::SecretSeal; -use bpstd::{Sats, XpubDerivable}; -use bpwallet::cli::{BpCommand, Config, Exec}; -use bpwallet::Wallet; -use rgb::containers::{ - BuilderSeal, ConsignmentExt, ContainerVer, ContentId, ContentSigs, Contract, FileContent, - Supplement, Transfer, UniversalFile, -}; -use rgb::interface::{AssignmentsFilter, ContractOp, IfaceId}; -use rgb::invoice::{Beneficiary, Pay2Vout, RgbInvoice, RgbInvoiceBuilder, XChainNet}; -use rgb::persistence::{MemContract, StashReadProvider, Stock}; -use rgb::resolvers::ContractIssueResolver; -use rgb::schema::SchemaId; -use rgb::validation::Validity; -use rgb::vm::{RgbIsa, WitnessOrd}; -use rgb::{ - Allocation, BundleId, ContractId, DescriptorRgb, GenesisSeal, GraphSeal, Identity, OpId, - OutputSeal, OwnedFraction, RgbDescr, RgbKeychain, RgbWallet, StateType, TokenIndex, - TransferParams, WalletError, WalletProvider, XChain, XOutpoint, XWitnessId, -}; -use rgbstd::interface::{AllocatedState, ContractIface, OwnedIface}; -use rgbstd::persistence::{MemContractState, StockError}; -use rgbstd::stl::rgb_contract_stl; -use rgbstd::{KnownState, OutputAssignment}; -use serde_crate::{Deserialize, Serialize}; -use strict_types::encoding::{FieldName, TypeName}; -use strict_types::StrictVal; - -use crate::RgbArgs; - -#[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] -#[display(lowercase)] -#[allow(clippy::large_enum_variant)] -pub enum Command { - #[clap(flatten)] - #[display(inner)] - General(bpwallet::cli::Command), - - #[clap(flatten)] - #[display(inner)] - Debug(DebugCommand), - - /// Prints out list of known RGB schemata - Schemata, - /// Prints out list of known RGB interfaces - Interfaces, - - /// Prints out list of known RGB contracts - #[display("contracts")] - Contracts, - - /// Imports RGB data into the stash: contracts, schema, interfaces, etc - #[display("import")] - Import { - /// Use BASE64 ASCII armoring for binary data - #[arg(short)] - armored: bool, - - /// File with RGB data - /// - /// If not provided, assumes `-a` and prints out data to STDOUT - file: PathBuf, - }, - - /// Exports existing RGB contract - #[display("export")] - Export { - /// Use BASE64 ASCII armoring for binary data - #[arg(short)] - armored: bool, - - /// Contract to export - contract: ContractId, - - /// File with RGB data - /// - /// If not provided, assumes `-a` and reads the data from STDIN - file: Option, - }, - - /// Convert binary RGB file into a text armored version - #[display("convert")] - Armor { - /// File with RGB data - /// - /// If not provided, assumes `-a` and reads the data from STDIN - file: PathBuf, - }, - - /// Reports information about state of a contract - #[display("state")] - State { - /// Show all state, including already spent and not owned by the wallet - #[arg(short, long)] - all: bool, - - /// Contract identifier - contract_id: ContractId, - - /// Interface to interpret the state data - iface: Option, - }, - - /// Print operation history for a default fungible token under a given - /// interface - #[display("history")] - History { - /// Print detailed information - #[arg(long)] - details: bool, - - /// Contract identifier - contract_id: ContractId, - - /// Interface to interpret the state data - iface: Option, - }, - - /// Display all known UTXOs belonging to this wallet - Utxos, - - /// Issues new contract - #[display("issue")] - Issue { - /// 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, - }, - - /// Create new invoice - #[display("invoice")] - Invoice { - /// Force address-based invoice - #[arg(short, long)] - address_based: bool, - - /// Interface to interpret the state data - #[arg(short, long)] - iface: Option, - - /// Operation to use for the invoice - /// - /// If no operation is provided, the interface default operation is used. - #[arg(short, long)] - operation: Option, - - /// State name to use for the invoice - /// - /// If no state name is provided, the interface default state name for the operation is - /// used. - #[arg(short, long, requires = "operation")] - state: Option, - - /// Contract identifier - contract_id: ContractId, - - /// Amount of tokens (in the smallest unit) to transfer - #[arg(short, long)] - amount: Option, - - /// Token index for NFT transfer - #[arg(long)] - token_index: Option, - - /// Fraction of an NFT token to transfer - #[arg(long, requires = "token_index")] - token_fraction: Option, - }, - - /// Prepare PSBT file for transferring RGB assets - /// - /// In the most of cases you need to use `transfer` command instead of `prepare` and `consign`. - #[display("prepare")] - Prepare { - /// Encode PSBT as V2 - #[clap(short = '2')] - v2: bool, - - /// Amount of satoshis which should be paid to the address-based - /// beneficiary - #[arg(long, default_value = "2000")] - sats: Sats, - - /// Invoice data - invoice: RgbInvoice, - - /// Fee - fee: Sats, - - /// Name of PSBT file to save. If not given, prints PSBT to STDOUT - psbt: Option, - }, - - /// Prepare consignment for transferring RGB assets - /// - /// In the most of the cases you need to use `transfer` command instead of `prepare` and - /// `consign`. - #[display("prepare")] - Consign { - /// Invoice data - invoice: RgbInvoice, - - /// Name of PSBT file containing prepared transfer data - psbt: PathBuf, - - /// File for generated transfer consignment - consignment: PathBuf, - }, - - /// Transfer RGB assets - #[display("transfer")] - Transfer { - /// Encode PSBT as V2 - #[arg(short = '2')] - v2: bool, - - /// Amount of satoshis which should be paid to the address-based - /// beneficiary - #[arg(long, default_value = "2000")] - sats: Sats, - - /// Invoice data - invoice: RgbInvoice, - - /// Fee for bitcoin transaction, in satoshis - #[arg(short, long, default_value = "400")] - fee: Sats, - - /// File for generated transfer consignment - consignment: PathBuf, - - /// Name of PSBT file to save. If not given, prints PSBT to STDOUT - psbt: Option, - }, - - /// Inspects any RGB data file - #[display("inspect")] - Inspect { - /// RGB file to inspect - file: PathBuf, - - /// Path to save the dumped data. If not given, prints PSBT to STDOUT. - path: Option, - - /// Export using directory format for the compound bundles - #[clap(long, requires("path"))] - dir: bool, - }, - - /// Reconstructs consignment from a YAML file - #[display("reconstruct")] - #[clap(hide = true)] - Reconstruct { - #[clap(long)] - contract: bool, - - /// RGB file with the consignment YAML data - src: PathBuf, - - /// Path for the resulting consignment file. If not given, prints the - /// consignment to STDOUT. - dst: Option, - }, - - /// Debug-dump all stash and inventory data - #[display("dump")] - Dump { - /// Directory to put the dump into - #[arg(default_value = "./rgb-dump")] - root_dir: String, - }, - - /// Validate transfer consignment - #[display("validate")] - Validate { - /// File with the transfer consignment - file: PathBuf, - }, - - /// Validate transfer consignment & accept to the stash - #[display("accept")] - Accept { - /// Force accepting consignments with non-mined terminal witness - #[arg(short, long)] - force: bool, - - /// File with the transfer consignment - file: PathBuf, - }, -} - -#[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)] -#[display(lowercase)] -#[clap(hide = true)] -pub enum DebugCommand { - /// List known tapret tweaks for a wallet - Taprets, -} - -impl Exec for RgbArgs { - type Error = WalletError; - const CONF_FILE_NAME: &'static str = "rgb.toml"; - - fn exec(self, config: Config, _name: &'static str) -> Result<(), WalletError> { - match &self.command { - Command::General(cmd) => { - self.inner.translate(cmd).exec(config, "rgb")?; - } - 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}"); - } - } - Command::Schemata => { - let stock = self.rgb_stock()?; - for info in stock.schemata()? { - print!("{info}"); - } - } - Command::Interfaces => { - let stock = self.rgb_stock()?; - for info in stock.ifaces()? { - print!("{info}"); - } - } - Command::Contracts => { - let stock = self.rgb_stock()?; - for info in stock.contracts()? { - print!("{info}"); - } - } - - Command::History { - contract_id, - iface, - details, - } => { - let wallet = self.rgb_wallet(&config)?; - let iface = match contract_default_iface_name(*contract_id, wallet.stock(), iface)? - { - ControlFlow::Continue(name) => name, - ControlFlow::Break(_) => return Ok(()), - }; - let mut history = wallet.history(*contract_id, iface)?; - history.sort_by_key(|op| op.witness.map(|w| w.ord).unwrap_or(WitnessOrd::Archived)); - if *details { - println!("Operation\tValue \tState\t{:78}\tWitness", "Seal"); - } else { - println!("Operation\tValue \t{:78}\tWitness", "Seal"); - } - for ContractOp { - direction, - ty, - opids, - state, - to, - witness, - } in history - { - print!("{:9}\t", direction.to_string()); - if let AllocatedState::Amount(amount) = state { - print!("{: >9}", amount.value()); - } else { - print!("{state:>9}"); - } - if *details { - print!("\t{ty}"); - } - println!( - "\t{}\t{}", - to.first().expect("at least one receiver is always present"), - witness - .map(|info| format!("{} ({})", info.id, info.ord)) - .unwrap_or_else(|| s!("~")) - ); - if *details { - println!( - "\topid={}", - opids - .iter() - .map(OpId::to_string) - .collect::>() - .join("\n\topid=") - ) - } - } - } - - Command::Import { armored, file } => { - let mut stock = self.rgb_stock()?; - assert!(!armored, "importing armored files is not yet supported"); - // TODO: Support armored files - let content = UniversalFile::load_file(file)?; - match content { - UniversalFile::Kit(kit) => { - let id = kit.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); - } - for schema in &kit.schemata { - let schema_id = schema.schema_id(); - schema_names.insert(schema_id, &schema.name); - eprintln!("- schema {} {:-}", schema.name, schema_id); - } - for iimpl in &kit.iimpls { - let iface = iface_names - .get(&iimpl.iface_id) - .map(|name| name.to_string()) - .unwrap_or_else(|| iimpl.iface_id.to_string()); - let schema = schema_names - .get(&iimpl.schema_id) - .map(|name| name.to_string()) - .unwrap_or_else(|| iimpl.schema_id.to_string()); - eprintln!("- implementation of {iface} for {schema}",); - } - for lib in &kit.scripts { - eprintln!("- script library {}", lib.id()); - } - 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 id = contract.consignment_id(); - eprintln!("Importing consignment {id}:"); - let resolver = self.resolver()?; - eprint!("- validating the contract {} ... ", contract.contract_id()); - let contract = contract - .validate(&resolver, self.general.network.is_testnet()) - .map_err(|(status, _)| { - eprintln!("failure"); - status.to_string() - })?; - eprintln!("success"); - stock.import_contract(contract, &resolver)?; - eprintln!("Consignment is imported"); - } - UniversalFile::Transfer(_) => { - return Err(s!("use `validate` and `accept` commands to work with \ - transfer consignments") - .into()); - } - } - } - Command::Export { - armored: _, - contract, - file, - } => { - let stock = self.rgb_stock()?; - let contract = stock - .export_contract(*contract) - .map_err(|err| err.to_string())?; - if let Some(file) = file { - // TODO: handle armored flag - contract.save_file(file)?; - eprintln!("Contract {contract} exported to '{}'", file.display()); - } else { - println!("{contract}"); - } - } - - Command::Armor { file } => { - let content = UniversalFile::load_file(file)?; - println!("{content}"); - } - - Command::State { - contract_id, - iface, - all, - } => { - let stock_path = self.general.base_dir(); - let stock = self.load_stock(stock_path)?; - - enum StockOrWallet { - Stock(Stock), - Wallet(RgbWallet>), - } - impl StockOrWallet { - fn stock(&self) -> &Stock { - match self { - StockOrWallet::Stock(stock) => stock, - StockOrWallet::Wallet(wallet) => wallet.stock(), - } - } - } - - let iface = match contract_default_iface_name(*contract_id, &stock, iface)? { - ControlFlow::Continue(name) => name, - ControlFlow::Break(_) => return Ok(()), - }; - - let stock_wallet = match self.rgb_wallet_from_stock(&config, stock) { - Ok(wallet) => StockOrWallet::Wallet(wallet), - Err((stock, _)) => StockOrWallet::Stock(stock), - }; - - let filter = match stock_wallet { - StockOrWallet::Wallet(ref wallet) if *all => Filter::WalletAll(wallet), - StockOrWallet::Wallet(ref wallet) => Filter::Wallet(wallet), - StockOrWallet::Stock(_) => { - println!("no wallets found"); - Filter::NoWallet - } - }; - - let contract = stock_wallet - .stock() - .contract_iface(*contract_id, tn!(iface.to_owned()))?; - - println!("\nGlobal:"); - for global in &contract.iface.global_state { - if let Ok(values) = contract.global(global.name.clone()) { - for val in values { - println!(" {} := {}", global.name, val); - } - } - } - - enum Filter<'w> { - Wallet(&'w RgbWallet>), - WalletAll(&'w RgbWallet>), - NoWallet, - } - impl<'w> AssignmentsFilter for Filter<'w> { - fn should_include( - &self, - outpoint: impl Into, - id: Option, - ) -> bool { - match self { - Filter::Wallet(wallet) => wallet - .wallet() - .filter_unspent() - .should_include(outpoint, id), - _ => true, - } - } - } - impl<'w> Filter<'w> { - fn comment(&self, outpoint: XOutpoint) -> &'static str { - let outpoint = outpoint - .into_bp() - .into_bitcoin() - .expect("liquid is not yet supported"); - match self { - Filter::Wallet(rgb) if rgb.wallet().is_unspent(outpoint) => "", - Filter::WalletAll(rgb) if rgb.wallet().is_unspent(outpoint) => { - "-- unspent" - } - Filter::WalletAll(rgb) if rgb.wallet().has_outpoint(outpoint) => { - "-- spent" - } - _ => "-- third-party", - } - } - } - - println!("\nOwned:"); - fn witness( - allocation: &OutputAssignment, - contract: &ContractIface>, - ) -> String { - allocation - .witness - .and_then(|w| contract.witness_info(w)) - .map(|info| format!("{} ({})", info.id, info.ord)) - .unwrap_or_else(|| s!("~")) - } - for owned in &contract.iface.assignments { - println!(" State \t{:78}\tWitness", "Seal"); - println!(" {}:", owned.name); - if let Ok(allocations) = contract.fungible(owned.name.clone(), &filter) { - for allocation in allocations { - println!( - " {: >9}\t{}\t{} {}", - allocation.state.value(), - allocation.seal, - witness(&allocation, &contract), - filter.comment(allocation.seal.to_outpoint()) - ); - } - } - if let Ok(allocations) = contract.data(owned.name.clone(), &filter) { - for allocation in allocations { - println!( - " {: >9}\t{}\t{} {}", - allocation.state, - allocation.seal, - witness(&allocation, &contract), - filter.comment(allocation.seal.to_outpoint()) - ); - } - } - if let Ok(allocations) = contract.attachments(owned.name.clone(), &filter) { - for allocation in allocations { - println!( - " {: >9}\t{}\t{} {}", - allocation.state, - allocation.seal, - witness(&allocation, &contract), - filter.comment(allocation.seal.to_outpoint()) - ); - } - } - if let Ok(allocations) = contract.rights(owned.name.clone(), &filter) { - for allocation in allocations { - println!( - " {: >9}\t{}\t{} {}", - "right", - allocation.seal, - witness(&allocation, &contract), - filter.comment(allocation.seal.to_outpoint()) - ); - } - } - } - } - Command::Issue { - schema: schema_id, - issuer, - contract, - } => { - let mut stock = self.rgb_stock()?; - - let file = fs::File::open(contract)?; - - let code = serde_yaml::from_reader::<_, serde_yaml::Value>(file)?; - - let code = code - .as_mapping() - .expect("invalid YAML root-level structure"); - - let iface_name = code - .get("interface") - .expect("contract must specify interface under which it is constructed") - .as_str() - .expect("interface name must be a string"); - 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(WalletError::from) - })? - .clone(); - let iface_id = iface.iface_id(); - 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(issuer.clone(), *schema_id, iface_id)?; - let types = builder.type_system().clone(); - - if let Some(globals) = code.get("globals") { - for (name, val) in globals - .as_mapping() - .expect("invalid YAML: globals must be an mapping") - { - let name = name - .as_str() - .expect("invalid YAML: global name must be a string"); - let state_type = iface_impl - .global_state - .iter() - .find(|info| info.name.as_str() == name) - .unwrap_or_else(|| panic!("unknown type name '{name}'")) - .id; - let sem_id = schema_ifaces - .schema - .global_types - .get(&state_type) - .expect("invalid schema implementation") - .sem_id; - let val = StrictVal::from(val.clone()); - let typed_val = types - .typify(val, sem_id) - .expect("global type doesn't match type definition"); - - let serialized = types - .strict_serialize_type::(&typed_val) - .expect("internal error"); - // Workaround for borrow checker: - let field_name = - FieldName::try_from(name.to_owned()).expect("invalid type name"); - builder = builder - .add_global_state(field_name, serialized) - .expect("invalid global state data"); - } - } - - if let Some(assignments) = code.get("assignments") { - for (name, val) in assignments - .as_mapping() - .expect("invalid YAML: assignments must be an mapping") - { - let name = name - .as_str() - .expect("invalid YAML: assignments name must be a string"); - let state_type = iface_impl - .assignments - .iter() - .find(|info| info.name.as_str() == name) - .expect("unknown type name") - .id; - let state_schema = schema_ifaces - .schema - .owned_types - .get(&state_type) - .expect("invalid schema implementation"); - - let assign = val.as_mapping().expect("an assignment must be a mapping"); - let seal = assign - .get("seal") - .expect("assignment doesn't provide seal information") - .as_str() - .expect("seal must be a string"); - let seal = OutputSeal::from_str(seal).expect("invalid seal definition"); - let seal = GenesisSeal::new_random(seal.method, seal.txid, seal.vout); - - // Workaround for borrow checker: - let field_name = - FieldName::try_from(name.to_owned()).expect("invalid type name"); - match state_schema.state_type() { - StateType::Void => todo!(), - StateType::Fungible => { - let amount = assign - .get("amount") - .expect("owned state must be a fungible amount") - .as_u64() - .expect("fungible state must be an integer"); - let seal = BuilderSeal::Revealed(XChain::Bitcoin(seal)); - builder = builder - .add_fungible_state(field_name, seal, amount) - .expect("invalid global state data"); - } - StateType::Structured => todo!(), - StateType::Attachment => todo!(), - } - } - } - - let contract = builder.issue_contract()?; - let id = contract.contract_id(); - stock.import_contract(contract, &ContractIssueResolver)?; - eprintln!( - "A new contract {id} is issued and added to the stash.\nUse `export` command \ - to export the contract." - ); - } - Command::Invoice { - address_based, - operation, - state, - contract_id, - iface, - amount, - token_index, - token_fraction, - } => { - let mut wallet = self.rgb_wallet(&config)?; - - let outpoint = wallet - .wallet() - .coinselect(Sats::ZERO, |utxo| { - RgbKeychain::contains_rgb(utxo.terminal.keychain) - }) - .next(); - let network = wallet.wallet().network(); - let beneficiary = match (address_based, outpoint) { - (false, None) => { - return Err(WalletError::Custom(s!( - "blinded invoice requested but no suitable outpoint is available" - ))); - } - (true, _) => { - let addr = wallet - .wallet() - .addresses(RgbKeychain::Rgb) - .next() - .expect("no addresses left") - .addr; - Beneficiary::WitnessVout(Pay2Vout { - address: addr.payload, - method: wallet.wallet().seal_close_method(), - }) - } - (_, Some(outpoint)) => { - let seal = XChain::Bitcoin(GraphSeal::new_random( - wallet.wallet().seal_close_method(), - outpoint.txid, - outpoint.vout, - )); - wallet.stock_mut().store_secret_seal(seal)?; - Beneficiary::BlindedSeal(*seal.to_secret_seal().as_reduced_unsafe()) - } - }; - - let iface = match contract_default_iface_name(*contract_id, wallet.stock(), iface)? - { - ControlFlow::Continue(name) => wallet.stock().iface(name)?, - ControlFlow::Break(_) => return Ok(()), - }; - let iface_name = &iface.name; - let Some(op_name) = operation - .clone() - .map(FieldName::try_from) - .transpose() - .map_err(|e| WalletError::Invoicing(format!("invalid operation name - {e}")))? - .or(iface.default_operation.clone()) - else { - return Err(WalletError::Invoicing(format!( - "interface {iface_name} doesn't have default operation" - ))); - }; - let Some(iface_op) = iface.transitions.get(&op_name) else { - return Err(WalletError::Invoicing(format!( - "interface {iface_name} doesn't have operation {op_name}" - ))); - }; - let state_name = state - .clone() - .map(FieldName::try_from) - .transpose() - .map_err(|e| WalletError::Invoicing(format!("invalid state name - {e}")))? - .or_else(|| iface_op.default_assignment.clone()) - .ok_or_else(|| { - WalletError::Invoicing(format!( - "interface {iface_name} doesn't have a default state for the \ - operation {op_name}" - )) - })?; - let Some(assign_iface) = iface.assignments.get(&state_name) else { - return Err(WalletError::Invoicing(format!( - "interface {iface_name} doesn't have state {state_name} in operation \ - {op_name}" - ))); - }; - - let mut builder = RgbInvoiceBuilder::new(XChainNet::bitcoin(network, beneficiary)) - .set_contract(*contract_id) - .set_interface(iface_name.clone()); - - if operation.is_some() { - builder = builder.set_operation(op_name); - if let Some(state) = state { - builder = builder.set_operation(fname!(state.clone())); - } - } - - match (assign_iface.owned_state, amount, token_index.map(|i| (i, token_fraction))) { - ( - OwnedIface::Rights - | OwnedIface::Amount - | OwnedIface::AnyData - | OwnedIface::Data(_), - None, - None, - ) => { - // There is no state which has to be added to the invoice - } - (OwnedIface::Rights, Some(_), None | Some(_)) - | (OwnedIface::Rights, None, Some(_)) => { - return Err(WalletError::Invoicing(format!( - "state {state_name} in interface {iface_name} defines a right and it \ - can't has a value or a token information" - ))); - } - (OwnedIface::Amount, _, Some(_)) => { - return Err(WalletError::Invoicing(format!( - "state {state_name} in interface {iface_name} defines a fungible \ - state, while a non-fungible token index is provided for the invoice. \ - Please use only --amount argument" - ))); - } - (OwnedIface::Amount, Some(amount), None) => { - builder = builder.set_amount_raw(*amount); - } - (OwnedIface::Data(_) | OwnedIface::AnyData, Some(_), _) => { - return Err(WalletError::Invoicing(format!( - "state {state_name} in interface {iface_name} defines a non-fungible \ - state, while a fungible amount is provided for the invoice. Please \ - use only --token-index and --token-fraction arguments" - ))); - } - (OwnedIface::Data(sem_id), None, Some(_)) - if sem_id - != rgb_contract_stl() - .types - .get(&tn!("Allocation")) - .expect("STL is broken") - .sem_id_named(&tn!("Allocation")) => - { - return Err(WalletError::Invoicing(format!( - "state {state_name} in interface {iface_name} has a type which can't \ - be used with a non-fungible state allocation" - ))); - } - (OwnedIface::AnyData | OwnedIface::Data(_), None, Some((index, fraction))) => { - builder = builder.set_allocation_raw(Allocation::with( - index, - fraction.unwrap_or(OwnedFraction::from(0)), - )) - } - - (OwnedIface::Any, _, _) => { - return Err(WalletError::Invoicing(format!( - "state {state_name} in interface {iface_name} can be of any type; \ - adding it to the invoice is impossible" - ))); - } - (OwnedIface::AnyAttach, _, _) => { - return Err(WalletError::Invoicing(s!( - "invoicing with attachments is not yet supported" - ))); - } - } - - let invoice = builder.finish(); - println!("{invoice}"); - } - Command::Prepare { - v2, - invoice, - fee, - sats, - psbt: psbt_file, - } => { - let mut wallet = self.rgb_wallet(&config)?; - // TODO: Support lock time and RBFs - let params = TransferParams::with(*fee, *sats); - - let (psbt, _) = wallet - .construct_psbt(invoice, params) - .map_err(|err| err.to_string())?; - - let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; - match psbt_file { - Some(file_name) => { - let mut psbt_file = File::create(file_name)?; - psbt.encode(ver, &mut psbt_file)?; - } - None => match ver { - PsbtVer::V0 => println!("{psbt}"), - PsbtVer::V2 => println!("{psbt:#}"), - }, - } - } - Command::Consign { - invoice, - psbt: psbt_name, - consignment: out_file, - } => { - 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 = 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)?; - } - Command::Transfer { - v2, - invoice, - fee, - sats, - psbt: psbt_file, - consignment: out_file, - } => { - let mut wallet = self.rgb_wallet(&config)?; - // TODO: Support lock time and RBFs - let params = TransferParams::with(*fee, *sats); - - let (mut psbt, _, transfer) = - wallet.pay(invoice, params).map_err(|err| err.to_string())?; - - transfer.save_file(out_file)?; - - psbt.version = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; - match psbt_file { - Some(file_name) => { - let mut psbt_file = File::create(file_name)?; - psbt.encode(psbt.version, &mut psbt_file)?; - } - None => println!("{psbt}"), - } - } - Command::Inspect { file, dir, path } => { - #[derive(Clone, Debug)] - #[derive(Serialize, Deserialize)] - #[serde(crate = "serde_crate", rename_all = "camelCase")] - pub struct ConsignmentInspection { - version: ContainerVer, - transfer: bool, - terminals: SmallOrdMap>, - supplements: TinyOrdSet, - signatures: TinyOrdMap, - } - - let content = UniversalFile::load_file(file)?; - let consignment = match content { - UniversalFile::Contract(contract) if *dir => Some(contract), - UniversalFile::Transfer(transfer) if *dir => Some(transfer.into_contract()), - content => { - let s = serde_yaml::to_string(&content).expect("unable to present as YAML"); - match path { - None => println!("{s}"), - Some(path) => fs::write(path, s)?, - } - None - } - }; - if let Some(consignment) = consignment { - let mut map = map![ - s!("genesis.yaml") => serde_yaml::to_string(&consignment.genesis)?, - s!("schema.yaml") => serde_yaml::to_string(&consignment.schema)?, - s!("bundles.yaml") => serde_yaml::to_string(&consignment.bundles)?, - s!("extensions.yaml") => serde_yaml::to_string(&consignment.extensions)?, - s!("types.sty") => consignment.types.to_string(), - ]; - for lib in consignment.scripts { - let mut buf = Vec::new(); - lib.print_disassemble::>(&mut buf)?; - map.insert(format!("{}.aluasm", lib.id().to_baid64_mnemonic()), unsafe { - String::from_utf8_unchecked(buf) - }); - } - for (iface, iimpl) in consignment.ifaces { - map.insert( - format!("iface-{}.yaml", iface.name), - serde_yaml::to_string(&iface)?, - ); - map.insert( - format!("impl-{}.yaml", iface.name), - serde_yaml::to_string(&iimpl)?, - ); - } - let contract = ConsignmentInspection { - version: consignment.version, - transfer: consignment.transfer, - terminals: consignment.terminals, - supplements: consignment.supplements, - signatures: consignment.signatures, - }; - map.insert(s!("consignment-meta.yaml"), serde_yaml::to_string(&contract)?); - let path = path.as_ref().expect("required by clap"); - fs::create_dir_all(path)?; - for (file, value) in map { - fs::write(format!("{}/{file}", path.display()), value)?; - } - } - } - Command::Reconstruct { - contract: false, - src, - dst, - } => { - let file = File::open(src)?; - let transfer: Transfer = serde_yaml::from_reader(&file)?; - match dst { - None => println!("{transfer}"), - Some(dst) => { - transfer.save_file(dst)?; - } - } - } - Command::Reconstruct { - contract: true, - src, - dst, - } => { - let file = File::open(src)?; - let contract: Contract = serde_yaml::from_reader(&file)?; - match dst { - None => println!("{contract}"), - Some(dst) => { - contract.save_file(dst)?; - } - } - } - Command::Dump { root_dir } => { - let stock = self.rgb_stock()?; - - fs::remove_dir_all(root_dir).ok(); - fs::create_dir_all(format!("{root_dir}/stash/schemata"))?; - fs::create_dir_all(format!("{root_dir}/stash/ifaces"))?; - fs::create_dir_all(format!("{root_dir}/stash/geneses"))?; - 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 (id, schema_ifaces) in stock.as_stash_provider().debug_schemata() { - fs::write( - format!( - "{root_dir}/stash/schemata/{}.{id:-#}.yaml", - schema_ifaces.schema.name - ), - serde_yaml::to_string(&schema_ifaces)?, - )?; - } - for (id, iface) in stock.as_stash_provider().debug_ifaces() { - fs::write( - 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"), - serde_yaml::to_string(genesis)?, - )?; - } - for (id, list) in stock.as_stash_provider().debug_suppl() { - for suppl in list { - fs::write( - format!( - "{root_dir}/stash/geneses/{id:-}.suppl.{}.yaml", - suppl.suppl_id() - ), - serde_yaml::to_string(suppl)?, - )?; - } - } - for (id, bundle) in stock.as_stash_provider().debug_bundles() { - fs::write( - format!("{root_dir}/stash/bundles/{id}.yaml"), - serde_yaml::to_string(bundle)?, - )?; - } - for (id, witness) in stock.as_stash_provider().debug_witnesses() { - fs::write( - format!("{root_dir}/stash/witnesses/{id}.yaml"), - serde_yaml::to_string(witness)?, - )?; - } - for (id, extension) in stock.as_stash_provider().debug_extensions() { - fs::write( - format!("{root_dir}/stash/extensions/{id}.yaml"), - 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}/stash/seal-secret.yaml"), - serde_yaml::to_string(stock.as_stash_provider().debug_secret_seals())?, - )?; - // TODO: Add sigs debugging - - // State - fs::write( - format!("{root_dir}/state/witnesses.yaml"), - serde_yaml::to_string(stock.as_state_provider().debug_witnesses())?, - )?; - for (id, state) in stock.as_state_provider().debug_contracts() { - fs::write( - format!("{root_dir}/state/{id:-}.yaml"), - serde_yaml::to_string(state)?, - )?; - } - - // Index - fs::write( - format!("{root_dir}/index/op-to-bundle.yaml"), - serde_yaml::to_string(stock.as_index_provider().debug_op_bundle_index())?, - )?; - fs::write( - format!("{root_dir}/index/bundle-to-contract.yaml"), - serde_yaml::to_string(stock.as_index_provider().debug_bundle_contract_index())?, - )?; - fs::write( - format!("{root_dir}/index/bundle-to-witness.yaml"), - serde_yaml::to_string(stock.as_index_provider().debug_bundle_witness_index())?, - )?; - fs::write( - format!("{root_dir}/index/contracts.yaml"), - serde_yaml::to_string(stock.as_index_provider().debug_contract_index())?, - )?; - fs::write( - format!("{root_dir}/index/terminals.yaml"), - serde_yaml::to_string(stock.as_index_provider().debug_terminal_index())?, - )?; - eprintln!("Dump is successfully generated and saved to '{root_dir}'"); - } - Command::Validate { file } => { - let mut resolver = self.resolver()?; - let consignment = Transfer::load_file(file)?; - resolver.add_terminals(&consignment); - let status = - match consignment.validate(&resolver, self.general.network.is_testnet()) { - Ok(consignment) => consignment.into_validation_status(), - Err((status, _)) => status, - }; - if status.validity() == Validity::Valid { - eprintln!("The provided consignment is valid") - } else { - eprintln!("{status}"); - } - } - Command::Accept { force: _, file } => { - // TODO: Ensure we properly handle unmined terminal transactions - let mut stock = self.rgb_stock()?; - let mut resolver = self.resolver()?; - let transfer = Transfer::load_file(file)?; - resolver.add_terminals(&transfer); - let valid = transfer - .validate(&resolver, self.general.network.is_testnet()) - .map_err(|(status, _)| status)?; - stock.accept_transfer(valid, &resolver)?; - eprintln!("Transfer accepted into the stash"); - } - } - Ok(()) - } -} - -fn contract_default_iface_name( - contract_id: ContractId, - stock: &Stock, - iface: &Option, -) -> Result, StockError> { - if let Some(iface) = iface { - return Ok(ControlFlow::Continue(tn!(iface.clone()))); - }; - let info = stock.contract_info(contract_id)?; - let schema = stock.schema(info.schema_id)?; - Ok(match schema.iimpls.len() { - 0 => { - eprintln!("contract doesn't implement any interface and thus can't be read"); - ControlFlow::Break(()) - } - 1 => ControlFlow::Continue( - schema - .iimpls - .first_key_value() - .expect("one interface is present") - .0 - .clone(), - ), - _ => { - eprintln!( - "contract implements multiple interface, please select one of them to read the \ - contract:" - ); - for iface in schema.iimpls.keys() { - eprintln!("{iface}"); - } - ControlFlow::Break(()) - } - }) -} diff --git a/cli/src/exec.rs b/cli/src/exec.rs new file mode 100644 index 0000000..d0c0693 --- /dev/null +++ b/cli/src/exec.rs @@ -0,0 +1,415 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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::fs::File; +use std::str::FromStr; + +use anyhow::Context; +use bpwallet::psbt::TxParams; +use bpwallet::{Outpoint, Psbt, PsbtVer, Sats, Wpkh, XpubDerivable}; +use rgb::invoice::{RgbBeneficiary, RgbInvoice}; +use rgb::popls::bp::{PaymentScript, PrefabBundle, WalletProvider}; +use rgb::{CallScope, CreateParams}; +use rgbp::descriptor::RgbDescr; +use rgbp::RgbWallet; +use strict_encoding::{StrictDeserialize, StrictSerialize}; +use strict_types::StrictVal; + +use crate::args::Args; +use crate::cmd::Cmd; +use crate::opts::WalletOpts; + +impl Args { + pub fn exec(&self) -> anyhow::Result<()> { + match &self.command { + // ===================================================================================== + // I. Wallet management + Cmd::Wallets => { + let data_dir = self.data_dir(); + for dir in data_dir + .read_dir() + .context("Unable to read data directory")? + { + let dir = match dir { + Ok(dir) => dir, + Err(err) => { + eprintln!("Can't read directory entry: {err}"); + continue; + } + }; + let path = dir.path(); + if dir + .file_type() + .context("Unable to read directory entry")? + .is_dir() + && path.extension() == Some("wallet".as_ref()) + { + let Some(wallet_name) = path + .file_stem() + .context("Unable to parse wallet file name")? + .to_str() + else { + continue; + }; + println!("{wallet_name}"); + } + } + } + + Cmd::Create { tapret_key_only, wpkh, name, descriptor } => { + let provider = self.wallet_provider(Some(name)); + let xpub = XpubDerivable::from_str(descriptor)?; + let noise = xpub.xpub().chain_code().to_byte_array(); + let descr = match (tapret_key_only, wpkh) { + (false, true) => RgbDescr::new_unfunded(Wpkh::from(xpub), noise), + (true, false) => RgbDescr::key_only_unfunded(xpub, noise), + (true, true) => unreachable!(), + (false, false) => anyhow::bail!( + "a type of wallet descriptor must be specified either with \ + `--tapret-key-only` or `--wpkh`" + ), + }; + RgbWallet::create(provider, descr, self.network, true) + .expect("Unable to create wallet"); + } + + Cmd::Sync { wallet, resolver } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let indexer = self.indexer(resolver); + runtime.wallet.update(&indexer, false); + println!(); + } + + Cmd::Fund { wallet } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let addr = runtime.wallet.next_address(); + println!("{addr}"); + } + + Cmd::Seals { wallet } => { + let runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + for utxo in runtime.wallet.utxos() { + println!("{utxo}"); + } + } + + // ===================================================================================== + // II. Contract management + Cmd::Contracts => { + let mound = self.mound(); + for info in mound.contracts_info() { + println!("---"); + println!( + "{}", + serde_yaml::to_string(&info).context("Unable to generate YAML")? + ); + } + } + + Cmd::Issue { params: None, wallet: _ } => { + println!( + "To issue a new contract please specify a parameters file. A contract may be \ + issued under one of the codex listed below." + ); + println!(); + println!("{:<32}\t{:<64}\tDeveloper", "Name", "ID"); + for (codex_id, schema) in self.mound().schemata() { + println!("{:<32}\t{codex_id}\t{}", schema.codex.name, schema.codex.developer); + } + } + Cmd::Issue { params: Some(params), wallet } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let file = File::open(params).context("Unable to open parameters file")?; + let params = serde_yaml::from_reader::<_, CreateParams>(file)?; + let contract_id = runtime.issue_to_file(params)?; + println!("A new contract issued with ID {contract_id}"); + } + + Cmd::Purge { force, contract } => { + todo!(); + //self.mound().purge(contract) + } + + Cmd::Import { articles } => { + todo!(); + //self.mound().import_file(articles) + } + + Cmd::Export { contract, file } => { + todo!(); + //self.mound().export_file(contract, file) + } + + Cmd::Backup { file } => { + todo!(); + } + + // ===================================================================================== + // III. Combined contract/wallet operations + Cmd::State { wallet, all, global, owned, contract } => { + let mut runtime = self.runtime(wallet); + if wallet.sync { + let indexer = self.indexer(&wallet.resolver); + runtime.wallet.update(&indexer, false); + println!(); + } + let contract_id = contract + .as_ref() + .map(|r| { + runtime + .mound + .find_contract_id(r.clone()) + .ok_or(anyhow::anyhow!("unknown contract '{r}'")) + }) + .transpose()?; + let state = if *all { + runtime.state_all(contract_id).collect::>() + } else { + runtime.state_own(contract_id).collect() + }; + for (contract_id, state) in state { + let contract = runtime.mound.contract(contract_id); + println!("{contract_id}\t{}", contract.stock().articles().contract.meta.name); + if *global { + if state.immutable.is_empty() { + println!("global: # no known global state is defined by the contract"); + } else { + println!( + "global: {:<16}\t{:<32}\t{:<32}\taddress", + "state name", "verified state", "unverified state" + ); + } + for (name, map) in &state.immutable { + let mut first = true; + for (addr, atom) in map { + print!("\t{:<16}", if first { name.as_str() } else { " " }); + print!("\t{:<32}", atom.verified.to_string()); + if let Some(unverified) = &atom.unverified { + print!("\t{unverified:<32}"); + } else { + print!("\t{:<32}", "~") + } + println!("\t{addr}"); + first = false; + } + } + + if state.computed.is_empty() { + println!( + "comp: # no known computed state is defined by the contract" + ); + } else { + print!( + "comp: {:<16}\t{:<32}\t{:<32}\taddress", + "state name", "verified state", "unverified state" + ); + } + for (name, val) in &state.computed { + println!("\t{name:<16}\t{val}"); + } + } + if *owned { + if state.owned.is_empty() { + println!("owned: # no known owned state is defined by the contract"); + } else { + println!( + "owned: {:<16}\t{:<32}\t{:<46}\toutpoint", + "state name", "value", "address" + ); + } + for (name, map) in &state.owned { + let mut first = true; + for (addr, assignment) in map { + print!("\t{:<16}", if first { name.as_str() } else { " " }); + print!("\t{:<32}", assignment.data.to_string()); + print!("\t{addr:<46}"); + println!("\t{}", assignment.seal); + first = false; + } + } + } + } + } + + Cmd::Invoice { + wallet, + seal_only, + wout, + nonce, + contract, + api, + method, + state, + value, + } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let beneficiary = if *wout { + let wout = runtime.wout(*nonce); + RgbBeneficiary::WitnessOut(wout) + } else { + let auth = runtime.auth_token(*nonce).ok_or(anyhow::anyhow!( + "Wallet has no unspent outputs; try `fund` first, or use `-w` flag to \ + generate a witness output-based seal" + ))?; + RgbBeneficiary::Token(auth) + }; + if *seal_only { + println!("{beneficiary}"); + return Ok(()); + } + + let contract_id = if let Some(contract) = contract { + let id = runtime + .mound + .find_contract_id(contract.clone()) + .ok_or(anyhow::anyhow!("unknown contract '{contract}'"))?; + CallScope::ContractId(id) + } else { + CallScope::ContractQuery(s!("")) + }; + let value = value.map(StrictVal::num); + let mut invoice = RgbInvoice::new(contract_id, beneficiary, value); + if let Some(api) = api { + invoice = invoice.use_api(api.clone()); + } + if let Some(method) = method { + invoice = invoice.use_method(method.clone()); + } + if let Some(state) = state { + invoice = invoice.use_state(state.clone()); + } + + println!("{invoice}"); + } + + Cmd::Pay { + wallet, + strategy, + invoice, + sats, + fee, + psbt2, + print, + psbt: psbt_filename, + consignment, + } => { + let mut runtime = self.runtime(wallet); + // TODO: sync wallet if needed + // TODO: Add params and giveway to arguments + let params = TxParams::with(*fee); + let (mut psbt, terminal) = + runtime.pay_invoice(invoice, *strategy, params, *sats)?; + let ver = if *psbt2 { PsbtVer::V2 } else { PsbtVer::V0 }; + + let psbt_filename = psbt_filename + .as_ref() + .unwrap_or(consignment) + .with_extension("psbt"); + let mut psbt_file = + File::create_new(psbt_filename).context("Unable to create PSBT")?; + psbt.encode(ver, &mut psbt_file)?; + if *print { + psbt.version = ver; + println!("{psbt}"); + } + runtime + .mound + .consign_to_file(invoice.scope, [terminal], consignment) + .context("Unable to consign contract")?; + } + + Cmd::Script { wallet, sats, strategy, invoice, output } => { + let mut runtime = self.runtime(wallet); + let script = runtime.script(invoice, *strategy, *sats)?; + let file = File::create_new(output).context("Unable to open script file")?; + serde_yaml::to_writer(file, &script).context("Unable to write script")?; + } + + Cmd::Exec { + wallet, + print, + script, + fee, + psbt2, + bundle: bundle_filename, + psbt: psbt_filename, + } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let src = File::open(script).context("Unable to open script file")?; + let script = serde_yaml::from_reader::<_, PaymentScript>(src)?; + + let params = TxParams::with(*fee); + let (mut psbt, bundle) = runtime.exec(script, params)?; + let psbt_filename = psbt_filename + .as_ref() + .unwrap_or(bundle_filename) + .with_extension("psbt"); + let mut psbt_file = + File::create_new(psbt_filename).context("Unable to create PSBT")?; + + bundle + .strict_serialize_to_file::<{ usize::MAX }>(&bundle_filename) + .context("Unable to write output file")?; + + // This PSBT can be sent to other payjoin parties so they add their inputs and + // outputs, or even re-order existing ones + let ver = if *psbt2 { PsbtVer::V2 } else { PsbtVer::V0 }; + psbt.encode(ver, &mut psbt_file) + .context("Unable to write PSBT")?; + if *print { + psbt.version = ver; + println!("{psbt}"); + } + } + + Cmd::Complete { wallet, bundle, psbt: psbt_filename } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + let bundle = PrefabBundle::strict_deserialize_from_file::<{ usize::MAX }>(bundle)?; + let mut psbt_file = File::open(psbt_filename).context("Unable to open PSBT")?; + let psbt = Psbt::decode(&mut psbt_file)?; + + let psbt = runtime.complete(psbt, &bundle)?; + + let mut psbt_file = File::create(psbt_filename).context("Unable to write PSBT")?; + psbt.encode(psbt.version, &mut psbt_file)?; + } + + Cmd::Consign { contract, terminals, output } => { + let mut mound = self.mound(); + let contract_id = mound + .find_contract_id(contract.clone()) + .ok_or(anyhow::anyhow!("unknown contract '{contract}'"))?; + mound + .consign_to_file(contract_id, terminals, output) + .context("Unable to consign contract")?; + } + + Cmd::Accept { wallet, input } => { + let mut runtime = self.runtime(&WalletOpts::default_with_name(wallet)); + runtime.consume_from_file(input)?; + } + } + Ok(()) + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 3d94c02..e7d098f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,69 +1,58 @@ -// RGB smart contracts for Bitcoin & Lightning +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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. +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. #[macro_use] extern crate amplify; #[macro_use] -extern crate strict_types; -#[macro_use] -extern crate log; -#[macro_use] extern crate clap; -extern crate serde_crate as serde; -mod command; -mod args; +pub mod opts; +pub mod args; +pub mod cmd; +mod exec; -use std::process::ExitCode; +use std::backtrace::Backtrace; +use std::fmt::Display; +use std::panic::set_hook; -use bpwallet::cli::{Config, Exec, LogLevel}; use clap::Parser; -use rgb::WalletError; - -pub use crate::args::RgbArgs; -pub use crate::command::Command; - -fn main() -> ExitCode { - if let Err(err) = run() { - eprintln!("Error: {err}"); - ExitCode::FAILURE - } else { - ExitCode::SUCCESS - } -} - -fn run() -> Result<(), WalletError> { - let mut args = RgbArgs::parse(); - args.process(); - LogLevel::from_verbosity_flag_count(args.verbose).apply(); - trace!("Command-line arguments: {:#?}", &args); - - 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); - args.exec(conf, "rgb")?; - println!(); - Ok(()) +use crate::args::Args; + +fn main() -> anyhow::Result<()> { + set_hook(Box::new(|info| { + eprintln!("Abnormal program termination through panic."); + if let Some(error) = info.payload().downcast_ref::<&dyn Display>() { + eprintln!("Error: {error}"); + if let Some(location) = info.location() { + eprintln!("Happened in {location}"); + } + } else { + eprintln!("Error: {info}"); + } + let backtrace = Backtrace::capture(); + eprintln!("Backtrace: {backtrace}"); + })); + Args::parse().exec() } diff --git a/cli/src/opts.rs b/cli/src/opts.rs new file mode 100644 index 0000000..5ccf1f7 --- /dev/null +++ b/cli/src/opts.rs @@ -0,0 +1,50 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 bpwallet::cli::ResolverOpt; + +use crate::cmd::RGB_WALLET_ENV; + +#[derive(Args, Clone, PartialEq, Eq, Debug)] +pub struct WalletOpts { + /// Wallet to use + #[clap(short, long, global = true, env = RGB_WALLET_ENV)] + pub wallet: Option, + + #[clap(long, global = true)] + pub sync: bool, + + #[clap(flatten)] + pub resolver: ResolverOpt, +} + +impl WalletOpts { + pub fn default_with_name(name: &Option) -> Self { + WalletOpts { + wallet: name.clone(), + sync: false, + resolver: ResolverOpt { electrum: None, esplora: None, mempool: None }, + } + } +} diff --git a/codecov.yml b/codecov.yml index 240d731..4a291d8 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,12 +8,12 @@ coverage: status: project: default: - target: 75% + # target: 75% threshold: 1% branches: - master patch: default: - target: 60% + # target: 60% threshold: 1% only_pulls: true diff --git a/doc/Payments.md b/doc/Payments.md new file mode 100644 index 0000000..c8b1095 --- /dev/null +++ b/doc/Payments.md @@ -0,0 +1,106 @@ +# Payments Workflows + +There are two main ways to do a payment (also called "state transfer") with `rgb` command-line ( +coming with `rgb-wallet` crate) and `Runtime` API (coming from [`rgb-runtime`] crate). The first +approach is for simple payments, allowing to pay for an invoice issued by some beneficiary; the +second approach allows arbitrary payment customization and is suited for advanced scenarios. + +The user of both payment workflows will work with the following entities: + +- **RGB invoice**: a simple payment specification structured as a URI, using `contract:` scheme; +- **PSBT**: partially-signed bitcoin transaction file; +- **RGB consignment**: a stream (which can be serialized to a file) transferring all client-side + information required for the payment between peers. + +Invoice is created by the beneficiery of the payment; which in return receives a consignment from +the payer. PSBT file is used by the payer internally, which must sign it and publish upon receiving +confirmation from the beneficiary. PSBT file can be transferred between parties on the side of the +payer, for instance different signers in multisig wallet, or payjoin peers. + +## Paying an invoice + +Paying invoice takes just one command-line call - `rgb pay` - which takes an invoice and produces +both ready-to-be-signed PSBT and a consignment file, which should be sent to the beneficiary before +publishing signed PSBT. + +With RGB runtime API, you will have to call two methods in a sequence: + +1. `Runtime::pay_invoice`, which will return a redy-to-sing PSBT and a **terminal**, containing + information about the final state which should be exported on the second step to the consignment; +2. `Runtime::consign` (or `Runtime::consign_to_file`), which takes the terminal received on the + step 1 and produces the consignment for the beneficiary. + +## Custom payment + +Custom payments are done in multiple steps, providing rich customization options on each of them. + +The user of custom payment workflows will be interacting with additional RGB data types and files: + +- **Payment script** (`PaymentScript` data type, which can be written and read from `*.yaml` + files): a scenario which describes multiple payments under potentially multiple contracts; +- **Prefabricated operation bundle** (`PrefabBundle` data type, which is stored in binary form as a + `*.pfab` file): packed information about multiple operations under multiple contracts. + +The custom payment process consists of the following steps, some of which are optional and are not +required in some scenarios: + +1. Converting invoice to a payment script (optional): `rgb script` command and `Runtime::script` + API. +2. Execute payment script, creating PSBT and prefabricated bundle: `rgb exec` command and + `Runtime::exec` API. The PSBT file, returned by this operation, is not ready to be signed! It + has to be _completed_ first, as described in step 4 below. +3. Customize PSBT (optional): a PSBT file from the previous step may be further modified in multiple + ways, like re-ordering inputs or outputs; adding more inputs; merging with other PSBT files + (which may also contain other RGB payments from other peers). All of these operations may happen + as a part of independent workflows for transaction aggregation, payjoin or coinjoin etc. +4. Complete PSBT, using `rgb complete` command or `Runtime::complete` API. This creates all + necessary deterministic bitcoin commitments, after which transaction can't be anymore modified + and becomes ready to be signed. +5. Share PSBT and prefabricated bundle with other signers (optional). It is important to share both + of the files, since without prefabricated bundle other signers will have no idea which RGB + operations the transaction commits to, and won't be able to properly sign it. +6. Produce and send consignment to the beneficiary using `rgb consign` and `Runtime::consign` (or + `Runtime::consign_to_file`) APIs. The consignment creation will require providing a + **terminal** (or multiple terminals, if required), which are **authentication tokens** present + in the payment script in outputs sent to the beneficiary. +7. Sign PSBT; finalize and extract transaction. It is important to note that unlike in non-RGB PSBT + workflows a partially-signed PSBT transaction can't be modified, merged etc., since this may + invalidate deterministic bitcoin commitments. If necessary such operations must be performed on + the step 3. PSBT signing is not managed by RGB runtime API or command-line tool and can be + performed using existing bitcoin wallets and hardware signers. +8. Receive confirmation from the beneficiary that he is fine with the consignment. Publish the + signed transaction to the network. + +Custom payments are useful in the following cases: + +- multisig wallets (makes step 5 required); +- custom coin selection (makes step 3 required); +- payment aggregation (may skip step 1, or use it to create separate scripts, which can be merged + together; also makes step 3 required); +- payjoin/coinjoin (makes step 3 required); +- lightning. + +## Changes from v0.11 + +### RGB data not kept in PSBT + +Previously, information about RGB operations were directly included into PSBT (in form of +proprietary RGB key types in global map). In v0.12 these data are separated into an independent +binary file (called _prefabricated operation bundle_). This have the main reason of enhanced privacy +for payjoin and coinjoin protocols. + +### Payment scripts + +Previously, creating custom payments required manual construction of all operations, which can be +done only when a PSBT was created; significantly complicating custom coin selection algorithm. + +Payment scripts, introduced in v0.12, simplify this, since they can reference witness-output based +seals before a PSBT is actually created. This also allows creating a multi-step payments with +scripts, and then operating on multiple transactions in the same time, which is useful in case of +off-chain transaction graph protocols like Lightning network. Finally, payment scripts allow simpler +editing and visualisation of the payment thanks for used YAML format. + +### Consignments as streams + +Version 0.12 introduces ability to stream consignment into a networking interface (or to a hardware +verification device), also supporting verification of the consignment from the stream. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..4a8b3fc --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,6 @@ +dump/ +*.contract/ +*.dump/ +*.rgb +*.psbt +*.pfab diff --git a/examples/DemoToken.yaml b/examples/DemoToken.yaml new file mode 100644 index 0000000..0a3582c --- /dev/null +++ b/examples/DemoToken.yaml @@ -0,0 +1,21 @@ +consensus: bitcoin +testnet: true +codexId: znwQGVhW-rfRa8el-pNaAsUW-UWxXsB_-4UlC~Tf-TERhBm4#mirage-queen-delphi +name: DemoToken +method: issue +timestamp: "2024-12-18T10:32:00-02:00" + +global: + - name: name + verified: Demo Token + - name: ticker + verified: DEMO + - name: precision + verified: centiMilli + - name: circulating + verified: 10000 + +owned: + - name: owned + seal: b7116550736fbe5d3e234d0141c6bc8d1825f94da78514a3cede5674e9a5eae9:1 + data: 10000 diff --git a/examples/Transfer.yaml b/examples/Transfer.yaml new file mode 100644 index 0000000..76cfddb --- /dev/null +++ b/examples/Transfer.yaml @@ -0,0 +1,15 @@ +- contractId: contract:G2KKVrys-4zQs3fu-yGt_7dH-A~IJUVA-6TURwEs-3fZUy3E + method: transfer + reading: [] + using: + - addr: iX33C_EhQPmp0pRj0VslnKqHTW2SJ53V5SEefJUclpE:0 + outpoint: b7116550736fbe5d3e234d0141c6bc8d1825f94da78514a3cede5674e9a5eae9:1 + val: 10000 + global: [] + owned: + - name: owned + seal: at:FWKMBVDc-NBk8dAZA-tYGt2HIP-MtzSRO17-8g3QvRIN-nuPjug + data: 10 + - name: owned + seal: null + data: 9990 diff --git a/examples/data/NonInflatableAsset.issuer b/examples/data/NonInflatableAsset.issuer new file mode 100644 index 0000000..58c9fe0 Binary files /dev/null and b/examples/data/NonInflatableAsset.issuer differ diff --git a/examples/data/alice.key b/examples/data/alice.key new file mode 100644 index 0000000..c30ec4e --- /dev/null +++ b/examples/data/alice.key @@ -0,0 +1,2 @@ + 2†‹ŕBIㆻč“wż•ős1ayWYA>2©Ę–V‡†rF°©ŠL3Kçĺ˝w˘9«,L|ŰgŽH¨érSPú#ŕ/*" + +[layer2] diff --git a/examples/data/bob.key b/examples/data/bob.key new file mode 100644 index 0000000..3b461df Binary files /dev/null and b/examples/data/bob.key differ diff --git a/examples/data/seed b/examples/data/seed new file mode 100644 index 0000000..7527682 --- /dev/null +++ b/examples/data/seed @@ -0,0 +1,3 @@ +°{Yęł>Đ +E­— t€ËÚ +Źr(éjT‘ĂiG–¦VąCj3É4K:>˘GI Ë upkŽŔä+ľPŞÝŕ*ëÝ߲µo38 űO»Ń‘á«éJLýľPĐül|%óvpe¤ő \ No newline at end of file diff --git a/examples/data2/NonInflatableAsset.issuer b/examples/data2/NonInflatableAsset.issuer new file mode 100644 index 0000000..58c9fe0 Binary files /dev/null and b/examples/data2/NonInflatableAsset.issuer differ diff --git a/examples/data2/bitcoin.testnet/bob.wallet/cache.yaml b/examples/data2/bitcoin.testnet/bob.wallet/cache.yaml new file mode 100644 index 0000000..db4d35a --- /dev/null +++ b/examples/data2/bitcoin.testnet/bob.wallet/cache.yaml @@ -0,0 +1,150 @@ +lastBlock: + height: 1 + time: 1231006505 + blockHash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f +lastChange: 0 +headers: [] +tx: + efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356: + txid: efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356 + status: !mined + height: 58373 + time: 1734446715 + blockHash: 000000003e3bb07f6583add889b2ece51f9ef2c422a02ede87615f22fdadc993 + inputs: + - outpoint: d0455f0a1f4f8a699d33c78967d1285102568aaad84ee347787b18271503a400:0 + payer: !counterparty tb1q0dzcgv7scppjxsnwlzpkt02vlmc5rtr40wyjgr + sequence: 4294967293 + coinbase: false + scriptSig: '' + witness: + - 3044022009e5737120398635e8486023b133ea077c440e10a4b3cecb3bc97cd870ae7a77022026c3896b0c95cd5e9dd5217ee0f5f60ddf318dfd1c7c6fb2682906d527b078be01 + - 030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb3 + value: 94361040651 + outputs: + - outpoint: efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356:0 + beneficiary: !counterparty tb1q0dzcgv7scppjxsnwlzpkt02vlmc5rtr40wyjgr + value: 94361035510 + spent: null + - outpoint: efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356:1 + beneficiary: !wallet + addr: tb1qh397g85qrtwfrg2wv4u6lada3wdzdnykuqcsyr + terminal: '&0/1' + value: 5000 + spent: null + fee: 141 + size: 222 + weight: 561 + version: 2 + locktime: 0 +utxo: +- efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356:1 +addr: + '0': + - terminal: '&0/0' + addr: tb1qufhxh8s93n29tqmks9vjfmesavstmggylaxsnw + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/1' + addr: tb1qh397g85qrtwfrg2wv4u6lada3wdzdnykuqcsyr + used: 1 + volume: 5000 + balance: 5000 + - terminal: '&0/2' + addr: tb1qccx59nfgs2mndv32nvsk9j9w34txldp9crperf + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/3' + addr: tb1q5srksygehryp9p5t2av4zd8lj22a40una5gkah + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/4' + addr: tb1qc8u0fgd7atlg3zua77ej930xc4r4wc8r2cjyjr + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/5' + addr: tb1qk4rrzt6q2r2tc86urwxjsxqkzhs58cd02nyxmw + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/6' + addr: tb1qkvgk9sa5jaqc05qn5lt63rg54k9tyrwl2pctkk + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/7' + addr: tb1qcqzj6f5qulrqcw2j08m4j0est9nutfwr09eemm + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/8' + addr: tb1qqza7l77pndcnkxkq2750c72l7y0kx3w7v4xmt0 + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/9' + addr: tb1q8pzl742785tke0sqy29lxntaqp0m6z5nsp6p59 + used: 0 + volume: 0 + balance: 0 + - terminal: '&0/10' + addr: tb1qxwa8jx6sllmq0zj00888xf08x2r0qgcv5qak66 + used: 0 + volume: 0 + balance: 0 + '1': + - terminal: '&1/0' + addr: tb1qhugqee7qw5fltp83ernxpl38l52mjywvn9mkkk + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/1' + addr: tb1ql8a9l7utw6kcrewzrzmry6wqcnxtmufr3dmms8 + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/2' + addr: tb1qcq5xgz6prr9heu55r3ydy8t0tfgw6xgapyhdy0 + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/3' + addr: tb1qge7w75zy7a99z4rfyhxjx6a4jxssxwdy7sq9au + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/4' + addr: tb1qs535dd5urn4jmnfqqrwrtyyelgyzhpg74v96hk + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/5' + addr: tb1qclqen4pz2gvnu0268wj3dn93ta4780th7uemlv + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/6' + addr: tb1q6wk9ngrh0vd30e5sp5zdjwwklkmhkxljjzn4rr + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/7' + addr: tb1qstpvrduxwu3wh0kn8mkvsxqxatj4uv3kuaxfvj + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/8' + addr: tb1qmp3mvxy07kztt5mwyw4asfa2l6kggyzh9p8yym + used: 0 + volume: 0 + balance: 0 + - terminal: '&1/9' + addr: tb1qld0axaw9urkyjruhel2cvpc0fmj9krkqvhcz03 + used: 0 + volume: 0 + balance: 0 +layer2: {} diff --git a/examples/data2/bitcoin.testnet/bob.wallet/data.toml b/examples/data2/bitcoin.testnet/bob.wallet/data.toml new file mode 100644 index 0000000..8bb0b40 --- /dev/null +++ b/examples/data2/bitcoin.testnet/bob.wallet/data.toml @@ -0,0 +1,14 @@ +name = "" + +[txAnnotations] + +[txoutAnnotations] + +[txinAnnotations] + +[addrAnnotations] + +[lastUsed] +0 = 12 + +[layer2] diff --git a/examples/data2/bitcoin.testnet/bob.wallet/descriptor.toml b/examples/data2/bitcoin.testnet/bob.wallet/descriptor.toml new file mode 100644 index 0000000..5248d21 --- /dev/null +++ b/examples/data2/bitcoin.testnet/bob.wallet/descriptor.toml @@ -0,0 +1,14 @@ +network = "testnet4" + +[generator] +noise = "b2594d09688565e0206593e720711eea03d560e38b491b4e3c66f9e3957aac86" +nonce = 0 + +[generator.deriver.opretOnly] +wpkh = "[cf05f8ea/84h/1h/1h]tpubDCpZeo9QAvMewp1ZhxjW25z3DSFC2eyt4wYimHuDNMkGzGhZsfUffCULroWTVUEfEaERv1rbbYPqG6fYb5Uivw86M3y4erJ7tGLRYcdbEFE/<0;1>/*" + +[[generator.seals]] +primary = "efb54853788df4a3ce31d7c74511f8d8c457e2e4b659773662e42d3bc5a44356:1" +secondary = "a5b0fc8e8a8231b5ae7edcc850986936eccc03177a714c91b4811e2da2044870ffffffffffffffff" + +[layer2] diff --git a/examples/demo.sh b/examples/demo.sh new file mode 100755 index 0000000..ce6a668 --- /dev/null +++ b/examples/demo.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -x + +cargo build --workspace --all-targets --all-features || exit 1 +export RUST_BACKTRACE=1 +RGB="./target/debug/rgb -d examples/data" +RGB_2="./target/debug/rgb -d examples/data2" + +rm -rf examples/data/bitcoin.testnet/DemoToken.contract +rm -rf examples/data2/bitcoin.testnet/DemoToken.contract +$RGB issue -w alice examples/DemoToken.yaml +cp -r examples/data/bitcoin.testnet/DemoToken.contract examples/data2/bitcoin.testnet/ + +$RGB contracts +$RGB state -go -w alice +#$RGB fund alice +AUTH_TOKEN=$($RGB_2 invoice -w bob --nonce 0 --seal-only DemoToken) +INVOICE=$($RGB_2 invoice -w bob --nonce 0 DemoToken 10) + +rm examples/transfer.psbt examples/Transfer.yaml examples/transfer.rgb +$RGB pay -w alice "$INVOICE" examples/transfer.rgb examples/transfer.psbt || exit 1 +$RGB state -goa -w alice --sync --mempool + +$RGB_2 accept -w bob examples/transfer.rgb || exit 1 + +$RGB_2 state -go -w bob --sync --mempool diff --git a/examples/demo_custom.sh b/examples/demo_custom.sh new file mode 100755 index 0000000..00cd9ef --- /dev/null +++ b/examples/demo_custom.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -x + +cargo build --workspace --all-targets --all-features || exit 1 +export RUST_BACKTRACE=1 +RGB="./target/debug/rgb -d examples/data" +RGB_2="./target/debug/rgb -d examples/data2" + +rm -rf examples/data/bitcoin.testnet/DemoToken.contract +rm -rf examples/data2/bitcoin.testnet/DemoToken.contract +$RGB issue -w alice examples/DemoToken.yaml +cp -r examples/data/bitcoin.testnet/DemoToken.contract examples/data2/bitcoin.testnet/ + +$RGB contracts +$RGB state -go -w alice +#$RGB fund alice +AUTH_TOKEN=$($RGB_2 invoice -w bob --nonce 0 --seal-only DemoToken) +INVOICE=$($RGB_2 invoice -w bob --nonce 0 DemoToken 10) + +rm examples/transfer.psbt examples/Transfer.yaml +$RGB script -w alice "$INVOICE" examples/Transfer.yaml || exit 1 +$RGB exec -w alice examples/Transfer.yaml examples/transfer.pfab 1000 examples/transfer.psbt || exit 1 + +$RGB complete -w alice examples/transfer.pfab examples/transfer.psbt || exit 1 + +rm examples/transfer.rgb +$RGB consign DemoToken -t "$AUTH_TOKEN" examples/transfer.rgb || exit 1 +$RGB state -goa -w alice --sync --mempool + +$RGB_2 accept -w bob examples/transfer.rgb || exit 1 + +$RGB_2 state -go -w bob --sync --mempool diff --git a/examples/rgb20-demo.con b/examples/rgb20-demo.con deleted file mode 100644 index bfeef2c..0000000 --- a/examples/rgb20-demo.con +++ /dev/null @@ -1,34 +0,0 @@ -contract Test: NonInflatableAsset - global spec - ticker = "Demo", - name = "Demo asset" - details = "Pay attention: the asset has no value" - precision = centiMicro - - global 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 - 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 = ~ - - global issuedSupply - 1_000_000__000_000_00 - - owned assetOwner - state = 1_000_000__000_000_00 - seal = - method := tapret1st - txid = #01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42 - vout = 0 diff --git a/examples/rgb20-demo.rgb b/examples/rgb20-demo.rgb deleted file mode 100644 index 0e2644a..0000000 Binary files a/examples/rgb20-demo.rgb and /dev/null differ diff --git a/examples/rgb20-demo.rgba b/examples/rgb20-demo.rgba deleted file mode 100644 index e8bd385..0000000 --- a/examples/rgb20-demo.rgba +++ /dev/null @@ -1,120 +0,0 @@ ------BEGIN RGB CONSIGNMENT----- -Id: rgb:csg:qX!1P1VF-il9DLFx-yjcpNvH-31pdCbd-gXhPSiG-7hvhtYY#austria-ricardo-demand -Version: 2 -Type: contract -Contract: rgb:Dl4Difx7-VxSWda4-Bv!Z89y-CbiVUkF-0vIxjRy-vvqO77A -Schema: rgb:sch:RDYhMTR!9gv8Y2GLv9UNBEK1hcrCmdLDFk9Qd5fnO8k#brave-dinner-banana -Interface: RGB20Fixed -Check-SHA256: db958c5cca1988c753665fc9a3da383615cbea62f0e1e1e6d7a7add2539d17ec - -0ssI2002ZbAu%+5_6z)DVT-@j4Fp29h04O2(!&-{P^aZh38Qb#nm# -0iX|oIUs*Z=yeHROa_%`=El@tIJ|sRf`I8Lo365whq9yq1JDNn05|{xL_$XkL}g-iXCPs7b7gb^B~W2` -AYpWLWo~q7Z*DpubZBKDVRLh3bRcM9b0BVSAa-GFb!7t42LS+;1d;?(RYFQdLsTGCPb?roPDCJANmNKr -Ra78JP9Q-}Ss+(ISs+YFO-WQqPDd;tR7gc2QbkZwMN>siR6$fpPfk-HK~6* -Ss+tIOiV>mARt9pP*O!xQ%qSPQ$6cNl#87Peve9MNCXZQd1yMRZ>$`K~7X4R8JsONJSu2MN&;uAV@`0MNdX7AVE$w%R9PS(R8JsDPE$}tLsTGCNJSt}QcqAtQdC(iAW&6OLr6hWMN%L^M^Z&aQy^4NAW&6OLr6hWMIcm2 -MIca8Pf$ftR9PV6fSf^7AW2i=fSoKL;((l2NJUabAW2i=fSoKLAXiCLNFYH>Odv)tPf|flAW2R}Pf|@mR7p=xEFe-vP)|}+Q!F4;R6$fl -O+`*rQ!F4LL{CFiO+`*rQy@=LAW}s`Pf|ovAVW`1Lq$?fNlr%~R7gc2P*P7&MN(8*AW%|IR!KxfL?BO6 -AWcC;MIb>|K}k$OLQF*GpK~qIiEFei#Qy@V{MNU*xAWu>tLr+dqR7q4-MNU*xAVOInK~7m9 -Q$tR7gouL?BQ>QdCJrQy@}BP*O!x -MNU*nPDdb5QXo)OQczD)R7p-pAXHBvQbkZwMN>siR3Jf4Ss+tIOiV>mEFe=zK}<{_PES-ILPa1_MNm>j -Q$uAX899LrF$SLqSYTSs+tIR3JuAQdCGFNJUabNlq>x -NlqY8RZ>GpK~qUiM<7&4MIca8Pf$ftR9PTTQXo`8OG!>gAWu|CMN%L^LsUsmP9R7{QbkoxL`708AW&6O -Lr6hWMN%M0K~o@3PgEd5PDCJ6NI^_YAWlzIAW}t4Ss+hNAVE%9AX8OCNFYQ>Q$tKoQ&mz$Q!F4tRa78S -K~zXZQY;`)RZ>GpK~qIiAX7*|OiUnBMNC;BPfko(AWu#pP*qYxNI_FYQsRJ_Qy@=QP9RB6Q&2@iR7p=x -AWudhR7gc2P*P7&MN(8*AVE$4jM@3U0R7gQoAXG?2AW&6OLr6hWMIca8Nkc^-Qbk5gMMG3mAVE$kh3aTNI4>qw4obv;hDB01)TImdq$#?)UpynIxG -faxfkuCkJcvZMe200000000000000001{4bZb@!tY+-a^Vr*qWb8}^Mkc}T^00000GyrpRX*x_=Q!#aT -EoW*(Ic```MlDZcWpq_lYga8cax-;PLsK>_VNqyvIaf7iEjUU=H+KL7&<6n5{J!HJ@Tgs1mpj@U3yhwA -`^&{wC3iS1tkb=;A&LP3007Yk09%X4R5&sPN*yA;lp<^AQ;QQiCWsumMiT;fc;H-Y_W=L^+6MrLj96u3 -I`KP|x6K-jit^gQ+!PC!a#7jT+VjUz9FBwm0004?4*>`O00Ynm0RRC2(FXwl0RY+u0RRC20iX{70RR60 -0juzt(u?g--(Ch+6*7N1n=+qwe6YFx|MoP&lb}4dCJ6ul0T3qu00E#60RaF10iX{70RR600juzt(u?g- --(Ch+6*7N1n=+qwe6YFx|MoP&lb}4dCIA2c00000000010SZz_LNYK$X?SI11XfR6o;2qj;#=~;t^vidEtAC%IlVX)dgF*wOtts}w(E1kon`A59t%UgjW&i*H0009FX>)UR -Wn@!zaBysS0f>xPWn((=JC(Q18jXtb+QHlu3zu?H+0@$e$59-PgaH5qb8uy20oVM#;~wy+U0;_w+8Yau -o__nw#aAVFI4rEwy|f{U0RaF7bY*gFa{*h6$5c2n1xg(vzLX+s=TnOlIwpu5x<(TMczEDkZ1({G0SaMr -b7gc-cWz~J0ssL4000033~6(7b!B8zb#QQOc>w?c00eVzWn%#V0RRPbWpZtE0RRC20SaMrb7gc-cWz~J -0RaF1009nZb8~fNWKC&vZDDj{XaNXxa$#Ze?--0RR613So0|Wpqz>Ze?-- -0RR600S|6(Zbfl*VQfKdZ*^{Ta{&rrb8}^MPj_x*asUAcbaG*Cb7p070uE_&b9H58O=)v&VRU0?WOH?J -aBO)Xb8uy2X=Z6#(b7gc-cWz~J00000009su2y}8`ZgXa3 -asU7T000624{mR6MR9duY(Z^rb#8QX000000S;+%b9H58O=)v&VRU0?00000GyrpRX*x_=Q!#aTEoW*( -Ic```MlDZcWpq_lYga8cax-;PLsK>_VNqyvIaf7iEjUU=H+KLd000X1U)Cjo-i6E2P9x&mnv%Qki+Oba -;k67*blZ=H=TQh>UMA(m1w0!?L{VGDpk+OvDhHAKF%fNXr25$w;Zs!r0000000007000000000O%am^t -lg}6qop{{FTg974FaQ3n`}K{nn9PGH_DcZ;0agu`_oR6wvcum54rF6FkJew+k!36?Lqfl$`8gF)R2-|n -!`LRkztQP;3W%Qi%!_9htpQ3t>=3qD6)+-@LI6MnH0sEektM3d!V}o -0nEa3l8<>f1KA&4t&LI4m}^5aI1iE}_s79eO?HmEkSbfMtWb&n35^vCNG$%?ywDnvz}K{0G9hl&cB^sg --3J`2zr)xjz`xPycM6D}`pk=G7OeqFKI{;-SrsrMkU}5;FWB;W7bg;sK59Oe@c3K=fV3eR7p&1RS^QDd -q`TfM1OfmAZf|a7*gwADFAe3iZ1@l19{2t5VaJV^T`{fc?xMUvnKPbj0R(ezZDp`<(~tJj3|i&b2NlXO -R2^DUyWY#wQk^*F-L`Td370(4qMgjGn~{4aFkgwNr28QlFe*-S#jFZ=4d$x=UULI21Z8+*Y#{__VRL9B -24rt+Y+-UF17U4&CIoP7b#p5OWMOk?Edyk4bS?yXWpZyY18;6+F#~jWZ!!gRXmVv`GX!RDb#gQWW@&b1 -H3M^Lcs2!dWp-t5Hw9&BXJ~Xd1a4_=WjO_7VRB`3UIuJ$WMOk?UjboZ0b*hSV`BkiWC3Mm0cK_aXJ-Lu -XaQ+y0cvUiYij{)YyoX;0d8&qZ*Ku`Z~<{~0djHyb8`W7bOCjB0d{r)cXt7Jcma8N0eX5rD{{BQuNq?v -w$uLzi?1~hlkP@ao_$9uVE}^UN!R2B0b{Bo6zH)>$g+grvznd|(VVK)`s##^Jh_COk!RL4N<*rD#rE}N -PvxSnUK**8Le1-kltSZ7azFKgf3Y*(iUtA%ba`-Pu?^n-fFP~dpvnj-AyBKaJW)+{-ce}5$#DguerIN2 -24rbxWpi{YTdJ&3iT??W6$?l#{@A?G8j--)v|TbGZq;_HaqHbhw}2&v0mUY=J6lL$MhcKn;XgI|zJmp* -01;Q@0XT>R0ssVVZ*FDSKfd5E4dt|K_z&S8_xG@u4Q3HlB(d+LiLJm-R=h;`?dxC37Wb8ul}WgrA) -cw=lK261(7bY*iQ1ZZJ%Xd?z>Z)|K~awG?EWpZO>ZgeFHVQp|_a&uvBWF`t>aBp*Ta&K^GWhV$?a$#d@ -Wpqp^2x4+!V{2t}QYi>wb97~LX>)5T1aNG1b1Ma7Z*6U1ECp?8Zgq1l17vS>E(LRJVRL9N1bSt1Z!iOI -Ze=k8ba!tu1$1a~Wo0u2W^Z+JGz4a8c4ajKb7^=s1#@L~Wo|bGWoc(MgX1!He)Z*DpXb7gI5 -LvL(vZaV~QWpi^p1!Zw{VQf7IXL4m>bY*fr2yt~~b98BMZa)HHbU*@MK|umvLP7#xLqh^zL_`8#MMVN% -Mn(c(M@Ir*NJs)-Nl5}OG^S@OiTh_O-%w{PEG<}Pfr40P*4J2QBeY4Qc?n6Q&R$8R8#_ARaF9C -R#pOES62dGSXcsISy=*KT3QNoaYAxoV{2t}Oj`+JVPk7kY+-X~Tnck>LULhaYh`p&T?J!da%FU025fI+ -VRL9-2x4JlYjkO2YhVFkVF6-d0b^qUWMlzlWdUYp0cU3cXlMaxX#r|#0c&dkY-|B-Z2@j>0dH>saBu-} -aRG920dsQ!baVlAbpdvE0e5!+cz6MMc>#KQ31dQXVPk7$bWD2$aA|O5dyr3U)7OiEGa`mzoq#(6;V_O`>9xR8a*>q2D50xZ(4$R31H0PIsUw_;fcDKIn~;D -0000000000|Nj6000000TX!suv0v9m0LPL+_79KRH|I99{YEOV7tT#ZPWpkW1p!`O$dXTU&2q#dT$Zax -d1hGe8*-eZ2I646q$?$f9S>WJ$5c2n1xg(vzLX+s=TnOlIwpu5x<(TMczEDkZ1)BN1axJ1bQsH&ZxWNw -7!I9y+{RnQn@2DI{;m7Md`c4>2IVr*pq1Y~7nX#oXeWo~q70tIbpY;0)*31nqsX-#QtY-t1vV`Xl1X-#QtY-t4rZE0h2 -Zw3iuWn*bgX=8G42MS|lZggo)X=8G42n23nZf^+)WMyM%PGN3u3JGInZggo*VQy~=1aN6%Zwv@zWn*bj -X=85<31ek$bZJm&V{Z-xW@T-3Zx0D%Wn*bZWo>kC5DH^uZggozWo>kC5d>j$bZ-(~UdWP9bIo$ZB3zcM -M|oyg?;CQQqXyz&yre57i5(9G0)iue^mXvL0b>G|!V30Z)+K@7h0D=S -BjVedlDqGVd368bwG2#j+mD9lQD0sr<;4X&8%0D>TgISeJ)kNFk^3Xf*%skbRRcZ*dS!BNFavLH -WibPEcW*KUbZByAWite3Z*_7s1ZHV=WiUrS2@UrbB_UrkK{UrtT}Ur$d0Ur8 -UsO~AUsY8CUshHEUsqQGUszZIUs+iKUs_rLVPOGcVgX}g0c2zWWn}?oW&vks0cdCeX=wp!Y5{9&0c>mm -ZEXQ=ZUJv^0dQ~uad821ashL50d#Z$b#(!Db^&*H0eE-;d3gbPdSj|16zH)>$g+grvznd|(VVK)`s##^ -Jh_COk!RL4N(lR@SaKRYGgJn%Xv1$>f_VvG%;GuzyszPjx|liD+IRr~000000093000000004kq#k^Az -$U%@qU7>2Bz={du0O&G0&#r1CLJBFZ06hf(#5#SRxw8U!bIFfg*5RxK^~=*jK)}Ad3JOG^S@OiTh_O-%w{PEG<}Pfr40P*4J2QBeY4Qc?n6Q&R$8R8#_ARaF9CR#pOES62dGSXcsI -Sy=*KT3P{NVF6-d0b^qUWMlzlWdUYp0cU3cXlMaxX#r|#0c&dkY-|B-Z2@j>0dH>saBu-}aRG920dsQ! -baVlAbpdvE0e5!+cz6MMc>#KQh>TceV>4NmyOwH13hJ -J{u?^n-fFP~dpvnj-AyBKaJW)+{-ce}5$#DguerIN21_K0id2nSM -uyu|U!T^j90F36+)E=H0H{s0>nH0sEektM3d!V}qb9G{Ld2nSf*z$T8ClZi8YCe|m_*?{lv>_T7tkE!8 -{87}TyWT7ZV`yP=b7gcd*z$T8ClZi8YCe|m_*?{lv>_T7tkE!8{87}TyWT9nkIU)BIae{Jw9R4r0N>}O -)+shqImKG);D@8R3aUm3Jkg?^%&nV|dnPbniKwLeAs8?!PIJYq3V03Xs{mee0000000000KL7v#00000 -#5#SRxw8U!bIFfg*5RxK^~=*jK)}Ad3JH<(%oY0=TGqa6l5KfYJkC@$v(fAa&d%-e7ws4kFK+d0H97bAybczVb@xPq-Dzr4oJgUK7Oif -U&jRjKPz&##IG7-47St%2#c>Z5R>jkTb_MKDq#SEVo@@XB+#WAdR)2C|*D$SwhJOvD$-0{Gfin@` -^Yz ->$s@6fa*%L>wyFU00eGtZe`d%zThtn<+N=058)p7{qSMOjh9_9t?BNfyg->Vo@@aGb8l^B+#WAdR)2C| -*D$SwhJOvD$-0{Gfin@`G@u4Q3HlB(d+LiLJm-R=h;`?dxBvhE0000004D$d000000QnaP1l_I#dHB_@bgMhk0_N&La@nc5 -HwP6O+keCip#vHLVPOGcVgX}g0c2zWWn}?oW&vks0cdCeX=wp!Y5{9&0c>mmZEXQ=ZUJv^0dQ~uad821 -ashL50d#Z$b#(!Db^&*H0eE-;d3gbPdi$wZavD7|R0gwX!*5!Gc?n?5;yM1jui=Thm^szjcmV+b0|P-! -RR}^*L`g?QQ&a;|M?xV03jhEB(4Y?i2MYiJ01F5J01E*E0La=00XZ-L(V!0j2Lu2B0RR910000 - ------END RGB CONSIGNMENT----- - - diff --git a/examples/rgb20-demo.yaml b/examples/rgb20-demo.yaml deleted file mode 100644 index 575c6d7..0000000 --- a/examples/rgb20-demo.yaml +++ /dev/null @@ -1,30 +0,0 @@ -interface: RGB20Fixed - -globals: - spec: - ticker: DEMO - name: Demo asset - details: "Pay attention: the asset has no value" - precision: 2 - 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 - 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: 100000000 - -assignments: - assetOwner: - seal: tapret1st:b449f7eaa3f98c145b27ad0eeb7b5679ceb567faef7a52479bc995792b65f804:1 - amount: 100000000 # this is 1 million (we have two digits for cents) diff --git a/examples/token.rs b/examples/token.rs new file mode 100644 index 0000000..f142f7d --- /dev/null +++ b/examples/token.rs @@ -0,0 +1,93 @@ +// Standard Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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. + +fn main() { + /* + let mut alice = DirRuntime::new_opret(alice_account); + let mut bob = DirRuntime::new_tapret(bob_account); + + let invoice = bob.invoice(); + alice.fulfill(invoice); + */ + + /* + let mut alice = OmniBarrow::load(""); + + let seal1 = alice.next_seal_pub(); + + let schema = Schema::load("").expect("unable to load schema"); + // Contract is articles object + let articles = alice.issue(); + .start_issue("firstIssue") + .append("ticker", svnum!(0u64), Some(ston!(ticker "TICK", name "Token"))) + .assign("tokenOwners", seal1, svnum!(100_0000), None) + .finish::("TokenContract", 1732529307); + + let mut stockpile = Stockpile::new(articles, "examples/token/data"); + + let bob = Wallet::load(""); + let seal2 = bob.next_seal_pub(); + let seal_change = alice.next_seal_priv(); + // The seal can be also vout-based, use `next_seal_vout` for that purpose + + // Instead of keeping the whole contract ops in the memory, this can be actually done + // using state combined with APIs! + let op = stockpile + .start_deed("transfer") + .using(seal1, ston!()) + .append("tokenOwners", seal2, svnum!(10_0000), None) + .append("tokenOwners", seal_change, svnum!(90_0000), None) + .commit(); + + // PSBT constructor analyses both inputs and outputs of the operation, detecting and checking + // relevant seals (for instance, vout-based). + let psbt = alice.construct_psbt(op); + //let alice_balances = stockpile.select_unspent(alice.utxos(), svnum!(10_0000)); + + for contract_id in alice.affected(psbt) { + // TODO: We need to do blank transitions as well + } + + let anchor = psbt.extract_anchor(stockpile.contract_id()); + // TODO: We need to extract an anchor per each contract + // This adds information about UTXOs to the wallet + let tx = alice.finalize_psbt(); + + // Ensuring Alice's contract is updated + stockpile.append_witness(tx, anchor); + + stockpile.consign_to_file([seal2], "examples/token/transfer.rgb"); + tx.broadcast(); + alice.save(""); + + // Bob's site: + // let diff = Deeds::diff("", transfer); + // let transitions = state.accept(diff); // This also does the verification + // Trace::extend("", transitions); + // Deeds::extend("", diff); + + // Locker, token (stash) and trace are append-only logs + // Only state, wallet (alice) must persist in memory + */ + todo!() +} diff --git a/psbt/Cargo.toml b/psbt/Cargo.toml index 0771446..beb4b02 100644 --- a/psbt/Cargo.toml +++ b/psbt/Cargo.toml @@ -1,42 +1,28 @@ [package] name = "rgb-psbt" -version = { workspace = true } -description = "Partially signed bitcoin transaction RGB extensions" -keywords = ["bitcoin", "invoices", "rgb", "smart-contracts", "psbt"] -categories = ["cryptography::cryptocurrencies"] -authors = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -rust-version = { workspace = true } -readme = "../README.md" +description = "Partially-signed transaction support for RGB protocol" +version.workspace = true +keywords.workspace = true +categories.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true +license.workspace = true [lib] -name = "psrgbt" -crate-type = ["cdylib", "rlib"] # We need this for WASM +name = "rgpsbt" [dependencies] amplify = { workspace = true } -baid64 = { workspace = true } -commit_verify = { workspace = true } -strict_encoding = { workspace = true } +rgb-std = { workspace = true, features = ["bitcoin"] } bp-core = { workspace = true } -bp-std = { workspace = true } -rgb-std = { workspace = true } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "0.2" -rand = { version = "0.8.4", optional = true } -getrandom = { version = "0.2", features = ["js"] } - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = "0.3" +bp-std = { workspace = true, optional = true } [features] default = [] -all = ["serde"] -serde = ["bp-core/serde", "bp-std/serde", "rgb-std/serde"] +all = ["bp", "rust-bitcoin"] -[package.metadata.docs.rs] -features = ["all"] +bp = ["bp-std"] +rust-bitcoin = [] diff --git a/psbt/src/bp.rs b/psbt/src/bp.rs new file mode 100644 index 0000000..e0adb6a --- /dev/null +++ b/psbt/src/bp.rs @@ -0,0 +1,85 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 amplify::ByteArray; +use bpstd::seals::{mmb, mpc}; +use bpstd::{Psbt, Sats, ScriptPubkey, Vout}; +use rgb::popls::bp::{OpRequestSet, PaymentScript, PrefabBundle, PrefabSeal}; + +use crate::{RgbPsbt, RgbPsbtCsvError, RgbPsbtPrepareError, ScriptResolver}; + +impl RgbPsbt for Psbt { + fn rgb_resolve( + &mut self, + script: PaymentScript, + change_vout: Option, + ) -> Result, RgbPsbtPrepareError> { + match self.opret_hosts().count() { + 0 => { + let host = self + .insert_output(0, ScriptPubkey::op_return(&[]), Sats::ZERO) + .map_err(|_| RgbPsbtPrepareError::Unfinalizable)?; + host.set_opret_host().ok(); + } + 1 => {} + _ => return Err(RgbPsbtPrepareError::MultipleHosts), + } + self.complete_construction(); + + script + .resolve_seals(self.script_resolver(), change_vout) + .map_err(|_| RgbPsbtPrepareError::ChangeRequired) + } + + fn rgb_fill_csv(&mut self, bundle: &PrefabBundle) -> Result<(), RgbPsbtCsvError> { + if self.is_modifiable() { + return Err(RgbPsbtCsvError::Modifiable); + } + for prefab in bundle { + let id = mpc::ProtocolId::from_byte_array(prefab.operation.contract_id.to_byte_array()); + let opid = prefab.operation.opid(); + let msg = mmb::Message::from_byte_array(opid.to_byte_array()); + for outpoint in &prefab.closes { + let input = self + .inputs_mut() + .find(|inp| inp.previous_outpoint == *outpoint) + .ok_or(RgbPsbtCsvError::InputAbsent(*outpoint))?; + input.set_mmb_message(id, msg).map_err(|_| { + RgbPsbtCsvError::InputAlreadyUsed(input.index(), prefab.operation.contract_id) + })?; + } + } + Ok(()) + } +} + +impl ScriptResolver for Psbt { + fn script_resolver(&self) -> impl Fn(&ScriptPubkey) -> Option { + |spk| -> Option { + self.outputs() + .find(|inp| &inp.script == spk) + .map(|inp| Vout::from_u32(inp.index() as u32)) + } + } +} diff --git a/psbt/src/common.rs b/psbt/src/common.rs new file mode 100644 index 0000000..7f6d934 --- /dev/null +++ b/psbt/src/common.rs @@ -0,0 +1,69 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 bp::{ScriptPubkey, Vout}; +use rgb::popls::bp::{OpRequestSet, PaymentScript, PrefabBundle, PrefabSeal}; +use rgb::{ContractId, Outpoint}; + +pub trait RgbPsbt { + fn rgb_resolve( + &mut self, + script: PaymentScript, + change_vout: Option, + ) -> Result, RgbPsbtPrepareError>; + + fn rgb_fill_csv(&mut self, bundle: &PrefabBundle) -> Result<(), RgbPsbtCsvError>; +} + +pub trait ScriptResolver { + fn script_resolver(&self) -> impl Fn(&ScriptPubkey) -> Option; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum RgbPsbtPrepareError { + /// in order to complete RGB processing the PSBT must have PSBT_GLOBAL_TX_MODIFIABLE flag set + /// on. + Unfinalizable, + + /// multiple transaction outputs are market as DBC commitment hosts (while only one should be). + MultipleHosts, + + /// transfer doesn't create BTC change output, which is required for RGB change. + ChangeRequired, +} + +/// Errors embedding RGB-related information. +#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum RgbPsbtCsvError { + /// input spending {0} which used by RGB operation is absent from PSBT. + InputAbsent(Outpoint), + + /// input {0} is already used for {1}. + InputAlreadyUsed(usize, ContractId), + + /// transaction is still modifiable. + Modifiable, +} diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index ad736c4..5e48b11 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -1,131 +1,32 @@ -// Partially signed bitcoin transaction RGB extensions +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2020-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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. +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. #[macro_use] extern crate amplify; -mod rgb; +mod common; +#[cfg(feature = "bp")] +mod bp; -use bp::dbc::opret::OpretProof; -use bp::dbc::tapret::TapretProof; -pub use bpstd::psbt::*; -pub use rgb::*; -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, - PSBT_IN_RGB_CONSUMED_BY, PSBT_OUT_RGB_VELOCITY_HINT, PSBT_RGB_PREFIX, -}; - -#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] -#[display(doc_comments)] -pub enum EmbedError { - /// provided transaction batch references inputs which are absent from the - /// PSBT. Possible it was created for a different PSBT. - AbsentInputs, - - /// the provided PSBT is invalid since it doublespends on some of its - /// inputs. - PsbtRepeatedInputs, -} - -#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] -#[display(inner)] -pub enum CommitError { - #[from] - Rgb(RgbPsbtError), - - #[from] - Dbc(DbcPsbtError), -} - -#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] -#[display(doc_comments)] -pub enum ExtractError {} - -// TODO: Batch must be homomorphic by the outpoint type (chain) - -pub trait RgbPsbt { - fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError>; - #[allow(clippy::result_large_err)] - fn rgb_commit(&mut self) -> Result; - fn rgb_extract(&self) -> Result; -} - -impl RgbPsbt for Psbt { - fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError> { - for info in batch { - let contract_id = info.transition.contract_id; - let mut inputs = info.inputs.release(); - for input in self.inputs_mut() { - 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); - } - self.push_rgb_transition(info.transition, info.method) - .expect("transitions are unique since they are in BTreeMap indexed by opid"); - } - Ok(()) - } - - fn rgb_commit(&mut self) -> Result { - // Convert RGB data to MPCs? Or should we do it at the moment we add them... No, - // since we may require more DBC methods with each additional state transition - let bundles = self.rgb_bundles_to_mpc()?; - // DBC commitment for the required methods - let methods = bundles - .values() - .flat_map(|b| b.iter()) - .map(|b| CloseMethodSet::from(b.close_method)) - .reduce(|methods, method| methods | method) - .ok_or(RgbPsbtError::NoContracts)?; - let (mut tapret_anchor, mut opret_anchor) = (None, None); - if methods.has_tapret_first() { - tapret_anchor = Some(self.dbc_commit::()?); - } - if methods.has_opret_first() { - opret_anchor = Some(self.dbc_commit::()?); - } - let anchor = match (tapret_anchor, opret_anchor) { - (None, None) => return Err(RgbPsbtError::NoContracts.into()), - (Some(tapret), None) => AnchorSet::Tapret(tapret), - (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().into()); - Ok(Fascia { - witness: XPubWitness::Bitcoin(witness), - anchor, - bundles, - }) - } - - fn rgb_extract(&self) -> Result { - todo!("implement RGB PSBT fascia extraction for multi-party protocols") - } -} +pub use common::{RgbPsbt, RgbPsbtCsvError, RgbPsbtPrepareError, ScriptResolver}; diff --git a/psbt/src/rgb.rs b/psbt/src/rgb.rs deleted file mode 100644 index 0a1f14d..0000000 --- a/psbt/src/rgb.rs +++ /dev/null @@ -1,434 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -use amplify::confinement::{Confined, SmallOrdMap, U24}; -use amplify::{confinement, FromSliceError}; -use bp::dbc::Method; -use bp::seals::txout::CloseMethod; -use bpstd::psbt; -use bpstd::psbt::{KeyAlreadyPresent, KeyMap, MpcPsbtError, PropKey, Psbt}; -use commit_verify::mpc; -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 -// contract version which skips all info not important for hardware -// signers -// /// Proprietary key subtype for storing RGB contract consignment in -// /// global map. -// pub const PSBT_GLOBAL_RGB_CONTRACT: u8 = 0x00; - -/// PSBT proprietary key prefix used for RGB. -pub const PSBT_RGB_PREFIX: &str = "RGB"; - -/// Proprietary key subtype for storing RGB state transition in global map. -pub const PSBT_GLOBAL_RGB_TRANSITION: u64 = 0x01; -/// Proprietary key subtype for storing information on which closed methods -/// should be used for each of RGB state transitions. -pub const PSBT_GLOBAL_RGB_CLOSE_METHODS: u64 = 0x02; -/// Proprietary key subtype for storing RGB state transition operation id which -/// consumes this input. -pub const PSBT_IN_RGB_CONSUMED_BY: u64 = 0x01; -/// Proprietary key subtype for storing hint for the velocity of the state -/// which can be assigned to the provided output. -pub const PSBT_OUT_RGB_VELOCITY_HINT: u64 = 0x01; - -/// Extension trait for static functions returning RGB-related proprietary keys. -pub trait ProprietaryKeyRgb { - /// Constructs [`PSBT_GLOBAL_RGB_TRANSITION`] proprietary key. - fn rgb_transition(opid: OpId) -> PropKey { - PropKey { - identifier: PSBT_RGB_PREFIX.to_owned(), - subtype: PSBT_GLOBAL_RGB_TRANSITION, - data: opid.to_vec().into(), - } - } - /// Constructs [`PSBT_GLOBAL_RGB_CLOSE_METHODS`] proprietary key. - fn rgb_closing_methods(opid: OpId) -> PropKey { - PropKey { - identifier: PSBT_RGB_PREFIX.to_owned(), - subtype: PSBT_GLOBAL_RGB_CLOSE_METHODS, - data: opid.to_vec().into(), - } - } - - /// Constructs [`PSBT_IN_RGB_CONSUMED_BY`] proprietary key. - fn rgb_in_consumed_by(contract_id: ContractId) -> PropKey { - PropKey { - identifier: PSBT_RGB_PREFIX.to_owned(), - subtype: PSBT_IN_RGB_CONSUMED_BY, - data: contract_id.to_vec().into(), - } - } - - /// Constructs [`PSBT_OUT_RGB_VELOCITY_HINT`] proprietary key. - fn rgb_out_velocity_hint() -> PropKey { - PropKey { - identifier: PSBT_RGB_PREFIX.to_owned(), - subtype: PSBT_OUT_RGB_VELOCITY_HINT, - data: none!(), - } - } -} - -impl ProprietaryKeyRgb for PropKey {} - -/// Errors processing RGB-related proprietary PSBT keys and their values. -#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum RgbPsbtError { - /// the key is already present in PSBT, but has a different value - AlreadySet, - - /// state transition {0} already present in PSBT is not related to the state - /// transition {1} which has to be added to RGB - UnrelatedTransitions(OpId, OpId, MergeRevealError), - - /// PSBT contains no contract information - NoContracts, - - /// contract {0} listed in the PSBT has zero known transition information. - NoTransitions(ContractId), - - /// invalid contract id data. - #[from(FromSliceError)] - InvalidContractId, - - /// state transition {0} doesn't provide information about seal closing - /// methods used by its inputs. - NoCloseMethod(OpId), - - /// invalid close method data for opid {0} - InvalidCloseMethod(OpId), - - /// PSBT doesn't specify an output which can host {0} commitment. - NoHostOutput(Method), - - /// PSBT contains too many contracts (more than 16 million). - TooManyContracts, - - /// PSBT contains too many state transitions for a bundle. - #[from(confinement::Error)] - TooManyTransitionsInBundle, - - /// the size of transition {0} exceeds 16 MB. - TransitionTooBig(OpId), - - /// state transition data in PSBT are invalid. Details: {0} - #[from] - InvalidTransition(DeserializeError), - - #[from] - #[display(inner)] - Mpc(MpcPsbtError), -} - -#[allow(clippy::result_large_err)] -pub trait RgbExt { - fn rgb_contract_ids(&self) -> Result, FromSliceError>; - - fn rgb_contract_consumers( - &self, - contract_id: ContractId, - ) -> Result, FromSliceError>; - - fn rgb_op_ids(&self, contract_id: ContractId) -> Result, FromSliceError>; - - fn rgb_transition(&self, opid: OpId) -> Result, RgbPsbtError>; - - fn rgb_close_method(&self, opid: OpId) -> Result, RgbPsbtError>; - - fn push_rgb_transition( - &mut self, - transition: Transition, - method: CloseMethod, - ) -> Result; - - fn rgb_bundles(&self) -> Result, RgbPsbtError> { - let mut map = BTreeMap::new(); - for contract_id in self.rgb_contract_ids()? { - let mut input_map = HashMap::>::new(); - let mut known_transitions = - HashMap::>::new(); - for (opid, vin) in self.rgb_contract_consumers(contract_id)? { - let (transition, method) = ( - self.rgb_transition(opid)?, - self.rgb_close_method(opid)? - .ok_or(RgbPsbtError::NoCloseMethod(opid))?, - ); - input_map.entry(method).or_default().insert(vin, opid)?; - if let Some(transition) = transition { - known_transitions - .entry(method) - .or_default() - .insert(opid, transition)?; - } - } - let mut bundles = vec![]; - for (method, input_map) in input_map { - let known_transitions = known_transitions.remove(&method).unwrap_or_default(); - bundles.push(TransitionBundle { - close_method: method, - input_map: InputMap::from( - Confined::try_from(input_map.release()) - .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, - ), - known_transitions: Confined::try_from(known_transitions.release()) - .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, - }); - } - let mut bundles = bundles.into_iter(); - let first = bundles - .next() - .ok_or(RgbPsbtError::NoTransitions(contract_id))?; - map.insert(contract_id, BundleDichotomy::with(first, bundles.next())); - } - Ok(map) - } - - fn rgb_bundles_to_mpc( - &mut self, - ) -> Result, 1, U24>, RgbPsbtError>; -} - -impl RgbExt for Psbt { - fn rgb_contract_ids(&self) -> Result, FromSliceError> { - self.inputs() - .flat_map(|input| { - input - .proprietary - .keys() - .filter(|prop_key| { - prop_key.identifier == PSBT_RGB_PREFIX - && prop_key.subtype == PSBT_IN_RGB_CONSUMED_BY - }) - .map(|prop_key| prop_key.data.as_slice()) - .map(ContractId::copy_from_slice) - }) - .collect() - } - - fn rgb_contract_consumers( - &self, - contract_id: ContractId, - ) -> Result, FromSliceError> { - let mut consumers: BTreeSet<(OpId, Vin)> = bset! {}; - for (no, input) in self.inputs().enumerate() { - if let Some(opid) = input.rgb_consumer(contract_id)? { - consumers.insert((opid, Vin::from_u32(no as u32))); - } - } - Ok(consumers) - } - - fn rgb_op_ids(&self, contract_id: ContractId) -> Result, FromSliceError> { - self.inputs() - .filter_map(|input| input.rgb_consumer(contract_id).transpose()) - .collect() - } - - fn rgb_transition(&self, opid: OpId) -> Result, RgbPsbtError> { - let Some(data) = self.proprietary(&PropKey::rgb_transition(opid)) else { - return Ok(None); - }; - let data = Confined::try_from_iter(data.iter().copied())?; - let transition = Transition::from_strict_serialized::(data)?; - Ok(Some(transition)) - } - - fn rgb_close_method(&self, opid: OpId) -> Result, RgbPsbtError> { - let Some(m) = self.proprietary(&PropKey::rgb_closing_methods(opid)) else { - return Ok(None); - }; - if m.len() == 1 { - if let Ok(method) = CloseMethod::try_from(m[0]) { - return Ok(Some(method)); - } - } - Err(RgbPsbtError::InvalidCloseMethod(opid)) - } - - fn push_rgb_transition( - &mut self, - mut transition: Transition, - method: CloseMethod, - ) -> Result { - let opid = transition.id(); - - let prev_method = self.rgb_close_method(opid)?; - if matches!(prev_method, Some(prev_method) if prev_method != method) { - return Err(RgbPsbtError::InvalidCloseMethod(opid)); - } - - let prev_transition = self.rgb_transition(opid)?; - if let Some(ref prev_transition) = prev_transition { - transition = transition - .merge_reveal(prev_transition.clone()) - .map_err(|err| { - RgbPsbtError::UnrelatedTransitions(prev_transition.id(), opid, err) - })?; - } - let serialized_transition = transition - .to_strict_serialized::() - .map_err(|_| RgbPsbtError::TransitionTooBig(opid))?; - - // Since we update transition it's ok to ignore the fact that it previously - // existed - let _ = - self.push_proprietary(PropKey::rgb_transition(opid), serialized_transition.release()); - let _ = self.push_proprietary(PropKey::rgb_closing_methods(opid), vec![method as u8]); - Ok(prev_transition.is_none()) - } - - fn rgb_bundles_to_mpc( - &mut self, - ) -> Result, 1, U24>, RgbPsbtError> { - let bundles = self.rgb_bundles()?; - - for (contract_id, bundle) in bundles - .iter() - .flat_map(|(id, b)| b.iter().map(move |b| (id, b))) - { - let protocol_id = mpc::ProtocolId::from(*contract_id); - let message = mpc::Message::from(bundle.bundle_id()); - if bundle.close_method == CloseMethod::TapretFirst { - // We need to do it each time due to Rust borrow checker - let tapret_host = self - .outputs_mut() - .find(|output| output.is_tapret_host()) - .ok_or(RgbPsbtError::NoHostOutput(Method::TapretFirst))?; - tapret_host.set_mpc_message(protocol_id, message)?; - } else if bundle.close_method == CloseMethod::OpretFirst { - // We need to do it each time due to Rust borrow checker - let opret_host = self - .outputs_mut() - .find(|output| output.is_opret_host()) - .ok_or(RgbPsbtError::NoHostOutput(Method::OpretFirst))?; - opret_host.set_mpc_message(protocol_id, message)?; - } - } - - let map = Confined::try_from(bundles).map_err(|_| RgbPsbtError::NoContracts)?; - - Ok(map) - } -} - -pub trait RgbInExt { - /// Returns information which state transition consumes this PSBT input. - /// - /// We do not error on invalid data in order to support future update of - /// this proprietary key to a standard one. In this case, the invalid - /// data will be filtered at the moment of PSBT deserialization and this - /// function will return `None` only in situations when the key is absent. - fn rgb_consumer(&self, contract_id: ContractId) -> Result, FromSliceError>; - - /// Adds information about state transition consuming this PSBT input. - /// - /// # Returns - /// - /// `Ok(false)`, if the same node id under the same contract was already - /// present in the input. `Ok(true)`, if the id node was successfully - /// added to the input. - /// - /// # Errors - /// - /// If the input already contains [`PSBT_IN_RGB_NODE_ID`] key with the given - /// `contract_id` but referencing different [`OpId`]. - #[allow(clippy::result_large_err)] - fn set_rgb_consumer( - &mut self, - contract_id: ContractId, - opid: OpId, - ) -> Result; -} - -impl RgbInExt for psbt::Input { - fn rgb_consumer(&self, contract_id: ContractId) -> Result, FromSliceError> { - let Some(data) = self - .proprietary - .get(&PropKey::rgb_in_consumed_by(contract_id)) - else { - return Ok(None); - }; - Ok(Some(OpId::copy_from_slice(data)?)) - } - - fn set_rgb_consumer( - &mut self, - contract_id: ContractId, - opid: OpId, - ) -> Result { - let key = PropKey::rgb_in_consumed_by(contract_id); - match self.rgb_consumer(contract_id) { - Ok(None) | Err(_) => { - let _ = self.push_proprietary(key, opid.to_vec()); - Ok(true) - } - Ok(Some(id)) if id == opid => Ok(false), - Ok(Some(_)) => Err(KeyAlreadyPresent(key)), - } - } -} - -pub trait RgbOutExt { - /// Returns hint for the velocity of the state which may be assigned to the - /// provided output. - /// - /// We do not error on invalid data in order to support future update of - /// this proprietary key to a standard one. In this case, the invalid - /// data will be filtered at the moment of PSBT deserialization and this - /// function will return `None` only in situations when the key is absent. - fn rgb_velocity_hint(&self) -> Option; - - /// Adds hint for the velocity of the state which may be assigned to the - /// PSBT output. - /// - /// # Returns - /// - /// `false`, if a velocity hint was already present in the input and - /// `true` otherwise. - fn set_rgb_velocity_hint(&mut self, hint: VelocityHint) -> bool; -} - -impl RgbOutExt for psbt::Output { - fn rgb_velocity_hint(&self) -> Option { - let data = self.proprietary.get(&PropKey::rgb_out_velocity_hint())?; - if data.len() != 1 { - None - } else { - data.first().map(VelocityHint::with_value) - } - } - - fn set_rgb_velocity_hint(&mut self, hint: VelocityHint) -> bool { - let prev = self.rgb_velocity_hint(); - self.push_proprietary(PropKey::rgb_out_velocity_hint(), vec![hint as u8]) - .ok(); - Some(hint) == prev - } -} diff --git a/src/coinselect.rs b/src/coinselect.rs new file mode 100644 index 0000000..e9552ea --- /dev/null +++ b/src/coinselect.rs @@ -0,0 +1,103 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 core::str::FromStr; + +use rgb::popls::bp::Coinselect; +use rgb::{CellAddr, StateCalc}; +use strict_types::StrictVal; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display, Default)] +#[display(lowercase)] +pub enum CoinselectStrategy { + /// Collect them most small outputs unless the invoiced value if reached + #[default] + Aggregate, + + /// Collect the minimum number of outputs (with the large value) to reduce the resulting input + /// count + SmallSize, +} + +impl FromStr for CoinselectStrategy { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "aggregate" => Ok(CoinselectStrategy::Aggregate), + "smallsize" => Ok(CoinselectStrategy::SmallSize), + s => Err(s.to_string()), + } + } +} + +impl Coinselect for CoinselectStrategy { + fn coinselect( + &mut self, + invoiced_state: &StrictVal, + calc: &mut (impl StateCalc + ?Sized), + owned_state: Vec<(CellAddr, &StrictVal)>, + ) -> Option> { + let res = match self { + CoinselectStrategy::Aggregate => owned_state + .into_iter() + .take_while(|(_, val)| { + if calc.is_satisfied(invoiced_state) { + return false; + } + calc.accumulate(val).is_ok() + }) + .map(|(addr, _)| addr) + .collect(), + CoinselectStrategy::SmallSize => owned_state + .into_iter() + .rev() + .take_while(|(_, val)| { + if calc.is_satisfied(invoiced_state) { + return false; + } + calc.accumulate(val).is_ok() + }) + .map(|(addr, _)| addr) + .collect(), + }; + if !calc.is_satisfied(invoiced_state) { + return None; + }; + Some(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn display_from_str() { + assert_eq!(CoinselectStrategy::Aggregate.to_string(), "aggregate"); + assert_eq!(CoinselectStrategy::SmallSize.to_string(), "smallsize"); + assert_eq!(CoinselectStrategy::Aggregate, "aggregate".parse().unwrap()); + assert_eq!(CoinselectStrategy::SmallSize, "smallsize".parse().unwrap()); + } +} diff --git a/src/descriptor.rs b/src/descriptor.rs index 103eee2..51c4804 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -1,398 +1,328 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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::{BTreeSet, HashMap}; -use std::fmt::{self, Display, Formatter}; -use std::iter; -use std::str::FromStr; - -use amplify::Wrapper; -use bp::dbc::tapret::TapretCommitment; -use bp::dbc::Method; -use bp::seals::txout::CloseMethod; -use bp::{LegacyPk, SigScript, Witness}; +// 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 alloc::collections::{BTreeMap, BTreeSet}; +use core::fmt::{self, Display, Formatter}; +use std::collections::HashMap; + +use amplify::{Bytes32, Wrapper, WrapperMut}; +use bpstd::dbc::tapret::TapretCommitment; +use bpstd::seals::WTxoSeal; use bpstd::{ - Derive, DeriveCompr, DeriveSet, DeriveXOnly, DerivedScript, Descriptor, Idx, IdxBase, - IndexError, IndexParseError, KeyOrigin, Keychain, LegacyKeySig, NormalIndex, SpkClass, - StdDescr, TapDerivation, TapScript, TapTree, TaprootKeySig, Terminal, TrKey, Wpkh, XOnlyPk, - XpubAccount, XpubDerivable, + Derive, DeriveCompr, DeriveKey, DeriveSet, DeriveXOnly, DerivedScript, Descriptor, KeyOrigin, + Keychain, LegacyKeySig, LegacyPk, NormalIndex, SigScript, SpkClass, StdDescr, TapDerivation, + TapScript, TapTree, TaprootKeySig, Terminal, Tr, TrKey, Witness, XOnlyPk, XpubAccount, + XpubDerivable, }; use commit_verify::CommitVerify; use indexmap::IndexMap; -#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] -#[display("terminal derivation {0} already has a taptweak assigned")] -pub struct TapTweakAlreadyAssigned(pub Terminal); - pub trait DescriptorRgb: Descriptor { - fn seal_close_method(&self) -> CloseMethod; - fn add_tapret_tweak( - &mut self, - terminal: Terminal, - tweak: TapretCommitment, - ) -> Result<(), TapTweakAlreadyAssigned>; -} - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") -)] -#[repr(u8)] -pub enum RgbKeychain { - #[display("0", alt = "0")] - External = 0, - - #[display("1", alt = "1")] - Internal = 1, - - #[display("9", alt = "9")] - Rgb = 9, - - #[display("10", alt = "10")] - Tapret = 10, + fn add_seal(&self, seal: WTxoSeal); } -impl RgbKeychain { - pub const RGB_ALL: [RgbKeychain; 2] = [RgbKeychain::Rgb, RgbKeychain::Tapret]; - - pub fn contains_rgb(keychain: impl Into) -> bool { - let k = keychain.into().into_inner(); - k == Self::Rgb as u8 || k == Self::Tapret as u8 - } - pub fn is_seal(self) -> bool { self == Self::Rgb || self == Self::Tapret } +#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug, Default, From)] +#[wrapper(Deref)] +#[wrapper_mut(DerefMut)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))] +pub struct SealDescr(BTreeSet); - pub const fn for_method(method: Method) -> Self { - match method { - Method::OpretFirst => Self::Rgb, - Method::TapretFirst => Self::Tapret, +impl Display for SealDescr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("seals(")?; + let mut iter = self.0.iter().peekable(); + while let Some(seal) = iter.next() { + Display::fmt(seal, f)?; + if iter.peek().is_some() { + f.write_str(",")?; + } } + f.write_str(")") } } -impl FromStr for RgbKeychain { - type Err = IndexParseError; +#[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Debug, Default, From)] +#[wrapper(Deref)] +#[wrapper_mut(DerefMut)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))] +pub struct TapretWeaks(BTreeMap>); - fn from_str(s: &str) -> Result { - match NormalIndex::from_str(s)? { - NormalIndex::ZERO => Ok(RgbKeychain::External), - NormalIndex::ONE => Ok(RgbKeychain::Internal), - val => Err(IndexError { - what: "non-standard keychain", - invalid: val.index(), - start: 0, - end: 1, +impl Display for TapretWeaks { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("tweaks(")?; + let mut iter1 = self.iter().peekable(); + while let Some((term, tweaks)) = iter1.next() { + write!(f, "{term}=")?; + let mut iter2 = tweaks.iter().peekable(); + while let Some(tweak) = iter2.next() { + write!(f, "{tweak}")?; + if iter2.peek().is_some() { + f.write_str(",")?; + } + } + if iter1.peek().is_some() { + f.write_str(";")?; } - .into()), } + f.write_str(")") } } -impl From for Keychain { - fn from(keychain: RgbKeychain) -> Self { Keychain::from(keychain as u8) } -} - -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Display, From)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") + serde( + rename_all = "camelCase", + bound( + serialize = "K::Compr: serde::Serialize, K::XOnly: serde::Serialize", + deserialize = "K::Compr: serde::Deserialize<'de>, K::XOnly: serde::Deserialize<'de>" + ) + ) )] -pub struct TapretKey { - pub tr: TrKey, - // TODO: Allow multiple tweaks per index by introducing derivation using new Terminal trait - pub tweaks: HashMap, +enum RgbDeriver { + #[from] + #[display(inner)] + OpretOnly(StdDescr), + + #[display("{tr},{tweaks}")] + Universal { + tr: Tr, + tweaks: TapretWeaks, + }, } -impl Display for TapretKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "tapret({},tweaks(", self.tr.as_internal_key())?; - let mut iter = self.tweaks.iter().peekable(); - while let Some((term, tweak)) = iter.next() { - if term.keychain != RgbKeychain::Tapret.into() { - write!(f, "{}/", term.keychain)?; - } - write!(f, "{}={tweak}", term.index)?; - if iter.peek().is_some() { - f.write_str(";")?; - } +impl Derive for RgbDeriver { + fn default_keychain(&self) -> Keychain { + match self { + RgbDeriver::OpretOnly(d) => d.default_keychain(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.default_keychain(), } - f.write_str("))") } -} -impl TapretKey { - pub fn new_unfunded(internal_key: K) -> Self { - TapretKey { - tr: TrKey::from(internal_key), - tweaks: empty!(), + fn keychains(&self) -> BTreeSet { + match self { + RgbDeriver::OpretOnly(d) => d.keychains(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.keychains(), } } -} - -impl Derive for TapretKey { - #[inline] - fn default_keychain(&self) -> Keychain { RgbKeychain::Rgb.into() } - - fn keychains(&self) -> BTreeSet { self.tr.keychains() } fn derive( &self, keychain: impl Into, index: impl Into, - ) -> DerivedScript { - let keychain = keychain.into(); - let index = index.into(); - let terminal = Terminal::new(keychain, index); - let internal_key = self.tr.as_internal_key().derive(keychain, index); - if keychain.into_inner() == RgbKeychain::Tapret as u8 { - if let Some(tweak) = self.tweaks.get(&terminal) { - let script_commitment = TapScript::commit(tweak); - let tap_tree = TapTree::with_single_leaf(script_commitment); - return DerivedScript::TaprootScript(internal_key.into(), tap_tree); + ) -> impl Iterator { + match self { + RgbDeriver::OpretOnly(d) => d.derive(keychain, index).collect::>().into_iter(), + RgbDeriver::Universal { tr, tweaks } => { + let keychain = keychain.into(); + let index = index.into(); + let terminal = Terminal::new(keychain, index); + let mut vec = Vec::with_capacity(tweaks.0.len()); + for internal_key in tr.as_internal_key().derive(keychain, index) { + vec.push(DerivedScript::TaprootKeyOnly(internal_key.into())); + for tweak in tweaks.get(&terminal).into_iter().flatten() { + let script_commitment = TapScript::commit(tweak); + let tap_tree = TapTree::with_single_leaf(script_commitment); + let script = DerivedScript::TaprootScript(internal_key.into(), tap_tree); + vec.push(script); + } + } + vec.into_iter() } } - DerivedScript::TaprootKeyOnly(internal_key.into()) } } -impl From for TapretKey { - fn from(internal_key: K) -> Self { - TapretKey { - tr: TrKey::from(internal_key), - tweaks: none!(), - } - } -} - -impl From> for TapretKey { - fn from(tr: TrKey) -> Self { - TapretKey { - tr, - tweaks: none!(), +impl + DeriveCompr + DeriveXOnly> Descriptor + for RgbDeriver +{ + fn class(&self) -> SpkClass { + match self { + RgbDeriver::OpretOnly(d) => d.class(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.class(), } } -} - -impl Descriptor for TapretKey { - fn class(&self) -> SpkClass { SpkClass::P2tr } - fn keys<'a>(&'a self) -> impl Iterator where K: 'a { - self.tr.keys() + match self { + RgbDeriver::OpretOnly(d) => d.keys().collect::>().into_iter(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.keys().collect::>().into_iter(), + } } fn vars<'a>(&'a self) -> impl Iterator where (): 'a { - self.tr.vars() + match self { + RgbDeriver::OpretOnly(d) => d.vars().collect::>().into_iter(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.vars().collect::>().into_iter(), + } } - fn xpubs(&self) -> impl Iterator { self.tr.xpubs() } - - fn legacy_keyset(&self, _terminal: Terminal) -> IndexMap { - IndexMap::new() + fn xpubs(&self) -> impl Iterator { + match self { + RgbDeriver::OpretOnly(d) => d.xpubs().collect::>().into_iter(), + RgbDeriver::Universal { tr, tweaks: _ } => tr.xpubs().collect::>().into_iter(), + } + } + fn legacy_keyset(&self, terminal: Terminal) -> IndexMap { + match self { + RgbDeriver::OpretOnly(d) => d.legacy_keyset(terminal), + RgbDeriver::Universal { tr, tweaks: _ } => tr.legacy_keyset(terminal), + } } - fn xonly_keyset(&self, terminal: Terminal) -> IndexMap { - self.tr.xonly_keyset(terminal) + match self { + RgbDeriver::OpretOnly(d) => d.xonly_keyset(terminal), + RgbDeriver::Universal { tr, tweaks: _ } => tr.xonly_keyset(terminal), + } } - fn legacy_witness( &self, - _keysigs: HashMap<&KeyOrigin, LegacyKeySig>, + keysigs: HashMap<&KeyOrigin, LegacyKeySig>, ) -> Option<(SigScript, Witness)> { - None + match self { + RgbDeriver::OpretOnly(d) => d.legacy_witness(keysigs), + RgbDeriver::Universal { tr, tweaks: _ } => tr.legacy_witness(keysigs), + } } - fn taproot_witness(&self, keysigs: HashMap<&KeyOrigin, TaprootKeySig>) -> Option { - self.tr.taproot_witness(keysigs) - } -} - -impl DescriptorRgb for TapretKey { - fn seal_close_method(&self) -> CloseMethod { CloseMethod::TapretFirst } - - fn add_tapret_tweak( - &mut self, - terminal: Terminal, - tweak: TapretCommitment, - ) -> Result<(), TapTweakAlreadyAssigned> { - if self.tweaks.contains_key(&terminal) { - return Err(TapTweakAlreadyAssigned(terminal)); + match self { + RgbDeriver::OpretOnly(d) => d.taproot_witness(keysigs), + RgbDeriver::Universal { tr, tweaks: _ } => tr.taproot_witness(keysigs), } - self.tweaks.insert(terminal, tweak); - Ok(()) } } -#[derive(Clone, Eq, PartialEq, Debug, From)] +#[derive(Clone, Display)] +#[display("rgb({deriver},{seals},noise({noise:x}))")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde( - crate = "serde_crate", rename_all = "camelCase", bound( - serialize = "S::Compr: serde::Serialize, S::XOnly: serde::Serialize", - deserialize = "S::Compr: serde::Deserialize<'de>, S::XOnly: serde::Deserialize<'de>" + serialize = "K::Compr: serde::Serialize, K::XOnly: serde::Serialize", + deserialize = "K::Compr: serde::Deserialize<'de>, K::XOnly: serde::Deserialize<'de>" ) ) )] -#[non_exhaustive] -pub enum RgbDescr { - #[from] - Wpkh(Wpkh), - #[from] - TapretKey(TapretKey), +pub struct RgbDescr { + deriver: RgbDeriver, + seals: SealDescr, + noise: Bytes32, + nonce: u64, } -impl Display for RgbDescr -where - S::Legacy: Display, - S::Compr: Display, - S::XOnly: Display, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - RgbDescr::Wpkh(d) => Display::fmt(d, f), - RgbDescr::TapretKey(d) => Display::fmt(d, f), +impl RgbDescr { + pub fn new_unfunded(deriver: impl Into>, noise: impl Into<[u8; 32]>) -> Self { + let deriver = match deriver.into() { + StdDescr::Wpkh(d) => RgbDeriver::OpretOnly(StdDescr::Wpkh(d)), + StdDescr::TrKey(tr) => RgbDeriver::Universal { tr: Tr::KeyOnly(tr), tweaks: empty!() }, + _ => unreachable!(), + }; + Self { + deriver, + seals: empty!(), + noise: noise.into().into(), + nonce: 0, } } -} -impl Derive for RgbDescr { - fn default_keychain(&self) -> Keychain { - match self { - RgbDescr::Wpkh(d) => d.default_keychain(), - RgbDescr::TapretKey(d) => d.default_keychain(), + pub fn key_only_unfunded(internal_key: K, noise: impl Into<[u8; 32]>) -> Self + where K: DeriveSet + DeriveKey { + Self { + deriver: RgbDeriver::Universal { + tr: Tr::KeyOnly(TrKey::from(internal_key)), + tweaks: empty!(), + }, + seals: empty!(), + noise: noise.into().into(), + nonce: 0, } } - fn keychains(&self) -> BTreeSet { - match self { - RgbDescr::Wpkh(d) => d.keychains(), - RgbDescr::TapretKey(d) => d.keychains(), - } + pub fn next_nonce(&mut self) -> u64 { + let nonce = self.nonce; + self.nonce += 1; + nonce } - fn derive(&self, change: impl Into, index: impl Into) -> DerivedScript { - match self { - RgbDescr::Wpkh(d) => d.derive(change, index), - RgbDescr::TapretKey(d) => d.derive(change, index), + pub fn noise(&self) -> Bytes32 { self.noise } + + pub fn seals(&self) -> impl Iterator + use<'_, K> { + self.seals.iter().copied() + } + + pub fn add_seal(&mut self, seal: WTxoSeal) { self.seals.insert(seal); } + + pub fn add_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) { + match &mut self.deriver { + RgbDeriver::OpretOnly(_) => { + panic!("attempting to add tapret tweaks to an opret-only wallet") + } + RgbDeriver::Universal { tr: _, tweaks } => { + tweaks.entry(terminal).or_default().insert(tweak); + } } } } -impl + DeriveCompr + DeriveXOnly> Descriptor for RgbDescr -where Self: Derive -{ - fn class(&self) -> SpkClass { - match self { - RgbDescr::Wpkh(d) => d.class(), - RgbDescr::TapretKey(d) => d.class(), - } +impl Derive for RgbDescr { + fn default_keychain(&self) -> Keychain { self.deriver.default_keychain() } + fn keychains(&self) -> BTreeSet { self.deriver.keychains() } + fn derive( + &self, + keychain: impl Into, + index: impl Into, + ) -> impl Iterator { + self.deriver.derive(keychain, index) } +} +impl + DeriveCompr + DeriveXOnly> Descriptor for RgbDescr { + fn class(&self) -> SpkClass { self.deriver.class() } fn keys<'a>(&'a self) -> impl Iterator where K: 'a { - match self { - RgbDescr::Wpkh(d) => d.keys().collect::>(), - RgbDescr::TapretKey(d) => d.keys().collect::>(), - } - .into_iter() + self.deriver.keys() } - fn vars<'a>(&'a self) -> impl Iterator where (): 'a { - iter::empty() + self.deriver.vars() } - - fn xpubs(&self) -> impl Iterator { - match self { - RgbDescr::Wpkh(d) => d.xpubs().collect::>(), - RgbDescr::TapretKey(d) => d.xpubs().collect::>(), - } - .into_iter() - } - + fn xpubs(&self) -> impl Iterator { self.deriver.xpubs() } fn legacy_keyset(&self, terminal: Terminal) -> IndexMap { - match self { - RgbDescr::Wpkh(d) => d.legacy_keyset(terminal), - RgbDescr::TapretKey(d) => d.legacy_keyset(terminal), - } + self.deriver.legacy_keyset(terminal) } - fn xonly_keyset(&self, terminal: Terminal) -> IndexMap { - match self { - RgbDescr::Wpkh(d) => d.xonly_keyset(terminal), - RgbDescr::TapretKey(d) => d.xonly_keyset(terminal), - } + self.deriver.xonly_keyset(terminal) } - fn legacy_witness( &self, keysigs: HashMap<&KeyOrigin, LegacyKeySig>, ) -> Option<(SigScript, Witness)> { - match self { - RgbDescr::Wpkh(d) => d.legacy_witness(keysigs), - RgbDescr::TapretKey(d) => d.legacy_witness(keysigs), - } + self.deriver.legacy_witness(keysigs) } - fn taproot_witness(&self, keysigs: HashMap<&KeyOrigin, TaprootKeySig>) -> Option { - match self { - RgbDescr::Wpkh(d) => d.taproot_witness(keysigs), - RgbDescr::TapretKey(d) => d.taproot_witness(keysigs), - } - } -} - -impl + DeriveCompr + DeriveXOnly> DescriptorRgb - for RgbDescr -where Self: Derive -{ - fn seal_close_method(&self) -> CloseMethod { - match self { - RgbDescr::Wpkh(_) => CloseMethod::OpretFirst, - RgbDescr::TapretKey(d) => d.seal_close_method(), - } - } - - fn add_tapret_tweak( - &mut self, - terminal: Terminal, - tweak: TapretCommitment, - ) -> Result<(), TapTweakAlreadyAssigned> { - match self { - RgbDescr::Wpkh(_) => panic!("adding tapret tweak to non-taproot descriptor"), - RgbDescr::TapretKey(d) => d.add_tapret_tweak(terminal, tweak), - } - } -} - -impl From for RgbDescr { - fn from(descr: StdDescr) -> Self { - match descr { - StdDescr::Wpkh(wpkh) => RgbDescr::Wpkh(wpkh), - StdDescr::TrKey(tr) => RgbDescr::TapretKey(tr.into()), - _ => todo!(), - } + self.deriver.taproot_witness(keysigs) } } diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 72d32b8..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,206 +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::convert::Infallible; -use std::io; - -use amplify::IoError; -use bpstd::Psbt; -use nonasync::persistence::PersistenceError; -use psrgbt::{CommitError, ConstructionError, EmbedError, TapretKeyError}; -use rgbstd::containers::LoadError; -use rgbstd::interface::{BuilderError, ContractError}; -use rgbstd::persistence::{ - ComposeError, ConsignError, ContractIfaceError, FasciaError, Stock, StockError, StockErrorAll, - StockErrorMem, -}; -use strict_types::encoding::Ident; - -use crate::{validation, TapTweakAlreadyAssigned}; - -#[derive(Debug, Display, Error, From)] -#[display(inner)] -pub enum WalletError { - #[from] - #[from(io::Error)] - File(IoError), - - #[from] - StockLoad(LoadError), - - WalletPersist(PersistenceError), - - StockPersist(PersistenceError), - - #[cfg(feature = "cli")] - #[from] - WalletExec(bpwallet::cli::ExecError), - - #[from] - Builder(BuilderError), - - #[from] - Contract(ContractError), - - Invoicing(String), - - #[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), - - /// 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!() } -} - -impl From<(Stock, WalletError)> for WalletError { - fn from((_, e): (Stock, WalletError)) -> Self { e } -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Display, Error, From)] -pub enum PayError { - #[from] - #[display(inner)] - Composition(CompositionError), - - #[display("{0}")] - Completion(CompletionError, Psbt), -} - -#[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/filters.rs b/src/filters.rs deleted file mode 100644 index 2cdbd44..0000000 --- a/src/filters.rs +++ /dev/null @@ -1,85 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use bp::Bp; -use bpwallet::{Layer2, Wallet}; -use rgbstd::interface::AssignmentsFilter; - -use crate::{DescriptorRgb, XChain, XOutpoint, XWitnessId}; - -pub struct WalletOutpointsFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a Wallet); - -// We need manual derivation to ensure we can be copied and cloned even if descriptor is not -// copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletOutpointsFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletOutpointsFilter<'a, K, D, L2> { - fn clone(&self) -> Self { *self } -} - -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletOutpointsFilter<'a, K, D, L2> -{ - fn should_include(&self, output: impl Into, _: Option) -> bool { - match output.into().into_bp() { - Bp::Bitcoin(outpoint) => self.0.has_outpoint(outpoint), - Bp::Liquid(_) => false, - } - } -} - -pub struct WalletUnspentFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a Wallet); - -// We need manual derivation to ensure we can be copied and cloned even if descriptor is not -// copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletUnspentFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletUnspentFilter<'a, K, D, L2> { - fn clone(&self) -> Self { *self } -} - -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletUnspentFilter<'a, K, D, L2> -{ - fn should_include(&self, output: impl Into, _: Option) -> bool { - match output.into().into_bp() { - Bp::Bitcoin(outpoint) => self.0.is_unspent(outpoint), - Bp::Liquid(_) => false, - } - } -} - -pub struct WalletWitnessFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a Wallet); - -// We need manual derivation to ensure we can be copied and cloned even if descriptor is not -// copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletWitnessFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletWitnessFilter<'a, K, D, L2> { - fn clone(&self) -> Self { *self } -} - -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletWitnessFilter<'a, K, D, L2> -{ - fn should_include(&self, _: impl Into, witness_id: Option) -> bool { - self.0 - .history() - .any(|row| !row.our_inputs.is_empty() && witness_id == Some(XChain::Bitcoin(row.txid))) - } -} diff --git a/src/indexers/any.rs b/src/indexers/any.rs deleted file mode 100644 index 08b3b7f..0000000 --- a/src/indexers/any.rs +++ /dev/null @@ -1,153 +0,0 @@ -// 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::collections::HashMap; - -use bp::Tx; -use bpstd::Network; -use rgbstd::containers::Consignment; -use rgbstd::validation::{ResolveWitness, WitnessResolverError}; -use rgbstd::XWitnessId; - -use crate::vm::{WitnessOrd, XWitnessTx}; -use crate::{Txid, XChain}; - -// We need to repeat methods of `WitnessResolve` trait here to avoid making -// wrappers around resolver types. TODO: Use wrappers instead -pub trait RgbResolver: Send { - fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String>; - fn resolve_pub_witness(&self, txid: Txid) -> Result, String>; - fn resolve_pub_witness_ord(&self, txid: Txid) -> Result; -} - -/// Type that contains any of the [`Resolver`] types defined by the library -#[derive(From)] -#[non_exhaustive] -pub struct AnyResolver { - inner: Box, - terminal_txes: HashMap, -} - -impl AnyResolver { - #[cfg(feature = "electrum_blocking")] - pub fn electrum_blocking(url: &str, config: Option) -> Result { - Ok(AnyResolver { - inner: Box::new( - electrum::Client::from_config(url, config.unwrap_or_default()) - .map_err(|e| e.to_string())?, - ), - terminal_txes: Default::default(), - }) - } - - #[cfg(feature = "esplora_blocking")] - pub fn esplora_blocking(url: &str, config: Option) -> Result { - Ok(AnyResolver { - inner: Box::new( - esplora::BlockingClient::from_config(url, config.unwrap_or_default()) - .map_err(|e| e.to_string())?, - ), - terminal_txes: Default::default(), - }) - } - - #[cfg(feature = "mempool_blocking")] - pub fn mempool_blocking(url: &str, config: Option) -> Result { - Ok(AnyResolver { - inner: Box::new(super::mempool_blocking::MemPoolClient::new( - url, - config.unwrap_or_default(), - )?), - terminal_txes: Default::default(), - }) - } - pub fn check(&self, network: Network) -> Result<(), String> { - let expected_block_hash = match network { - Network::Mainnet => "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", - Network::Testnet3 => "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", - Network::Testnet4 => "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043", - 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().cloned())) - .filter_map(|tx| match tx { - XChain::Bitcoin(tx) => Some(tx), - XChain::Liquid(_) | XChain::Other(_) => None, - }) - .map(|tx| (tx.txid(), tx)), - ); - } -} - -impl ResolveWitness for AnyResolver { - fn resolve_pub_witness( - &self, - witness_id: XWitnessId, - ) -> Result { - 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_err(|e| WitnessResolverError::Other(witness_id, e)) - .and_then(|r| r.ok_or(WitnessResolverError::Unknown(witness_id))) - .map(XChain::Bitcoin) - } - - fn resolve_pub_witness_ord( - &self, - witness_id: XWitnessId, - ) -> Result { - 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 self.terminal_txes.contains_key(&txid) { - return Ok(WitnessOrd::Tentative); - } - - self.inner - .resolve_pub_witness_ord(txid) - .map_err(|e| WitnessResolverError::Other(witness_id, e)) - } -} diff --git a/src/indexers/electrum_blocking.rs b/src/indexers/electrum_blocking.rs deleted file mode 100644 index 98aad72..0000000 --- a/src/indexers/electrum_blocking.rs +++ /dev/null @@ -1,148 +0,0 @@ -// 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 std::num::NonZeroU32; - -use bp::ConsensusDecode; -use bpstd::{Network, Tx, Txid}; -use electrum::{Client, ElectrumApi, Param}; -pub use electrum::{Config, ConfigBuilder, Error, Socks5Config}; -use rgbstd::vm::WitnessPos; - -use super::RgbResolver; -use crate::vm::WitnessOrd; - -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::Testnet4 => "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e", - 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_pub_witness_ord(&self, txid: Txid) -> Result { - // 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 = match self.raw_call("blockchain.transaction.get", vec![ - Param::String(txid.to_string()), - Param::Bool(true), - ]) { - Err(e) - if e.to_string() - .contains("No such mempool or blockchain transaction") => - { - return Ok(WitnessOrd::Archived); - } - Err(e) => return Err(e.to_string()), - Ok(v) => v, - }; - let forward = iter::from_fn(|| self.block_headers_pop().ok().flatten()).count() as isize; - - let Some(confirmations) = tx_details.get("confirmations") else { - return Ok(WitnessOrd::Tentative); - }; - let confirmations = check!(confirmations - .as_u64() - .and_then(|x| u32::try_from(x).ok()) - .ok_or(Error::InvalidResponse(tx_details.clone()))); - if confirmations == 0 { - return Ok(WitnessOrd::Tentative); - } - 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 height = - check!(NonZeroU32::new(tx_height).ok_or(Error::InvalidResponse(tx_details.clone()))); - let pos = check!(WitnessPos::bitcoin(height, block_time) - .ok_or(Error::InvalidResponse(tx_details.clone()))); - - Ok(WitnessOrd::Mined(pos)) - } - - fn resolve_pub_witness(&self, txid: Txid) -> Result, String> { - self.transaction_get_raw(&txid) - .map_err(|e| e.to_string()) - .and_then(|raw_tx| { - Tx::consensus_deserialize(raw_tx) - .map_err(|e| format!("cannot deserialize raw TX - {e}")) - }) - .map(Some) - .or_else(|e| { - if e.contains("No such mempool or blockchain transaction") { - Ok(None) - } else { - Err(e) - } - }) - } -} diff --git a/src/indexers/esplora_blocking.rs b/src/indexers/esplora_blocking.rs deleted file mode 100644 index e72e2fa..0000000 --- a/src/indexers/esplora_blocking.rs +++ /dev/null @@ -1,69 +0,0 @@ -// RGB smart contracts for Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::num::NonZeroU32; - -use bp::Tx; -use bpstd::{Network, Txid}; -use esplora::BlockingClient; -pub use esplora::{Builder, Config, Error}; -use rgbstd::vm::WitnessPos; - -use super::RgbResolver; -use crate::vm::WitnessOrd; - -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(()) - } - - fn resolve_pub_witness_ord(&self, txid: Txid) -> Result { - if self.tx(&txid)?.is_none() { - return Ok(WitnessOrd::Archived); - } - 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)) => { - let height = NonZeroU32::new(h).ok_or(Error::InvalidServerData)?; - WitnessOrd::Mined( - WitnessPos::bitcoin(height, t as i64).ok_or(Error::InvalidServerData)?, - ) - } - None => WitnessOrd::Tentative, - }; - Ok(ord) - } - - fn resolve_pub_witness(&self, txid: Txid) -> Result, String> { - self.tx(&txid).or_else(|e| match e { - Error::TransactionNotFound(_) => Ok(None), - e => Err(e.to_string()), - }) - } -} diff --git a/src/indexers/mempool_blocking.rs b/src/indexers/mempool_blocking.rs deleted file mode 100644 index 6dc0017..0000000 --- a/src/indexers/mempool_blocking.rs +++ /dev/null @@ -1,131 +0,0 @@ -// RGB smart contracts for Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use bp::Tx; -use bpstd::{Network, Txid}; -use esplora::{BlockingClient, Config, Error}; -use rgbstd::vm::WitnessOrd; - -use super::RgbResolver; - -#[derive(Clone, Debug)] -/// Represents a client for interacting with a mempool. -// Currently, this client is wrapping an `esplora::BlockingClient` instance. -// If the mempool service changes in the future and is not compatible with -// esplora::BlockingClient, Only the internal implementation needs to be -// modified -pub struct MemPoolClient { - inner: BlockingClient, -} - -impl MemPoolClient { - /// Creates a new `MemPoolClient` instance. - /// - /// # Arguments - /// - /// * `url` - The URL of the mempool server. - /// * `config` - The configuration for the mempool client. - /// - /// # Returns - /// - /// Returns a `Result` containing the `MemPoolClient` instance if - /// successful, or an `Error` if an error occurred. - #[allow(clippy::result_large_err)] - pub fn new(url: &str, config: Config) -> Result { - let inner = BlockingClient::from_config(url, config)?; - Ok(MemPoolClient { inner }) - } -} - -impl RgbResolver for MemPoolClient { - fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String> { - self.inner.check(network, expected_block_hash) - } - - fn resolve_pub_witness_ord(&self, txid: Txid) -> Result { - self.inner.resolve_pub_witness_ord(txid) - } - - fn resolve_pub_witness(&self, txid: Txid) -> Result, String> { - self.inner.resolve_pub_witness(txid) - } -} - -#[cfg(test)] -mod test { - use esplora::Config; - #[test] - fn test_mempool_client_mainnet_tx() { - let client = super::MemPoolClient::new("https://mempool.space/api", Config::default()) - .expect("Failed to create client"); - let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" - .parse() - .unwrap(); - let status = client.inner.tx_status(&txid).unwrap(); - assert_eq!(status.block_height, Some(0)); - assert_eq!(status.block_time, Some(1231006505)); - } - - #[test] - fn test_mempool_client_testnet_tx() { - let client = - super::MemPoolClient::new("https://mempool.space/testnet/api", Config::default()) - .expect("Failed to create client"); - - let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" - .parse() - .unwrap(); - let status = client.inner.tx_status(&txid).unwrap(); - assert_eq!(status.block_height, Some(0)); - assert_eq!(status.block_time, Some(1296688602)); - } - - #[test] - fn test_mempool_client_testnet4_tx() { - let client = - super::MemPoolClient::new("https://mempool.space/testnet4/api", Config::default()) - .expect("Failed to create client"); - let txid = "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e" - .parse() - .unwrap(); - let status = client.inner.tx_status(&txid).unwrap(); - assert_eq!(status.block_height, Some(0)); - assert_eq!(status.block_time, Some(1714777860)); - } - - #[test] - fn test_mempool_client_testnet4_tx_detail() { - let client = - super::MemPoolClient::new("https://mempool.space/testnet4/api", Config::default()) - .expect("Failed to create client"); - let txid = "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e" - .parse() - .unwrap(); - let tx = client - .inner - .tx(&txid) - .expect("Failed to get tx") - .expect("Tx not found"); - assert!(tx.inputs.len() > 0); - assert!(tx.outputs.len() > 0); - assert_eq!(tx.outputs[0].value, 5_000_000_000); - } -} diff --git a/src/indexers/mod.rs b/src/indexers/mod.rs deleted file mode 100644 index 4566320..0000000 --- a/src/indexers/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -// RGB smart contracts for Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod any; -#[cfg(feature = "esplora_blocking")] -pub mod esplora_blocking; -#[cfg(feature = "electrum_blocking")] -pub mod electrum_blocking; - -#[cfg(feature = "mempool_blocking")] -pub mod mempool_blocking; - -pub use any::{AnyResolver, RgbResolver}; diff --git a/src/lib.rs b/src/lib.rs index d37fcf1..9fceccb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,61 +1,44 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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. +// 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. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; #[macro_use] extern crate amplify; #[cfg(feature = "serde")] #[macro_use] -extern crate serde_crate as serde; +extern crate serde; -mod descriptor; -mod indexers; -mod filters; -pub mod pay; -mod errors; +pub mod descriptor; mod wallet; +mod coinselect; +mod runtime; -pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapTweakAlreadyAssigned, TapretKey}; -pub use errors::{CompletionError, CompositionError, PayError, WalletError}; -pub use pay::{TransferParams, WalletProvider}; -pub use rgbstd::*; -pub mod resolvers { - #[cfg(any(feature = "electrum_blocking", feature = "esplora_blocking"))] - pub use super::indexers::*; - pub use super::indexers::{AnyResolver, RgbResolver}; - use super::validation::{ResolveWitness, WitnessResolverError}; - use super::vm::{WitnessOrd, XWitnessTx}; - use super::XWitnessId; - - pub struct ContractIssueResolver; - impl ResolveWitness for ContractIssueResolver { - fn resolve_pub_witness(&self, _: XWitnessId) -> Result { - panic!("contract issue resolver must not be used for an already-existing contracts") - } - fn resolve_pub_witness_ord( - &self, - _: XWitnessId, - ) -> Result { - panic!("contract issue resolver must not be used for an already-existing contracts") - } - } -} -pub use filters::{WalletOutpointsFilter, WalletUnspentFilter, WalletWitnessFilter}; +pub use coinselect::CoinselectStrategy; +#[cfg(feature = "fs")] +pub use runtime::file::{ConsignmentStream, RgbDirRuntime, Transfer}; +pub use runtime::{PayError, RgbRuntime, TransferError}; pub use wallet::RgbWallet; diff --git a/src/pay.rs b/src/pay.rs deleted file mode 100644 index 43fc017..0000000 --- a/src/pay.rs +++ /dev/null @@ -1,374 +0,0 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::collections::{BTreeMap, BTreeSet}; -use std::marker::PhantomData; - -use bp::dbc::tapret::TapretProof; -use bp::seals::txout::ExplicitSeal; -use bp::{Outpoint, Sats, ScriptPubkey, Vout}; -use bpstd::{psbt, Address}; -use bpwallet::{Layer2, Layer2Tx, NoLayer2, TxRow, Wallet, WalletDescr}; -use psrgbt::{ - Beneficiary as BpBeneficiary, Psbt, PsbtConstructor, PsbtMeta, RgbPsbt, TapretKeyError, - TxParams, -}; -use rgbstd::containers::Transfer; -use rgbstd::interface::AssignmentsFilter; -use rgbstd::invoice::{Amount, Beneficiary, InvoiceState, RgbInvoice}; -use rgbstd::persistence::{IndexProvider, StashProvider, StateProvider, Stock}; -use rgbstd::validation::ResolveWitness; -use rgbstd::{ContractId, DataState, XChain, XOutpoint}; - -use crate::invoice::NonFungible; -use crate::validation::WitnessResolverError; -use crate::vm::{WitnessOrd, XWitnessTx}; -use crate::{ - CompletionError, CompositionError, DescriptorRgb, PayError, RgbKeychain, Txid, - WalletOutpointsFilter, WalletUnspentFilter, WalletWitnessFilter, XWitnessId, -}; - -#[derive(Clone, PartialEq, Debug)] -pub struct TransferParams { - pub tx: TxParams, - pub min_amount: Sats, -} - -impl TransferParams { - pub fn with(fee: Sats, min_amount: Sats) -> Self { - TransferParams { - tx: TxParams::with(fee), - min_amount, - } - } -} - -struct ContractOutpointsFilter< - 'stock, - 'wallet, - W: WalletProvider + ?Sized, - K, - S: StashProvider, - H: StateProvider, - P: IndexProvider, - L2: Layer2 = NoLayer2, -> where W::Descr: DescriptorRgb -{ - contract_id: ContractId, - stock: &'stock Stock, - wallet: &'wallet W, - _key_phantom: PhantomData, - _layer2_phantom: PhantomData, -} - -impl< - 'stock, - 'wallet, - W: WalletProvider + ?Sized, - K, - S: StashProvider, - H: StateProvider, - P: IndexProvider, - L2: Layer2, - > AssignmentsFilter for ContractOutpointsFilter<'stock, 'wallet, W, K, S, H, P, L2> -where W::Descr: DescriptorRgb -{ - fn should_include(&self, output: impl Into, id: Option) -> bool { - let output = output.into(); - if !self.wallet.filter_unspent().should_include(output, id) { - 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 -{ - fn filter_outpoints(&self) -> impl AssignmentsFilter + Clone; - fn filter_unspent(&self) -> impl AssignmentsFilter + Clone; - fn filter_witnesses(&self) -> impl AssignmentsFilter + Clone; - fn with_descriptor_mut( - &mut self, - f: impl FnOnce(&mut WalletDescr) -> R, - ) -> R; - fn utxos(&self) -> impl Iterator; - fn txos(&self) -> impl Iterator; - fn txids(&self) -> impl Iterator; - fn history(&self) -> impl Iterator> + '_; - - // TODO: Add method `color` to add RGB information to an already existing PSBT - - #[allow(clippy::result_large_err)] - fn pay( - &mut self, - stock: &mut Stock, - invoice: &RgbInvoice, - params: TransferParams, - ) -> Result<(Psbt, PsbtMeta, Transfer), PayError> { - let (mut psbt, meta) = self.construct_psbt_rgb(stock, invoice, params)?; - // ... here we pass PSBT around signers, if necessary - let transfer = match self.transfer(stock, invoice, &mut psbt) { - Ok(transfer) => transfer, - Err(e) => return Err(PayError::Completion(e, psbt)), - }; - Ok((psbt, meta, transfer)) - } - - #[allow(clippy::result_large_err)] - fn construct_psbt_rgb( - &mut self, - stock: &Stock, - invoice: &RgbInvoice, - 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 = stock.iface(iface_name.clone()).map_err(|e| e.to_string())?; - let operation = invoice - .operation - .as_ref() - .or(iface.default_operation.as_ref()) - .ok_or(CompositionError::NoOperation)?; - - let assignment_name = invoice - .assignment - .as_ref() - .or_else(|| { - iface - .transitions - .get(operation) - .and_then(|t| t.default_assignment.as_ref()) - }) - .cloned() - .ok_or(CompositionError::NoAssignment)?; - - let filter = ContractOutpointsFilter { - contract_id, - stock, - wallet: self, - _key_phantom: PhantomData, - _layer2_phantom: PhantomData, - }; - let contract = stock - .contract_iface(contract_id, iface_name) - .map_err(|e| e.to_string())?; - let prev_outputs = match invoice.owned_state { - InvoiceState::Amount(amount) => { - let state: BTreeMap<_, Vec> = contract - .fungible(assignment_name, &filter)? - .fold(bmap![], |mut set, a| { - set.entry(a.seal).or_default().push(a.state); - set - }); - let mut state: Vec<_> = state - .into_iter() - .map(|(seal, vals)| (vals.iter().copied().sum::(), seal, vals)) - .collect(); - state.sort_by_key(|(sum, _, _)| *sum); - let mut sum = Amount::ZERO; - state - .iter() - .rev() - .take_while(|(val, _, _)| { - if sum >= amount { - false - } else { - sum += *val; - true - } - }) - .map(|(_, seal, _)| *seal) - .collect::>() - } - InvoiceState::Data(NonFungible::RGB21(allocation)) => { - let data_state = DataState::from(allocation); - contract - .data(assignment_name, &filter)? - .filter(|x| x.state == data_state) - .map(|x| x.seal) - .collect::>() - } - _ => return Err(CompositionError::Unsupported), - }; - let beneficiaries = match invoice.beneficiary.into_inner() { - Beneficiary::BlindedSeal(_) => vec![], - Beneficiary::WitnessVout(pay2vout) => { - vec![BpBeneficiary::new( - Address::new(pay2vout.address, invoice.address_network()), - params.min_amount, - )] - } - }; - let prev_outpoints = prev_outputs - .iter() - // TODO: Support liquid - .map(|o| o.as_reduced_unsafe()) - .map(|o| Outpoint::new(o.txid, o.vout)); - params.tx.change_keychain = RgbKeychain::for_method(method).into(); - let (mut psbt, mut meta) = - self.construct_psbt(prev_outpoints, &beneficiaries, params.tx)?; - - let beneficiary_script = - if let Beneficiary::WitnessVout(pay2vout) = invoice.beneficiary.into_inner() { - Some(pay2vout.address.script_pubkey()) - } else { - None - }; - psbt.outputs_mut() - .find(|o| o.script.is_p2tr() && Some(&o.script) != beneficiary_script.as_ref()) - .map(|o| o.set_tapret_host().expect("just created")); - // TODO: Add descriptor id to the tapret host data - - let change_script = meta - .change_vout - .and_then(|vout| psbt.output(vout.to_usize())) - .map(|output| output.script.clone()); - psbt.sort_outputs_by(|output| !output.is_tapret_host()) - .expect("PSBT must be modifiable at this stage"); - if let Some(change_script) = change_script { - for output in psbt.outputs() { - if output.script == change_script { - meta.change_vout = Some(output.vout()); - break; - } - } - } - - let beneficiary_vout = match invoice.beneficiary.into_inner() { - Beneficiary::WitnessVout(pay2vout) => { - let s = pay2vout.address.script_pubkey(); - let vout = psbt - .outputs() - .find(|output| output.script == s) - .map(psbt::Output::vout) - .expect("PSBT without beneficiary address"); - debug_assert_ne!(Some(vout), meta.change_vout); - Some(vout) - } - Beneficiary::BlindedSeal(_) => None, - }; - 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() { - let output = psbt.construct_output_expect(ScriptPubkey::op_return(&[]), Sats::ZERO); - output.set_opret_host().expect("just created"); - } - - psbt.complete_construction(); - psbt.rgb_embed(batch)?; - Ok((psbt, meta)) - } - - #[allow(clippy::result_large_err)] - fn transfer( - &mut self, - stock: &mut Stock, - invoice: &RgbInvoice, - psbt: &mut Psbt, - ) -> Result { - let contract_id = invoice.contract.ok_or(CompletionError::NoContract)?; - - let fascia = psbt.rgb_commit()?; - if fascia.anchor.has_tapret() { - let output = psbt - .dbc_output::() - .ok_or(TapretKeyError::NotTaprootOutput)?; - let terminal = output - .terminal_derivation() - .ok_or(CompletionError::InconclusiveDerivation)?; - let tapret_commitment = output.tapret_commitment()?; - self.with_descriptor_mut(|descr| { - descr.with_descriptor_mut(|d| d.add_tapret_tweak(terminal, tapret_commitment)) - })?; - } - - let witness_txid = psbt.txid(); - let (beneficiary1, beneficiary2) = match invoice.beneficiary.into_inner() { - Beneficiary::WitnessVout(pay2vout) => { - let s = pay2vout.address.script_pubkey(); - let vout = psbt - .outputs() - .position(|output| output.script == s) - .ok_or(CompletionError::NoBeneficiaryOutput)?; - let vout = Vout::from_u32(vout as u32); - let seal = XChain::Bitcoin(ExplicitSeal::new( - pay2vout.method, - Outpoint::new(witness_txid, vout), - )); - (None, vec![seal]) - } - Beneficiary::BlindedSeal(seal) => (Some(XChain::Bitcoin(seal)), vec![]), - }; - - struct FasciaResolver { - witness_id: XWitnessId, - } - impl ResolveWitness for FasciaResolver { - fn resolve_pub_witness( - &self, - _: XWitnessId, - ) -> Result { - unreachable!() - } - fn resolve_pub_witness_ord( - &self, - witness_id: XWitnessId, - ) -> Result { - assert_eq!(witness_id, self.witness_id); - Ok(WitnessOrd::Tentative) - } - } - - stock - .consume_fascia(fascia, FasciaResolver { - witness_id: XChain::Bitcoin(witness_txid), - }) - .map_err(|e| e.to_string())?; - let transfer = stock - .transfer(contract_id, beneficiary2, beneficiary1) - .map_err(|e| e.to_string())?; - - Ok(transfer) - } -} - -impl, L2: Layer2> WalletProvider for Wallet { - fn filter_outpoints(&self) -> impl AssignmentsFilter + Clone { WalletOutpointsFilter(self) } - fn filter_unspent(&self) -> impl AssignmentsFilter + Clone { WalletUnspentFilter(self) } - fn filter_witnesses(&self) -> impl AssignmentsFilter + Clone { WalletWitnessFilter(self) } - fn with_descriptor_mut( - &mut self, - f: impl FnOnce(&mut WalletDescr) -> R, - ) -> R { - self.descriptor_mut(f) - } - fn utxos(&self) -> impl Iterator { self.coins().map(|coin| coin.outpoint) } - fn txos(&self) -> impl Iterator { self.txos().map(|txo| txo.outpoint) } - fn txids(&self) -> impl Iterator { self.transactions().keys().copied() } - - fn history(&self) -> impl Iterator> + '_ { self.history() } -} diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..1269cc4 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,206 @@ +// Wallet Library for RGB smart contracts +// +// SPDX-License-Identifier: Apache-2.0 +// +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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::ops::{Deref, DerefMut}; + +use bpstd::psbt::{ConstructionError, DbcPsbtError, TxParams}; +use bpstd::seals::{TxoSeal, WTxoSeal}; +use bpstd::{Psbt, Sats}; +use rgb::invoice::{RgbBeneficiary, RgbInvoice}; +use rgb::popls::bp::{ + Barrow, BundleError, FulfillError, IncludeError, OpRequestSet, PaymentScript, PrefabBundle, +}; +use rgb::{AuthToken, ContractId, Excavate, Pile, RgbSealDef, Supply}; +use rgpsbt::{RgbPsbt, RgbPsbtCsvError, RgbPsbtPrepareError, ScriptResolver}; + +use crate::wallet::RgbWallet; +use crate::CoinselectStrategy; + +pub struct RgbRuntime, X: Excavate>( + Barrow, +); + +impl, X: Excavate> + From> for RgbRuntime +{ + fn from(barrow: Barrow) -> Self { Self(barrow) } +} + +impl, X: Excavate> Deref + for RgbRuntime +{ + type Target = Barrow; + fn deref(&self) -> &Self::Target { &self.0 } +} +impl, X: Excavate> DerefMut + for RgbRuntime +{ + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl, X: Excavate> + RgbRuntime +{ + /// Pay an invoice producing PSBT ready to be signed. + /// + /// Should not be used in multi-party protocols like coinjoins, when a PSBT may needs to be + /// modified in the number of inputs or outputs. Use `construct_psbt` method for such + /// scenarios. + /// + /// If you need more flexibility in constructing payments (do multiple payments with multiple + /// contracts, use global state etc.) in a single PSBT, please use `pay_custom` APIs and + /// [`PrefabBundleSet`] stead of this simplified API. + pub fn pay_invoice( + &mut self, + invoice: &RgbInvoice, + strategy: CoinselectStrategy, + params: TxParams, + giveaway: Option, + ) -> Result<(Psbt, AuthToken), PayError> { + let request = self.fulfill(invoice, strategy, giveaway)?; + let script = OpRequestSet::with(request.clone()); + let psbt = self.transfer(script, params)?; + let terminal = match invoice.auth { + RgbBeneficiary::Token(auth) => auth, + RgbBeneficiary::WitnessOut(wout) => request + .resolve_seal(wout, psbt.script_resolver()) + .expect("witness out must be present in the PSBT") + .auth_token(), + }; + Ok((psbt, terminal)) + } + + /// Convert invoice into a payment script. + pub fn script( + &mut self, + invoice: &RgbInvoice, + strategy: CoinselectStrategy, + giveaway: Option, + ) -> Result { + let request = self.fulfill(invoice, strategy, giveaway)?; + Ok(OpRequestSet::with(request)) + } + + /// Construct transfer, consisting of PSBT and a consignment stream + // TODO: Return a dedicated Transfer object which can stream a consignment + pub fn transfer( + &mut self, + script: PaymentScript, + params: TxParams, + ) -> Result { + let (psbt, bundle) = self.exec(script, params)?; + let psbt = self.complete(psbt, &bundle)?; + Ok(psbt) + } + + /// Execute payment script creating PSBT and prefabricated operation bundle. + /// + /// The returned PSBT contain only anonymous client-side validation information and is + /// modifiable, thus it can be forwarded to other payjoin participants. + // TODO: PSBT is not modifiable since it commits to Vouts in the bundle! + pub fn exec( + &mut self, + script: PaymentScript, + params: TxParams, + ) -> Result<(Psbt, PrefabBundle), TransferError> { + let (mut psbt, meta) = self.0.wallet.compose_psbt(&script, params)?; + + // From this moment transaction becomes unmodifiable + let request = psbt.rgb_resolve(script, meta.change_vout)?; + let bundle = self.bundle(request, meta.change_vout)?; + + psbt.rgb_fill_csv(&bundle)?; + + Ok((psbt, bundle)) + } + + /// Completes PSBT and includes prefabricated bundle into the contract stockpile. + pub fn complete( + &mut self, + mut psbt: Psbt, + bundle: &PrefabBundle, + ) -> Result { + let (mpc, dbc) = psbt.dbc_commit()?; + let tx = psbt.to_unsigned_tx(); + + let prevouts = psbt + .inputs() + .map(|inp| inp.previous_outpoint) + .collect::>(); + self.include(bundle, &tx.into(), mpc, dbc, &prevouts)?; + + Ok(psbt) + } +} + +#[derive(Clone, Debug, Display, Error, From)] +#[display(inner)] +pub enum PayError { + #[from] + Fulfill(FulfillError), + #[from] + Transfer(TransferError), +} + +#[derive(Clone, Debug, Display, Error, From)] +#[display(inner)] +pub enum TransferError { + #[from] + PsbtConstruct(ConstructionError), + + #[from] + PsbtRgbCsv(RgbPsbtCsvError), + + #[from] + PsbtDbc(DbcPsbtError), + + #[from] + PsbtPrepare(RgbPsbtPrepareError), + + #[from] + Bundle(BundleError), + + #[from] + Include(IncludeError), +} + +#[cfg(feature = "fs")] +pub mod file { + use std::io; + + use rgb::{DirExcavator, FilePile, FileSupply}; + + use super::*; + + pub type RgbDirRuntime = RgbRuntime, DirExcavator>; + + pub trait ConsignmentStream { + fn write(self, writer: impl io::Write) -> io::Result<()>; + } + + pub struct Transfer { + pub psbt: Psbt, + pub consignment: C, + } +} diff --git a/src/wallet.rs b/src/wallet.rs index 172c82b..2150461 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,163 +1,134 @@ -// RGB wallet library for smart contracts on Bitcoin & Lightning network +// Wallet Library for RGB smart contracts // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2023 by -// Dr Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems (InDCS), Switzerland. +// Copyright (C) 2025 RGB Consortium, Switzerland. +// Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are 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 +// 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 +// 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. +// 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::marker::PhantomData; -#[cfg(feature = "fs")] -use std::path::PathBuf; +use std::convert::Infallible; -use bpstd::XpubDerivable; -#[cfg(feature = "fs")] -use bpwallet::fs::FsTextStore; -#[cfg(feature = "fs")] -use bpwallet::Wallet; -use bpwallet::{Layer2, NoLayer2}; -#[cfg(not(target_arch = "wasm32"))] -use nonasync::persistence::PersistenceProvider; -use psrgbt::{Psbt, PsbtMeta}; -use rgbstd::containers::Transfer; -use rgbstd::interface::{ContractOp, IfaceRef}; -#[cfg(feature = "fs")] -use rgbstd::persistence::fs::FsBinStore; -use rgbstd::persistence::{ - ContractIfaceError, IndexProvider, MemIndex, MemStash, MemState, StashProvider, StateProvider, - Stock, StockError, -}; +use amplify::Bytes32; +use bpstd::psbt::{Beneficiary, ConstructionError, PsbtConstructor, PsbtMeta, TxParams}; +use bpstd::seals::WTxoSeal; +use bpstd::{Address, Keychain, Network, Outpoint, Psbt, XpubDerivable}; +use bpwallet::{Layer2Empty, NoLayer2, Wallet, WalletCache, WalletData, WalletDescr}; +use nonasync::persistence::{PersistenceError, PersistenceProvider}; +use rgb::popls::bp::{PaymentScript, WalletProvider}; +use rgb::{AuthToken, EitherSeal, RgbSealDef}; -use super::{ - CompletionError, CompositionError, ContractId, DescriptorRgb, PayError, TransferParams, - WalletError, WalletProvider, -}; -use crate::invoice::RgbInvoice; +use crate::descriptor::RgbDescr; -#[derive(Getters)] -pub struct RgbWallet< - W: WalletProvider, - K = XpubDerivable, - S: StashProvider = MemStash, - H: StateProvider = MemState, - P: IndexProvider = MemIndex, - L2: Layer2 = NoLayer2, -> where W::Descr: DescriptorRgb -{ - stock: Stock, - wallet: W, - #[getter(skip)] - _key_phantom: PhantomData, - #[getter(skip)] - _layer2_phantom: PhantomData, -} +// TODO: Use layer 2 supporting Lightning +#[derive(Wrapper, WrapperMut, From)] +#[wrapper(Deref)] +#[wrapper_mut(DerefMut)] +pub struct RgbWallet(pub Wallet, NoLayer2>); -#[cfg(feature = "fs")] -impl, S: StashProvider, H: StateProvider, P: IndexProvider, L2: Layer2> - RgbWallet, K, S, H, P, L2> -{ - #[allow(clippy::result_large_err)] - pub fn load( - stock_path: PathBuf, - wallet_path: PathBuf, - autosave: bool, - ) -> Result - where - D: serde::Serialize + for<'de> serde::Deserialize<'de>, - L2::Descr: serde::Serialize + for<'de> serde::Deserialize<'de>, - L2::Data: serde::Serialize + for<'de> serde::Deserialize<'de>, - L2::Cache: serde::Serialize + for<'de> serde::Deserialize<'de>, - FsBinStore: PersistenceProvider, - FsBinStore: PersistenceProvider, - FsBinStore: PersistenceProvider

, - FsTextStore: PersistenceProvider, - { - use nonasync::persistence::PersistenceError; - let provider = FsBinStore::new(stock_path) - .map_err(|e| WalletError::StockPersist(PersistenceError::with(e)))?; - let stock = Stock::load(provider, autosave).map_err(WalletError::StockPersist)?; - let provider = FsTextStore::new(wallet_path) - .map_err(|e| WalletError::WalletPersist(PersistenceError::with(e)))?; - let wallet = Wallet::load(provider, autosave).map_err(WalletError::WalletPersist)?; - Ok(Self { - wallet, - stock, - _key_phantom: PhantomData, - _layer2_phantom: PhantomData, - }) - } -} +impl WalletProvider for RgbWallet { + fn noise_seed(&self) -> Bytes32 { self.noise() } -impl< - K, - W: WalletProvider, - S: StashProvider, - H: StateProvider, - P: IndexProvider, - L2: Layer2, - > RgbWallet -where W::Descr: DescriptorRgb -{ - pub fn new(stock: Stock, wallet: W) -> Self { - Self { - stock, - wallet, - _key_phantom: PhantomData, - _layer2_phantom: PhantomData, - } - } + fn has_utxo(&self, outpoint: Outpoint) -> bool { self.0.utxo(outpoint).is_some() } - pub fn stock_mut(&mut self) -> &mut Stock { &mut self.stock } + fn utxos(&self) -> impl Iterator { self.0.utxos().map(|utxo| utxo.outpoint) } - pub fn wallet_mut(&mut self) -> &mut W { &mut self.wallet } + fn register_seal(&mut self, seal: WTxoSeal) { + let _ = self.0.with_descriptor(|d| { + d.add_seal(seal); + Ok::<_, Infallible>(()) + }); + } - pub fn history( + fn resolve_seals( &self, - contract_id: ContractId, - iface: impl Into, - ) -> Result, StockError> { - let contract = self.stock.contract_iface(contract_id, iface.into())?; - let wallet = &self.wallet; - Ok(contract.history(wallet.filter_outpoints(), wallet.filter_witnesses())) + seals: impl Iterator, + ) -> impl Iterator { + seals.flat_map(|auth| { + self.0 + .descriptor() + .seals() + .filter(move |seal| seal.auth_token() == auth) + }) } - #[allow(clippy::result_large_err)] - pub fn pay( - &mut self, - invoice: &RgbInvoice, - params: TransferParams, - ) -> Result<(Psbt, PsbtMeta, Transfer), PayError> { - self.wallet.pay(&mut self.stock, invoice, params) + fn next_address(&mut self) -> Address { self.0.next_address(Keychain::OUTER, true) } + + fn next_nonce(&mut self) -> u64 { + let res = self + .0 + .with_descriptor(|d| Ok::<_, Infallible>(d.next_nonce())); + unsafe { res.unwrap_unchecked() } } +} - #[allow(clippy::result_large_err)] - pub fn construct_psbt( - &mut self, - invoice: &RgbInvoice, - params: TransferParams, - ) -> Result<(Psbt, PsbtMeta), CompositionError> { - self.wallet.construct_psbt_rgb(&self.stock, invoice, params) +impl RgbWallet { + pub fn create

( + provider: P, + descr: RgbDescr, + network: Network, + autosave: bool, + ) -> Result + where + P: Clone + + PersistenceProvider, Layer2Empty>> + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider + + 'static, + { + let mut wallet = Wallet::new_layer1(descr, network); + wallet.make_persistent(provider, autosave)?; + Ok(Self(wallet)) + } + + pub fn load

(provider: P, autosave: bool) -> Result + where P: Clone + + PersistenceProvider, Layer2Empty>> + + PersistenceProvider> + + PersistenceProvider> + + PersistenceProvider + + 'static { + Wallet::load(provider, autosave).map(RgbWallet) } - #[allow(clippy::result_large_err)] - pub fn transfer( + pub fn compose_psbt( &mut self, - invoice: &RgbInvoice, - psbt: &mut Psbt, - ) -> Result { - self.wallet.transfer(&mut self.stock, invoice, psbt) + bundle: &PaymentScript, + params: TxParams, + ) -> Result<(Psbt, PsbtMeta), ConstructionError> { + let closes = bundle + .iter() + .flat_map(|params| ¶ms.using) + .map(|used| used.outpoint); + let network = self.network(); + let beneficiaries = bundle + .iter() + .flat_map(|params| ¶ms.owned) + .filter_map(|assignment| match &assignment.state.seal { + EitherSeal::Alt(seal) => seal.as_ref(), + EitherSeal::Token(_) => None, + }) + .map(|seal| { + let address = Address::with(&seal.wout.script_pubkey(), network) + .expect("script pubkey which is not representable as an address"); + Beneficiary::new(address, seal.sats) + }); + self.construct_psbt(closes, beneficiaries, params) } }