diff --git a/Cargo.lock b/Cargo.lock index b75a70e..c10100b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -49,7 +49,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -103,15 +103,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "argon2" @@ -121,7 +121,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -164,7 +164,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -200,7 +200,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -242,8 +242,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals 0.3.0", - "bitcoin_hashes 0.14.0", + "bitcoin-internals", + "bitcoin_hashes 0.14.1", ] [[package]] @@ -290,7 +290,7 @@ dependencies = [ "bip39", "bitcoin 0.30.2", "electrum-client", - "getrandom 0.2.16", + "getrandom 0.2.17", "js-sys", "log", "miniscript", @@ -320,9 +320,9 @@ checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bip32" @@ -345,11 +345,11 @@ dependencies = [ [[package]] name = "bip39" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.14.1", "rand 0.8.5", "rand_core 0.6.4", "serde", @@ -373,17 +373,17 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.7" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda569d741b895131a88ee5589a467e73e9c4718e958ac9308e4f7dc44b6945" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", - "bech32 0.11.0", - "bitcoin-internals 0.3.0", + "bech32 0.11.1", + "bitcoin-internals", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes 0.14.0", - "hex-conservative 0.2.1", + "bitcoin_hashes 0.14.1", + "hex-conservative", "hex_lit", "secp256k1 0.29.1", "serde", @@ -397,7 +397,7 @@ checksum = "1d59480a0145d7695f784675d7414830e12e95f0f71010ca41ec93617c5d3171" dependencies = [ "bip32", "bip39", - "bitcoin 0.32.7", + "bitcoin 0.32.8", "hex", "secp256k1 0.30.0", "serde", @@ -406,12 +406,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -423,9 +417,9 @@ dependencies = [ [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-private" @@ -439,7 +433,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-internals", "serde", ] @@ -455,22 +449,12 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" -dependencies = [ - "bitcoin-internals 0.2.0", - "hex-conservative 0.1.2", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.1", + "hex-conservative", "serde", ] @@ -482,23 +466,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] [[package]] name = "bitkitcore" -version = "0.1.46" +version = "0.1.48" dependencies = [ "android_logger", "async-trait", "base64 0.22.1", "bdk", "bip39", - "bitcoin 0.32.7", + "bitcoin 0.32.8", "bitcoin-address-generator", "btleplug", "chrono", @@ -525,7 +509,7 @@ dependencies = [ "serial_test", "tempfile", "test-case", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "trezor-connect-rs", "uniffi", @@ -565,7 +549,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -601,7 +585,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ae4213cc2a8dc663acecac67bbdad05142be4d8ef372b6903abf878b0c690a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bluez-generated", "dbus", "dbus-tokio", @@ -610,7 +594,7 @@ dependencies = [ "log", "serde", "serde-xml-rs", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -626,9 +610,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "borsh-derive", "cfg_aliases", @@ -636,15 +620,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -663,7 +647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a11621cb2c8c024e444734292482b1ad86fb50ded066cf46252e46643c8748" dependencies = [ "async-trait", - "bitflags 2.10.0", + "bitflags 2.11.0", "bluez-async", "dashmap 6.1.0", "dbus", @@ -676,7 +660,7 @@ dependencies = [ "objc2-foundation", "once_cell", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "uuid", @@ -686,9 +670,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecheck" @@ -720,15 +704,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -753,7 +737,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -767,9 +751,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.51" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -793,11 +777,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -820,9 +815,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -830,9 +825,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -841,21 +836,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cobs" @@ -863,7 +858,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -904,6 +899,20 @@ name = "cookie_store" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "time", + "url", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" dependencies = [ "cookie", "document-features", @@ -927,16 +936,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -962,6 +961,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -1030,9 +1038,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1070,7 +1078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1087,7 +1095,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -1186,7 +1194,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -1377,9 +1385,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -1389,9 +1397,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1409,10 +1417,10 @@ dependencies = [ ] [[package]] -name = "fnv" -version = "1.0.7" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -1465,9 +1473,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1493,9 +1501,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1503,15 +1511,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1520,9 +1528,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1539,32 +1547,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1574,7 +1582,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1620,9 +1627,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1631,9 +1638,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1651,11 +1658,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1723,9 +1744,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hashlink" @@ -1762,7 +1792,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd54745cfacb7b97dee45e8fdb91814b62bccddb481debb7de0f9ee6b7bf5b43" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "heed-traits", "heed-types", @@ -1798,15 +1828,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" - -[[package]] -name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -1837,12 +1861,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1883,9 +1906,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1911,13 +1934,13 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.4", + "webpki-roots 1.0.6", ] [[package]] @@ -1938,14 +1961,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1962,9 +1984,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2032,9 +2054,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -2046,9 +2068,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -2065,6 +2087,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -2088,12 +2116,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -2117,15 +2147,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -2142,9 +2172,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" @@ -2183,9 +2213,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -2205,9 +2235,9 @@ dependencies = [ [[package]] name = "lazy-regex" -version = "3.4.2" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -2216,14 +2246,14 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.4.2" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -2232,11 +2262,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libdbus-sys" @@ -2277,7 +2313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ab9f6ea77e20e3129235e62a2e6bd64ed932363df104e864ee65ccffb54a8f" dependencies = [ "bech32 0.9.1", - "bitcoin 0.32.7", + "bitcoin 0.32.8", "lightning-types", ] @@ -2288,15 +2324,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f" dependencies = [ "bech32 0.9.1", - "bitcoin 0.32.7", - "hex-conservative 0.2.1", + "bitcoin 0.32.8", + "hex-conservative", ] [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -2330,8 +2366,8 @@ dependencies = [ "aes", "anyhow", "base64 0.22.1", - "bech32 0.11.0", - "bitcoin 0.32.7", + "bech32 0.11.1", + "bitcoin 0.32.8", "cbc", "email_address", "reqwest", @@ -2352,9 +2388,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loom" @@ -2393,13 +2429,13 @@ dependencies = [ "ed25519-dalek", "flume", "futures-lite", - "getrandom 0.2.16", + "getrandom 0.2.17", "lru", "serde", "serde_bencode", "serde_bytes", "sha1_smol", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2414,9 +2450,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -2453,9 +2489,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2470,9 +2506,9 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -2480,7 +2516,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -2503,7 +2539,7 @@ checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" dependencies = [ "base32", "document-features", - "getrandom 0.2.16", + "getrandom 0.2.17", "httpdate", "js-sys", "once_cell", @@ -2575,7 +2611,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-foundation", ] @@ -2592,7 +2628,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -2600,9 +2636,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -2612,11 +2648,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -2633,29 +2669,29 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -2795,7 +2831,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -2809,9 +2845,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2836,7 +2872,7 @@ dependencies = [ "futures-buffered", "futures-lite", "genawaiter", - "getrandom 0.2.16", + "getrandom 0.2.17", "heed", "log", "lru", @@ -2844,13 +2880,13 @@ dependencies = [ "ntimestamp", "page_size", "reqwest", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-webpki 0.102.8", "self_cell", "serde", "sha1_smol", "simple-dns", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -2885,7 +2921,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2897,7 +2933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2946,23 +2982,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2993,7 +3029,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.109", + "syn 2.0.117", "tempfile", ] @@ -3007,7 +3043,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -3055,7 +3091,7 @@ dependencies = [ "base64 0.22.1", "bytes", "cookie", - "cookie_store", + "cookie_store 0.21.1", "flume", "futures-lite", "futures-util", @@ -3063,7 +3099,7 @@ dependencies = [ "pkarr", "pubky-common 0.5.4", "reqwest", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -3086,7 +3122,7 @@ dependencies = [ "pkarr", "pubky-common 0.6.0", "reqwest", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -3131,7 +3167,7 @@ dependencies = [ "pubky-timestamp", "rand 0.9.2", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3152,7 +3188,7 @@ dependencies = [ "pubky-timestamp", "rand 0.9.2", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", ] @@ -3164,7 +3200,7 @@ checksum = "44aafc63c1eab1905e8a8378d2b085500540c9935b6d028908c8a50a5a246a3d" dependencies = [ "base32", "document-features", - "getrandom 0.2.16", + "getrandom 0.2.17", "httpdate", "js-sys", "once_cell", @@ -3193,9 +3229,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.37", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3203,9 +3239,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", @@ -3213,10 +3249,10 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3238,9 +3274,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3251,6 +3287,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -3297,7 +3339,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", ] [[package]] @@ -3317,7 +3370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3326,18 +3379,24 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -3353,14 +3412,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3370,9 +3429,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3381,9 +3440,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rend" @@ -3396,14 +3455,14 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", "cookie", - "cookie_store", + "cookie_store 0.22.1", "futures-core", "http", "http-body", @@ -3418,7 +3477,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "serde", @@ -3435,7 +3494,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.4", + "webpki-roots 1.0.6", ] [[package]] @@ -3456,7 +3515,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3473,9 +3532,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ "bitvec", "bytecheck", @@ -3491,9 +3550,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ "proc-macro2", "quote", @@ -3516,7 +3575,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -3534,16 +3593,16 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "url", ] [[package]] name = "rust_decimal" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" dependencies = [ "arrayvec", "borsh", @@ -3572,11 +3631,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -3597,35 +3656,35 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3654,9 +3713,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -3671,9 +3730,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salsa20" @@ -3704,9 +3763,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -3749,7 +3808,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -3805,7 +3864,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.14.0", + "bitcoin_hashes 0.14.1", "rand 0.8.5", "secp256k1-sys 0.10.1", "serde", @@ -3817,7 +3876,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ - "bitcoin_hashes 0.14.0", + "bitcoin_hashes 0.14.1", "rand 0.8.5", "secp256k1-sys 0.10.1", ] @@ -3842,25 +3901,12 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", + "bitflags 2.11.0", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3868,9 +3914,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3921,7 +3967,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "xml", ] @@ -3962,20 +4008,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -3992,11 +4038,12 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" dependencies = [ - "futures", + "futures-executor", + "futures-util", "log", "once_cell", "parking_lot 0.12.5", @@ -4006,13 +4053,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -4028,7 +4075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -4049,10 +4096,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4068,9 +4116,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simdutf8" @@ -4084,7 +4132,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -4101,9 +4149,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "sled" @@ -4135,12 +4183,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4216,9 +4264,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4251,7 +4299,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -4262,12 +4310,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4291,7 +4339,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -4302,7 +4350,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", "test-case-core", ] @@ -4326,11 +4374,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4341,18 +4389,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -4407,9 +4455,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4422,9 +4470,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -4439,13 +4487,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -4464,7 +4512,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.37", "tokio", ] @@ -4504,18 +4552,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", "toml_datetime", @@ -4525,18 +4573,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4549,11 +4597,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-util", "http", @@ -4579,9 +4627,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -4596,14 +4644,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4622,9 +4670,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -4640,14 +4688,14 @@ dependencies = [ [[package]] name = "trezor-connect-rs" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d1fb22b5b5d47cdf083ce5a02e3b50c625fdc81333278999e75f8ef5ad6320" +checksum = "a35eab33b03faa9e024fd8e036daf64c9e753b4034944fe89498fe03efae13aa" dependencies = [ "aes-gcm", "async-trait", "base64 0.22.1", - "bitcoin 0.32.7", + "bitcoin 0.32.8", "btleplug", "bytes", "futures", @@ -4686,9 +4734,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -4699,11 +4747,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "uniffi" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d968cb62160c11f2573e6be724ef8b1b18a277aededd17033f8a912d73e2b4" +checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" dependencies = [ "anyhow", "camino", @@ -4717,9 +4771,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b39ef1acbe1467d5d210f274fae344cb6f8766339330cb4c9688752899bf6b" +checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" dependencies = [ "anyhow", "askama", @@ -4743,9 +4797,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d990b553d6b9a7ee9c3ae71134674739913d52350b56152b0e613595bb5a6f" +checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" dependencies = [ "anyhow", "bytes", @@ -4755,22 +4809,22 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f4f224becf14885c10e6e400b95cc4d1985738140cb194ccc2044563f8a56b" +checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" dependencies = [ "anyhow", "indexmap", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] name = "uniffi_macros" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b481d385af334871d70904e6a5f129be7cd38c18fcf8dd8fd1f646b426a56d58" +checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" dependencies = [ "camino", "fs-err", @@ -4778,16 +4832,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.109", + "syn 2.0.117", "toml", "uniffi_meta", ] [[package]] name = "uniffi_meta" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f817868a3b171bb7bf259e882138d104deafde65684689b4694c846d322491" +checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" dependencies = [ "anyhow", "siphasher 0.3.11", @@ -4797,9 +4851,9 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b147e133ad7824e32426b90bc41fda584363563f2ba747f590eca1fd6fd14e6" +checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" dependencies = [ "anyhow", "heck", @@ -4810,9 +4864,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caed654fb73da5abbc7a7e9c741532284532ba4762d6fe5071372df22a41730a" +checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" dependencies = [ "anyhow", "textwrap", @@ -4857,14 +4911,15 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -4875,13 +4930,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.1" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", - "rand 0.9.2", + "rand 0.10.0", "wasm-bindgen", ] @@ -4930,18 +4985,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -4952,11 +5016,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -4965,9 +5030,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4975,31 +5040,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5042,9 +5141,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -5156,7 +5255,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -5167,7 +5266,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -5404,18 +5503,100 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -5469,28 +5650,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -5510,7 +5691,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", "synstructure", ] @@ -5531,7 +5712,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] [[package]] @@ -5564,5 +5745,11 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.117", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 3bf1a07..3738dc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.1.46" +version = "0.1.48" edition = "2021" [lib] @@ -46,11 +46,11 @@ btleplug = "0.11" # Trezor connect library - non-iOS platforms get USB + Bluetooth [target.'cfg(not(target_os = "ios"))'.dependencies] -trezor-connect-rs = { version = "0.2.2", features = ["psbt"] } +trezor-connect-rs = { version = "0.2.4", features = ["psbt"] } # iOS: Bluetooth only (libusb has no iOS backend, so no USB support) [target.'cfg(target_os = "ios")'.dependencies] -trezor-connect-rs = { version = "0.2.2", default-features = false, features = ["bluetooth", "psbt"] } +trezor-connect-rs = { version = "0.2.4", default-features = false, features = ["bluetooth", "psbt"] } # JNI for Android (must match btleplug's jni version) [target.'cfg(target_os = "android")'.dependencies] diff --git a/Package.swift b/Package.swift index 854a88c..5a517ba 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.1.46" -let checksum = "2d0518b22404db4e4e2c78bf7d676f29893454b5a0c311dfb470d48127faf754" +let tag = "v0.1.48" +let checksum = "198887dcce722343abf54945765eff820b95871971cdf678c6db11f5b3e5a929" let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip" let package = Package( diff --git a/README.md b/README.md index 5a9c2df..c5ac9eb 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ - Derive addresses for specified paths - Retrieve account information - Handle responses from Trezor devices + - Query account info from extended public keys (xpub/ypub/zpub) via Electrum + - Query single address balance and UTXOs via Electrum + - Compose and sign transactions ## Available Modules: Methods - Scanner @@ -436,6 +439,23 @@ common: Option, ) -> Result ``` + - [get_account_info_from_xpub](src/modules/trezor/README.md): Query account info from an extended public key via Electrum (no device required) + ```rust + async fn get_account_info_from_xpub( + extended_key: String, + electrum_url: String, + network: Option, + gap_limit: Option, + ) -> Result + ``` + - [get_address_info_from_address](src/modules/trezor/README.md): Query balance and UTXOs for a single Bitcoin address via Electrum (no device required) + ```rust + async fn get_address_info_from_address( + address: String, + electrum_url: String, + network: Option, + ) -> Result + ``` ## Building the Bindings diff --git a/bindings/android/gradle.properties b/bindings/android/gradle.properties index 0c81396..32de70b 100644 --- a/bindings/android/gradle.properties +++ b/bindings/android/gradle.properties @@ -3,4 +3,4 @@ android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official group=com.synonym -version=0.1.46 +version=0.1.48 diff --git a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so index 5f3f500..f2a8c92 100755 Binary files a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so deleted file mode 100755 index 5071975..0000000 Binary files a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so and /dev/null differ diff --git a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so index 722c122..af5c024 100755 Binary files a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so deleted file mode 100755 index 8daad22..0000000 Binary files a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so and /dev/null differ diff --git a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so index b7ad282..a12bafb 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so b/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so deleted file mode 100755 index e3b6340..0000000 Binary files a/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so and /dev/null differ diff --git a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so index 4a7a2a1..17adbcc 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so b/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so deleted file mode 100755 index eeaa46a..0000000 Binary files a/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so and /dev/null differ diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt index 5defca6..322161e 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt @@ -1382,6 +1382,16 @@ internal typealias UniffiVTableCallbackInterfaceTrezorUiCallbackUniffiByValue = + + + + + + + + + + @@ -1621,6 +1631,18 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_mnemonic_to_seed() != 40039.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx() != 45163.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_onchain_compose_transaction() != 20767.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_onchain_get_account_info() != 30087.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_onchain_get_address_info() != 4749.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_open_channel() != 21402.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1672,6 +1694,9 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_test_notification() != 32857.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type() != 16116.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_trezor_clear_credentials() != 41940.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2021,6 +2046,18 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_mnemonic_to_seed( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_onchain_compose_transaction( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_onchain_get_account_info( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_onchain_get_address_info( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_open_channel( ): Short @JvmStatic @@ -2072,6 +2109,9 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_test_notification( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_trezor_clear_credentials( ): Short @JvmStatic @@ -2698,6 +2738,29 @@ internal object UniffiLib : Library { uniffiCallStatus: UniffiRustCallStatus, ): RustBufferByValue @JvmStatic + external fun uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx( + `serializedTx`: RustBufferByValue, + `electrumUrl`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_onchain_compose_transaction( + `params`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_onchain_get_account_info( + `extendedKey`: RustBufferByValue, + `electrumUrl`: RustBufferByValue, + `network`: RustBufferByValue, + `gapLimit`: RustBufferByValue, + `scriptType`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_onchain_get_address_info( + `address`: RustBufferByValue, + `electrumUrl`: RustBufferByValue, + `network`: RustBufferByValue, + ): Long + @JvmStatic external fun uniffi_bitkitcore_fn_func_open_channel( `orderId`: RustBufferByValue, `connectionString`: RustBufferByValue, @@ -2791,6 +2854,11 @@ internal object UniffiLib : Library { `customUrl`: RustBufferByValue, ): Long @JvmStatic + external fun uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type( + `accountType`: RustBufferByValue, + uniffiCallStatus: UniffiRustCallStatus, + ): RustBufferByValue + @JvmStatic external fun uniffi_bitkitcore_fn_func_trezor_clear_credentials( `deviceId`: RustBufferByValue, ): Long @@ -3405,6 +3473,27 @@ public object FfiConverterLong: FfiConverter { } +public object FfiConverterFloat: FfiConverter { + override fun lift(value: Float): Float { + return value + } + + override fun read(buf: ByteBuffer): Float { + return buf.getFloat() + } + + override fun lower(value: Float): Float { + return value + } + + override fun allocationSize(value: Float): ULong = 4UL + + override fun write(value: Float, buf: ByteBuffer) { + buf.putFloat(value) + } +} + + public object FfiConverterDouble: FfiConverter { override fun lift(value: Double): Double { return value @@ -4392,6 +4481,83 @@ public object FfiConverterTypeAccountAddresses: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AccountInfoResult { + return AccountInfoResult( + FfiConverterTypeComposeAccount.read(buf), + FfiConverterULong.read(buf), + FfiConverterUInt.read(buf), + FfiConverterTypeAccountType.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: AccountInfoResult): ULong = ( + FfiConverterTypeComposeAccount.allocationSize(value.`account`) + + FfiConverterULong.allocationSize(value.`balance`) + + FfiConverterUInt.allocationSize(value.`utxoCount`) + + FfiConverterTypeAccountType.allocationSize(value.`accountType`) + + FfiConverterUInt.allocationSize(value.`blockHeight`) + ) + + override fun write(value: AccountInfoResult, buf: ByteBuffer) { + FfiConverterTypeComposeAccount.write(value.`account`, buf) + FfiConverterULong.write(value.`balance`, buf) + FfiConverterUInt.write(value.`utxoCount`, buf) + FfiConverterTypeAccountType.write(value.`accountType`, buf) + FfiConverterUInt.write(value.`blockHeight`, buf) + } +} + + + + +public object FfiConverterTypeAccountUtxo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AccountUtxo { + return AccountUtxo( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterULong.read(buf), + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterOptionalBoolean.read(buf), + ) + } + + override fun allocationSize(value: AccountUtxo): ULong = ( + FfiConverterString.allocationSize(value.`txid`) + + FfiConverterUInt.allocationSize(value.`vout`) + + FfiConverterULong.allocationSize(value.`amount`) + + FfiConverterUInt.allocationSize(value.`blockHeight`) + + FfiConverterString.allocationSize(value.`address`) + + FfiConverterString.allocationSize(value.`path`) + + FfiConverterUInt.allocationSize(value.`confirmations`) + + FfiConverterBoolean.allocationSize(value.`coinbase`) + + FfiConverterBoolean.allocationSize(value.`own`) + + FfiConverterOptionalBoolean.allocationSize(value.`required`) + ) + + override fun write(value: AccountUtxo, buf: ByteBuffer) { + FfiConverterString.write(value.`txid`, buf) + FfiConverterUInt.write(value.`vout`, buf) + FfiConverterULong.write(value.`amount`, buf) + FfiConverterUInt.write(value.`blockHeight`, buf) + FfiConverterString.write(value.`address`, buf) + FfiConverterString.write(value.`path`, buf) + FfiConverterUInt.write(value.`confirmations`, buf) + FfiConverterBoolean.write(value.`coinbase`, buf) + FfiConverterBoolean.write(value.`own`, buf) + FfiConverterOptionalBoolean.write(value.`required`, buf) + } +} + + + + public object FfiConverterTypeActivityTags: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): ActivityTags { return ActivityTags( @@ -4556,6 +4722,59 @@ public object FfiConverterTypeClosedChannelDetails: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ComposeAccount { + return ComposeAccount( + FfiConverterString.read(buf), + FfiConverterTypeAccountAddresses.read(buf), + FfiConverterSequenceTypeAccountUtxo.read(buf), + ) + } + + override fun allocationSize(value: ComposeAccount): ULong = ( + FfiConverterString.allocationSize(value.`path`) + + FfiConverterTypeAccountAddresses.allocationSize(value.`addresses`) + + FfiConverterSequenceTypeAccountUtxo.allocationSize(value.`utxo`) + ) + + override fun write(value: ComposeAccount, buf: ByteBuffer) { + FfiConverterString.write(value.`path`, buf) + FfiConverterTypeAccountAddresses.write(value.`addresses`, buf) + FfiConverterSequenceTypeAccountUtxo.write(value.`utxo`, buf) + } +} + + + + +public object FfiConverterTypeComposeParams: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ComposeParams { + return ComposeParams( + FfiConverterTypeWalletParams.read(buf), + FfiConverterSequenceTypeComposeOutput.read(buf), + FfiConverterSequenceFloat.read(buf), + FfiConverterOptionalTypeCoinSelection.read(buf), + ) + } + + override fun allocationSize(value: ComposeParams): ULong = ( + FfiConverterTypeWalletParams.allocationSize(value.`wallet`) + + FfiConverterSequenceTypeComposeOutput.allocationSize(value.`outputs`) + + FfiConverterSequenceFloat.allocationSize(value.`feeRates`) + + FfiConverterOptionalTypeCoinSelection.allocationSize(value.`coinSelection`) + ) + + override fun write(value: ComposeParams, buf: ByteBuffer) { + FfiConverterTypeWalletParams.write(value.`wallet`, buf) + FfiConverterSequenceTypeComposeOutput.write(value.`outputs`, buf) + FfiConverterSequenceFloat.write(value.`feeRates`, buf) + FfiConverterOptionalTypeCoinSelection.write(value.`coinSelection`, buf) + } +} + + + + public object FfiConverterTypeCreateCjitOptions: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): CreateCjitOptions { return CreateCjitOptions( @@ -6274,6 +6493,37 @@ public object FfiConverterTypePubkyProfileLink: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SingleAddressInfoResult { + return SingleAddressInfoResult( + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + FfiConverterSequenceTypeAccountUtxo.read(buf), + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: SingleAddressInfoResult): ULong = ( + FfiConverterString.allocationSize(value.`address`) + + FfiConverterULong.allocationSize(value.`balance`) + + FfiConverterSequenceTypeAccountUtxo.allocationSize(value.`utxos`) + + FfiConverterUInt.allocationSize(value.`transfers`) + + FfiConverterUInt.allocationSize(value.`blockHeight`) + ) + + override fun write(value: SingleAddressInfoResult, buf: ByteBuffer) { + FfiConverterString.write(value.`address`, buf) + FfiConverterULong.write(value.`balance`, buf) + FfiConverterSequenceTypeAccountUtxo.write(value.`utxos`, buf) + FfiConverterUInt.write(value.`transfers`, buf) + FfiConverterUInt.write(value.`blockHeight`, buf) + } +} + + + + public object FfiConverterTypeSweepResult: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): SweepResult { return SweepResult( @@ -6800,17 +7050,20 @@ public object FfiConverterTypeTrezorSignedTx: FfiConverterRustBuffer{ - override fun read(buf: ByteBuffer): Activity { - return when(buf.getInt()) { - 1 -> Activity.Onchain( - FfiConverterTypeOnchainActivity.read(buf), - ) - 2 -> Activity.Lightning( - FfiConverterTypeLightningActivity.read(buf), - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } +public object FfiConverterTypeWalletParams: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): WalletParams { + return WalletParams( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalTypeNetwork.read(buf), + FfiConverterOptionalTypeAccountType.read(buf), + ) } - override fun allocationSize(value: Activity): ULong = when(value) { - is Activity.Onchain -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeOnchainActivity.allocationSize(value.v1) - ) - } - is Activity.Lightning -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeLightningActivity.allocationSize(value.v1) - ) - } - } + override fun allocationSize(value: WalletParams): ULong = ( + FfiConverterString.allocationSize(value.`extendedKey`) + + FfiConverterString.allocationSize(value.`electrumUrl`) + + FfiConverterOptionalString.allocationSize(value.`fingerprint`) + + FfiConverterOptionalTypeNetwork.allocationSize(value.`network`) + + FfiConverterOptionalTypeAccountType.allocationSize(value.`accountType`) + ) - override fun write(value: Activity, buf: ByteBuffer) { - when(value) { - is Activity.Onchain -> { - buf.putInt(1) - FfiConverterTypeOnchainActivity.write(value.v1, buf) - Unit - } - is Activity.Lightning -> { - buf.putInt(2) - FfiConverterTypeLightningActivity.write(value.v1, buf) - Unit - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + override fun write(value: WalletParams, buf: ByteBuffer) { + FfiConverterString.write(value.`extendedKey`, buf) + FfiConverterString.write(value.`electrumUrl`, buf) + FfiConverterOptionalString.write(value.`fingerprint`, buf) + FfiConverterOptionalTypeNetwork.write(value.`network`, buf) + FfiConverterOptionalTypeAccountType.write(value.`accountType`, buf) } } -public object ActivityExceptionErrorHandler : UniffiRustCallStatusErrorHandler { - override fun lift(errorBuf: RustBufferByValue): ActivityException = FfiConverterTypeActivityError.lift(errorBuf) +public object AccountInfoExceptionErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(errorBuf: RustBufferByValue): AccountInfoException = FfiConverterTypeAccountInfoError.lift(errorBuf) } -public object FfiConverterTypeActivityError : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ActivityException { +public object FfiConverterTypeAccountInfoError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AccountInfoException { return when (buf.getInt()) { - 1 -> ActivityException.InvalidActivity( + 1 -> AccountInfoException.InvalidExtendedKey( FfiConverterString.read(buf), ) - 2 -> ActivityException.InitializationException( + 2 -> AccountInfoException.InvalidAddress( FfiConverterString.read(buf), ) - 3 -> ActivityException.InsertException( + 3 -> AccountInfoException.ElectrumException( FfiConverterString.read(buf), ) - 4 -> ActivityException.RetrievalException( + 4 -> AccountInfoException.WalletException( FfiConverterString.read(buf), ) - 5 -> ActivityException.DataException( + 5 -> AccountInfoException.SyncException( FfiConverterString.read(buf), ) - 6 -> ActivityException.ConnectionException( + 6 -> AccountInfoException.UnsupportedKeyType( FfiConverterString.read(buf), ) - 7 -> ActivityException.SerializationException( + 7 -> AccountInfoException.NetworkMismatch( + FfiConverterString.read(buf), + ) + 8 -> AccountInfoException.InvalidTxid( FfiConverterString.read(buf), ) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } - override fun allocationSize(value: ActivityException): ULong { + override fun allocationSize(value: AccountInfoException): ULong { return when (value) { - is ActivityException.InvalidActivity -> ( + is AccountInfoException.InvalidExtendedKey -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.InitializationException -> ( + is AccountInfoException.InvalidAddress -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.InsertException -> ( + is AccountInfoException.ElectrumException -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.RetrievalException -> ( + is AccountInfoException.WalletException -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.DataException -> ( + is AccountInfoException.SyncException -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.ConnectionException -> ( + is AccountInfoException.UnsupportedKeyType -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) ) - is ActivityException.SerializationException -> ( + is AccountInfoException.NetworkMismatch -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is AccountInfoException.InvalidTxid -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`errorDetails`) @@ -7178,43 +7420,48 @@ public object FfiConverterTypeActivityError : FfiConverterRustBuffer { + is AccountInfoException.InvalidExtendedKey -> { buf.putInt(1) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.InitializationException -> { + is AccountInfoException.InvalidAddress -> { buf.putInt(2) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.InsertException -> { + is AccountInfoException.ElectrumException -> { buf.putInt(3) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.RetrievalException -> { + is AccountInfoException.WalletException -> { buf.putInt(4) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.DataException -> { + is AccountInfoException.SyncException -> { buf.putInt(5) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.ConnectionException -> { + is AccountInfoException.UnsupportedKeyType -> { buf.putInt(6) FfiConverterString.write(value.`errorDetails`, buf) Unit } - is ActivityException.SerializationException -> { + is AccountInfoException.NetworkMismatch -> { buf.putInt(7) FfiConverterString.write(value.`errorDetails`, buf) Unit } + is AccountInfoException.InvalidTxid -> { + buf.putInt(8) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -7223,16 +7470,16 @@ public object FfiConverterTypeActivityError : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ActivityFilter = try { - ActivityFilter.entries[buf.getInt() - 1] +public object FfiConverterTypeAccountType: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AccountType = try { + AccountType.entries[buf.getInt() - 1] } catch (e: IndexOutOfBoundsException) { throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: ActivityFilter): ULong = 4UL + override fun allocationSize(value: AccountType): ULong = 4UL - override fun write(value: ActivityFilter, buf: ByteBuffer) { + override fun write(value: AccountType, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) } } @@ -7241,15 +7488,199 @@ public object FfiConverterTypeActivityFilter: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ActivityType = try { - ActivityType.entries[buf.getInt() - 1] - } catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) +public object FfiConverterTypeActivity : FfiConverterRustBuffer{ + override fun read(buf: ByteBuffer): Activity { + return when(buf.getInt()) { + 1 -> Activity.Onchain( + FfiConverterTypeOnchainActivity.read(buf), + ) + 2 -> Activity.Lightning( + FfiConverterTypeLightningActivity.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } } - override fun allocationSize(value: ActivityType): ULong = 4UL - + override fun allocationSize(value: Activity): ULong = when(value) { + is Activity.Onchain -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeOnchainActivity.allocationSize(value.v1) + ) + } + is Activity.Lightning -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeLightningActivity.allocationSize(value.v1) + ) + } + } + + override fun write(value: Activity, buf: ByteBuffer) { + when(value) { + is Activity.Onchain -> { + buf.putInt(1) + FfiConverterTypeOnchainActivity.write(value.v1, buf) + Unit + } + is Activity.Lightning -> { + buf.putInt(2) + FfiConverterTypeLightningActivity.write(value.v1, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + +public object ActivityExceptionErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(errorBuf: RustBufferByValue): ActivityException = FfiConverterTypeActivityError.lift(errorBuf) +} + +public object FfiConverterTypeActivityError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ActivityException { + return when (buf.getInt()) { + 1 -> ActivityException.InvalidActivity( + FfiConverterString.read(buf), + ) + 2 -> ActivityException.InitializationException( + FfiConverterString.read(buf), + ) + 3 -> ActivityException.InsertException( + FfiConverterString.read(buf), + ) + 4 -> ActivityException.RetrievalException( + FfiConverterString.read(buf), + ) + 5 -> ActivityException.DataException( + FfiConverterString.read(buf), + ) + 6 -> ActivityException.ConnectionException( + FfiConverterString.read(buf), + ) + 7 -> ActivityException.SerializationException( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: ActivityException): ULong { + return when (value) { + is ActivityException.InvalidActivity -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.InitializationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.InsertException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.RetrievalException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.DataException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.ConnectionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is ActivityException.SerializationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + } + } + + override fun write(value: ActivityException, buf: ByteBuffer) { + when (value) { + is ActivityException.InvalidActivity -> { + buf.putInt(1) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.InitializationException -> { + buf.putInt(2) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.InsertException -> { + buf.putInt(3) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.RetrievalException -> { + buf.putInt(4) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.DataException -> { + buf.putInt(5) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.ConnectionException -> { + buf.putInt(6) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is ActivityException.SerializationException -> { + buf.putInt(7) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + + +public object FfiConverterTypeActivityFilter: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ActivityFilter = try { + ActivityFilter.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: ActivityFilter): ULong = 4UL + + override fun write(value: ActivityFilter, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + +public object FfiConverterTypeActivityType: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ActivityType = try { + ActivityType.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: ActivityType): ULong = 4UL + override fun write(value: ActivityType, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) } @@ -7570,6 +8001,83 @@ public object FfiConverterTypeBlocktankError : FfiConverterRustBuffer { + override fun lift(errorBuf: RustBufferByValue): BroadcastException = FfiConverterTypeBroadcastError.lift(errorBuf) +} + +public object FfiConverterTypeBroadcastError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BroadcastException { + return when (buf.getInt()) { + 1 -> BroadcastException.InvalidHex( + FfiConverterString.read(buf), + ) + 2 -> BroadcastException.InvalidTransaction( + FfiConverterString.read(buf), + ) + 3 -> BroadcastException.ElectrumException( + FfiConverterString.read(buf), + ) + 4 -> BroadcastException.TaskException( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: BroadcastException): ULong { + return when (value) { + is BroadcastException.InvalidHex -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BroadcastException.InvalidTransaction -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BroadcastException.ElectrumException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BroadcastException.TaskException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + } + } + + override fun write(value: BroadcastException, buf: ByteBuffer) { + when (value) { + is BroadcastException.InvalidHex -> { + buf.putInt(1) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BroadcastException.InvalidTransaction -> { + buf.putInt(2) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BroadcastException.ElectrumException -> { + buf.putInt(3) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BroadcastException.TaskException -> { + buf.putInt(4) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + public object FfiConverterTypeBtBolt11InvoiceState: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): BtBolt11InvoiceState = try { @@ -7686,10 +8194,114 @@ public object FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CJitStateEnum = try { + CJitStateEnum.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: CJitStateEnum): ULong = 4UL + + override fun write(value: CJitStateEnum, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + +public object FfiConverterTypeCoinSelection: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinSelection = try { + CoinSelection.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: CoinSelection): ULong = 4UL + + override fun write(value: CoinSelection, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + +public object FfiConverterTypeComposeOutput : FfiConverterRustBuffer{ + override fun read(buf: ByteBuffer): ComposeOutput { + return when(buf.getInt()) { + 1 -> ComposeOutput.Payment( + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + ) + 2 -> ComposeOutput.SendMax( + FfiConverterString.read(buf), + ) + 3 -> ComposeOutput.OpReturn( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: ComposeOutput): ULong = when(value) { + is ComposeOutput.Payment -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`address`) + + FfiConverterULong.allocationSize(value.`amountSats`) + ) + } + is ComposeOutput.SendMax -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`address`) + ) + } + is ComposeOutput.OpReturn -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`dataHex`) + ) + } + } - override fun write(value: BtPaymentState2, buf: ByteBuffer) { - buf.putInt(value.ordinal + 1) + override fun write(value: ComposeOutput, buf: ByteBuffer) { + when(value) { + is ComposeOutput.Payment -> { + buf.putInt(1) + FfiConverterString.write(value.`address`, buf) + FfiConverterULong.write(value.`amountSats`, buf) + Unit + } + is ComposeOutput.SendMax -> { + buf.putInt(2) + FfiConverterString.write(value.`address`, buf) + Unit + } + is ComposeOutput.OpReturn -> { + buf.putInt(3) + FfiConverterString.write(value.`dataHex`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -7697,17 +8309,58 @@ public object FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): CJitStateEnum = try { - CJitStateEnum.entries[buf.getInt() - 1] - } catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) +public object FfiConverterTypeComposeResult : FfiConverterRustBuffer{ + override fun read(buf: ByteBuffer): ComposeResult { + return when(buf.getInt()) { + 1 -> ComposeResult.Success( + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + FfiConverterFloat.read(buf), + FfiConverterULong.read(buf), + ) + 2 -> ComposeResult.Error( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } } - override fun allocationSize(value: CJitStateEnum): ULong = 4UL + override fun allocationSize(value: ComposeResult): ULong = when(value) { + is ComposeResult.Success -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`psbt`) + + FfiConverterULong.allocationSize(value.`fee`) + + FfiConverterFloat.allocationSize(value.`feeRate`) + + FfiConverterULong.allocationSize(value.`totalSpent`) + ) + } + is ComposeResult.Error -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`error`) + ) + } + } - override fun write(value: CJitStateEnum, buf: ByteBuffer) { - buf.putInt(value.ordinal + 1) + override fun write(value: ComposeResult, buf: ByteBuffer) { + when(value) { + is ComposeResult.Success -> { + buf.putInt(1) + FfiConverterString.write(value.`psbt`, buf) + FfiConverterULong.write(value.`fee`, buf) + FfiConverterFloat.write(value.`feeRate`, buf) + FfiConverterULong.write(value.`totalSpent`, buf) + Unit + } + is ComposeResult.Error -> { + buf.putInt(2) + FfiConverterString.write(value.`error`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -9616,6 +10269,35 @@ public object FfiConverterOptionalTypeTrezorFeatures: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AccountType? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeAccountType.read(buf) + } + + override fun allocationSize(value: AccountType?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeAccountType.allocationSize(value) + } + } + + override fun write(value: AccountType?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeAccountType.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeActivity: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): Activity? { if (buf.get().toInt() == 0) { @@ -9761,6 +10443,35 @@ public object FfiConverterOptionalTypeCJitStateEnum: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinSelection? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeCoinSelection.read(buf) + } + + override fun allocationSize(value: CoinSelection?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeCoinSelection.allocationSize(value) + } + } + + override fun write(value: CoinSelection?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeCoinSelection.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeNetwork: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): Network? { if (buf.get().toInt() == 0) { @@ -10051,6 +10762,31 @@ public object FfiConverterOptionalMapStringString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterFloat.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterFloat.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterFloat.write(it, buf) + } + } +} + + + + public object FfiConverterSequenceString: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List { val len = buf.getInt() @@ -10076,6 +10812,31 @@ public object FfiConverterSequenceString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeAccountUtxo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterTypeAccountUtxo.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeAccountUtxo.write(it, buf) + } + } +} + + + + public object FfiConverterSequenceTypeActivityTags: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List { val len = buf.getInt() @@ -10675,6 +11436,56 @@ public object FfiConverterSequenceTypeActivity: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeComposeOutput.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterTypeComposeOutput.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeComposeOutput.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeComposeResult: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeComposeResult.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterTypeComposeResult.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeComposeResult.write(it, buf) + } + } +} + + + public object FfiConverterMapStringString: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): Map { val len = buf.getInt() @@ -11574,6 +12385,100 @@ public fun `mnemonicToSeed`(`mnemonicPhrase`: kotlin.String, `passphrase`: kotli }) } +/** + * Broadcast a signed raw transaction via Electrum. + * + * Takes a hex-encoded serialized transaction and an Electrum server URL. + * Returns the transaction ID on success. + */ +@Throws(BroadcastException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `onchainBroadcastRawTx`(`serializedTx`: kotlin.String, `electrumUrl`: kotlin.String): kotlin.String { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx( + FfiConverterString.lower(`serializedTx`), + FfiConverterString.lower(`electrumUrl`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterString.lift(it) }, + // Error FFI converter + BroadcastExceptionErrorHandler, + ) +} + +/** + * Compose a transaction for multiple fee rates, returning one PSBT per rate. + * + * Creates a BDK wallet from the extended key, syncs via Electrum, then + * builds PSBTs using BDK's TxBuilder. The PSBTs include BIP32 derivation + * paths (when fingerprint is provided) and are ready for signing by any + * PSBT-compatible signer (Trezor, Ledger, software wallet, etc.). + */ +public suspend fun `onchainComposeTransaction`(`params`: ComposeParams): List { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_onchain_compose_transaction( + FfiConverterTypeComposeParams.lower(`params`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterSequenceTypeComposeResult.lift(it) }, + // Error FFI converter + UniffiNullRustCallStatusErrorHandler, + ) +} + +/** + * Query account information for an extended public key via Electrum. + */ +@Throws(AccountInfoException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `onchainGetAccountInfo`(`extendedKey`: kotlin.String, `electrumUrl`: kotlin.String, `network`: Network?, `gapLimit`: kotlin.UInt?, `scriptType`: AccountType?): AccountInfoResult { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_account_info( + FfiConverterString.lower(`extendedKey`), + FfiConverterString.lower(`electrumUrl`), + FfiConverterOptionalTypeNetwork.lower(`network`), + FfiConverterOptionalUInt.lower(`gapLimit`), + FfiConverterOptionalTypeAccountType.lower(`scriptType`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterTypeAccountInfoResult.lift(it) }, + // Error FFI converter + AccountInfoExceptionErrorHandler, + ) +} + +/** + * Query balance and UTXOs for a single Bitcoin address via Electrum. + */ +@Throws(AccountInfoException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `onchainGetAddressInfo`(`address`: kotlin.String, `electrumUrl`: kotlin.String, `network`: Network?): SingleAddressInfoResult { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_address_info( + FfiConverterString.lower(`address`), + FfiConverterString.lower(`electrumUrl`), + FfiConverterOptionalTypeNetwork.lower(`network`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterTypeSingleAddressInfoResult.lift(it) }, + // Error FFI converter + AccountInfoExceptionErrorHandler, + ) +} + @Throws(BlocktankException::class, kotlin.coroutines.cancellation.CancellationException::class) public suspend fun `openChannel`(`orderId`: kotlin.String, `connectionString`: kotlin.String): IBtOrder { return uniffiRustCallAsync( @@ -11855,6 +12760,18 @@ public suspend fun `testNotification`(`deviceToken`: kotlin.String, `secretMessa ) } +/** + * Convert an account type to its corresponding Trezor script type. + */ +public fun `trezorAccountTypeToScriptType`(`accountType`: AccountType): TrezorScriptType { + return FfiConverterTypeTrezorScriptType.lift(uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type( + FfiConverterTypeAccountType.lower(`accountType`), + uniffiRustCallStatus, + ) + }) +} + /** * Clear stored Bluetooth pairing credentials for a specific Trezor device. * diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt index 4b1e29c..7379bbd 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt @@ -110,6 +110,8 @@ public object NoPointer + + /** * Callback interface for native Trezor transport operations @@ -281,16 +283,16 @@ public interface TrezorUiCallback { /** - * Account addresses + * Grouped address lists for an account. */ @kotlinx.serialization.Serializable public data class AccountAddresses ( /** - * Used addresses + * Used receive addresses (have at least one transaction) */ val `used`: List, /** - * Unused addresses + * Unused receive addresses (no transactions yet) */ val `unused`: List, /** @@ -303,6 +305,88 @@ public data class AccountAddresses ( +/** + * Result from querying an extended public key via Electrum. + */ +@kotlinx.serialization.Serializable +public data class AccountInfoResult ( + /** + * The account structure with addresses and UTXOs + */ + val `account`: ComposeAccount, + /** + * Total confirmed balance in satoshis + */ + val `balance`: kotlin.ULong, + /** + * Number of UTXOs + */ + val `utxoCount`: kotlin.UInt, + /** + * The detected or specified account type + */ + val `accountType`: AccountType, + /** + * The current blockchain tip height + */ + val `blockHeight`: kotlin.UInt +) { + public companion object +} + + + +/** + * A UTXO associated with an account or address. + */ +@kotlinx.serialization.Serializable +public data class AccountUtxo ( + /** + * Transaction ID (hex) + */ + val `txid`: kotlin.String, + /** + * Output index + */ + val `vout`: kotlin.UInt, + /** + * Amount in satoshis + */ + val `amount`: kotlin.ULong, + /** + * Block height where the UTXO was confirmed (0 if unconfirmed) + */ + val `blockHeight`: kotlin.UInt, + /** + * Address holding this UTXO + */ + val `address`: kotlin.String, + /** + * BIP32 derivation path (e.g., "m/84'/0'/0'/0/0") + */ + val `path`: kotlin.String, + /** + * Number of confirmations (0 if unconfirmed) + */ + val `confirmations`: kotlin.UInt, + /** + * Whether this is a coinbase output + */ + val `coinbase`: kotlin.Boolean, + /** + * Whether this UTXO is owned by the account + */ + val `own`: kotlin.Boolean, + /** + * Whether this UTXO must be included in the transaction + */ + val `required`: kotlin.Boolean? +) { + public companion object +} + + + @kotlinx.serialization.Serializable public data class ActivityTags ( val `activityId`: kotlin.String, @@ -314,20 +398,20 @@ public data class ActivityTags ( /** - * Address information + * Information about a single address in an account. */ @kotlinx.serialization.Serializable public data class AddressInfo ( /** - * Address string + * The Bitcoin address */ val `address`: kotlin.String, /** - * Derivation path + * BIP32 derivation path */ val `path`: kotlin.String, /** - * Number of transfers + * Number of transfers (real count in `get_address_info`, 1/0 presence flag in `get_account_info`) */ val `transfers`: kotlin.UInt ) { @@ -383,6 +467,56 @@ public data class ClosedChannelDetails ( +/** + * Full account structure with addresses and UTXOs. + */ +@kotlinx.serialization.Serializable +public data class ComposeAccount ( + /** + * Account derivation path (e.g., "m/84'/0'/0'") + */ + val `path`: kotlin.String, + /** + * Categorized addresses + */ + val `addresses`: AccountAddresses, + /** + * Unspent transaction outputs + */ + val `utxo`: List +) { + public companion object +} + + + +/** + * Parameters for composing a signer-agnostic transaction. + */ +@kotlinx.serialization.Serializable +public data class ComposeParams ( + /** + * Wallet configuration (key, server, network) + */ + val `wallet`: WalletParams, + /** + * Desired transaction outputs + */ + val `outputs`: List, + /** + * Fee rates to evaluate (sat/vB), one PSBT per rate + */ + val `feeRates`: List, + /** + * UTXO selection strategy (defaults to BranchAndBound) + */ + val `coinSelection`: CoinSelection? +) { + public companion object +} + + + @kotlinx.serialization.Serializable public data class CreateCjitOptions ( val `source`: kotlin.String?, @@ -1173,6 +1307,37 @@ public data class PubkyProfileLink ( +/** + * Result from querying a single Bitcoin address. + */ +@kotlinx.serialization.Serializable +public data class SingleAddressInfoResult ( + /** + * The queried address + */ + val `address`: kotlin.String, + /** + * Total confirmed balance in satoshis + */ + val `balance`: kotlin.ULong, + /** + * UTXOs for this address + */ + val `utxos`: List, + /** + * Number of transactions involving this address + */ + val `transfers`: kotlin.UInt, + /** + * Current blockchain tip height + */ + val `blockHeight`: kotlin.UInt +) { + public companion object +} + + + @kotlinx.serialization.Serializable public data class SweepResult ( /** @@ -1719,7 +1884,11 @@ public data class TrezorSignedTx ( /** * Serialized transaction (hex) */ - val `serializedTx`: kotlin.String + val `serializedTx`: kotlin.String, + /** + * Broadcast transaction ID (populated when push=true) + */ + val `txid`: kotlin.String? ) { public companion object } @@ -1967,6 +2136,161 @@ public data class ValidationResult ( +/** + * Common parameters for creating and syncing a watch-only BDK wallet. + */ +@kotlinx.serialization.Serializable +public data class WalletParams ( + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + */ + val `extendedKey`: kotlin.String, + /** + * Electrum server URL for wallet sync + */ + val `electrumUrl`: kotlin.String, + /** + * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + */ + val `fingerprint`: kotlin.String?, + /** + * Bitcoin network (auto-detected from key prefix if not specified) + */ + val `network`: Network?, + /** + * Override account type for ambiguous key prefixes (xpub/tpub) + */ + val `accountType`: AccountType? +) { + public companion object +} + + + + + +/** + * Errors specific to account info operations (BDK/Electrum-based). + */ +public sealed class AccountInfoException: kotlin.Exception() { + + /** + * The provided extended public key is invalid or cannot be parsed + */ + public class InvalidExtendedKey( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * The provided address is invalid + */ + public class InvalidAddress( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * Electrum connection or query failed + */ + public class ElectrumException( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * BDK wallet creation or operation error + */ + public class WalletException( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * Wallet sync with Electrum failed + */ + public class SyncException( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * The key type/prefix is not recognized + */ + public class UnsupportedKeyType( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * Network mismatch between key prefix and specified network + */ + public class NetworkMismatch( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + /** + * Invalid transaction ID provided + */ + public class InvalidTxid( + public val `errorDetails`: kotlin.String, + ) : AccountInfoException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + +} + + + + +/** + * Account type classification for extended public keys. + * + * Determines the BIP standard, derivation path purpose, and script type. + */ + +@kotlinx.serialization.Serializable +public enum class AccountType { + + /** + * BIP44 legacy (P2PKH) — xpub/tpub prefix + */ + LEGACY, + /** + * BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + */ + WRAPPED_SEGWIT, + /** + * BIP84 native segwit (P2WPKH) — zpub/vpub prefix + */ + NATIVE_SEGWIT, + /** + * BIP86 taproot (P2TR) + */ + TAPROOT; + public companion object +} + + + + + @kotlinx.serialization.Serializable public sealed class Activity { @@ -2253,6 +2577,42 @@ public sealed class BlocktankException: kotlin.Exception() { +public sealed class BroadcastException: kotlin.Exception() { + + public class InvalidHex( + public val `errorDetails`: kotlin.String, + ) : BroadcastException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class InvalidTransaction( + public val `errorDetails`: kotlin.String, + ) : BroadcastException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class ElectrumException( + public val `errorDetails`: kotlin.String, + ) : BroadcastException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class TaskException( + public val `errorDetails`: kotlin.String, + ) : BroadcastException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + +} + + + + + @kotlinx.serialization.Serializable public enum class BtBolt11InvoiceState { @@ -2382,6 +2742,117 @@ public enum class CJitStateEnum { +/** + * Coin selection strategy for transaction composition. + */ + +@kotlinx.serialization.Serializable +public enum class CoinSelection { + + /** + * Branch-and-bound (default). Minimizes change by searching for exact matches. + */ + BRANCH_AND_BOUND, + /** + * Selects largest UTXOs first. Useful for UTXO consolidation. + */ + LARGEST_FIRST, + /** + * Selects oldest UTXOs first. Maximizes coin-age spending. + */ + OLDEST_FIRST; + public companion object +} + + + + + + +/** + * Output specification for transaction composition. + */ +@kotlinx.serialization.Serializable +public sealed class ComposeOutput { + + /** + * Payment to a specific address with a fixed amount (satoshis) + */@kotlinx.serialization.Serializable + public data class Payment( + val `address`: kotlin.String, + val `amountSats`: kotlin.ULong, + ) : ComposeOutput() { + } + + /** + * Send all remaining funds (after fees) to an address + */@kotlinx.serialization.Serializable + public data class SendMax( + val `address`: kotlin.String, + ) : ComposeOutput() { + } + + /** + * OP_RETURN data output (hex-encoded payload) + */@kotlinx.serialization.Serializable + public data class OpReturn( + val `dataHex`: kotlin.String, + ) : ComposeOutput() { + } + +} + + + + + + +/** + * Result of composing a transaction at a single fee rate. + */ +@kotlinx.serialization.Serializable +public sealed class ComposeResult { + + /** + * Successfully built a signable PSBT + */@kotlinx.serialization.Serializable + public data class Success( + /** + * Base64-encoded PSBT ready for signing + */ + val `psbt`: kotlin.String, + /** + * Total fee in satoshis + */ + val `fee`: kotlin.ULong, + /** + * Target fee rate in sat/vB (actual may differ slightly due to rounding) + */ + val `feeRate`: kotlin.Float, + /** + * Total value spent (payments + fee, excluding change). + * Uses BDK's `sent - received` semantics, which may undercount for + * self-transfers where the destination is also owned by the wallet. + */ + val `totalSpent`: kotlin.ULong, + ) : ComposeResult() { + } + + /** + * Composition failed (e.g. insufficient funds) + */@kotlinx.serialization.Serializable + public data class Error( + val `error`: kotlin.String, + ) : ComposeResult() { + } + +} + + + + + + public sealed class DbException: kotlin.Exception() { @@ -3239,6 +3710,18 @@ public enum class WordCount { + + + + + + + + + + + + diff --git a/bindings/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 076e229..39e6b69 100644 Binary files a/bindings/ios/BitkitCore.xcframework.zip and b/bindings/ios/BitkitCore.xcframework.zip differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h index de38032..be0b90c 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h @@ -790,6 +790,26 @@ RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_entropy(RustBuffer mnemonic_phr RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_seed(RustBuffer mnemonic_phrase, RustBuffer passphrase, RustCallStatus *_Nonnull out_status ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint64_t uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx(RustBuffer serialized_tx, RustBuffer electrum_url +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint64_t uniffi_bitkitcore_fn_func_onchain_compose_transaction(RustBuffer params +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_account_info(RustBuffer extended_key, RustBuffer electrum_url, RustBuffer network, RustBuffer gap_limit, RustBuffer script_type +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_address_info(RustBuffer address, RustBuffer electrum_url, RustBuffer network +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL uint64_t uniffi_bitkitcore_fn_func_open_channel(RustBuffer order_id, RustBuffer connection_string @@ -877,6 +897,11 @@ uint64_t uniffi_bitkitcore_fn_func_start_pubky_auth(RustBuffer caps uint64_t uniffi_bitkitcore_fn_func_test_notification(RustBuffer device_token, RustBuffer secret_message, RustBuffer notification_type, RustBuffer custom_url ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +RustBuffer uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type(RustBuffer account_type, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS uint64_t uniffi_bitkitcore_fn_func_trezor_clear_credentials(RustBuffer device_id @@ -1740,6 +1765,30 @@ uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_entropy(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_MNEMONIC_TO_SEED uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_seed(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint16_t uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint16_t uniffi_bitkitcore_checksum_func_onchain_compose_transaction(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_account_info(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_address_info(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_OPEN_CHANNEL @@ -1842,6 +1891,12 @@ uint16_t uniffi_bitkitcore_checksum_func_start_pubky_auth(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TEST_NOTIFICATION uint16_t uniffi_bitkitcore_checksum_func_test_notification(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +uint16_t uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_CLEAR_CREDENTIALS diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a index 809be5c..6ca7957 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h index de38032..be0b90c 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h @@ -790,6 +790,26 @@ RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_entropy(RustBuffer mnemonic_phr RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_seed(RustBuffer mnemonic_phrase, RustBuffer passphrase, RustCallStatus *_Nonnull out_status ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint64_t uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx(RustBuffer serialized_tx, RustBuffer electrum_url +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint64_t uniffi_bitkitcore_fn_func_onchain_compose_transaction(RustBuffer params +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_account_info(RustBuffer extended_key, RustBuffer electrum_url, RustBuffer network, RustBuffer gap_limit, RustBuffer script_type +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_address_info(RustBuffer address, RustBuffer electrum_url, RustBuffer network +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL uint64_t uniffi_bitkitcore_fn_func_open_channel(RustBuffer order_id, RustBuffer connection_string @@ -877,6 +897,11 @@ uint64_t uniffi_bitkitcore_fn_func_start_pubky_auth(RustBuffer caps uint64_t uniffi_bitkitcore_fn_func_test_notification(RustBuffer device_token, RustBuffer secret_message, RustBuffer notification_type, RustBuffer custom_url ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +RustBuffer uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type(RustBuffer account_type, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS uint64_t uniffi_bitkitcore_fn_func_trezor_clear_credentials(RustBuffer device_id @@ -1740,6 +1765,30 @@ uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_entropy(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_MNEMONIC_TO_SEED uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_seed(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint16_t uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint16_t uniffi_bitkitcore_checksum_func_onchain_compose_transaction(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_account_info(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_address_info(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_OPEN_CHANNEL @@ -1842,6 +1891,12 @@ uint16_t uniffi_bitkitcore_checksum_func_start_pubky_auth(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TEST_NOTIFICATION uint16_t uniffi_bitkitcore_checksum_func_test_notification(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +uint16_t uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_CLEAR_CREDENTIALS diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a index d3df61a..234efe0 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a differ diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index e11c432..2f92f67 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -467,6 +467,22 @@ fileprivate struct FfiConverterInt64: FfiConverterPrimitive { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterFloat: FfiConverterPrimitive { + typealias FfiType = Float + typealias SwiftType = Float + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + return try lift(readFloat(&buf)) + } + + public static func write(_ value: Float, into buf: inout [UInt8]) { + writeFloat(&buf, lower(value)) + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -1549,15 +1565,15 @@ public func FfiConverterTypeTrezorUiCallback_lower(_ value: TrezorUiCallback) -> /** - * Account addresses + * Grouped address lists for an account. */ public struct AccountAddresses { /** - * Used addresses + * Used receive addresses (have at least one transaction) */ public var used: [AddressInfo] /** - * Unused addresses + * Unused receive addresses (no transactions yet) */ public var unused: [AddressInfo] /** @@ -1569,10 +1585,10 @@ public struct AccountAddresses { // declare one manually. public init( /** - * Used addresses + * Used receive addresses (have at least one transaction) */used: [AddressInfo], /** - * Unused addresses + * Unused receive addresses (no transactions yet) */unused: [AddressInfo], /** * Change addresses @@ -1649,6 +1665,334 @@ public func FfiConverterTypeAccountAddresses_lower(_ value: AccountAddresses) -> } +/** + * Result from querying an extended public key via Electrum. + */ +public struct AccountInfoResult { + /** + * The account structure with addresses and UTXOs + */ + public var account: ComposeAccount + /** + * Total confirmed balance in satoshis + */ + public var balance: UInt64 + /** + * Number of UTXOs + */ + public var utxoCount: UInt32 + /** + * The detected or specified account type + */ + public var accountType: AccountType + /** + * The current blockchain tip height + */ + public var blockHeight: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The account structure with addresses and UTXOs + */account: ComposeAccount, + /** + * Total confirmed balance in satoshis + */balance: UInt64, + /** + * Number of UTXOs + */utxoCount: UInt32, + /** + * The detected or specified account type + */accountType: AccountType, + /** + * The current blockchain tip height + */blockHeight: UInt32) { + self.account = account + self.balance = balance + self.utxoCount = utxoCount + self.accountType = accountType + self.blockHeight = blockHeight + } +} + +#if compiler(>=6) +extension AccountInfoResult: Sendable {} +#endif + + +extension AccountInfoResult: Equatable, Hashable { + public static func ==(lhs: AccountInfoResult, rhs: AccountInfoResult) -> Bool { + if lhs.account != rhs.account { + return false + } + if lhs.balance != rhs.balance { + return false + } + if lhs.utxoCount != rhs.utxoCount { + return false + } + if lhs.accountType != rhs.accountType { + return false + } + if lhs.blockHeight != rhs.blockHeight { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(account) + hasher.combine(balance) + hasher.combine(utxoCount) + hasher.combine(accountType) + hasher.combine(blockHeight) + } +} + +extension AccountInfoResult: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountInfoResult: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountInfoResult { + return + try AccountInfoResult( + account: FfiConverterTypeComposeAccount.read(from: &buf), + balance: FfiConverterUInt64.read(from: &buf), + utxoCount: FfiConverterUInt32.read(from: &buf), + accountType: FfiConverterTypeAccountType.read(from: &buf), + blockHeight: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: AccountInfoResult, into buf: inout [UInt8]) { + FfiConverterTypeComposeAccount.write(value.account, into: &buf) + FfiConverterUInt64.write(value.balance, into: &buf) + FfiConverterUInt32.write(value.utxoCount, into: &buf) + FfiConverterTypeAccountType.write(value.accountType, into: &buf) + FfiConverterUInt32.write(value.blockHeight, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoResult_lift(_ buf: RustBuffer) throws -> AccountInfoResult { + return try FfiConverterTypeAccountInfoResult.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoResult_lower(_ value: AccountInfoResult) -> RustBuffer { + return FfiConverterTypeAccountInfoResult.lower(value) +} + + +/** + * A UTXO associated with an account or address. + */ +public struct AccountUtxo { + /** + * Transaction ID (hex) + */ + public var txid: String + /** + * Output index + */ + public var vout: UInt32 + /** + * Amount in satoshis + */ + public var amount: UInt64 + /** + * Block height where the UTXO was confirmed (0 if unconfirmed) + */ + public var blockHeight: UInt32 + /** + * Address holding this UTXO + */ + public var address: String + /** + * BIP32 derivation path (e.g., "m/84'/0'/0'/0/0") + */ + public var path: String + /** + * Number of confirmations (0 if unconfirmed) + */ + public var confirmations: UInt32 + /** + * Whether this is a coinbase output + */ + public var coinbase: Bool + /** + * Whether this UTXO is owned by the account + */ + public var own: Bool + /** + * Whether this UTXO must be included in the transaction + */ + public var required: Bool? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Transaction ID (hex) + */txid: String, + /** + * Output index + */vout: UInt32, + /** + * Amount in satoshis + */amount: UInt64, + /** + * Block height where the UTXO was confirmed (0 if unconfirmed) + */blockHeight: UInt32, + /** + * Address holding this UTXO + */address: String, + /** + * BIP32 derivation path (e.g., "m/84'/0'/0'/0/0") + */path: String, + /** + * Number of confirmations (0 if unconfirmed) + */confirmations: UInt32, + /** + * Whether this is a coinbase output + */coinbase: Bool, + /** + * Whether this UTXO is owned by the account + */own: Bool, + /** + * Whether this UTXO must be included in the transaction + */required: Bool?) { + self.txid = txid + self.vout = vout + self.amount = amount + self.blockHeight = blockHeight + self.address = address + self.path = path + self.confirmations = confirmations + self.coinbase = coinbase + self.own = own + self.required = required + } +} + +#if compiler(>=6) +extension AccountUtxo: Sendable {} +#endif + + +extension AccountUtxo: Equatable, Hashable { + public static func ==(lhs: AccountUtxo, rhs: AccountUtxo) -> Bool { + if lhs.txid != rhs.txid { + return false + } + if lhs.vout != rhs.vout { + return false + } + if lhs.amount != rhs.amount { + return false + } + if lhs.blockHeight != rhs.blockHeight { + return false + } + if lhs.address != rhs.address { + return false + } + if lhs.path != rhs.path { + return false + } + if lhs.confirmations != rhs.confirmations { + return false + } + if lhs.coinbase != rhs.coinbase { + return false + } + if lhs.own != rhs.own { + return false + } + if lhs.required != rhs.required { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(txid) + hasher.combine(vout) + hasher.combine(amount) + hasher.combine(blockHeight) + hasher.combine(address) + hasher.combine(path) + hasher.combine(confirmations) + hasher.combine(coinbase) + hasher.combine(own) + hasher.combine(required) + } +} + +extension AccountUtxo: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountUtxo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountUtxo { + return + try AccountUtxo( + txid: FfiConverterString.read(from: &buf), + vout: FfiConverterUInt32.read(from: &buf), + amount: FfiConverterUInt64.read(from: &buf), + blockHeight: FfiConverterUInt32.read(from: &buf), + address: FfiConverterString.read(from: &buf), + path: FfiConverterString.read(from: &buf), + confirmations: FfiConverterUInt32.read(from: &buf), + coinbase: FfiConverterBool.read(from: &buf), + own: FfiConverterBool.read(from: &buf), + required: FfiConverterOptionBool.read(from: &buf) + ) + } + + public static func write(_ value: AccountUtxo, into buf: inout [UInt8]) { + FfiConverterString.write(value.txid, into: &buf) + FfiConverterUInt32.write(value.vout, into: &buf) + FfiConverterUInt64.write(value.amount, into: &buf) + FfiConverterUInt32.write(value.blockHeight, into: &buf) + FfiConverterString.write(value.address, into: &buf) + FfiConverterString.write(value.path, into: &buf) + FfiConverterUInt32.write(value.confirmations, into: &buf) + FfiConverterBool.write(value.coinbase, into: &buf) + FfiConverterBool.write(value.own, into: &buf) + FfiConverterOptionBool.write(value.required, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountUtxo_lift(_ buf: RustBuffer) throws -> AccountUtxo { + return try FfiConverterTypeAccountUtxo.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountUtxo_lower(_ value: AccountUtxo) -> RustBuffer { + return FfiConverterTypeAccountUtxo.lower(value) +} + + public struct ActivityTags { public var activityId: String public var tags: [String] @@ -1722,19 +2066,19 @@ public func FfiConverterTypeActivityTags_lower(_ value: ActivityTags) -> RustBuf /** - * Address information + * Information about a single address in an account. */ public struct AddressInfo { /** - * Address string + * The Bitcoin address */ public var address: String /** - * Derivation path + * BIP32 derivation path */ public var path: String /** - * Number of transfers + * Number of transfers (real count in `get_address_info`, 1/0 presence flag in `get_account_info`) */ public var transfers: UInt32 @@ -1742,13 +2086,13 @@ public struct AddressInfo { // declare one manually. public init( /** - * Address string + * The Bitcoin address */address: String, /** - * Derivation path + * BIP32 derivation path */path: String, /** - * Number of transfers + * Number of transfers (real count in `get_address_info`, 1/0 presence flag in `get_account_info`) */transfers: UInt32) { self.address = address self.path = path @@ -2174,37 +2518,253 @@ public func FfiConverterTypeClosedChannelDetails_lower(_ value: ClosedChannelDet } -public struct CreateCjitOptions { - public var source: String? - public var discountCode: String? +/** + * Full account structure with addresses and UTXOs. + */ +public struct ComposeAccount { + /** + * Account derivation path (e.g., "m/84'/0'/0'") + */ + public var path: String + /** + * Categorized addresses + */ + public var addresses: AccountAddresses + /** + * Unspent transaction outputs + */ + public var utxo: [AccountUtxo] // Default memberwise initializers are never public by default, so we // declare one manually. - public init(source: String?, discountCode: String?) { - self.source = source - self.discountCode = discountCode + public init( + /** + * Account derivation path (e.g., "m/84'/0'/0'") + */path: String, + /** + * Categorized addresses + */addresses: AccountAddresses, + /** + * Unspent transaction outputs + */utxo: [AccountUtxo]) { + self.path = path + self.addresses = addresses + self.utxo = utxo } } #if compiler(>=6) -extension CreateCjitOptions: Sendable {} +extension ComposeAccount: Sendable {} #endif -extension CreateCjitOptions: Equatable, Hashable { - public static func ==(lhs: CreateCjitOptions, rhs: CreateCjitOptions) -> Bool { - if lhs.source != rhs.source { +extension ComposeAccount: Equatable, Hashable { + public static func ==(lhs: ComposeAccount, rhs: ComposeAccount) -> Bool { + if lhs.path != rhs.path { return false } - if lhs.discountCode != rhs.discountCode { + if lhs.addresses != rhs.addresses { + return false + } + if lhs.utxo != rhs.utxo { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(source) - hasher.combine(discountCode) + hasher.combine(path) + hasher.combine(addresses) + hasher.combine(utxo) + } +} + +extension ComposeAccount: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeComposeAccount: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeAccount { + return + try ComposeAccount( + path: FfiConverterString.read(from: &buf), + addresses: FfiConverterTypeAccountAddresses.read(from: &buf), + utxo: FfiConverterSequenceTypeAccountUtxo.read(from: &buf) + ) + } + + public static func write(_ value: ComposeAccount, into buf: inout [UInt8]) { + FfiConverterString.write(value.path, into: &buf) + FfiConverterTypeAccountAddresses.write(value.addresses, into: &buf) + FfiConverterSequenceTypeAccountUtxo.write(value.utxo, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeComposeAccount_lift(_ buf: RustBuffer) throws -> ComposeAccount { + return try FfiConverterTypeComposeAccount.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeComposeAccount_lower(_ value: ComposeAccount) -> RustBuffer { + return FfiConverterTypeComposeAccount.lower(value) +} + + +/** + * Parameters for composing a signer-agnostic transaction. + */ +public struct ComposeParams { + /** + * Wallet configuration (key, server, network) + */ + public var wallet: WalletParams + /** + * Desired transaction outputs + */ + public var outputs: [ComposeOutput] + /** + * Fee rates to evaluate (sat/vB), one PSBT per rate + */ + public var feeRates: [Float] + /** + * UTXO selection strategy (defaults to BranchAndBound) + */ + public var coinSelection: CoinSelection? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Wallet configuration (key, server, network) + */wallet: WalletParams, + /** + * Desired transaction outputs + */outputs: [ComposeOutput], + /** + * Fee rates to evaluate (sat/vB), one PSBT per rate + */feeRates: [Float], + /** + * UTXO selection strategy (defaults to BranchAndBound) + */coinSelection: CoinSelection?) { + self.wallet = wallet + self.outputs = outputs + self.feeRates = feeRates + self.coinSelection = coinSelection + } +} + +#if compiler(>=6) +extension ComposeParams: Sendable {} +#endif + + +extension ComposeParams: Equatable, Hashable { + public static func ==(lhs: ComposeParams, rhs: ComposeParams) -> Bool { + if lhs.wallet != rhs.wallet { + return false + } + if lhs.outputs != rhs.outputs { + return false + } + if lhs.feeRates != rhs.feeRates { + return false + } + if lhs.coinSelection != rhs.coinSelection { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(wallet) + hasher.combine(outputs) + hasher.combine(feeRates) + hasher.combine(coinSelection) + } +} + +extension ComposeParams: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeComposeParams: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeParams { + return + try ComposeParams( + wallet: FfiConverterTypeWalletParams.read(from: &buf), + outputs: FfiConverterSequenceTypeComposeOutput.read(from: &buf), + feeRates: FfiConverterSequenceFloat.read(from: &buf), + coinSelection: FfiConverterOptionTypeCoinSelection.read(from: &buf) + ) + } + + public static func write(_ value: ComposeParams, into buf: inout [UInt8]) { + FfiConverterTypeWalletParams.write(value.wallet, into: &buf) + FfiConverterSequenceTypeComposeOutput.write(value.outputs, into: &buf) + FfiConverterSequenceFloat.write(value.feeRates, into: &buf) + FfiConverterOptionTypeCoinSelection.write(value.coinSelection, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeComposeParams_lift(_ buf: RustBuffer) throws -> ComposeParams { + return try FfiConverterTypeComposeParams.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeComposeParams_lower(_ value: ComposeParams) -> RustBuffer { + return FfiConverterTypeComposeParams.lower(value) +} + + +public struct CreateCjitOptions { + public var source: String? + public var discountCode: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(source: String?, discountCode: String?) { + self.source = source + self.discountCode = discountCode + } +} + +#if compiler(>=6) +extension CreateCjitOptions: Sendable {} +#endif + + +extension CreateCjitOptions: Equatable, Hashable { + public static func ==(lhs: CreateCjitOptions, rhs: CreateCjitOptions) -> Bool { + if lhs.source != rhs.source { + return false + } + if lhs.discountCode != rhs.discountCode { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(source) + hasher.combine(discountCode) } } @@ -7439,6 +7999,135 @@ public func FfiConverterTypePubkyProfileLink_lower(_ value: PubkyProfileLink) -> } +/** + * Result from querying a single Bitcoin address. + */ +public struct SingleAddressInfoResult { + /** + * The queried address + */ + public var address: String + /** + * Total confirmed balance in satoshis + */ + public var balance: UInt64 + /** + * UTXOs for this address + */ + public var utxos: [AccountUtxo] + /** + * Number of transactions involving this address + */ + public var transfers: UInt32 + /** + * Current blockchain tip height + */ + public var blockHeight: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The queried address + */address: String, + /** + * Total confirmed balance in satoshis + */balance: UInt64, + /** + * UTXOs for this address + */utxos: [AccountUtxo], + /** + * Number of transactions involving this address + */transfers: UInt32, + /** + * Current blockchain tip height + */blockHeight: UInt32) { + self.address = address + self.balance = balance + self.utxos = utxos + self.transfers = transfers + self.blockHeight = blockHeight + } +} + +#if compiler(>=6) +extension SingleAddressInfoResult: Sendable {} +#endif + + +extension SingleAddressInfoResult: Equatable, Hashable { + public static func ==(lhs: SingleAddressInfoResult, rhs: SingleAddressInfoResult) -> Bool { + if lhs.address != rhs.address { + return false + } + if lhs.balance != rhs.balance { + return false + } + if lhs.utxos != rhs.utxos { + return false + } + if lhs.transfers != rhs.transfers { + return false + } + if lhs.blockHeight != rhs.blockHeight { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(address) + hasher.combine(balance) + hasher.combine(utxos) + hasher.combine(transfers) + hasher.combine(blockHeight) + } +} + +extension SingleAddressInfoResult: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSingleAddressInfoResult: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SingleAddressInfoResult { + return + try SingleAddressInfoResult( + address: FfiConverterString.read(from: &buf), + balance: FfiConverterUInt64.read(from: &buf), + utxos: FfiConverterSequenceTypeAccountUtxo.read(from: &buf), + transfers: FfiConverterUInt32.read(from: &buf), + blockHeight: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: SingleAddressInfoResult, into buf: inout [UInt8]) { + FfiConverterString.write(value.address, into: &buf) + FfiConverterUInt64.write(value.balance, into: &buf) + FfiConverterSequenceTypeAccountUtxo.write(value.utxos, into: &buf) + FfiConverterUInt32.write(value.transfers, into: &buf) + FfiConverterUInt32.write(value.blockHeight, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSingleAddressInfoResult_lift(_ buf: RustBuffer) throws -> SingleAddressInfoResult { + return try FfiConverterTypeSingleAddressInfoResult.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSingleAddressInfoResult_lower(_ value: SingleAddressInfoResult) -> RustBuffer { + return FfiConverterTypeSingleAddressInfoResult.lower(value) +} + + public struct SweepResult { /** * The transaction ID of the sweep transaction @@ -9617,6 +10306,10 @@ public struct TrezorSignedTx { * Serialized transaction (hex) */ public var serializedTx: String + /** + * Broadcast transaction ID (populated when push=true) + */ + public var txid: String? // Default memberwise initializers are never public by default, so we // declare one manually. @@ -9626,9 +10319,13 @@ public struct TrezorSignedTx { */signatures: [String], /** * Serialized transaction (hex) - */serializedTx: String) { + */serializedTx: String, + /** + * Broadcast transaction ID (populated when push=true) + */txid: String?) { self.signatures = signatures self.serializedTx = serializedTx + self.txid = txid } } @@ -9645,12 +10342,16 @@ extension TrezorSignedTx: Equatable, Hashable { if lhs.serializedTx != rhs.serializedTx { return false } + if lhs.txid != rhs.txid { + return false + } return true } public func hash(into hasher: inout Hasher) { hasher.combine(signatures) hasher.combine(serializedTx) + hasher.combine(txid) } } @@ -9666,13 +10367,15 @@ public struct FfiConverterTypeTrezorSignedTx: FfiConverterRustBuffer { return try TrezorSignedTx( signatures: FfiConverterSequenceString.read(from: &buf), - serializedTx: FfiConverterString.read(from: &buf) + serializedTx: FfiConverterString.read(from: &buf), + txid: FfiConverterOptionString.read(from: &buf) ) } public static func write(_ value: TrezorSignedTx, into buf: inout [UInt8]) { FfiConverterSequenceString.write(value.signatures, into: &buf) FfiConverterString.write(value.serializedTx, into: &buf) + FfiConverterOptionString.write(value.txid, into: &buf) } } @@ -10660,27 +11363,434 @@ public func FfiConverterTypeValidationResult_lower(_ value: ValidationResult) -> return FfiConverterTypeValidationResult.lower(value) } -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. - -public enum Activity { - - case onchain(OnchainActivity - ) - case lightning(LightningActivity - ) -} - -#if compiler(>=6) -extension Activity: Sendable {} -#endif +/** + * Common parameters for creating and syncing a watch-only BDK wallet. + */ +public struct WalletParams { + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + */ + public var extendedKey: String + /** + * Electrum server URL for wallet sync + */ + public var electrumUrl: String + /** + * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + */ + public var fingerprint: String? + /** + * Bitcoin network (auto-detected from key prefix if not specified) + */ + public var network: Network? + /** + * Override account type for ambiguous key prefixes (xpub/tpub) + */ + public var accountType: AccountType? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + */extendedKey: String, + /** + * Electrum server URL for wallet sync + */electrumUrl: String, + /** + * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + */fingerprint: String?, + /** + * Bitcoin network (auto-detected from key prefix if not specified) + */network: Network?, + /** + * Override account type for ambiguous key prefixes (xpub/tpub) + */accountType: AccountType?) { + self.extendedKey = extendedKey + self.electrumUrl = electrumUrl + self.fingerprint = fingerprint + self.network = network + self.accountType = accountType + } +} + +#if compiler(>=6) +extension WalletParams: Sendable {} +#endif + + +extension WalletParams: Equatable, Hashable { + public static func ==(lhs: WalletParams, rhs: WalletParams) -> Bool { + if lhs.extendedKey != rhs.extendedKey { + return false + } + if lhs.electrumUrl != rhs.electrumUrl { + return false + } + if lhs.fingerprint != rhs.fingerprint { + return false + } + if lhs.network != rhs.network { + return false + } + if lhs.accountType != rhs.accountType { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(extendedKey) + hasher.combine(electrumUrl) + hasher.combine(fingerprint) + hasher.combine(network) + hasher.combine(accountType) + } +} + +extension WalletParams: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWalletParams: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WalletParams { + return + try WalletParams( + extendedKey: FfiConverterString.read(from: &buf), + electrumUrl: FfiConverterString.read(from: &buf), + fingerprint: FfiConverterOptionString.read(from: &buf), + network: FfiConverterOptionTypeNetwork.read(from: &buf), + accountType: FfiConverterOptionTypeAccountType.read(from: &buf) + ) + } + + public static func write(_ value: WalletParams, into buf: inout [UInt8]) { + FfiConverterString.write(value.extendedKey, into: &buf) + FfiConverterString.write(value.electrumUrl, into: &buf) + FfiConverterOptionString.write(value.fingerprint, into: &buf) + FfiConverterOptionTypeNetwork.write(value.network, into: &buf) + FfiConverterOptionTypeAccountType.write(value.accountType, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletParams_lift(_ buf: RustBuffer) throws -> WalletParams { + return try FfiConverterTypeWalletParams.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletParams_lower(_ value: WalletParams) -> RustBuffer { + return FfiConverterTypeWalletParams.lower(value) +} + + +/** + * Errors specific to account info operations (BDK/Electrum-based). + */ +public enum AccountInfoError: Swift.Error { + + + + /** + * The provided extended public key is invalid or cannot be parsed + */ + case InvalidExtendedKey(errorDetails: String + ) + /** + * The provided address is invalid + */ + case InvalidAddress(errorDetails: String + ) + /** + * Electrum connection or query failed + */ + case ElectrumError(errorDetails: String + ) + /** + * BDK wallet creation or operation error + */ + case WalletError(errorDetails: String + ) + /** + * Wallet sync with Electrum failed + */ + case SyncError(errorDetails: String + ) + /** + * The key type/prefix is not recognized + */ + case UnsupportedKeyType(errorDetails: String + ) + /** + * Network mismatch between key prefix and specified network + */ + case NetworkMismatch(errorDetails: String + ) + /** + * Invalid transaction ID provided + */ + case InvalidTxid(errorDetails: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountInfoError: FfiConverterRustBuffer { + typealias SwiftType = AccountInfoError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountInfoError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .InvalidExtendedKey( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .InvalidAddress( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .ElectrumError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .WalletError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .SyncError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .UnsupportedKeyType( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .NetworkMismatch( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .InvalidTxid( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AccountInfoError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InvalidExtendedKey(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidAddress(errorDetails): + writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ElectrumError(errorDetails): + writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .WalletError(errorDetails): + writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SyncError(errorDetails): + writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .UnsupportedKeyType(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .NetworkMismatch(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidTxid(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoError_lift(_ buf: RustBuffer) throws -> AccountInfoError { + return try FfiConverterTypeAccountInfoError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoError_lower(_ value: AccountInfoError) -> RustBuffer { + return FfiConverterTypeAccountInfoError.lower(value) +} + + +extension AccountInfoError: Equatable, Hashable {} + +extension AccountInfoError: Codable {} + + + + +extension AccountInfoError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeActivity: FfiConverterRustBuffer { - typealias SwiftType = Activity + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Account type classification for extended public keys. + * + * Determines the BIP standard, derivation path purpose, and script type. + */ + +public enum AccountType { + + /** + * BIP44 legacy (P2PKH) — xpub/tpub prefix + */ + case legacy + /** + * BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + */ + case wrappedSegwit + /** + * BIP84 native segwit (P2WPKH) — zpub/vpub prefix + */ + case nativeSegwit + /** + * BIP86 taproot (P2TR) + */ + case taproot +} + + +#if compiler(>=6) +extension AccountType: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountType: FfiConverterRustBuffer { + typealias SwiftType = AccountType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountType { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .legacy + + case 2: return .wrappedSegwit + + case 3: return .nativeSegwit + + case 4: return .taproot + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AccountType, into buf: inout [UInt8]) { + switch value { + + + case .legacy: + writeInt(&buf, Int32(1)) + + + case .wrappedSegwit: + writeInt(&buf, Int32(2)) + + + case .nativeSegwit: + writeInt(&buf, Int32(3)) + + + case .taproot: + writeInt(&buf, Int32(4)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountType_lift(_ buf: RustBuffer) throws -> AccountType { + return try FfiConverterTypeAccountType.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountType_lower(_ value: AccountType) -> RustBuffer { + return FfiConverterTypeAccountType.lower(value) +} + + +extension AccountType: Equatable, Hashable {} + +extension AccountType: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum Activity { + + case onchain(OnchainActivity + ) + case lightning(LightningActivity + ) +} + + +#if compiler(>=6) +extension Activity: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivity: FfiConverterRustBuffer { + typealias SwiftType = Activity public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Activity { let variant: Int32 = try readInt(&buf) @@ -11472,13 +12582,121 @@ public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { FfiConverterString.write(errorDetails, into: &buf) - case let .InvalidParameter(errorDetails): - writeInt(&buf, Int32(12)) + case let .InvalidParameter(errorDetails): + writeInt(&buf, Int32(12)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DatabaseError(errorDetails): + writeInt(&buf, Int32(13)) + FfiConverterString.write(errorDetails, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBlocktankError_lift(_ buf: RustBuffer) throws -> BlocktankError { + return try FfiConverterTypeBlocktankError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBlocktankError_lower(_ value: BlocktankError) -> RustBuffer { + return FfiConverterTypeBlocktankError.lower(value) +} + + +extension BlocktankError: Equatable, Hashable {} + +extension BlocktankError: Codable {} + + + + +extension BlocktankError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + + +public enum BroadcastError: Swift.Error { + + + + case InvalidHex(errorDetails: String + ) + case InvalidTransaction(errorDetails: String + ) + case ElectrumError(errorDetails: String + ) + case TaskError(errorDetails: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBroadcastError: FfiConverterRustBuffer { + typealias SwiftType = BroadcastError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BroadcastError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .InvalidHex( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .InvalidTransaction( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .ElectrumError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .TaskError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BroadcastError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InvalidHex(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidTransaction(errorDetails): + writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ElectrumError(errorDetails): + writeInt(&buf, Int32(3)) FfiConverterString.write(errorDetails, into: &buf) - case let .DatabaseError(errorDetails): - writeInt(&buf, Int32(13)) + case let .TaskError(errorDetails): + writeInt(&buf, Int32(4)) FfiConverterString.write(errorDetails, into: &buf) } @@ -11489,26 +12707,26 @@ public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBlocktankError_lift(_ buf: RustBuffer) throws -> BlocktankError { - return try FfiConverterTypeBlocktankError.lift(buf) +public func FfiConverterTypeBroadcastError_lift(_ buf: RustBuffer) throws -> BroadcastError { + return try FfiConverterTypeBroadcastError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBlocktankError_lower(_ value: BlocktankError) -> RustBuffer { - return FfiConverterTypeBlocktankError.lower(value) +public func FfiConverterTypeBroadcastError_lower(_ value: BroadcastError) -> RustBuffer { + return FfiConverterTypeBroadcastError.lower(value) } -extension BlocktankError: Equatable, Hashable {} +extension BroadcastError: Equatable, Hashable {} -extension BlocktankError: Codable {} +extension BroadcastError: Codable {} -extension BlocktankError: Foundation.LocalizedError { +extension BroadcastError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -12081,29 +13299,307 @@ public struct FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { } } - public static func write(_ value: BtPaymentState2, into buf: inout [UInt8]) { + public static func write(_ value: BtPaymentState2, into buf: inout [UInt8]) { + switch value { + + + case .created: + writeInt(&buf, Int32(1)) + + + case .paid: + writeInt(&buf, Int32(2)) + + + case .refunded: + writeInt(&buf, Int32(3)) + + + case .refundAvailable: + writeInt(&buf, Int32(4)) + + + case .canceled: + writeInt(&buf, Int32(5)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBtPaymentState2_lift(_ buf: RustBuffer) throws -> BtPaymentState2 { + return try FfiConverterTypeBtPaymentState2.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBtPaymentState2_lower(_ value: BtPaymentState2) -> RustBuffer { + return FfiConverterTypeBtPaymentState2.lower(value) +} + + +extension BtPaymentState2: Equatable, Hashable {} + +extension BtPaymentState2: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum CJitStateEnum { + + case created + case completed + case expired + case failed +} + + +#if compiler(>=6) +extension CJitStateEnum: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { + typealias SwiftType = CJitStateEnum + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CJitStateEnum { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .created + + case 2: return .completed + + case 3: return .expired + + case 4: return .failed + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: CJitStateEnum, into buf: inout [UInt8]) { + switch value { + + + case .created: + writeInt(&buf, Int32(1)) + + + case .completed: + writeInt(&buf, Int32(2)) + + + case .expired: + writeInt(&buf, Int32(3)) + + + case .failed: + writeInt(&buf, Int32(4)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCJitStateEnum_lift(_ buf: RustBuffer) throws -> CJitStateEnum { + return try FfiConverterTypeCJitStateEnum.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCJitStateEnum_lower(_ value: CJitStateEnum) -> RustBuffer { + return FfiConverterTypeCJitStateEnum.lower(value) +} + + +extension CJitStateEnum: Equatable, Hashable {} + +extension CJitStateEnum: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Coin selection strategy for transaction composition. + */ + +public enum CoinSelection { + + /** + * Branch-and-bound (default). Minimizes change by searching for exact matches. + */ + case branchAndBound + /** + * Selects largest UTXOs first. Useful for UTXO consolidation. + */ + case largestFirst + /** + * Selects oldest UTXOs first. Maximizes coin-age spending. + */ + case oldestFirst +} + + +#if compiler(>=6) +extension CoinSelection: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCoinSelection: FfiConverterRustBuffer { + typealias SwiftType = CoinSelection + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CoinSelection { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .branchAndBound + + case 2: return .largestFirst + + case 3: return .oldestFirst + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: CoinSelection, into buf: inout [UInt8]) { + switch value { + + + case .branchAndBound: + writeInt(&buf, Int32(1)) + + + case .largestFirst: + writeInt(&buf, Int32(2)) + + + case .oldestFirst: + writeInt(&buf, Int32(3)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCoinSelection_lift(_ buf: RustBuffer) throws -> CoinSelection { + return try FfiConverterTypeCoinSelection.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCoinSelection_lower(_ value: CoinSelection) -> RustBuffer { + return FfiConverterTypeCoinSelection.lower(value) +} + + +extension CoinSelection: Equatable, Hashable {} + +extension CoinSelection: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Output specification for transaction composition. + */ + +public enum ComposeOutput { + + /** + * Payment to a specific address with a fixed amount (satoshis) + */ + case payment(address: String, amountSats: UInt64 + ) + /** + * Send all remaining funds (after fees) to an address + */ + case sendMax(address: String + ) + /** + * OP_RETURN data output (hex-encoded payload) + */ + case opReturn(dataHex: String + ) +} + + +#if compiler(>=6) +extension ComposeOutput: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeComposeOutput: FfiConverterRustBuffer { + typealias SwiftType = ComposeOutput + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeOutput { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .payment(address: try FfiConverterString.read(from: &buf), amountSats: try FfiConverterUInt64.read(from: &buf) + ) + + case 2: return .sendMax(address: try FfiConverterString.read(from: &buf) + ) + + case 3: return .opReturn(dataHex: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ComposeOutput, into buf: inout [UInt8]) { switch value { - case .created: + case let .payment(address,amountSats): writeInt(&buf, Int32(1)) + FfiConverterString.write(address, into: &buf) + FfiConverterUInt64.write(amountSats, into: &buf) + - - case .paid: + case let .sendMax(address): writeInt(&buf, Int32(2)) + FfiConverterString.write(address, into: &buf) + - - case .refunded: + case let .opReturn(dataHex): writeInt(&buf, Int32(3)) - - - case .refundAvailable: - writeInt(&buf, Int32(4)) - - - case .canceled: - writeInt(&buf, Int32(5)) - + FfiConverterString.write(dataHex, into: &buf) + } } } @@ -12112,21 +13608,21 @@ public struct FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState2_lift(_ buf: RustBuffer) throws -> BtPaymentState2 { - return try FfiConverterTypeBtPaymentState2.lift(buf) +public func FfiConverterTypeComposeOutput_lift(_ buf: RustBuffer) throws -> ComposeOutput { + return try FfiConverterTypeComposeOutput.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState2_lower(_ value: BtPaymentState2) -> RustBuffer { - return FfiConverterTypeBtPaymentState2.lower(value) +public func FfiConverterTypeComposeOutput_lower(_ value: ComposeOutput) -> RustBuffer { + return FfiConverterTypeComposeOutput.lower(value) } -extension BtPaymentState2: Equatable, Hashable {} +extension ComposeOutput: Equatable, Hashable {} -extension BtPaymentState2: Codable {} +extension ComposeOutput: Codable {} @@ -12135,61 +13631,79 @@ extension BtPaymentState2: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Result of composing a transaction at a single fee rate. + */ -public enum CJitStateEnum { +public enum ComposeResult { - case created - case completed - case expired - case failed + /** + * Successfully built a signable PSBT + */ + case success( + /** + * Base64-encoded PSBT ready for signing + */psbt: String, + /** + * Total fee in satoshis + */fee: UInt64, + /** + * Target fee rate in sat/vB (actual may differ slightly due to rounding) + */feeRate: Float, + /** + * Total value spent (payments + fee, excluding change). + * Uses BDK's `sent - received` semantics, which may undercount for + * self-transfers where the destination is also owned by the wallet. + */totalSpent: UInt64 + ) + /** + * Composition failed (e.g. insufficient funds) + */ + case error(error: String + ) } #if compiler(>=6) -extension CJitStateEnum: Sendable {} +extension ComposeResult: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { - typealias SwiftType = CJitStateEnum +public struct FfiConverterTypeComposeResult: FfiConverterRustBuffer { + typealias SwiftType = ComposeResult - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CJitStateEnum { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeResult { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .created - - case 2: return .completed - - case 3: return .expired + case 1: return .success(psbt: try FfiConverterString.read(from: &buf), fee: try FfiConverterUInt64.read(from: &buf), feeRate: try FfiConverterFloat.read(from: &buf), totalSpent: try FfiConverterUInt64.read(from: &buf) + ) - case 4: return .failed + case 2: return .error(error: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: CJitStateEnum, into buf: inout [UInt8]) { + public static func write(_ value: ComposeResult, into buf: inout [UInt8]) { switch value { - case .created: + case let .success(psbt,fee,feeRate,totalSpent): writeInt(&buf, Int32(1)) + FfiConverterString.write(psbt, into: &buf) + FfiConverterUInt64.write(fee, into: &buf) + FfiConverterFloat.write(feeRate, into: &buf) + FfiConverterUInt64.write(totalSpent, into: &buf) + - - case .completed: + case let .error(error): writeInt(&buf, Int32(2)) - - - case .expired: - writeInt(&buf, Int32(3)) - - - case .failed: - writeInt(&buf, Int32(4)) - + FfiConverterString.write(error, into: &buf) + } } } @@ -12198,21 +13712,21 @@ public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCJitStateEnum_lift(_ buf: RustBuffer) throws -> CJitStateEnum { - return try FfiConverterTypeCJitStateEnum.lift(buf) +public func FfiConverterTypeComposeResult_lift(_ buf: RustBuffer) throws -> ComposeResult { + return try FfiConverterTypeComposeResult.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCJitStateEnum_lower(_ value: CJitStateEnum) -> RustBuffer { - return FfiConverterTypeCJitStateEnum.lower(value) +public func FfiConverterTypeComposeResult_lower(_ value: ComposeResult) -> RustBuffer { + return FfiConverterTypeComposeResult.lower(value) } -extension CJitStateEnum: Equatable, Hashable {} +extension ComposeResult: Equatable, Hashable {} -extension CJitStateEnum: Codable {} +extension ComposeResult: Codable {} @@ -14855,6 +16369,30 @@ fileprivate struct FfiConverterOptionTypeTrezorFeatures: FfiConverterRustBuffer } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeAccountType: FfiConverterRustBuffer { + typealias SwiftType = AccountType? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeAccountType.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeAccountType.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -14975,6 +16513,30 @@ fileprivate struct FfiConverterOptionTypeCJitStateEnum: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeCoinSelection: FfiConverterRustBuffer { + typealias SwiftType = CoinSelection? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeCoinSelection.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeCoinSelection.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -15215,6 +16777,31 @@ fileprivate struct FfiConverterOptionDictionaryStringString: FfiConverterRustBuf } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceFloat: FfiConverterRustBuffer { + typealias SwiftType = [Float] + + public static func write(_ value: [Float], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterFloat.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [Float] { + let len: Int32 = try readInt(&buf) + var seq = [Float]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterFloat.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -15240,6 +16827,31 @@ fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeAccountUtxo: FfiConverterRustBuffer { + typealias SwiftType = [AccountUtxo] + + public static func write(_ value: [AccountUtxo], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeAccountUtxo.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [AccountUtxo] { + let len: Int32 = try readInt(&buf) + var seq = [AccountUtxo]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeAccountUtxo.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -15840,6 +17452,56 @@ fileprivate struct FfiConverterSequenceTypeActivity: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeComposeOutput: FfiConverterRustBuffer { + typealias SwiftType = [ComposeOutput] + + public static func write(_ value: [ComposeOutput], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeComposeOutput.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ComposeOutput] { + let len: Int32 = try readInt(&buf) + var seq = [ComposeOutput]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeComposeOutput.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeComposeResult: FfiConverterRustBuffer { + typealias SwiftType = [ComposeResult] + + public static func write(_ value: [ComposeResult], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeComposeResult.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ComposeResult] { + let len: Int32 = try readInt(&buf) + var seq = [ComposeResult]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeComposeResult.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -16547,6 +18209,83 @@ public func mnemonicToSeed(mnemonicPhrase: String, passphrase: String?)throws - ) }) } +/** + * Broadcast a signed raw transaction via Electrum. + * + * Takes a hex-encoded serialized transaction and an Electrum server URL. + * Returns the transaction ID on success. + */ +public func onchainBroadcastRawTx(serializedTx: String, electrumUrl: String)async throws -> String { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx(FfiConverterString.lower(serializedTx),FfiConverterString.lower(electrumUrl) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterString.lift, + errorHandler: FfiConverterTypeBroadcastError_lift + ) +} +/** + * Compose a transaction for multiple fee rates, returning one PSBT per rate. + * + * Creates a BDK wallet from the extended key, syncs via Electrum, then + * builds PSBTs using BDK's TxBuilder. The PSBTs include BIP32 derivation + * paths (when fingerprint is provided) and are ready for signing by any + * PSBT-compatible signer (Trezor, Ledger, software wallet, etc.). + */ +public func onchainComposeTransaction(params: ComposeParams)async -> [ComposeResult] { + return + try! await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_onchain_compose_transaction(FfiConverterTypeComposeParams_lower(params) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeComposeResult.lift, + errorHandler: nil + + ) +} +/** + * Query account information for an extended public key via Electrum. + */ +public func onchainGetAccountInfo(extendedKey: String, electrumUrl: String, network: Network?, gapLimit: UInt32?, scriptType: AccountType?)async throws -> AccountInfoResult { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_onchain_get_account_info(FfiConverterString.lower(extendedKey),FfiConverterString.lower(electrumUrl),FfiConverterOptionTypeNetwork.lower(network),FfiConverterOptionUInt32.lower(gapLimit),FfiConverterOptionTypeAccountType.lower(scriptType) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeAccountInfoResult_lift, + errorHandler: FfiConverterTypeAccountInfoError_lift + ) +} +/** + * Query balance and UTXOs for a single Bitcoin address via Electrum. + */ +public func onchainGetAddressInfo(address: String, electrumUrl: String, network: Network?)async throws -> SingleAddressInfoResult { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_onchain_get_address_info(FfiConverterString.lower(address),FfiConverterString.lower(electrumUrl),FfiConverterOptionTypeNetwork.lower(network) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeSingleAddressInfoResult_lift, + errorHandler: FfiConverterTypeAccountInfoError_lift + ) +} public func openChannel(orderId: String, connectionString: String)async throws -> IBtOrder { return try await uniffiRustCallAsync( @@ -16755,6 +18494,16 @@ public func testNotification(deviceToken: String, secretMessage: String, notific errorHandler: FfiConverterTypeBlocktankError_lift ) } +/** + * Convert an account type to its corresponding Trezor script type. + */ +public func trezorAccountTypeToScriptType(accountType: AccountType) -> TrezorScriptType { + return try! FfiConverterTypeTrezorScriptType_lift(try! rustCall() { + uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type( + FfiConverterTypeAccountType_lower(accountType),$0 + ) +}) +} /** * Clear stored Bluetooth pairing credentials for a specific Trezor device. * @@ -17471,6 +19220,18 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_mnemonic_to_seed() != 40039) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx() != 45163) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_onchain_compose_transaction() != 20767) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_onchain_get_account_info() != 30087) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_onchain_get_address_info() != 4749) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_open_channel() != 21402) { return InitializationResult.apiChecksumMismatch } @@ -17522,6 +19283,9 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_test_notification() != 32857) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type() != 16116) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_trezor_clear_credentials() != 41940) { return InitializationResult.apiChecksumMismatch } diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index de38032..be0b90c 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -790,6 +790,26 @@ RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_entropy(RustBuffer mnemonic_phr RustBuffer uniffi_bitkitcore_fn_func_mnemonic_to_seed(RustBuffer mnemonic_phrase, RustBuffer passphrase, RustCallStatus *_Nonnull out_status ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint64_t uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx(RustBuffer serialized_tx, RustBuffer electrum_url +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint64_t uniffi_bitkitcore_fn_func_onchain_compose_transaction(RustBuffer params +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_account_info(RustBuffer extended_key, RustBuffer electrum_url, RustBuffer network, RustBuffer gap_limit, RustBuffer script_type +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint64_t uniffi_bitkitcore_fn_func_onchain_get_address_info(RustBuffer address, RustBuffer electrum_url, RustBuffer network +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_OPEN_CHANNEL uint64_t uniffi_bitkitcore_fn_func_open_channel(RustBuffer order_id, RustBuffer connection_string @@ -877,6 +897,11 @@ uint64_t uniffi_bitkitcore_fn_func_start_pubky_auth(RustBuffer caps uint64_t uniffi_bitkitcore_fn_func_test_notification(RustBuffer device_token, RustBuffer secret_message, RustBuffer notification_type, RustBuffer custom_url ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +RustBuffer uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type(RustBuffer account_type, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_TREZOR_CLEAR_CREDENTIALS uint64_t uniffi_bitkitcore_fn_func_trezor_clear_credentials(RustBuffer device_id @@ -1740,6 +1765,30 @@ uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_entropy(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_MNEMONIC_TO_SEED uint16_t uniffi_bitkitcore_checksum_func_mnemonic_to_seed(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_BROADCAST_RAW_TX +uint16_t uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_COMPOSE_TRANSACTION +uint16_t uniffi_bitkitcore_checksum_func_onchain_compose_transaction(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ACCOUNT_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_account_info(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ONCHAIN_GET_ADDRESS_INFO +uint16_t uniffi_bitkitcore_checksum_func_onchain_get_address_info(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_OPEN_CHANNEL @@ -1842,6 +1891,12 @@ uint16_t uniffi_bitkitcore_checksum_func_start_pubky_auth(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TEST_NOTIFICATION uint16_t uniffi_bitkitcore_checksum_func_test_notification(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_ACCOUNT_TYPE_TO_SCRIPT_TYPE +uint16_t uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_TREZOR_CLEAR_CREDENTIALS diff --git a/bindings/python/bitkitcore/bitkitcore.py b/bindings/python/bitkitcore/bitkitcore.py index 6fe9ce7..1eb6ec2 100644 --- a/bindings/python/bitkitcore/bitkitcore.py +++ b/bindings/python/bitkitcore/bitkitcore.py @@ -587,6 +587,14 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_mnemonic_to_seed() != 40039: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx() != 45163: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_onchain_compose_transaction() != 20767: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_onchain_get_account_info() != 30087: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_onchain_get_address_info() != 4749: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_open_channel() != 21402: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_prepare_sweep_transaction() != 18273: @@ -621,6 +629,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_test_notification() != 32857: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type() != 16116: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_trezor_clear_credentials() != 41940: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_trezor_connect() != 6551: @@ -1340,6 +1350,29 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_bitkitcore_fn_func_mnemonic_to_seed.restype = _UniffiRustBuffer +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_compose_transaction.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_compose_transaction.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_account_info.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_account_info.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_address_info.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_address_info.restype = ctypes.c_uint64 _UniffiLib.uniffi_bitkitcore_fn_func_open_channel.argtypes = ( _UniffiRustBuffer, _UniffiRustBuffer, @@ -1433,6 +1466,11 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiRustBuffer, ) _UniffiLib.uniffi_bitkitcore_fn_func_test_notification.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type.restype = _UniffiRustBuffer _UniffiLib.uniffi_bitkitcore_fn_func_trezor_clear_credentials.argtypes = ( _UniffiRustBuffer, ) @@ -2053,6 +2091,18 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_mnemonic_to_seed.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_mnemonic_to_seed.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_broadcast_raw_tx.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_compose_transaction.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_compose_transaction.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_get_account_info.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_get_account_info.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_get_address_info.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_onchain_get_address_info.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_open_channel.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_open_channel.restype = ctypes.c_uint16 @@ -2104,6 +2154,9 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_test_notification.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_test_notification.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_trezor_account_type_to_script_type.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_trezor_clear_credentials.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_trezor_clear_credentials.restype = ctypes.c_uint16 @@ -2353,6 +2406,15 @@ def read(buf): def write(value, buf): buf.write_i64(value) +class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_float() + + @staticmethod + def write(value, buf): + buf.write_float(value) + class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): @staticmethod def read(buf): @@ -2442,17 +2504,17 @@ def write(value, buf): class AccountAddresses: """ - Account addresses + Grouped address lists for an account. """ used: "typing.List[AddressInfo]" """ - Used addresses + Used receive addresses (have at least one transaction) """ unused: "typing.List[AddressInfo]" """ - Unused addresses + Unused receive addresses (no transactions yet) """ change: "typing.List[AddressInfo]" @@ -2499,6 +2561,223 @@ def write(value, buf): _UniffiConverterSequenceTypeAddressInfo.write(value.change, buf) +class AccountInfoResult: + """ + Result from querying an extended public key via Electrum. + """ + + account: "ComposeAccount" + """ + The account structure with addresses and UTXOs + """ + + balance: "int" + """ + Total confirmed balance in satoshis + """ + + utxo_count: "int" + """ + Number of UTXOs + """ + + account_type: "AccountType" + """ + The detected or specified account type + """ + + block_height: "int" + """ + The current blockchain tip height + """ + + def __init__(self, *, account: "ComposeAccount", balance: "int", utxo_count: "int", account_type: "AccountType", block_height: "int"): + self.account = account + self.balance = balance + self.utxo_count = utxo_count + self.account_type = account_type + self.block_height = block_height + + def __str__(self): + return "AccountInfoResult(account={}, balance={}, utxo_count={}, account_type={}, block_height={})".format(self.account, self.balance, self.utxo_count, self.account_type, self.block_height) + + def __eq__(self, other): + if self.account != other.account: + return False + if self.balance != other.balance: + return False + if self.utxo_count != other.utxo_count: + return False + if self.account_type != other.account_type: + return False + if self.block_height != other.block_height: + return False + return True + +class _UniffiConverterTypeAccountInfoResult(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return AccountInfoResult( + account=_UniffiConverterTypeComposeAccount.read(buf), + balance=_UniffiConverterUInt64.read(buf), + utxo_count=_UniffiConverterUInt32.read(buf), + account_type=_UniffiConverterTypeAccountType.read(buf), + block_height=_UniffiConverterUInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeComposeAccount.check_lower(value.account) + _UniffiConverterUInt64.check_lower(value.balance) + _UniffiConverterUInt32.check_lower(value.utxo_count) + _UniffiConverterTypeAccountType.check_lower(value.account_type) + _UniffiConverterUInt32.check_lower(value.block_height) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeComposeAccount.write(value.account, buf) + _UniffiConverterUInt64.write(value.balance, buf) + _UniffiConverterUInt32.write(value.utxo_count, buf) + _UniffiConverterTypeAccountType.write(value.account_type, buf) + _UniffiConverterUInt32.write(value.block_height, buf) + + +class AccountUtxo: + """ + A UTXO associated with an account or address. + """ + + txid: "str" + """ + Transaction ID (hex) + """ + + vout: "int" + """ + Output index + """ + + amount: "int" + """ + Amount in satoshis + """ + + block_height: "int" + """ + Block height where the UTXO was confirmed (0 if unconfirmed) + """ + + address: "str" + """ + Address holding this UTXO + """ + + path: "str" + """ + BIP32 derivation path (e.g., "m/84'/0'/0'/0/0") + """ + + confirmations: "int" + """ + Number of confirmations (0 if unconfirmed) + """ + + coinbase: "bool" + """ + Whether this is a coinbase output + """ + + own: "bool" + """ + Whether this UTXO is owned by the account + """ + + required: "typing.Optional[bool]" + """ + Whether this UTXO must be included in the transaction + """ + + def __init__(self, *, txid: "str", vout: "int", amount: "int", block_height: "int", address: "str", path: "str", confirmations: "int", coinbase: "bool", own: "bool", required: "typing.Optional[bool]"): + self.txid = txid + self.vout = vout + self.amount = amount + self.block_height = block_height + self.address = address + self.path = path + self.confirmations = confirmations + self.coinbase = coinbase + self.own = own + self.required = required + + def __str__(self): + return "AccountUtxo(txid={}, vout={}, amount={}, block_height={}, address={}, path={}, confirmations={}, coinbase={}, own={}, required={})".format(self.txid, self.vout, self.amount, self.block_height, self.address, self.path, self.confirmations, self.coinbase, self.own, self.required) + + def __eq__(self, other): + if self.txid != other.txid: + return False + if self.vout != other.vout: + return False + if self.amount != other.amount: + return False + if self.block_height != other.block_height: + return False + if self.address != other.address: + return False + if self.path != other.path: + return False + if self.confirmations != other.confirmations: + return False + if self.coinbase != other.coinbase: + return False + if self.own != other.own: + return False + if self.required != other.required: + return False + return True + +class _UniffiConverterTypeAccountUtxo(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return AccountUtxo( + txid=_UniffiConverterString.read(buf), + vout=_UniffiConverterUInt32.read(buf), + amount=_UniffiConverterUInt64.read(buf), + block_height=_UniffiConverterUInt32.read(buf), + address=_UniffiConverterString.read(buf), + path=_UniffiConverterString.read(buf), + confirmations=_UniffiConverterUInt32.read(buf), + coinbase=_UniffiConverterBool.read(buf), + own=_UniffiConverterBool.read(buf), + required=_UniffiConverterOptionalBool.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.txid) + _UniffiConverterUInt32.check_lower(value.vout) + _UniffiConverterUInt64.check_lower(value.amount) + _UniffiConverterUInt32.check_lower(value.block_height) + _UniffiConverterString.check_lower(value.address) + _UniffiConverterString.check_lower(value.path) + _UniffiConverterUInt32.check_lower(value.confirmations) + _UniffiConverterBool.check_lower(value.coinbase) + _UniffiConverterBool.check_lower(value.own) + _UniffiConverterOptionalBool.check_lower(value.required) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.txid, buf) + _UniffiConverterUInt32.write(value.vout, buf) + _UniffiConverterUInt64.write(value.amount, buf) + _UniffiConverterUInt32.write(value.block_height, buf) + _UniffiConverterString.write(value.address, buf) + _UniffiConverterString.write(value.path, buf) + _UniffiConverterUInt32.write(value.confirmations, buf) + _UniffiConverterBool.write(value.coinbase, buf) + _UniffiConverterBool.write(value.own, buf) + _UniffiConverterOptionalBool.write(value.required, buf) + + class ActivityTags: activity_id: "str" tags: "typing.List[str]" @@ -2537,22 +2816,22 @@ def write(value, buf): class AddressInfo: """ - Address information + Information about a single address in an account. """ address: "str" """ - Address string + The Bitcoin address """ path: "str" """ - Derivation path + BIP32 derivation path """ transfers: "int" """ - Number of transfers + Number of transfers (real count in `get_address_info`, 1/0 presence flag in `get_account_info`) """ def __init__(self, *, address: "str", path: "str", transfers: "int"): @@ -2821,6 +3100,135 @@ def write(value, buf): _UniffiConverterString.write(value.channel_closure_reason, buf) +class ComposeAccount: + """ + Full account structure with addresses and UTXOs. + """ + + path: "str" + """ + Account derivation path (e.g., "m/84'/0'/0'") + """ + + addresses: "AccountAddresses" + """ + Categorized addresses + """ + + utxo: "typing.List[AccountUtxo]" + """ + Unspent transaction outputs + """ + + def __init__(self, *, path: "str", addresses: "AccountAddresses", utxo: "typing.List[AccountUtxo]"): + self.path = path + self.addresses = addresses + self.utxo = utxo + + def __str__(self): + return "ComposeAccount(path={}, addresses={}, utxo={})".format(self.path, self.addresses, self.utxo) + + def __eq__(self, other): + if self.path != other.path: + return False + if self.addresses != other.addresses: + return False + if self.utxo != other.utxo: + return False + return True + +class _UniffiConverterTypeComposeAccount(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ComposeAccount( + path=_UniffiConverterString.read(buf), + addresses=_UniffiConverterTypeAccountAddresses.read(buf), + utxo=_UniffiConverterSequenceTypeAccountUtxo.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.path) + _UniffiConverterTypeAccountAddresses.check_lower(value.addresses) + _UniffiConverterSequenceTypeAccountUtxo.check_lower(value.utxo) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.path, buf) + _UniffiConverterTypeAccountAddresses.write(value.addresses, buf) + _UniffiConverterSequenceTypeAccountUtxo.write(value.utxo, buf) + + +class ComposeParams: + """ + Parameters for composing a signer-agnostic transaction. + """ + + wallet: "WalletParams" + """ + Wallet configuration (key, server, network) + """ + + outputs: "typing.List[ComposeOutput]" + """ + Desired transaction outputs + """ + + fee_rates: "typing.List[float]" + """ + Fee rates to evaluate (sat/vB), one PSBT per rate + """ + + coin_selection: "typing.Optional[CoinSelection]" + """ + UTXO selection strategy (defaults to BranchAndBound) + """ + + def __init__(self, *, wallet: "WalletParams", outputs: "typing.List[ComposeOutput]", fee_rates: "typing.List[float]", coin_selection: "typing.Optional[CoinSelection]"): + self.wallet = wallet + self.outputs = outputs + self.fee_rates = fee_rates + self.coin_selection = coin_selection + + def __str__(self): + return "ComposeParams(wallet={}, outputs={}, fee_rates={}, coin_selection={})".format(self.wallet, self.outputs, self.fee_rates, self.coin_selection) + + def __eq__(self, other): + if self.wallet != other.wallet: + return False + if self.outputs != other.outputs: + return False + if self.fee_rates != other.fee_rates: + return False + if self.coin_selection != other.coin_selection: + return False + return True + +class _UniffiConverterTypeComposeParams(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ComposeParams( + wallet=_UniffiConverterTypeWalletParams.read(buf), + outputs=_UniffiConverterSequenceTypeComposeOutput.read(buf), + fee_rates=_UniffiConverterSequenceFloat.read(buf), + coin_selection=_UniffiConverterOptionalTypeCoinSelection.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeWalletParams.check_lower(value.wallet) + _UniffiConverterSequenceTypeComposeOutput.check_lower(value.outputs) + _UniffiConverterSequenceFloat.check_lower(value.fee_rates) + _UniffiConverterOptionalTypeCoinSelection.check_lower(value.coin_selection) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeWalletParams.write(value.wallet, buf) + _UniffiConverterSequenceTypeComposeOutput.write(value.outputs, buf) + _UniffiConverterSequenceFloat.write(value.fee_rates, buf) + _UniffiConverterOptionalTypeCoinSelection.write(value.coin_selection, buf) + + class CreateCjitOptions: source: "typing.Optional[str]" discount_code: "typing.Optional[str]" @@ -6149,13 +6557,94 @@ def write(value, buf): _UniffiConverterString.write(value.url, buf) -class SweepResult: - txid: "str" +class SingleAddressInfoResult: """ - The transaction ID of the sweep transaction + Result from querying a single Bitcoin address. """ - amount_swept: "int" + address: "str" + """ + The queried address + """ + + balance: "int" + """ + Total confirmed balance in satoshis + """ + + utxos: "typing.List[AccountUtxo]" + """ + UTXOs for this address + """ + + transfers: "int" + """ + Number of transactions involving this address + """ + + block_height: "int" + """ + Current blockchain tip height + """ + + def __init__(self, *, address: "str", balance: "int", utxos: "typing.List[AccountUtxo]", transfers: "int", block_height: "int"): + self.address = address + self.balance = balance + self.utxos = utxos + self.transfers = transfers + self.block_height = block_height + + def __str__(self): + return "SingleAddressInfoResult(address={}, balance={}, utxos={}, transfers={}, block_height={})".format(self.address, self.balance, self.utxos, self.transfers, self.block_height) + + def __eq__(self, other): + if self.address != other.address: + return False + if self.balance != other.balance: + return False + if self.utxos != other.utxos: + return False + if self.transfers != other.transfers: + return False + if self.block_height != other.block_height: + return False + return True + +class _UniffiConverterTypeSingleAddressInfoResult(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return SingleAddressInfoResult( + address=_UniffiConverterString.read(buf), + balance=_UniffiConverterUInt64.read(buf), + utxos=_UniffiConverterSequenceTypeAccountUtxo.read(buf), + transfers=_UniffiConverterUInt32.read(buf), + block_height=_UniffiConverterUInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.address) + _UniffiConverterUInt64.check_lower(value.balance) + _UniffiConverterSequenceTypeAccountUtxo.check_lower(value.utxos) + _UniffiConverterUInt32.check_lower(value.transfers) + _UniffiConverterUInt32.check_lower(value.block_height) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.address, buf) + _UniffiConverterUInt64.write(value.balance, buf) + _UniffiConverterSequenceTypeAccountUtxo.write(value.utxos, buf) + _UniffiConverterUInt32.write(value.transfers, buf) + _UniffiConverterUInt32.write(value.block_height, buf) + + +class SweepResult: + txid: "str" + """ + The transaction ID of the sweep transaction + """ + + amount_swept: "int" """ The total amount swept (in satoshis) """ @@ -7512,18 +8001,26 @@ class TrezorSignedTx: Serialized transaction (hex) """ - def __init__(self, *, signatures: "typing.List[str]", serialized_tx: "str"): + txid: "typing.Optional[str]" + """ + Broadcast transaction ID (populated when push=true) + """ + + def __init__(self, *, signatures: "typing.List[str]", serialized_tx: "str", txid: "typing.Optional[str]"): self.signatures = signatures self.serialized_tx = serialized_tx + self.txid = txid def __str__(self): - return "TrezorSignedTx(signatures={}, serialized_tx={})".format(self.signatures, self.serialized_tx) + return "TrezorSignedTx(signatures={}, serialized_tx={}, txid={})".format(self.signatures, self.serialized_tx, self.txid) def __eq__(self, other): if self.signatures != other.signatures: return False if self.serialized_tx != other.serialized_tx: return False + if self.txid != other.txid: + return False return True class _UniffiConverterTypeTrezorSignedTx(_UniffiConverterRustBuffer): @@ -7532,17 +8029,20 @@ def read(buf): return TrezorSignedTx( signatures=_UniffiConverterSequenceString.read(buf), serialized_tx=_UniffiConverterString.read(buf), + txid=_UniffiConverterOptionalString.read(buf), ) @staticmethod def check_lower(value): _UniffiConverterSequenceString.check_lower(value.signatures) _UniffiConverterString.check_lower(value.serialized_tx) + _UniffiConverterOptionalString.check_lower(value.txid) @staticmethod def write(value, buf): _UniffiConverterSequenceString.write(value.signatures, buf) _UniffiConverterString.write(value.serialized_tx, buf) + _UniffiConverterOptionalString.write(value.txid, buf) class TrezorTransportReadResult: @@ -8144,118 +8644,112 @@ def write(value, buf): _UniffiConverterTypeAddressType.write(value.address_type, buf) +class WalletParams: + """ + Common parameters for creating and syncing a watch-only BDK wallet. + """ + extended_key: "str" + """ + Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + """ + electrum_url: "str" + """ + Electrum server URL for wallet sync + """ -class Activity: - def __init__(self): - raise RuntimeError("Activity cannot be instantiated directly") - - # Each enum variant is a nested class of the enum itself. - class ONCHAIN: - def __init__(self, *values): - if len(values) != 1: - raise TypeError(f"Expected 1 arguments, found {len(values)}") - self._values = values - - def __getitem__(self, index): - return self._values[index] - - def __str__(self): - return f"Activity.ONCHAIN{self._values!r}" - - def __eq__(self, other): - if not other.is_ONCHAIN(): - return False - return self._values == other._values - class LIGHTNING: - def __init__(self, *values): - if len(values) != 1: - raise TypeError(f"Expected 1 arguments, found {len(values)}") - self._values = values - - def __getitem__(self, index): - return self._values[index] - - def __str__(self): - return f"Activity.LIGHTNING{self._values!r}" - - def __eq__(self, other): - if not other.is_LIGHTNING(): - return False - return self._values == other._values - + fingerprint: "typing.Optional[str]" + """ + Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + """ - # For each variant, we have `is_NAME` and `is_name` methods for easily checking - # whether an instance is that variant. - def is_ONCHAIN(self) -> bool: - return isinstance(self, Activity.ONCHAIN) - def is_onchain(self) -> bool: - return isinstance(self, Activity.ONCHAIN) - def is_LIGHTNING(self) -> bool: - return isinstance(self, Activity.LIGHTNING) - def is_lightning(self) -> bool: - return isinstance(self, Activity.LIGHTNING) - + network: "typing.Optional[Network]" + """ + Bitcoin network (auto-detected from key prefix if not specified) + """ -# Now, a little trick - we make each nested variant class be a subclass of the main -# enum class, so that method calls and instance checks etc will work intuitively. -# We might be able to do this a little more neatly with a metaclass, but this'll do. -Activity.ONCHAIN = type("Activity.ONCHAIN", (Activity.ONCHAIN, Activity,), {}) # type: ignore -Activity.LIGHTNING = type("Activity.LIGHTNING", (Activity.LIGHTNING, Activity,), {}) # type: ignore + account_type: "typing.Optional[AccountType]" + """ + Override account type for ambiguous key prefixes (xpub/tpub) + """ + def __init__(self, *, extended_key: "str", electrum_url: "str", fingerprint: "typing.Optional[str]", network: "typing.Optional[Network]", account_type: "typing.Optional[AccountType]"): + self.extended_key = extended_key + self.electrum_url = electrum_url + self.fingerprint = fingerprint + self.network = network + self.account_type = account_type + def __str__(self): + return "WalletParams(extended_key={}, electrum_url={}, fingerprint={}, network={}, account_type={})".format(self.extended_key, self.electrum_url, self.fingerprint, self.network, self.account_type) + def __eq__(self, other): + if self.extended_key != other.extended_key: + return False + if self.electrum_url != other.electrum_url: + return False + if self.fingerprint != other.fingerprint: + return False + if self.network != other.network: + return False + if self.account_type != other.account_type: + return False + return True -class _UniffiConverterTypeActivity(_UniffiConverterRustBuffer): +class _UniffiConverterTypeWalletParams(_UniffiConverterRustBuffer): @staticmethod def read(buf): - variant = buf.read_i32() - if variant == 1: - return Activity.ONCHAIN( - _UniffiConverterTypeOnchainActivity.read(buf), - ) - if variant == 2: - return Activity.LIGHTNING( - _UniffiConverterTypeLightningActivity.read(buf), - ) - raise InternalError("Raw enum value doesn't match any cases") + return WalletParams( + extended_key=_UniffiConverterString.read(buf), + electrum_url=_UniffiConverterString.read(buf), + fingerprint=_UniffiConverterOptionalString.read(buf), + network=_UniffiConverterOptionalTypeNetwork.read(buf), + account_type=_UniffiConverterOptionalTypeAccountType.read(buf), + ) @staticmethod def check_lower(value): - if value.is_ONCHAIN(): - _UniffiConverterTypeOnchainActivity.check_lower(value._values[0]) - return - if value.is_LIGHTNING(): - _UniffiConverterTypeLightningActivity.check_lower(value._values[0]) - return - raise ValueError(value) + _UniffiConverterString.check_lower(value.extended_key) + _UniffiConverterString.check_lower(value.electrum_url) + _UniffiConverterOptionalString.check_lower(value.fingerprint) + _UniffiConverterOptionalTypeNetwork.check_lower(value.network) + _UniffiConverterOptionalTypeAccountType.check_lower(value.account_type) @staticmethod def write(value, buf): - if value.is_ONCHAIN(): - buf.write_i32(1) - _UniffiConverterTypeOnchainActivity.write(value._values[0], buf) - if value.is_LIGHTNING(): - buf.write_i32(2) - _UniffiConverterTypeLightningActivity.write(value._values[0], buf) - - + _UniffiConverterString.write(value.extended_key, buf) + _UniffiConverterString.write(value.electrum_url, buf) + _UniffiConverterOptionalString.write(value.fingerprint, buf) + _UniffiConverterOptionalTypeNetwork.write(value.network, buf) + _UniffiConverterOptionalTypeAccountType.write(value.account_type, buf) -# ActivityError +# AccountInfoError # We want to define each variant as a nested class that's also a subclass, # which is tricky in Python. To accomplish this we're going to create each # class separately, then manually add the child classes to the base class's # __dict__. All of this happens in dummy class to avoid polluting the module # namespace. -class ActivityError(Exception): +class AccountInfoError(Exception): + """ + Errors specific to account info operations (BDK/Electrum-based). + """ + pass -_UniffiTempActivityError = ActivityError +_UniffiTempAccountInfoError = AccountInfoError + +class AccountInfoError: # type: ignore + """ + Errors specific to account info operations (BDK/Electrum-based). + """ + + class InvalidExtendedKey(_UniffiTempAccountInfoError): + """ + The provided extended public key is invalid or cannot be parsed + """ -class ActivityError: # type: ignore - class InvalidActivity(_UniffiTempActivityError): def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8263,9 +8757,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.InvalidActivity({})".format(str(self)) - _UniffiTempActivityError.InvalidActivity = InvalidActivity # type: ignore - class InitializationError(_UniffiTempActivityError): + return "AccountInfoError.InvalidExtendedKey({})".format(str(self)) + _UniffiTempAccountInfoError.InvalidExtendedKey = InvalidExtendedKey # type: ignore + class InvalidAddress(_UniffiTempAccountInfoError): + """ + The provided address is invalid + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8273,9 +8771,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.InitializationError({})".format(str(self)) - _UniffiTempActivityError.InitializationError = InitializationError # type: ignore - class InsertError(_UniffiTempActivityError): + return "AccountInfoError.InvalidAddress({})".format(str(self)) + _UniffiTempAccountInfoError.InvalidAddress = InvalidAddress # type: ignore + class ElectrumError(_UniffiTempAccountInfoError): + """ + Electrum connection or query failed + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8283,9 +8785,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.InsertError({})".format(str(self)) - _UniffiTempActivityError.InsertError = InsertError # type: ignore - class RetrievalError(_UniffiTempActivityError): + return "AccountInfoError.ElectrumError({})".format(str(self)) + _UniffiTempAccountInfoError.ElectrumError = ElectrumError # type: ignore + class WalletError(_UniffiTempAccountInfoError): + """ + BDK wallet creation or operation error + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8293,9 +8799,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.RetrievalError({})".format(str(self)) - _UniffiTempActivityError.RetrievalError = RetrievalError # type: ignore - class DataError(_UniffiTempActivityError): + return "AccountInfoError.WalletError({})".format(str(self)) + _UniffiTempAccountInfoError.WalletError = WalletError # type: ignore + class SyncError(_UniffiTempAccountInfoError): + """ + Wallet sync with Electrum failed + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8303,9 +8813,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.DataError({})".format(str(self)) - _UniffiTempActivityError.DataError = DataError # type: ignore - class ConnectionError(_UniffiTempActivityError): + return "AccountInfoError.SyncError({})".format(str(self)) + _UniffiTempAccountInfoError.SyncError = SyncError # type: ignore + class UnsupportedKeyType(_UniffiTempAccountInfoError): + """ + The key type/prefix is not recognized + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8313,9 +8827,13 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.ConnectionError({})".format(str(self)) - _UniffiTempActivityError.ConnectionError = ConnectionError # type: ignore - class SerializationError(_UniffiTempActivityError): + return "AccountInfoError.UnsupportedKeyType({})".format(str(self)) + _UniffiTempAccountInfoError.UnsupportedKeyType = UnsupportedKeyType # type: ignore + class NetworkMismatch(_UniffiTempAccountInfoError): + """ + Network mismatch between key prefix and specified network + """ + def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -8323,83 +8841,452 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "ActivityError.SerializationError({})".format(str(self)) - _UniffiTempActivityError.SerializationError = SerializationError # type: ignore + return "AccountInfoError.NetworkMismatch({})".format(str(self)) + _UniffiTempAccountInfoError.NetworkMismatch = NetworkMismatch # type: ignore + class InvalidTxid(_UniffiTempAccountInfoError): + """ + Invalid transaction ID provided + """ -ActivityError = _UniffiTempActivityError # type: ignore -del _UniffiTempActivityError + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "AccountInfoError.InvalidTxid({})".format(str(self)) + _UniffiTempAccountInfoError.InvalidTxid = InvalidTxid # type: ignore +AccountInfoError = _UniffiTempAccountInfoError # type: ignore +del _UniffiTempAccountInfoError -class _UniffiConverterTypeActivityError(_UniffiConverterRustBuffer): + +class _UniffiConverterTypeAccountInfoError(_UniffiConverterRustBuffer): @staticmethod def read(buf): variant = buf.read_i32() if variant == 1: - return ActivityError.InvalidActivity( + return AccountInfoError.InvalidExtendedKey( _UniffiConverterString.read(buf), ) if variant == 2: - return ActivityError.InitializationError( + return AccountInfoError.InvalidAddress( _UniffiConverterString.read(buf), ) if variant == 3: - return ActivityError.InsertError( + return AccountInfoError.ElectrumError( _UniffiConverterString.read(buf), ) if variant == 4: - return ActivityError.RetrievalError( + return AccountInfoError.WalletError( _UniffiConverterString.read(buf), ) if variant == 5: - return ActivityError.DataError( + return AccountInfoError.SyncError( _UniffiConverterString.read(buf), ) if variant == 6: - return ActivityError.ConnectionError( + return AccountInfoError.UnsupportedKeyType( _UniffiConverterString.read(buf), ) if variant == 7: - return ActivityError.SerializationError( + return AccountInfoError.NetworkMismatch( + _UniffiConverterString.read(buf), + ) + if variant == 8: + return AccountInfoError.InvalidTxid( _UniffiConverterString.read(buf), ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod def check_lower(value): - if isinstance(value, ActivityError.InvalidActivity): + if isinstance(value, AccountInfoError.InvalidExtendedKey): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.InitializationError): + if isinstance(value, AccountInfoError.InvalidAddress): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.InsertError): + if isinstance(value, AccountInfoError.ElectrumError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.RetrievalError): + if isinstance(value, AccountInfoError.WalletError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.DataError): + if isinstance(value, AccountInfoError.SyncError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.ConnectionError): + if isinstance(value, AccountInfoError.UnsupportedKeyType): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, ActivityError.SerializationError): + if isinstance(value, AccountInfoError.NetworkMismatch): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, AccountInfoError.InvalidTxid): _UniffiConverterString.check_lower(value.error_details) return @staticmethod def write(value, buf): - if isinstance(value, ActivityError.InvalidActivity): + if isinstance(value, AccountInfoError.InvalidExtendedKey): buf.write_i32(1) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, ActivityError.InitializationError): + if isinstance(value, AccountInfoError.InvalidAddress): buf.write_i32(2) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, ActivityError.InsertError): + if isinstance(value, AccountInfoError.ElectrumError): buf.write_i32(3) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, ActivityError.RetrievalError): + if isinstance(value, AccountInfoError.WalletError): + buf.write_i32(4) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, AccountInfoError.SyncError): + buf.write_i32(5) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, AccountInfoError.UnsupportedKeyType): + buf.write_i32(6) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, AccountInfoError.NetworkMismatch): + buf.write_i32(7) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, AccountInfoError.InvalidTxid): + buf.write_i32(8) + _UniffiConverterString.write(value.error_details, buf) + + + + + +class AccountType(enum.Enum): + """ + Account type classification for extended public keys. + + Determines the BIP standard, derivation path purpose, and script type. + """ + + LEGACY = 0 + """ + BIP44 legacy (P2PKH) — xpub/tpub prefix + """ + + + WRAPPED_SEGWIT = 1 + """ + BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + """ + + + NATIVE_SEGWIT = 2 + """ + BIP84 native segwit (P2WPKH) — zpub/vpub prefix + """ + + + TAPROOT = 3 + """ + BIP86 taproot (P2TR) + """ + + + + +class _UniffiConverterTypeAccountType(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return AccountType.LEGACY + if variant == 2: + return AccountType.WRAPPED_SEGWIT + if variant == 3: + return AccountType.NATIVE_SEGWIT + if variant == 4: + return AccountType.TAPROOT + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == AccountType.LEGACY: + return + if value == AccountType.WRAPPED_SEGWIT: + return + if value == AccountType.NATIVE_SEGWIT: + return + if value == AccountType.TAPROOT: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == AccountType.LEGACY: + buf.write_i32(1) + if value == AccountType.WRAPPED_SEGWIT: + buf.write_i32(2) + if value == AccountType.NATIVE_SEGWIT: + buf.write_i32(3) + if value == AccountType.TAPROOT: + buf.write_i32(4) + + + + + + + +class Activity: + def __init__(self): + raise RuntimeError("Activity cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class ONCHAIN: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Activity.ONCHAIN{self._values!r}" + + def __eq__(self, other): + if not other.is_ONCHAIN(): + return False + return self._values == other._values + class LIGHTNING: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Activity.LIGHTNING{self._values!r}" + + def __eq__(self, other): + if not other.is_LIGHTNING(): + return False + return self._values == other._values + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_ONCHAIN(self) -> bool: + return isinstance(self, Activity.ONCHAIN) + def is_onchain(self) -> bool: + return isinstance(self, Activity.ONCHAIN) + def is_LIGHTNING(self) -> bool: + return isinstance(self, Activity.LIGHTNING) + def is_lightning(self) -> bool: + return isinstance(self, Activity.LIGHTNING) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +Activity.ONCHAIN = type("Activity.ONCHAIN", (Activity.ONCHAIN, Activity,), {}) # type: ignore +Activity.LIGHTNING = type("Activity.LIGHTNING", (Activity.LIGHTNING, Activity,), {}) # type: ignore + + + + +class _UniffiConverterTypeActivity(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return Activity.ONCHAIN( + _UniffiConverterTypeOnchainActivity.read(buf), + ) + if variant == 2: + return Activity.LIGHTNING( + _UniffiConverterTypeLightningActivity.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_ONCHAIN(): + _UniffiConverterTypeOnchainActivity.check_lower(value._values[0]) + return + if value.is_LIGHTNING(): + _UniffiConverterTypeLightningActivity.check_lower(value._values[0]) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_ONCHAIN(): + buf.write_i32(1) + _UniffiConverterTypeOnchainActivity.write(value._values[0], buf) + if value.is_LIGHTNING(): + buf.write_i32(2) + _UniffiConverterTypeLightningActivity.write(value._values[0], buf) + + + + +# ActivityError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ActivityError(Exception): + pass + +_UniffiTempActivityError = ActivityError + +class ActivityError: # type: ignore + class InvalidActivity(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.InvalidActivity({})".format(str(self)) + _UniffiTempActivityError.InvalidActivity = InvalidActivity # type: ignore + class InitializationError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.InitializationError({})".format(str(self)) + _UniffiTempActivityError.InitializationError = InitializationError # type: ignore + class InsertError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.InsertError({})".format(str(self)) + _UniffiTempActivityError.InsertError = InsertError # type: ignore + class RetrievalError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.RetrievalError({})".format(str(self)) + _UniffiTempActivityError.RetrievalError = RetrievalError # type: ignore + class DataError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.DataError({})".format(str(self)) + _UniffiTempActivityError.DataError = DataError # type: ignore + class ConnectionError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.ConnectionError({})".format(str(self)) + _UniffiTempActivityError.ConnectionError = ConnectionError # type: ignore + class SerializationError(_UniffiTempActivityError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "ActivityError.SerializationError({})".format(str(self)) + _UniffiTempActivityError.SerializationError = SerializationError # type: ignore + +ActivityError = _UniffiTempActivityError # type: ignore +del _UniffiTempActivityError + + +class _UniffiConverterTypeActivityError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ActivityError.InvalidActivity( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ActivityError.InitializationError( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ActivityError.InsertError( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ActivityError.RetrievalError( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return ActivityError.DataError( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return ActivityError.ConnectionError( + _UniffiConverterString.read(buf), + ) + if variant == 7: + return ActivityError.SerializationError( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ActivityError.InvalidActivity): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.InitializationError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.InsertError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.RetrievalError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.DataError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.ConnectionError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, ActivityError.SerializationError): + _UniffiConverterString.check_lower(value.error_details) + return + + @staticmethod + def write(value, buf): + if isinstance(value, ActivityError.InvalidActivity): + buf.write_i32(1) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, ActivityError.InitializationError): + buf.write_i32(2) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, ActivityError.InsertError): + buf.write_i32(3) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, ActivityError.RetrievalError): buf.write_i32(4) _UniffiConverterString.write(value.error_details, buf) if isinstance(value, ActivityError.DataError): @@ -9019,14 +9906,124 @@ def write(value, buf): buf.write_i32(10) _UniffiConverterTypeBtChannelOrderErrorType.write(value.error_type, buf) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BlocktankError.OrderState): - buf.write_i32(11) + if isinstance(value, BlocktankError.OrderState): + buf.write_i32(11) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BlocktankError.InvalidParameter): + buf.write_i32(12) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BlocktankError.DatabaseError): + buf.write_i32(13) + _UniffiConverterString.write(value.error_details, buf) + + +# BroadcastError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class BroadcastError(Exception): + pass + +_UniffiTempBroadcastError = BroadcastError + +class BroadcastError: # type: ignore + class InvalidHex(_UniffiTempBroadcastError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BroadcastError.InvalidHex({})".format(str(self)) + _UniffiTempBroadcastError.InvalidHex = InvalidHex # type: ignore + class InvalidTransaction(_UniffiTempBroadcastError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BroadcastError.InvalidTransaction({})".format(str(self)) + _UniffiTempBroadcastError.InvalidTransaction = InvalidTransaction # type: ignore + class ElectrumError(_UniffiTempBroadcastError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BroadcastError.ElectrumError({})".format(str(self)) + _UniffiTempBroadcastError.ElectrumError = ElectrumError # type: ignore + class TaskError(_UniffiTempBroadcastError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BroadcastError.TaskError({})".format(str(self)) + _UniffiTempBroadcastError.TaskError = TaskError # type: ignore + +BroadcastError = _UniffiTempBroadcastError # type: ignore +del _UniffiTempBroadcastError + + +class _UniffiConverterTypeBroadcastError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return BroadcastError.InvalidHex( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return BroadcastError.InvalidTransaction( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return BroadcastError.ElectrumError( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return BroadcastError.TaskError( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, BroadcastError.InvalidHex): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BroadcastError.InvalidTransaction): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BroadcastError.ElectrumError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BroadcastError.TaskError): + _UniffiConverterString.check_lower(value.error_details) + return + + @staticmethod + def write(value, buf): + if isinstance(value, BroadcastError.InvalidHex): + buf.write_i32(1) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BroadcastError.InvalidTransaction): + buf.write_i32(2) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BlocktankError.InvalidParameter): - buf.write_i32(12) + if isinstance(value, BroadcastError.ElectrumError): + buf.write_i32(3) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BlocktankError.DatabaseError): - buf.write_i32(13) + if isinstance(value, BroadcastError.TaskError): + buf.write_i32(4) _UniffiConverterString.write(value.error_details, buf) @@ -9427,53 +10424,418 @@ def write(value, buf): -class CJitStateEnum(enum.Enum): - CREATED = 0 - - COMPLETED = 1 - - EXPIRED = 2 - - FAILED = 3 - +class CJitStateEnum(enum.Enum): + CREATED = 0 + + COMPLETED = 1 + + EXPIRED = 2 + + FAILED = 3 + + + +class _UniffiConverterTypeCJitStateEnum(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return CJitStateEnum.CREATED + if variant == 2: + return CJitStateEnum.COMPLETED + if variant == 3: + return CJitStateEnum.EXPIRED + if variant == 4: + return CJitStateEnum.FAILED + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == CJitStateEnum.CREATED: + return + if value == CJitStateEnum.COMPLETED: + return + if value == CJitStateEnum.EXPIRED: + return + if value == CJitStateEnum.FAILED: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == CJitStateEnum.CREATED: + buf.write_i32(1) + if value == CJitStateEnum.COMPLETED: + buf.write_i32(2) + if value == CJitStateEnum.EXPIRED: + buf.write_i32(3) + if value == CJitStateEnum.FAILED: + buf.write_i32(4) + + + + + + + +class CoinSelection(enum.Enum): + """ + Coin selection strategy for transaction composition. + """ + + BRANCH_AND_BOUND = 0 + """ + Branch-and-bound (default). Minimizes change by searching for exact matches. + """ + + + LARGEST_FIRST = 1 + """ + Selects largest UTXOs first. Useful for UTXO consolidation. + """ + + + OLDEST_FIRST = 2 + """ + Selects oldest UTXOs first. Maximizes coin-age spending. + """ + + + + +class _UniffiConverterTypeCoinSelection(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return CoinSelection.BRANCH_AND_BOUND + if variant == 2: + return CoinSelection.LARGEST_FIRST + if variant == 3: + return CoinSelection.OLDEST_FIRST + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == CoinSelection.BRANCH_AND_BOUND: + return + if value == CoinSelection.LARGEST_FIRST: + return + if value == CoinSelection.OLDEST_FIRST: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == CoinSelection.BRANCH_AND_BOUND: + buf.write_i32(1) + if value == CoinSelection.LARGEST_FIRST: + buf.write_i32(2) + if value == CoinSelection.OLDEST_FIRST: + buf.write_i32(3) + + + + + + + +class ComposeOutput: + """ + Output specification for transaction composition. + """ + + def __init__(self): + raise RuntimeError("ComposeOutput cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class PAYMENT: + """ + Payment to a specific address with a fixed amount (satoshis) + """ + + address: "str" + amount_sats: "int" + + def __init__(self,address: "str", amount_sats: "int"): + self.address = address + self.amount_sats = amount_sats + + def __str__(self): + return "ComposeOutput.PAYMENT(address={}, amount_sats={})".format(self.address, self.amount_sats) + + def __eq__(self, other): + if not other.is_PAYMENT(): + return False + if self.address != other.address: + return False + if self.amount_sats != other.amount_sats: + return False + return True + + class SEND_MAX: + """ + Send all remaining funds (after fees) to an address + """ + + address: "str" + + def __init__(self,address: "str"): + self.address = address + + def __str__(self): + return "ComposeOutput.SEND_MAX(address={})".format(self.address) + + def __eq__(self, other): + if not other.is_SEND_MAX(): + return False + if self.address != other.address: + return False + return True + + class OP_RETURN: + """ + OP_RETURN data output (hex-encoded payload) + """ + + data_hex: "str" + + def __init__(self,data_hex: "str"): + self.data_hex = data_hex + + def __str__(self): + return "ComposeOutput.OP_RETURN(data_hex={})".format(self.data_hex) + + def __eq__(self, other): + if not other.is_OP_RETURN(): + return False + if self.data_hex != other.data_hex: + return False + return True + + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_PAYMENT(self) -> bool: + return isinstance(self, ComposeOutput.PAYMENT) + def is_payment(self) -> bool: + return isinstance(self, ComposeOutput.PAYMENT) + def is_SEND_MAX(self) -> bool: + return isinstance(self, ComposeOutput.SEND_MAX) + def is_send_max(self) -> bool: + return isinstance(self, ComposeOutput.SEND_MAX) + def is_OP_RETURN(self) -> bool: + return isinstance(self, ComposeOutput.OP_RETURN) + def is_op_return(self) -> bool: + return isinstance(self, ComposeOutput.OP_RETURN) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +ComposeOutput.PAYMENT = type("ComposeOutput.PAYMENT", (ComposeOutput.PAYMENT, ComposeOutput,), {}) # type: ignore +ComposeOutput.SEND_MAX = type("ComposeOutput.SEND_MAX", (ComposeOutput.SEND_MAX, ComposeOutput,), {}) # type: ignore +ComposeOutput.OP_RETURN = type("ComposeOutput.OP_RETURN", (ComposeOutput.OP_RETURN, ComposeOutput,), {}) # type: ignore + + + + +class _UniffiConverterTypeComposeOutput(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ComposeOutput.PAYMENT( + _UniffiConverterString.read(buf), + _UniffiConverterUInt64.read(buf), + ) + if variant == 2: + return ComposeOutput.SEND_MAX( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ComposeOutput.OP_RETURN( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_PAYMENT(): + _UniffiConverterString.check_lower(value.address) + _UniffiConverterUInt64.check_lower(value.amount_sats) + return + if value.is_SEND_MAX(): + _UniffiConverterString.check_lower(value.address) + return + if value.is_OP_RETURN(): + _UniffiConverterString.check_lower(value.data_hex) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_PAYMENT(): + buf.write_i32(1) + _UniffiConverterString.write(value.address, buf) + _UniffiConverterUInt64.write(value.amount_sats, buf) + if value.is_SEND_MAX(): + buf.write_i32(2) + _UniffiConverterString.write(value.address, buf) + if value.is_OP_RETURN(): + buf.write_i32(3) + _UniffiConverterString.write(value.data_hex, buf) + + + + + + + +class ComposeResult: + """ + Result of composing a transaction at a single fee rate. + """ + + def __init__(self): + raise RuntimeError("ComposeResult cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class SUCCESS: + """ + Successfully built a signable PSBT + """ + + psbt: "str" + """ + Base64-encoded PSBT ready for signing + """ + + fee: "int" + """ + Total fee in satoshis + """ + + fee_rate: "float" + """ + Target fee rate in sat/vB (actual may differ slightly due to rounding) + """ + + total_spent: "int" + """ + Total value spent (payments + fee, excluding change). + Uses BDK's `sent - received` semantics, which may undercount for + self-transfers where the destination is also owned by the wallet. + """ + + + def __init__(self,psbt: "str", fee: "int", fee_rate: "float", total_spent: "int"): + self.psbt = psbt + self.fee = fee + self.fee_rate = fee_rate + self.total_spent = total_spent + + def __str__(self): + return "ComposeResult.SUCCESS(psbt={}, fee={}, fee_rate={}, total_spent={})".format(self.psbt, self.fee, self.fee_rate, self.total_spent) + + def __eq__(self, other): + if not other.is_SUCCESS(): + return False + if self.psbt != other.psbt: + return False + if self.fee != other.fee: + return False + if self.fee_rate != other.fee_rate: + return False + if self.total_spent != other.total_spent: + return False + return True + + class ERROR: + """ + Composition failed (e.g. insufficient funds) + """ + + error: "str" + + def __init__(self,error: "str"): + self.error = error + + def __str__(self): + return "ComposeResult.ERROR(error={})".format(self.error) + + def __eq__(self, other): + if not other.is_ERROR(): + return False + if self.error != other.error: + return False + return True + + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_SUCCESS(self) -> bool: + return isinstance(self, ComposeResult.SUCCESS) + def is_success(self) -> bool: + return isinstance(self, ComposeResult.SUCCESS) + def is_ERROR(self) -> bool: + return isinstance(self, ComposeResult.ERROR) + def is_error(self) -> bool: + return isinstance(self, ComposeResult.ERROR) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +ComposeResult.SUCCESS = type("ComposeResult.SUCCESS", (ComposeResult.SUCCESS, ComposeResult,), {}) # type: ignore +ComposeResult.ERROR = type("ComposeResult.ERROR", (ComposeResult.ERROR, ComposeResult,), {}) # type: ignore + + -class _UniffiConverterTypeCJitStateEnum(_UniffiConverterRustBuffer): +class _UniffiConverterTypeComposeResult(_UniffiConverterRustBuffer): @staticmethod def read(buf): variant = buf.read_i32() if variant == 1: - return CJitStateEnum.CREATED + return ComposeResult.SUCCESS( + _UniffiConverterString.read(buf), + _UniffiConverterUInt64.read(buf), + _UniffiConverterFloat.read(buf), + _UniffiConverterUInt64.read(buf), + ) if variant == 2: - return CJitStateEnum.COMPLETED - if variant == 3: - return CJitStateEnum.EXPIRED - if variant == 4: - return CJitStateEnum.FAILED + return ComposeResult.ERROR( + _UniffiConverterString.read(buf), + ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod def check_lower(value): - if value == CJitStateEnum.CREATED: - return - if value == CJitStateEnum.COMPLETED: + if value.is_SUCCESS(): + _UniffiConverterString.check_lower(value.psbt) + _UniffiConverterUInt64.check_lower(value.fee) + _UniffiConverterFloat.check_lower(value.fee_rate) + _UniffiConverterUInt64.check_lower(value.total_spent) return - if value == CJitStateEnum.EXPIRED: - return - if value == CJitStateEnum.FAILED: + if value.is_ERROR(): + _UniffiConverterString.check_lower(value.error) return raise ValueError(value) @staticmethod def write(value, buf): - if value == CJitStateEnum.CREATED: + if value.is_SUCCESS(): buf.write_i32(1) - if value == CJitStateEnum.COMPLETED: + _UniffiConverterString.write(value.psbt, buf) + _UniffiConverterUInt64.write(value.fee, buf) + _UniffiConverterFloat.write(value.fee_rate, buf) + _UniffiConverterUInt64.write(value.total_spent, buf) + if value.is_ERROR(): buf.write_i32(2) - if value == CJitStateEnum.EXPIRED: - buf.write_i32(3) - if value == CJitStateEnum.FAILED: - buf.write_i32(4) + _UniffiConverterString.write(value.error, buf) @@ -12368,6 +13730,33 @@ def read(cls, buf): +class _UniffiConverterOptionalTypeAccountType(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterTypeAccountType.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterTypeAccountType.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterTypeAccountType.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + class _UniffiConverterOptionalTypeActivity(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -12503,6 +13892,33 @@ def read(cls, buf): +class _UniffiConverterOptionalTypeCoinSelection(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterTypeCoinSelection.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterTypeCoinSelection.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterTypeCoinSelection.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + class _UniffiConverterOptionalTypeNetwork(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -12773,6 +14189,31 @@ def read(cls, buf): +class _UniffiConverterSequenceFloat(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterFloat.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterFloat.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterFloat.read(buf) for i in range(count) + ] + + + class _UniffiConverterSequenceString(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -12798,6 +14239,31 @@ def read(cls, buf): +class _UniffiConverterSequenceTypeAccountUtxo(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeAccountUtxo.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeAccountUtxo.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeAccountUtxo.read(buf) for i in range(count) + ] + + + class _UniffiConverterSequenceTypeActivityTags(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -13398,6 +14864,56 @@ def read(cls, buf): +class _UniffiConverterSequenceTypeComposeOutput(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeComposeOutput.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeComposeOutput.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeComposeOutput.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeComposeResult(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeComposeResult.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeComposeResult.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeComposeResult.read(buf) for i in range(count) + ] + + + class _UniffiConverterMapStringString(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, items): @@ -15433,6 +16949,120 @@ def mnemonic_to_seed(mnemonic_phrase: "str",passphrase: "typing.Optional[str]") _UniffiConverterString.lower(mnemonic_phrase), _UniffiConverterOptionalString.lower(passphrase))) +async def onchain_broadcast_raw_tx(serialized_tx: "str",electrum_url: "str") -> "str": + + """ + Broadcast a signed raw transaction via Electrum. + + Takes a hex-encoded serialized transaction and an Electrum server URL. + Returns the transaction ID on success. + """ + + _UniffiConverterString.check_lower(serialized_tx) + + _UniffiConverterString.check_lower(electrum_url) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_onchain_broadcast_raw_tx( + _UniffiConverterString.lower(serialized_tx), + _UniffiConverterString.lower(electrum_url)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeBroadcastError, + + ) +async def onchain_compose_transaction(params: "ComposeParams") -> "typing.List[ComposeResult]": + + """ + Compose a transaction for multiple fee rates, returning one PSBT per rate. + + Creates a BDK wallet from the extended key, syncs via Electrum, then + builds PSBTs using BDK's TxBuilder. The PSBTs include BIP32 derivation + paths (when fingerprint is provided) and are ready for signing by any + PSBT-compatible signer (Trezor, Ledger, software wallet, etc.). + """ + + _UniffiConverterTypeComposeParams.check_lower(params) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_onchain_compose_transaction( + _UniffiConverterTypeComposeParams.lower(params)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterSequenceTypeComposeResult.lift, + + # Error FFI converter + + None, + + ) +async def onchain_get_account_info(extended_key: "str",electrum_url: "str",network: "typing.Optional[Network]",gap_limit: "typing.Optional[int]",script_type: "typing.Optional[AccountType]") -> "AccountInfoResult": + + """ + Query account information for an extended public key via Electrum. + """ + + _UniffiConverterString.check_lower(extended_key) + + _UniffiConverterString.check_lower(electrum_url) + + _UniffiConverterOptionalTypeNetwork.check_lower(network) + + _UniffiConverterOptionalUInt32.check_lower(gap_limit) + + _UniffiConverterOptionalTypeAccountType.check_lower(script_type) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_account_info( + _UniffiConverterString.lower(extended_key), + _UniffiConverterString.lower(electrum_url), + _UniffiConverterOptionalTypeNetwork.lower(network), + _UniffiConverterOptionalUInt32.lower(gap_limit), + _UniffiConverterOptionalTypeAccountType.lower(script_type)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypeAccountInfoResult.lift, + + # Error FFI converter +_UniffiConverterTypeAccountInfoError, + + ) +async def onchain_get_address_info(address: "str",electrum_url: "str",network: "typing.Optional[Network]") -> "SingleAddressInfoResult": + + """ + Query balance and UTXOs for a single Bitcoin address via Electrum. + """ + + _UniffiConverterString.check_lower(address) + + _UniffiConverterString.check_lower(electrum_url) + + _UniffiConverterOptionalTypeNetwork.check_lower(network) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_onchain_get_address_info( + _UniffiConverterString.lower(address), + _UniffiConverterString.lower(electrum_url), + _UniffiConverterOptionalTypeNetwork.lower(network)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypeSingleAddressInfoResult.lift, + + # Error FFI converter +_UniffiConverterTypeAccountInfoError, + + ) async def open_channel(order_id: "str",connection_string: "str") -> "IBtOrder": _UniffiConverterString.check_lower(order_id) @@ -15741,6 +17371,17 @@ async def test_notification(device_token: "str",secret_message: "str",notificati _UniffiConverterTypeBlocktankError, ) + +def trezor_account_type_to_script_type(account_type: "AccountType") -> "TrezorScriptType": + """ + Convert an account type to its corresponding Trezor script type. + """ + + _UniffiConverterTypeAccountType.check_lower(account_type) + + return _UniffiConverterTypeTrezorScriptType.lift(_uniffi_rust_call(_UniffiLib.uniffi_bitkitcore_fn_func_trezor_account_type_to_script_type, + _UniffiConverterTypeAccountType.lower(account_type))) + async def trezor_clear_credentials(device_id: "str") -> None: """ @@ -16335,6 +17976,8 @@ def wipe_all_transaction_details() -> None: __all__ = [ "InternalError", + "AccountInfoError", + "AccountType", "Activity", "ActivityError", "ActivityFilter", @@ -16343,6 +17986,7 @@ def wipe_all_transaction_details() -> None: "AddressType", "BitcoinNetworkEnum", "BlocktankError", + "BroadcastError", "BtBolt11InvoiceState", "BtChannelOrderErrorType", "BtOpenChannelState", @@ -16351,6 +17995,9 @@ def wipe_all_transaction_details() -> None: "BtPaymentState", "BtPaymentState2", "CJitStateEnum", + "CoinSelection", + "ComposeOutput", + "ComposeResult", "DbError", "DecodingError", "LnurlError", @@ -16369,11 +18016,15 @@ def wipe_all_transaction_details() -> None: "TrezorTransportType", "WordCount", "AccountAddresses", + "AccountInfoResult", + "AccountUtxo", "ActivityTags", "AddressInfo", "ChannelLiquidityOptions", "ChannelLiquidityParams", "ClosedChannelDetails", + "ComposeAccount", + "ComposeParams", "CreateCjitOptions", "CreateOrderOptions", "DefaultLspBalanceParams", @@ -16421,6 +18072,7 @@ def wipe_all_transaction_details() -> None: "PubkyAuth", "PubkyProfile", "PubkyProfileLink", + "SingleAddressInfoResult", "SweepResult", "SweepTransactionPreview", "SweepableBalances", @@ -16447,6 +18099,7 @@ def wipe_all_transaction_details() -> None: "TxInput", "TxOutput", "ValidationResult", + "WalletParams", "activity_wipe_all", "add_pre_activity_metadata", "add_pre_activity_metadata_tags", @@ -16510,6 +18163,10 @@ def wipe_all_transaction_details() -> None: "mark_activity_as_seen", "mnemonic_to_entropy", "mnemonic_to_seed", + "onchain_broadcast_raw_tx", + "onchain_compose_transaction", + "onchain_get_account_info", + "onchain_get_address_info", "open_channel", "prepare_sweep_transaction", "refresh_active_cjit_entries", @@ -16527,6 +18184,7 @@ def wipe_all_transaction_details() -> None: "resolve_pubky_url", "start_pubky_auth", "test_notification", + "trezor_account_type_to_script_type", "trezor_clear_credentials", "trezor_connect", "trezor_disconnect", diff --git a/bindings/python/bitkitcore/libbitkitcore.dylib b/bindings/python/bitkitcore/libbitkitcore.dylib index b68b77c..31084da 100755 Binary files a/bindings/python/bitkitcore/libbitkitcore.dylib and b/bindings/python/bitkitcore/libbitkitcore.dylib differ diff --git a/src/lib.rs b/src/lib.rs index d29e5de..5fb2724 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,6 @@ fn init_android_logger() { mod modules; -use std::sync::Arc; use once_cell::sync::OnceCell; // Re-export Trezor callback types and traits so UniFFI discovers them at the crate root @@ -36,10 +35,12 @@ pub use modules::lnurl; pub use modules::onchain; pub use modules::activity; use crate::modules::pubky::{PubkyError, PubkyProfile}; -use crate::activity::{ActivityError, ActivityDB, OnchainActivity, LightningActivity, Activity, ActivityFilter, SortDirection, PaymentType, DbError, ClosedChannelDetails, ActivityTags, PreActivityMetadata, TransactionDetails, TxInput, TxOutput}; +use crate::activity::{ActivityError, ActivityDB, OnchainActivity, LightningActivity, Activity, ActivityFilter, SortDirection, PaymentType, DbError, ClosedChannelDetails, ActivityTags, PreActivityMetadata, TransactionDetails}; use crate::modules::blocktank::{BlocktankDB, BlocktankError, IBtInfo, IBtOrder, CreateOrderOptions, BtOrderState2, IBt0ConfMinTxFeeWindow, IBtEstimateFeeResponse, IBtEstimateFeeResponse2, CreateCjitOptions, ICJitEntry, CJitStateEnum, IBtBolt11Invoice, IGift, ChannelLiquidityOptions, ChannelLiquidityParams, DefaultLspBalanceParams}; -use crate::onchain::{AddressError, ValidationResult, GetAddressResponse, Network, GetAddressesResponse, SweepError, SweepResult, SweepTransactionPreview, SweepableBalances}; -use crate::modules::trezor::{TrezorError, TrezorDeviceInfo, TrezorTransportType, TrezorFeatures, TrezorGetAddressParams, TrezorAddressResponse, TrezorGetPublicKeyParams, TrezorPublicKeyResponse, TrezorScriptType, TrezorManager, TrezorSignMessageParams, TrezorSignedMessageResponse, TrezorVerifyMessageParams, TrezorSignTxParams, TrezorSignedTx, TrezorTxInput, TrezorTxOutput, TrezorCoinType, AddressInfo, AccountAddresses}; +use crate::onchain::{AddressError, BroadcastError, AccountInfoError, ValidationResult, GetAddressResponse, Network, GetAddressesResponse, SweepError, SweepResult, SweepTransactionPreview, SweepableBalances, broadcast_raw_tx, AccountInfoResult, SingleAddressInfoResult, AccountType, get_account_info, get_address_info}; +use crate::modules::trezor::{TrezorError, TrezorDeviceInfo, TrezorFeatures, TrezorGetAddressParams, TrezorAddressResponse, TrezorGetPublicKeyParams, TrezorPublicKeyResponse, TrezorScriptType, TrezorManager, TrezorSignMessageParams, TrezorSignedMessageResponse, TrezorVerifyMessageParams, TrezorSignTxParams, TrezorSignedTx, TrezorCoinType}; +use crate::modules::trezor::account_type_to_script_type; +use crate::onchain::{compose_transaction, ComposeParams, ComposeResult}; pub use crate::onchain::WordCount; use std::sync::Mutex as StdMutex; @@ -1743,3 +1744,87 @@ pub async fn trezor_clear_credentials(device_id: String) -> Result<(), TrezorErr get_trezor_manager().clear_credentials(&device_id).await }).await.unwrap_or_else(|e| Err(TrezorError::IoError { error_details: format!("Runtime error: {}", e) })) } + +// ============================================================================ +// Account info FFI exports +// ============================================================================ + +/// Query account information for an extended public key via Electrum. +#[uniffi::export] +pub async fn onchain_get_account_info( + extended_key: String, + electrum_url: String, + network: Option, + gap_limit: Option, + script_type: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + get_account_info(&extended_key, &electrum_url, network, gap_limit, script_type).await + }).await.unwrap_or_else(|e| Err(AccountInfoError::SyncError { + error_details: format!("Runtime error: {}", e), + })) +} + +/// Query balance and UTXOs for a single Bitcoin address via Electrum. +#[uniffi::export] +pub async fn onchain_get_address_info( + address: String, + electrum_url: String, + network: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + get_address_info(&address, &electrum_url, network).await + }).await.unwrap_or_else(|e| Err(AccountInfoError::SyncError { + error_details: format!("Runtime error: {}", e), + })) +} + +/// Convert an account type to its corresponding Trezor script type. +#[uniffi::export] +pub fn trezor_account_type_to_script_type(account_type: AccountType) -> TrezorScriptType { + account_type_to_script_type(account_type) +} + +// ============================================================================ +// Compose FFI exports (signer-agnostic) +// ============================================================================ + +/// Compose a transaction for multiple fee rates, returning one PSBT per rate. +/// +/// Creates a BDK wallet from the extended key, syncs via Electrum, then +/// builds PSBTs using BDK's TxBuilder. The PSBTs include BIP32 derivation +/// paths (when fingerprint is provided) and are ready for signing by any +/// PSBT-compatible signer (Trezor, Ledger, software wallet, etc.). +#[uniffi::export] +pub async fn onchain_compose_transaction(params: ComposeParams) -> Vec { + let rt = ensure_runtime(); + let num_rates = params.fee_rates.len(); + rt.spawn(async move { + compose_transaction(params).await + }) + .await + .unwrap_or_else(|e| { + vec![ComposeResult::Error { + error: format!("Runtime error: {}", e), + }; num_rates] + }) +} + +/// Broadcast a signed raw transaction via Electrum. +/// +/// Takes a hex-encoded serialized transaction and an Electrum server URL. +/// Returns the transaction ID on success. +#[uniffi::export] +pub async fn onchain_broadcast_raw_tx( + serialized_tx: String, + electrum_url: String, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + broadcast_raw_tx(serialized_tx, &electrum_url).await + }).await.unwrap_or_else(|e| Err(BroadcastError::TaskError { + error_details: format!("Runtime error: {}", e), + })) +} diff --git a/src/modules/activity/implementation.rs b/src/modules/activity/implementation.rs index 9af5534..713d44e 100644 --- a/src/modules/activity/implementation.rs +++ b/src/modules/activity/implementation.rs @@ -178,6 +178,17 @@ const TRIGGER_STATEMENTS: &[&str] = &[ END" ]; +/// Migrations to apply to the activities table. +/// Each entry is (column_name, ALTER TABLE statement). The column is checked +/// via `PRAGMA table_info` before running the statement to avoid relying on +/// locale-dependent SQLite error messages. +const MIGRATIONS: &[(&str, &str)] = &[ + ( + "seen_at", + "ALTER TABLE activities ADD COLUMN seen_at INTEGER CHECK (seen_at IS NULL OR seen_at > 0)", + ), +]; + impl ActivityDB { /// Creates a new ActivityDB instance with the specified database path. /// Initializes the database schema if it doesn't exist. @@ -281,6 +292,27 @@ impl ActivityDB { } } + // Run migrations. Check if each column already exists via PRAGMA table_info + // before running ALTER TABLE, so we don't rely on locale-dependent error messages. + for (column, statement) in MIGRATIONS { + let column_exists: bool = self + .conn + .prepare("PRAGMA table_info(activities)") + .and_then(|mut stmt| { + stmt.query_map([], |row| row.get::<_, String>(1)) + .map(|rows| rows.filter_map(|r| r.ok()).any(|name| name == *column)) + }) + .unwrap_or(false); + + if !column_exists { + self.conn.execute(statement, []).map_err(|e| { + ActivityError::InitializationError { + error_details: format!("Error running migration: {}", e), + } + })?; + } + } + Ok(()) } diff --git a/src/modules/onchain/compose.rs b/src/modules/onchain/compose.rs new file mode 100644 index 0000000..0fa94d8 --- /dev/null +++ b/src/modules/onchain/compose.rs @@ -0,0 +1,222 @@ +//! Generic transaction composition using BDK's TxBuilder. +//! +//! Produces signer-agnostic PSBTs that can be signed by any +//! PSBT-compatible hardware or software wallet (Trezor, Ledger, etc.). + +use base64::{engine::general_purpose, Engine as _}; +use bdk::bitcoin::address::NetworkUnchecked; +use bdk::bitcoin::{Address as BdkAddress, Network as BdkNetwork}; +use bdk::database::MemoryDatabase; +use bdk::wallet::coin_selection::{ + CoinSelectionAlgorithm, LargestFirstCoinSelection, OldestFirstCoinSelection, +}; +use bdk::wallet::Wallet; +use bdk::FeeRate; + +use super::errors::AccountInfoError; +use super::implementation::{connect_electrum, create_and_sync_wallet, resolve_wallet_setup}; +use super::types::{CoinSelection, ComposeOutput, ComposeParams, ComposeResult}; + +/// Compose transactions for multiple fee rates, returning one PSBT per rate. +/// +/// Creates a BDK wallet from the extended key, syncs via Electrum, then +/// builds a PSBT for each requested fee rate. The PSBTs include BIP32 +/// derivation paths (when `fingerprint` is provided) and are ready for +/// signing by any PSBT-aware signer without additional network calls. +pub async fn compose_transaction(params: ComposeParams) -> Vec { + let num_rates = params.fee_rates.len(); + if num_rates == 0 { + return vec![]; + } + match compose_inner(params).await { + Ok(results) => results, + Err(e) => { + let err = ComposeResult::Error { + error: e.to_string(), + }; + vec![err; num_rates] + } + } +} + +async fn compose_inner(params: ComposeParams) -> Result, AccountInfoError> { + let setup = resolve_wallet_setup( + ¶ms.wallet.extended_key, + params.wallet.network, + params.wallet.account_type, + params.wallet.fingerprint.as_deref(), + )?; + + validate_outputs(¶ms.outputs, setup.network).map_err(|e| { + AccountInfoError::WalletError { + error_details: e, + } + })?; + + for rate in ¶ms.fee_rates { + if !rate.is_finite() || *rate <= 0.0 { + return Err(AccountInfoError::WalletError { + error_details: format!("Fee rate must be a positive number, got {}", rate), + }); + } + } + + let electrum_url = params.wallet.electrum_url; + let outputs = params.outputs; + let fee_rates = params.fee_rates; + let coin_selection = params.coin_selection; + + tokio::task::spawn_blocking(move || { + let client = connect_electrum(&electrum_url)?; + let mut wallet = create_and_sync_wallet(&setup, client)?; + + let mut results = Vec::with_capacity(fee_rates.len()); + for rate in &fee_rates { + let result = + match build_psbt(&mut wallet, &outputs, *rate, setup.network, coin_selection.as_ref()) { + Ok(r) => r, + Err(msg) => ComposeResult::Error { error: msg }, + }; + results.push(result); + } + Ok(results) + }) + .await + .map_err(|e| AccountInfoError::SyncError { + error_details: format!("Task failed: {}", e), + })? +} + +fn build_psbt( + wallet: &mut Wallet, + outputs: &[ComposeOutput], + fee_rate: f32, + network: BdkNetwork, + coin_selection: Option<&CoinSelection>, +) -> Result { + match coin_selection.unwrap_or(&CoinSelection::BranchAndBound) { + CoinSelection::BranchAndBound => { + let builder = wallet.build_tx(); + finish_psbt(builder, outputs, fee_rate, network) + } + CoinSelection::LargestFirst => { + let builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + finish_psbt(builder, outputs, fee_rate, network) + } + CoinSelection::OldestFirst => { + let builder = wallet.build_tx().coin_selection(OldestFirstCoinSelection); + finish_psbt(builder, outputs, fee_rate, network) + } + } +} + +/// Build a PSBT from pre-validated outputs and fee rate. +fn finish_psbt>( + mut builder: bdk::wallet::tx_builder::TxBuilder<'_, MemoryDatabase, Cs, bdk::wallet::tx_builder::CreateTx>, + outputs: &[ComposeOutput], + fee_rate: f32, + network: BdkNetwork, +) -> Result { + builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate)); + builder.enable_rbf(); + + for output in outputs { + match output { + ComposeOutput::Payment { + address, + amount_sats, + } => { + let script = parse_address(address, network)?; + builder.add_recipient(script, *amount_sats); + } + ComposeOutput::SendMax { address } => { + let script = parse_address(address, network)?; + builder.drain_to(script); + builder.drain_wallet(); + } + ComposeOutput::OpReturn { data_hex } => { + let data = hex::decode(data_hex) + .map_err(|e| format!("Invalid OP_RETURN hex: {}", e))?; + let push_data = bdk::bitcoin::script::PushBytesBuf::try_from(data) + .map_err(|e| format!("OP_RETURN data too large: {}", e))?; + let script = bdk::bitcoin::blockdata::script::Builder::new() + .push_opcode(bdk::bitcoin::blockdata::opcodes::all::OP_RETURN) + .push_slice(push_data) + .into_script(); + builder.add_recipient(script, 0); + } + } + } + + let (psbt, details) = builder.finish().map_err(|e| format!("{}", e))?; + + let fee = details + .fee + .ok_or("BDK could not determine the transaction fee")?; + // Note: for self-transfers (e.g. consolidation), received includes the + // destination output so total_spent will undercount. See ComposeResult docs. + let total_spent = details.sent.saturating_sub(details.received); + let psbt_base64 = general_purpose::STANDARD.encode(psbt.serialize()); + + Ok(ComposeResult::Success { + psbt: psbt_base64, + fee, + fee_rate, + total_spent, + }) +} + +fn parse_address(address: &str, network: BdkNetwork) -> Result { + let addr = address + .parse::>() + .map_err(|e| format!("Invalid address '{}': {}", address, e))? + .require_network(network) + .map_err(|e| format!("Address network mismatch: {}", e))?; + Ok(addr.script_pubkey()) +} + +/// Pre-validate outputs before the Electrum sync for fast feedback. +pub(crate) fn validate_outputs(outputs: &[ComposeOutput], network: BdkNetwork) -> Result<(), String> { + if outputs.is_empty() { + return Err("At least one output is required".into()); + } + let has_recipient = outputs + .iter() + .any(|o| matches!(o, ComposeOutput::Payment { .. } | ComposeOutput::SendMax { .. })); + if !has_recipient { + return Err("At least one Payment or SendMax output is required".into()); + } + + let mut has_drain = false; + for output in outputs { + match output { + ComposeOutput::Payment { address, amount_sats } => { + if *amount_sats == 0 { + return Err("Payment amount must be greater than zero".into()); + } + parse_address(address, network)?; + } + ComposeOutput::SendMax { address } => { + if has_drain { + return Err("Only one SendMax output is allowed".into()); + } + parse_address(address, network)?; + has_drain = true; + } + ComposeOutput::OpReturn { data_hex } => { + if data_hex.is_empty() { + return Err("OP_RETURN data must not be empty".into()); + } + let data = hex::decode(data_hex) + .map_err(|e| format!("Invalid OP_RETURN hex: {}", e))?; + if data.len() > 80 { + return Err(format!( + "OP_RETURN data exceeds 80-byte standard relay limit ({} bytes)", + data.len() + )); + } + } + } + } + Ok(()) +} diff --git a/src/modules/onchain/errors.rs b/src/modules/onchain/errors.rs index 465e2b9..62e07d9 100644 --- a/src/modules/onchain/errors.rs +++ b/src/modules/onchain/errors.rs @@ -27,3 +27,53 @@ pub enum SweepError { #[error("Invalid mnemonic format")] InvalidMnemonic, } + +#[derive(uniffi::Error, Debug, Error)] +#[non_exhaustive] +pub enum BroadcastError { + #[error("Invalid transaction hex: {error_details}")] + InvalidHex { error_details: String }, + #[error("Invalid transaction data: {error_details}")] + InvalidTransaction { error_details: String }, + #[error("Electrum error: {error_details}")] + ElectrumError { error_details: String }, + #[error("Task error: {error_details}")] + TaskError { error_details: String }, +} + +/// Errors specific to account info operations (BDK/Electrum-based). +#[derive(uniffi::Error, Debug, Error)] +#[non_exhaustive] +pub enum AccountInfoError { + /// The provided extended public key is invalid or cannot be parsed + #[error("Invalid extended public key: {error_details}")] + InvalidExtendedKey { error_details: String }, + + /// The provided address is invalid + #[error("Invalid address: {error_details}")] + InvalidAddress { error_details: String }, + + /// Electrum connection or query failed + #[error("Electrum connection failed: {error_details}")] + ElectrumError { error_details: String }, + + /// BDK wallet creation or operation error + #[error("Wallet error: {error_details}")] + WalletError { error_details: String }, + + /// Wallet sync with Electrum failed + #[error("Sync failed: {error_details}")] + SyncError { error_details: String }, + + /// The key type/prefix is not recognized + #[error("Unsupported key type: {error_details}")] + UnsupportedKeyType { error_details: String }, + + /// Network mismatch between key prefix and specified network + #[error("Network mismatch: {error_details}")] + NetworkMismatch { error_details: String }, + + /// Invalid transaction ID provided + #[error("Invalid transaction ID: {error_details}")] + InvalidTxid { error_details: String }, +} diff --git a/src/modules/onchain/implementation.rs b/src/modules/onchain/implementation.rs index d154229..ec923d7 100644 --- a/src/modules/onchain/implementation.rs +++ b/src/modules/onchain/implementation.rs @@ -1,7 +1,9 @@ +use std::collections::HashMap; use std::str::FromStr; use base64::{engine::general_purpose, Engine as _}; use bdk::bitcoin::absolute::LockTime; +use bdk::bitcoin::bip32::ExtendedPubKey; use bdk::bitcoin::consensus::deserialize; use bdk::bitcoin::psbt::PartiallySignedTransaction as Psbt; use bdk::bitcoin::{ @@ -14,29 +16,24 @@ use bdk::electrum_client::ElectrumApi; use bdk::keys::bip39::Mnemonic; use bdk::template::{Bip44, Bip49, Bip86}; use bdk::wallet::signer::SignOptions; -use bdk::wallet::{SyncOptions, Wallet}; +use bdk::wallet::{AddressIndex as BdkAddressIndex, SyncOptions, Wallet}; use bdk::KeychainKind; use bitcoin::address::{Address, NetworkUnchecked}; use bitcoin::Network; use bitcoin_address_generator; -use super::types::{AddressType, ValidationResult}; +use super::errors::AccountInfoError; +use super::types::{ + AccountAddresses, AccountInfoResult, AccountType, AccountUtxo, AddressInfo, + AddressType, ComposeAccount, Network as OnchainNetwork, SingleAddressInfoResult, + ValidationResult, +}; use crate::modules::scanner::NetworkType; use crate::onchain::types::{ GetAddressResponse, GetAddressesResponse, SweepResult, SweepTransactionPreview, SweepableBalances, WordCount, }; -use crate::onchain::{AddressError, SweepError}; - - -fn network_to_bdk(network: Network) -> BdkNetwork { - match network { - Network::Bitcoin => BdkNetwork::Bitcoin, - Network::Testnet | Network::Testnet4 => BdkNetwork::Testnet, - Network::Signet => BdkNetwork::Signet, - Network::Regtest => BdkNetwork::Regtest, - } -} +use crate::onchain::{AddressError, BroadcastError, SweepError}; struct SweepWallets { legacy_wallet: Wallet, @@ -196,7 +193,7 @@ impl BitcoinAddressValidator { network: Network, bip39_passphrase: Option<&str>, ) -> Result { - let bdk_network = network_to_bdk(network); + let bdk_network = onchain_to_bdk_network(network.into()); let mnemonic = Mnemonic::from_str(mnemonic_phrase).map_err(|_| SweepError::InvalidMnemonic)?; let key = (mnemonic.clone(), bip39_passphrase.map(String::from)); @@ -356,7 +353,7 @@ impl BitcoinAddressValidator { destination_address: &str, fee_rate_sats_per_vbyte: Option, ) -> Result { - let bdk_network = network_to_bdk(network); + let bdk_network = onchain_to_bdk_network(network.into()); let wallets = Self::create_sweep_wallets(mnemonic_phrase, network, bip39_passphrase)?; let dest_addr = BdkAddress::from_str(destination_address) @@ -557,6 +554,644 @@ impl BitcoinAddressValidator { } } +/// Broadcast a signed raw transaction via Electrum. +/// +/// Takes a hex-encoded serialized transaction and an Electrum server URL. +/// Returns the transaction ID on success. +pub async fn broadcast_raw_tx( + serialized_tx: String, + electrum_url: &str, +) -> Result { + let tx_bytes = hex::decode(&serialized_tx).map_err(|e| BroadcastError::InvalidHex { + error_details: format!("Invalid transaction hex: {}", e), + })?; + + // Validate that the bytes are a valid transaction + let _tx: Transaction = deserialize(&tx_bytes).map_err(|e| BroadcastError::InvalidTransaction { + error_details: format!("Invalid transaction data: {}", e), + })?; + + let electrum_url_owned = electrum_url.to_string(); + + let txid = tokio::task::spawn_blocking(move || { + let client = bdk::electrum_client::Client::new(&electrum_url_owned).map_err(|e| { + BroadcastError::ElectrumError { + error_details: format!("Failed to connect to Electrum: {}", e), + } + })?; + + client + .transaction_broadcast_raw(&tx_bytes) + .map_err(|e| BroadcastError::ElectrumError { + error_details: format!("Broadcast failed: {}", e), + }) + }) + .await + .map_err(|e| BroadcastError::TaskError { + error_details: format!("Broadcast task failed: {}", e), + })??; + + Ok(txid.to_string()) +} + +// ============================================================================ +// Account info: key/account type detection helpers +// ============================================================================ + +/// Detect the account type from an extended public key prefix. +/// `xpub`/`tpub` default to `Legacy`; use `account_type_override` for other script types. +pub fn detect_account_type(extended_key: &str) -> Result { + let prefix = extended_key.get(..4).ok_or(AccountInfoError::InvalidExtendedKey { + error_details: "Key too short".to_string(), + })?; + match prefix { + "xpub" | "tpub" => Ok(AccountType::Legacy), + "ypub" | "upub" => Ok(AccountType::WrappedSegwit), + "zpub" | "vpub" => Ok(AccountType::NativeSegwit), + prefix => Err(AccountInfoError::UnsupportedKeyType { + error_details: format!("Unsupported key prefix: {}", prefix), + }), + } +} + +/// Detect network from an extended public key prefix. +pub fn detect_network_from_key(extended_key: &str) -> Result { + let prefix = extended_key.get(..4).ok_or(AccountInfoError::InvalidExtendedKey { + error_details: "Key too short".to_string(), + })?; + match prefix { + "xpub" | "ypub" | "zpub" => Ok(BdkNetwork::Bitcoin), + "tpub" | "upub" | "vpub" => Ok(BdkNetwork::Testnet), + prefix => Err(AccountInfoError::UnsupportedKeyType { + error_details: format!("Cannot determine network from prefix: {}", prefix), + }), + } +} + +/// Convert ypub/zpub/upub/vpub to xpub/tpub by swapping the version bytes. +/// BDK only understands standard xpub/tpub format. +pub fn normalize_extended_key(extended_key: &str) -> Result { + let prefix = extended_key.get(..4).ok_or(AccountInfoError::InvalidExtendedKey { + error_details: "Key too short".to_string(), + })?; + let target_version: Option<[u8; 4]> = match prefix { + "xpub" | "tpub" => None, // Already standard format + "ypub" | "zpub" => Some([0x04, 0x88, 0xB2, 0x1E]), // Convert to xpub + "upub" | "vpub" => Some([0x04, 0x35, 0x87, 0xCF]), // Convert to tpub + _ => { + return Err(AccountInfoError::UnsupportedKeyType { + error_details: format!("Unknown key prefix: {}", prefix), + }) + } + }; + + match target_version { + None => Ok(extended_key.to_string()), + Some(version) => { + let mut decoded = bdk::bitcoin::base58::decode_check(extended_key).map_err(|e| { + AccountInfoError::InvalidExtendedKey { + error_details: format!("Base58 decode failed: {:?}", e), + } + })?; + + if decoded.len() < 4 { + return Err(AccountInfoError::InvalidExtendedKey { + error_details: "Decoded key too short".to_string(), + }); + } + + decoded[0..4].copy_from_slice(&version); + Ok(bdk::bitcoin::base58::encode_check(&decoded)) + } + } +} + +/// Build BDK descriptor strings for external and internal keychains. +/// +/// When `key_origin` is provided as `(fingerprint_hex, derivation_path)`, +/// the descriptors include key origin info needed for PSBT BIP32 derivation +/// paths, e.g. `wpkh([73c5da0a/84'/0'/0']xpub.../0/*)`. +pub fn build_descriptors( + normalized_xpub: &str, + account_type: AccountType, + key_origin: Option<(&str, &str)>, +) -> (String, String) { + let key_expr = match key_origin { + Some((fingerprint, path)) => format!("[{}/{}]{}", fingerprint, path, normalized_xpub), + None => normalized_xpub.to_string(), + }; + let (external, internal) = match account_type { + AccountType::Legacy => ( + format!("pkh({}/0/*)", key_expr), + format!("pkh({}/1/*)", key_expr), + ), + AccountType::WrappedSegwit => ( + format!("sh(wpkh({}/0/*))", key_expr), + format!("sh(wpkh({}/1/*))", key_expr), + ), + AccountType::NativeSegwit => ( + format!("wpkh({}/0/*)", key_expr), + format!("wpkh({}/1/*)", key_expr), + ), + AccountType::Taproot => ( + format!("tr({}/0/*)", key_expr), + format!("tr({}/1/*)", key_expr), + ), + }; + (external, internal) +} + +/// Determine the BIP derivation base path. +pub fn derive_base_path(account_type: AccountType, network: BdkNetwork, account_index: u32) -> String { + let purpose = match account_type { + AccountType::Legacy => 44, + AccountType::WrappedSegwit => 49, + AccountType::NativeSegwit => 84, + AccountType::Taproot => 86, + }; + let coin_type = match network { + BdkNetwork::Bitcoin => 0, + _ => 1, // testnet/signet/regtest all use coin_type 1 + }; + format!("m/{}'/{}'/{}'", purpose, coin_type, account_index) +} + +// ============================================================================ +// Shared wallet setup helper +// ============================================================================ + +pub(crate) struct WalletSetup { + pub external_desc: String, + pub internal_desc: String, + pub network: BdkNetwork, + pub base_path: String, + pub account_type: AccountType, +} + +/// Resolve an extended key into descriptors, network, and derivation path. +pub(crate) fn resolve_wallet_setup( + extended_key: &str, + network: Option, + account_type_override: Option, + fingerprint: Option<&str>, +) -> Result { + let account_type = match account_type_override { + Some(st) => match extended_key.get(..4).unwrap_or("") { + "xpub" | "tpub" => st, + _ => detect_account_type(extended_key)?, + }, + _ => detect_account_type(extended_key)?, + }; + + let detected_network = detect_network_from_key(extended_key)?; + + if let Some(net) = network { + let specified = onchain_to_bdk_network(net); + // Regtest uses the same key prefixes (tpub/upub/vpub) as Testnet, + // so treat them as compatible for key validation purposes. + let networks_compatible = specified == detected_network + || (specified == BdkNetwork::Regtest && detected_network == BdkNetwork::Testnet); + if !networks_compatible { + return Err(AccountInfoError::NetworkMismatch { + error_details: format!( + "Key prefix suggests {:?} but {:?} was specified", + detected_network, specified + ), + }); + } + } + + let normalized_key = normalize_extended_key(extended_key)?; + + let xpub = ExtendedPubKey::from_str(&normalized_key).map_err(|e| { + AccountInfoError::InvalidExtendedKey { + error_details: format!("Failed to parse extended key: {}", e), + } + })?; + let account_index = match xpub.child_number { + bdk::bitcoin::bip32::ChildNumber::Hardened { index } => index, + bdk::bitcoin::bip32::ChildNumber::Normal { index } => index, + }; + let base_path = derive_base_path(account_type, detected_network, account_index); + + let normalized_fp = if let Some(fp) = fingerprint { + if fp.len() != 8 || !fp.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(AccountInfoError::InvalidExtendedKey { + error_details: format!( + "Fingerprint must be 8 hex characters (e.g. \"73c5da0a\"), got \"{}\"", + fp + ), + }); + } + Some(fp.to_ascii_lowercase()) + } else { + None + }; + + let derivation = base_path.strip_prefix("m/").unwrap_or(&base_path); + let key_origin: Option<(&str, &str)> = + normalized_fp.as_deref().map(|fp| (fp, derivation)); + let (external_desc, internal_desc) = + build_descriptors(&normalized_key, account_type, key_origin); + + // Use the specified network when provided and compatible, so that + // regtest addresses (bcrt1q) are validated correctly even though the + // key prefix (tpub) is detected as testnet. + let effective_network = network + .map(onchain_to_bdk_network) + .unwrap_or(detected_network); + + Ok(WalletSetup { + external_desc, + internal_desc, + network: effective_network, + base_path, + account_type, + }) +} + +/// Convert our Network enum to BDK's Network. +pub(crate) fn onchain_to_bdk_network(network: OnchainNetwork) -> BdkNetwork { + match network { + OnchainNetwork::Bitcoin => BdkNetwork::Bitcoin, + OnchainNetwork::Testnet | OnchainNetwork::Testnet4 => BdkNetwork::Testnet, + OnchainNetwork::Signet => BdkNetwork::Signet, + OnchainNetwork::Regtest => BdkNetwork::Regtest, + } +} + +/// Connect to Electrum and return the raw client. +pub(crate) fn connect_electrum( + electrum_url: &str, +) -> Result { + bdk::electrum_client::Client::new(electrum_url).map_err(|e| AccountInfoError::ElectrumError { + error_details: format!("Failed to connect to Electrum: {}", e), + }) +} + +/// Create a BDK wallet and sync it via the provided Electrum client. +/// +/// The client is consumed; make any pre-sync calls (e.g. `block_headers_subscribe`) +/// before passing it here. +pub(crate) fn create_and_sync_wallet( + setup: &WalletSetup, + client: bdk::electrum_client::Client, +) -> Result, AccountInfoError> { + let wallet = Wallet::new( + &setup.external_desc, + Some(&setup.internal_desc), + setup.network, + MemoryDatabase::new(), + ) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to create wallet: {}", e), + })?; + + let blockchain = ElectrumBlockchain::from(client); + wallet + .sync(&blockchain, SyncOptions::default()) + .map_err(|e| AccountInfoError::SyncError { + error_details: format!("Failed to sync wallet: {}", e), + })?; + + Ok(wallet) +} + +// ============================================================================ +// Account info: main async functions +// ============================================================================ + +/// Query account information for an extended public key (xpub/ypub/zpub) via Electrum. +pub async fn get_account_info( + extended_key: &str, + electrum_url: &str, + network: Option, + gap_limit: Option, + script_type: Option, +) -> Result { + let setup = resolve_wallet_setup(extended_key, network, script_type, None)?; + let gap = gap_limit.unwrap_or(20); + let base_path = setup.base_path.clone(); + let account_type = setup.account_type; + + let electrum_url_owned = electrum_url.to_string(); + + let result = tokio::task::spawn_blocking(move || { + let base_path = &setup.base_path; + + // Single Electrum connection: get tip height first, then sync wallet + let client = connect_electrum(&electrum_url_owned)?; + let header = client.block_headers_subscribe().map_err(|e| { + AccountInfoError::ElectrumError { + error_details: format!("Failed to get block height: {}", e), + } + })?; + let tip_height = u32::try_from(header.height).map_err(|_| { + AccountInfoError::ElectrumError { + error_details: format!("Invalid block height: {}", header.height), + } + })?; + + let wallet = create_and_sync_wallet(&setup, client)?; + + // Get the next unused external address index + let next_external = wallet + .get_address(BdkAddressIndex::LastUnused) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to get address: {}", e), + })?; + let max_external = next_external.index.saturating_add(gap); + + // Get the next unused change address index + let next_change = wallet + .get_internal_address(BdkAddressIndex::LastUnused) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to get change address: {}", e), + })?; + let max_change = next_change.index.saturating_add(gap); + + // Build address lists using BDK's LastUnused boundary (no extra Electrum calls) + let mut address_paths: HashMap = HashMap::new(); + + // External addresses + let mut used_addresses: Vec = Vec::new(); + let mut unused_addresses: Vec = Vec::new(); + + for index in 0..max_external { + let addr = wallet + .get_address(BdkAddressIndex::Peek(index)) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to peek address at index {}: {}", index, e), + })?; + + let addr_str = addr.address.to_string(); + let path = format!("{}/0/{}", base_path, index); + let script = addr.address.script_pubkey(); + + address_paths.insert(script, path.clone()); + + let is_used = index < next_external.index; + let info = AddressInfo { + address: addr_str, + path, + transfers: if is_used { 1 } else { 0 }, + }; + + if is_used { + used_addresses.push(info); + } else { + unused_addresses.push(info); + } + } + + // Change addresses + let mut change_addresses: Vec = Vec::new(); + + for index in 0..max_change { + let addr = wallet + .get_internal_address(BdkAddressIndex::Peek(index)) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!( + "Failed to peek change address at index {}: {}", + index, e + ), + })?; + + let addr_str = addr.address.to_string(); + let path = format!("{}/1/{}", base_path, index); + let script = addr.address.script_pubkey(); + + address_paths.insert(script, path.clone()); + + let is_used = index < next_change.index; + change_addresses.push(AddressInfo { + address: addr_str, + path, + transfers: if is_used { 1 } else { 0 }, + }); + } + + // Extract UTXOs + let utxos = wallet.list_unspent().map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to list UTXOs: {}", e), + })?; + + // Get transaction details for confirmation info and coinbase detection + let transactions = + wallet + .list_transactions(true) + .map_err(|e| AccountInfoError::WalletError { + error_details: format!("Failed to list transactions: {}", e), + })?; + + // Map UTXOs to AccountUtxo + let mut account_utxos: Vec = Vec::new(); + for utxo in &utxos { + let utxo_script = &utxo.txout.script_pubkey; + + let utxo_path = match address_paths.get(utxo_script) { + Some(path) => path.clone(), + None => { + log::warn!( + "No derivation path found for UTXO {}:{}", + utxo.outpoint.txid, utxo.outpoint.vout, + ); + String::new() + } + }; + + let addr_str = BdkAddress::from_script(utxo_script, setup.network) + .map(|a| a.to_string()) + .unwrap_or_default(); + + // Get confirmation info and coinbase status from transaction details + let tx_detail = transactions + .iter() + .find(|tx| tx.txid == utxo.outpoint.txid); + + let (block_height, confirmations) = tx_detail + .and_then(|tx| tx.confirmation_time.as_ref()) + .map(|conf| { + let height = conf.height; + let confs = tip_height.saturating_sub(height) + 1; + (height, confs) + }) + .unwrap_or((0, 0)); + + let coinbase = tx_detail + .and_then(|tx| tx.transaction.as_ref()) + .map_or(false, |t| t.is_coin_base()); + + account_utxos.push(AccountUtxo { + txid: utxo.outpoint.txid.to_string(), + vout: utxo.outpoint.vout, + amount: utxo.txout.value, + block_height, + address: addr_str, + path: utxo_path, + confirmations, + coinbase, + own: true, + required: None, + }); + } + + let balance: u64 = utxos.iter().map(|u| u.txout.value).sum(); + + Ok(( + used_addresses, + unused_addresses, + change_addresses, + account_utxos, + balance, + u32::try_from(utxos.len()).unwrap_or(u32::MAX), + tip_height, + )) + }) + .await + .map_err(|e| AccountInfoError::SyncError { + error_details: format!("Task failed: {}", e), + })??; + + let ( + used_addresses, + unused_addresses, + change_addresses, + account_utxos, + balance, + utxo_count, + block_height, + ) = result; + + let account = ComposeAccount { + path: base_path, + addresses: AccountAddresses { + used: used_addresses, + unused: unused_addresses, + change: change_addresses, + }, + utxo: account_utxos, + }; + + Ok(AccountInfoResult { + account, + balance, + utxo_count, + account_type, + block_height, + }) +} + +/// Query balance and UTXOs for a single Bitcoin address via Electrum. +/// +/// When `network` is `None`, the address is accepted without network validation +/// (`assume_checked`). In that case, if the Electrum server is on a different +/// network the query will silently return empty or incorrect results. +pub async fn get_address_info( + address: &str, + electrum_url: &str, + network: Option, +) -> Result { + // Parse with BDK's bitcoin crate for script_pubkey generation + let bdk_addr = BdkAddress::from_str(address) + .map_err(|e| AccountInfoError::InvalidAddress { + error_details: format!("Invalid address: {}", e), + })?; + let bdk_addr = match network { + Some(net) => { + let bdk_network = onchain_to_bdk_network(net); + bdk_addr.require_network(bdk_network).map_err(|e| { + AccountInfoError::NetworkMismatch { + error_details: format!("Address network mismatch: {}", e), + } + })? + } + None => bdk_addr.assume_checked(), + }; + + let electrum_url_owned = electrum_url.to_string(); + let addr_str = address.to_string(); + + let result = tokio::task::spawn_blocking(move || { + let client = connect_electrum(&electrum_url_owned)?; + + let script = bdk_addr.script_pubkey(); + + // Get block height + let header = client.block_headers_subscribe().map_err(|e| { + AccountInfoError::ElectrumError { + error_details: format!("Failed to get block height: {}", e), + } + })?; + let tip_height = u32::try_from(header.height).map_err(|_| { + AccountInfoError::ElectrumError { + error_details: format!("Invalid block height: {}", header.height), + } + })?; + + // Get UTXOs for this address + let utxos = client.script_list_unspent(&script).map_err(|e| { + AccountInfoError::ElectrumError { + error_details: format!("Failed to list UTXOs: {}", e), + } + })?; + + // Get history for transfer count + let history = client.script_get_history(&script).map_err(|e| { + AccountInfoError::ElectrumError { + error_details: format!("Failed to get history: {}", e), + } + })?; + + let account_utxos: Vec = utxos + .iter() + .map(|utxo| { + let height = u32::try_from(utxo.height).unwrap_or(0); + let confirmations = if height > 0 { + tip_height.saturating_sub(height) + 1 + } else { + 0 + }; + + let vout = u32::try_from(utxo.tx_pos).map_err(|_| { + AccountInfoError::WalletError { + error_details: format!("Output index {} exceeds u32", utxo.tx_pos), + } + })?; + + Ok(AccountUtxo { + txid: utxo.tx_hash.to_string(), + vout, + amount: utxo.value, + block_height: height, + address: addr_str.clone(), + path: String::new(), // No derivation path for single address + confirmations, + coinbase: false, + own: true, + required: None, + }) + }) + .collect::, AccountInfoError>>()?; + + let balance: u64 = utxos.iter().map(|u| u.value).sum(); + + Ok::<_, AccountInfoError>(SingleAddressInfoResult { + address: addr_str, + balance, + utxos: account_utxos, + transfers: u32::try_from(history.len()).unwrap_or(u32::MAX), + block_height: tip_height, + }) + }) + .await + .map_err(|e| AccountInfoError::SyncError { + error_details: format!("Task failed: {}", e), + })??; + + Ok(result) +} + fn parse_address(address: &str) -> Result, AddressError> { Address::from_str(address) .map_err(|e| { diff --git a/src/modules/onchain/mod.rs b/src/modules/onchain/mod.rs index 92c202f..0f984d2 100644 --- a/src/modules/onchain/mod.rs +++ b/src/modules/onchain/mod.rs @@ -1,13 +1,22 @@ mod errors; mod implementation; mod types; +mod compose; -pub use errors::{AddressError, SweepError}; -pub use implementation::BitcoinAddressValidator; +pub use errors::{AccountInfoError, AddressError, BroadcastError, SweepError}; +pub use implementation::{ + broadcast_raw_tx, build_descriptors, derive_base_path, detect_account_type, + detect_network_from_key, get_account_info, get_address_info, normalize_extended_key, + BitcoinAddressValidator, +}; pub use types::{ - AddressType, GetAddressResponse, GetAddressesResponse, Network, SweepResult, - SweepTransactionPreview, SweepableBalances, ValidationResult, WordCount, + AccountAddresses, AccountInfoResult, AccountType, AccountUtxo, AddressInfo, + AddressType, CoinSelection, ComposeAccount, ComposeOutput, ComposeParams, + ComposeResult, GetAddressResponse, GetAddressesResponse, Network, + SingleAddressInfoResult, SweepResult, SweepTransactionPreview, SweepableBalances, + ValidationResult, WalletParams, WordCount, }; +pub use compose::compose_transaction; #[cfg(test)] mod tests; diff --git a/src/modules/onchain/tests.rs b/src/modules/onchain/tests.rs index ecdf572..bbba215 100644 --- a/src/modules/onchain/tests.rs +++ b/src/modules/onchain/tests.rs @@ -510,4 +510,776 @@ mod tests { assert!(result.amount_swept > 0); assert!(result.fee_paid > 0); } + + #[test] + fn test_broadcast_raw_tx_invalid_hex() { + use crate::modules::onchain::broadcast_raw_tx; + use crate::modules::onchain::BroadcastError; + + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(broadcast_raw_tx( + "not_valid_hex".to_string(), + "ssl://electrum.blockstream.info:60002", + )); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), BroadcastError::InvalidHex { .. })); + } + + #[test] + fn test_broadcast_raw_tx_invalid_tx_data() { + use crate::modules::onchain::broadcast_raw_tx; + use crate::modules::onchain::BroadcastError; + + let rt = tokio::runtime::Runtime::new().unwrap(); + // Valid hex but not a valid transaction + let result = rt.block_on(broadcast_raw_tx( + "deadbeef".to_string(), + "ssl://electrum.blockstream.info:60002", + )); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), BroadcastError::InvalidTransaction { .. })); + } + + // ======================================================================== + // Account Info Tests + // ======================================================================== + + const ACCOUNT_INFO_ELECTRUM_URL: &str = "ssl://fulcrum.bitkit.stag0.blocktank.to:18484"; + + const TEST_TPUB: &str = "tpubDDWohsp5dx2iMJ9N7iHbgAEDhH4BJB9NWW1fEW3yA3AFNDREmpzteCXNqppMLUmKFY5q5e3PXtS5CuqWCQbYcGhpPqYAgQSYdwknW9J6sQv"; + const TEST_UPUB: &str = "upub5DWPhYKrgLiETEmgLykymFzBK6gXUNvkE67HLSEh5zcWNRdx2Hxd8HypxaDaK1p62a1kXwe9eBcV3pGm7yEpHh4ebrspSoer4x8Ko29egtv"; + const TEST_VPUB: &str = "vpub5ZgC33hDPuhHSDYrGeoi685MKtdAUB3pkS4rarJ5dv3RABneKLCbhJu2FNfcajo1GukBAwEMBXZWvU7fykTeyKEJrgV6E6NC7BZ3jzp3ffp"; + + const TEST_LEGACY_ADDR: &str = "mixttbUXpVWVpx3qHh7KUiVnxWiNxL2uu9"; + const TEST_P2SH_ADDR: &str = "2N7mA1KBJX8iprzzoFjcbkY1Z2WRZ3bjnsK"; + const TEST_REGTEST_BECH32_ADDR: &str = "bcrt1qj2gz3meule5mc4r4knv65vjds3g88rlxs0jlmq"; + const TEST_TESTNET_BECH32_ADDR: &str = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"; + + // --- Unit Tests: Helper Functions --- + + #[test] + fn test_detect_account_type() { + use crate::modules::onchain::detect_account_type; + use crate::modules::onchain::AccountType; + + // Standard prefixes + assert_eq!(detect_account_type("xpub6ABC").unwrap(), AccountType::Legacy); + assert_eq!(detect_account_type("tpub6ABC").unwrap(), AccountType::Legacy); + assert_eq!(detect_account_type("ypub6ABC").unwrap(), AccountType::WrappedSegwit); + assert_eq!(detect_account_type("upub6ABC").unwrap(), AccountType::WrappedSegwit); + assert_eq!(detect_account_type("zpub6ABC").unwrap(), AccountType::NativeSegwit); + assert_eq!(detect_account_type("vpub6ABC").unwrap(), AccountType::NativeSegwit); + + // Actual test keys + assert_eq!(detect_account_type(TEST_TPUB).unwrap(), AccountType::Legacy); + assert_eq!(detect_account_type(TEST_UPUB).unwrap(), AccountType::WrappedSegwit); + assert_eq!(detect_account_type(TEST_VPUB).unwrap(), AccountType::NativeSegwit); + + // Error cases + assert!(detect_account_type("invalid_key").is_err()); + assert!(detect_account_type("ab").is_err()); // too short + } + + #[test] + fn test_detect_network_from_key() { + use crate::modules::onchain::detect_network_from_key; + use bdk::bitcoin::Network as BdkNetwork; + + // Mainnet prefixes + assert_eq!(detect_network_from_key("xpub6ABC").unwrap(), BdkNetwork::Bitcoin); + assert_eq!(detect_network_from_key("ypub6ABC").unwrap(), BdkNetwork::Bitcoin); + assert_eq!(detect_network_from_key("zpub6ABC").unwrap(), BdkNetwork::Bitcoin); + + // Testnet prefixes + assert_eq!(detect_network_from_key("tpub6ABC").unwrap(), BdkNetwork::Testnet); + assert_eq!(detect_network_from_key("upub6ABC").unwrap(), BdkNetwork::Testnet); + assert_eq!(detect_network_from_key("vpub6ABC").unwrap(), BdkNetwork::Testnet); + + // Actual test keys + assert_eq!(detect_network_from_key(TEST_TPUB).unwrap(), BdkNetwork::Testnet); + assert_eq!(detect_network_from_key(TEST_UPUB).unwrap(), BdkNetwork::Testnet); + assert_eq!(detect_network_from_key(TEST_VPUB).unwrap(), BdkNetwork::Testnet); + + // Error cases + assert!(detect_network_from_key("invalid").is_err()); + assert!(detect_network_from_key("ab").is_err()); + } + + #[test] + fn test_normalize_extended_key() { + use crate::modules::onchain::normalize_extended_key; + + // tpub should remain unchanged + let normalized_tpub = normalize_extended_key(TEST_TPUB).unwrap(); + assert!(normalized_tpub.starts_with("tpub"), "tpub should remain as tpub"); + assert_eq!(normalized_tpub, TEST_TPUB); + + // upub should be converted to tpub + let normalized_upub = normalize_extended_key(TEST_UPUB).unwrap(); + assert!(normalized_upub.starts_with("tpub"), "upub should be converted to tpub, got: {}", &normalized_upub[..4]); + + // vpub should be converted to tpub + let normalized_vpub = normalize_extended_key(TEST_VPUB).unwrap(); + assert!(normalized_vpub.starts_with("tpub"), "vpub should be converted to tpub, got: {}", &normalized_vpub[..4]); + + // Error cases + assert!(normalize_extended_key("ab").is_err()); + assert!(normalize_extended_key("invalidkey").is_err()); + } + + #[test] + fn test_build_descriptors() { + use crate::modules::onchain::build_descriptors; + use crate::modules::onchain::AccountType; + + let test_key = "tpub_test_key"; + + let (ext, int) = build_descriptors(test_key, AccountType::Legacy, None); + assert_eq!(ext, "pkh(tpub_test_key/0/*)"); + assert_eq!(int, "pkh(tpub_test_key/1/*)"); + + let (ext, int) = build_descriptors(test_key, AccountType::WrappedSegwit, None); + assert_eq!(ext, "sh(wpkh(tpub_test_key/0/*))"); + assert_eq!(int, "sh(wpkh(tpub_test_key/1/*))"); + + let (ext, int) = build_descriptors(test_key, AccountType::NativeSegwit, None); + assert_eq!(ext, "wpkh(tpub_test_key/0/*)"); + assert_eq!(int, "wpkh(tpub_test_key/1/*)"); + + let (ext, int) = build_descriptors(test_key, AccountType::Taproot, None); + assert_eq!(ext, "tr(tpub_test_key/0/*)"); + assert_eq!(int, "tr(tpub_test_key/1/*)"); + + // With key origin info + let origin = Some(("73c5da0a", "84'/0'/0'")); + let (ext, int) = build_descriptors(test_key, AccountType::NativeSegwit, origin); + assert_eq!(ext, "wpkh([73c5da0a/84'/0'/0']tpub_test_key/0/*)"); + assert_eq!(int, "wpkh([73c5da0a/84'/0'/0']tpub_test_key/1/*)"); + } + + #[test] + fn test_derive_base_path() { + use crate::modules::onchain::derive_base_path; + use crate::modules::onchain::AccountType; + use bdk::bitcoin::Network as BdkNetwork; + + assert_eq!(derive_base_path(AccountType::Legacy, BdkNetwork::Bitcoin, 0), "m/44'/0'/0'"); + assert_eq!(derive_base_path(AccountType::WrappedSegwit, BdkNetwork::Bitcoin, 0), "m/49'/0'/0'"); + assert_eq!(derive_base_path(AccountType::NativeSegwit, BdkNetwork::Bitcoin, 0), "m/84'/0'/0'"); + assert_eq!(derive_base_path(AccountType::Taproot, BdkNetwork::Bitcoin, 0), "m/86'/0'/0'"); + + // Testnet uses coin_type 1 + assert_eq!(derive_base_path(AccountType::Legacy, BdkNetwork::Testnet, 0), "m/44'/1'/0'"); + assert_eq!(derive_base_path(AccountType::NativeSegwit, BdkNetwork::Testnet, 0), "m/84'/1'/0'"); + + // Non-zero account index + assert_eq!(derive_base_path(AccountType::WrappedSegwit, BdkNetwork::Bitcoin, 2), "m/49'/0'/2'"); + assert_eq!(derive_base_path(AccountType::NativeSegwit, BdkNetwork::Testnet, 5), "m/84'/1'/5'"); + } + + #[test] + fn test_fingerprint_validation() { + use crate::modules::onchain::implementation::resolve_wallet_setup; + + // Valid 8-char hex fingerprint + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("73c5da0a")); + assert!(result.is_ok(), "Valid fingerprint should be accepted"); + + // Too short + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("73c5")); + assert!(result.is_err(), "Short fingerprint should be rejected"); + + // Too long + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("73c5da0aff")); + assert!(result.is_err(), "Long fingerprint should be rejected"); + + // Non-hex characters + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("73c5zz0a")); + assert!(result.is_err(), "Non-hex fingerprint should be rejected"); + + // Empty string + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("")); + assert!(result.is_err(), "Empty fingerprint should be rejected"); + + // None fingerprint (valid — key origin omitted from descriptor) + let result = resolve_wallet_setup(TEST_VPUB, None, None, None); + assert!(result.is_ok(), "None fingerprint should be accepted"); + + // Uppercase hex is accepted and normalized to lowercase in descriptor + let result = resolve_wallet_setup(TEST_VPUB, None, None, Some("73C5DA0A")); + assert!(result.is_ok(), "Uppercase hex fingerprint should be accepted"); + let setup = result.unwrap(); + assert!( + setup.external_desc.contains("73c5da0a"), + "Fingerprint should be lowercased in descriptor, got: {}", + setup.external_desc + ); + } + + #[test] + fn test_validate_outputs() { + use crate::modules::onchain::compose::validate_outputs; + use crate::modules::onchain::ComposeOutput; + use bdk::bitcoin::Network as BdkNetwork; + + let net = BdkNetwork::Testnet; + let valid_addr = TEST_TESTNET_BECH32_ADDR; + + // Empty outputs + let r = validate_outputs(&[], net); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("At least one output")); + + // OP_RETURN only (no recipient) + let r = validate_outputs( + &[ComposeOutput::OpReturn { data_hex: "cafe".into() }], + net, + ); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("Payment or SendMax")); + + // Zero-amount payment + let r = validate_outputs( + &[ComposeOutput::Payment { address: valid_addr.into(), amount_sats: 0 }], + net, + ); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("greater than zero")); + + // Multiple SendMax + let r = validate_outputs( + &[ + ComposeOutput::SendMax { address: valid_addr.into() }, + ComposeOutput::SendMax { address: valid_addr.into() }, + ], + net, + ); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("Only one SendMax")); + + // Empty OP_RETURN data + let r = validate_outputs( + &[ + ComposeOutput::Payment { address: valid_addr.into(), amount_sats: 1_000 }, + ComposeOutput::OpReturn { data_hex: "".into() }, + ], + net, + ); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("must not be empty")); + + // OP_RETURN > 80 bytes (81 bytes = 162 hex chars) + let r = validate_outputs( + &[ + ComposeOutput::Payment { address: valid_addr.into(), amount_sats: 1_000 }, + ComposeOutput::OpReturn { data_hex: "aa".repeat(81) }, + ], + net, + ); + assert!(r.is_err()); + assert!(r.unwrap_err().contains("80-byte")); + + // Invalid address for network (regtest addr on testnet) + let r = validate_outputs( + &[ComposeOutput::Payment { + address: TEST_REGTEST_BECH32_ADDR.into(), + amount_sats: 1_000, + }], + net, + ); + assert!(r.is_err()); + let err = r.unwrap_err(); + assert!(err.contains("network mismatch") || err.contains("Address")); + + // Valid outputs pass + let r = validate_outputs( + &[ + ComposeOutput::Payment { address: valid_addr.into(), amount_sats: 5_000 }, + ComposeOutput::OpReturn { data_hex: "deadbeef".into() }, + ], + net, + ); + assert!(r.is_ok()); + + // Valid SendMax + Payment + OpReturn + let r = validate_outputs( + &[ + ComposeOutput::Payment { address: valid_addr.into(), amount_sats: 1_000 }, + ComposeOutput::SendMax { address: valid_addr.into() }, + ComposeOutput::OpReturn { data_hex: "cafe".into() }, + ], + net, + ); + assert!(r.is_ok()); + } + + #[tokio::test] + async fn test_compose_empty_fee_rates() { + use crate::modules::onchain::{ComposeParams, ComposeOutput}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 5_000, + }], + fee_rates: vec![], + coin_selection: None, + }; + + let results = crate::modules::onchain::compose_transaction(params).await; + assert!(results.is_empty(), "Empty fee_rates should return empty vec"); + } + + // --- Integration Tests: get_account_info --- + + #[tokio::test] + #[ignore] + async fn test_get_account_info_tpub() { + use crate::modules::onchain::get_account_info; + use crate::modules::onchain::AccountType; + + let result = get_account_info( + TEST_TPUB, + ACCOUNT_INFO_ELECTRUM_URL, + None, + None, + None, + ) + .await; + + let info = result.expect("get_account_info(tpub) should succeed"); + assert_eq!(info.account_type, AccountType::Legacy); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(info.utxo_count >= 1, "Expected at least 1 UTXO, got {}", info.utxo_count); + assert!(info.block_height > 0, "Expected block_height > 0"); + assert!(info.account.path.starts_with("m/44'/1'/"), "Expected BIP44 testnet path, got {}", info.account.path); + assert!(!info.account.utxo.is_empty(), "Expected non-empty UTXOs"); + + // Verify address structure + assert!(!info.account.addresses.unused.is_empty(), "Expected unused addresses"); + for addr in &info.account.addresses.used { + assert!(!addr.address.is_empty()); + assert!(addr.path.starts_with("m/44'/1'/")); + assert!(addr.transfers > 0); + } + + for utxo in &info.account.utxo { + assert!(!utxo.txid.is_empty(), "UTXO should have non-empty txid"); + let amount: u64 = utxo.amount; + assert!(amount > 0, "UTXO amount should be > 0"); + assert!(!utxo.path.is_empty(), "UTXO should have a derivation path"); + } + + println!("tpub account info: balance={}, utxos={}, path={}, block_height={}", + info.balance, info.utxo_count, info.account.path, info.block_height); + } + + #[tokio::test] + #[ignore] + async fn test_get_account_info_upub() { + use crate::modules::onchain::get_account_info; + use crate::modules::onchain::AccountType; + + let result = get_account_info( + TEST_UPUB, + ACCOUNT_INFO_ELECTRUM_URL, + None, + None, + None, + ) + .await; + + let info = result.expect("get_account_info(upub) should succeed"); + assert_eq!(info.account_type, AccountType::WrappedSegwit); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(info.utxo_count >= 1, "Expected at least 1 UTXO, got {}", info.utxo_count); + assert!(info.block_height > 0); + assert!(info.account.path.starts_with("m/49'/1'/"), "Expected BIP49 testnet path, got {}", info.account.path); + assert!(!info.account.utxo.is_empty()); + + for utxo in &info.account.utxo { + assert!(!utxo.txid.is_empty()); + let amount: u64 = utxo.amount; + assert!(amount > 0); + } + + println!("upub account info: balance={}, utxos={}, path={}, block_height={}", + info.balance, info.utxo_count, info.account.path, info.block_height); + } + + #[tokio::test] + #[ignore] + async fn test_get_account_info_vpub() { + use crate::modules::onchain::get_account_info; + use crate::modules::onchain::AccountType; + + let result = get_account_info( + TEST_VPUB, + ACCOUNT_INFO_ELECTRUM_URL, + None, + None, + None, + ) + .await; + + let info = result.expect("get_account_info(vpub) should succeed"); + assert_eq!(info.account_type, AccountType::NativeSegwit); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(info.utxo_count >= 1, "Expected at least 1 UTXO, got {}", info.utxo_count); + assert!(info.block_height > 0); + assert!(info.account.path.starts_with("m/84'/1'/"), "Expected BIP84 testnet path, got {}", info.account.path); + assert!(!info.account.utxo.is_empty()); + + for utxo in &info.account.utxo { + assert!(!utxo.txid.is_empty()); + let amount: u64 = utxo.amount; + assert!(amount > 0); + } + + println!("vpub account info: balance={}, utxos={}, path={}, block_height={}", + info.balance, info.utxo_count, info.account.path, info.block_height); + } + + // --- Integration Tests: get_address_info --- + + #[tokio::test] + #[ignore] + async fn test_get_address_info_legacy() { + use crate::modules::onchain::get_address_info; + + let result = get_address_info( + TEST_LEGACY_ADDR, + ACCOUNT_INFO_ELECTRUM_URL, + None, + ) + .await; + + let info = result.expect("get_address_info(legacy) should succeed"); + assert_eq!(info.address, TEST_LEGACY_ADDR); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(!info.utxos.is_empty(), "Expected non-empty UTXOs"); + assert!(info.transfers >= 1, "Expected at least 1 transfer, got {}", info.transfers); + assert!(info.block_height > 0); + + for utxo in &info.utxos { + assert_eq!(utxo.address, TEST_LEGACY_ADDR); + assert!(!utxo.txid.is_empty()); + let amount: u64 = utxo.amount; + assert!(amount > 0); + } + + println!("Legacy address info: balance={}, utxos={}, transfers={}, block_height={}", + info.balance, info.utxos.len(), info.transfers, info.block_height); + } + + #[tokio::test] + #[ignore] + async fn test_get_address_info_p2sh() { + use crate::modules::onchain::get_address_info; + + let result = get_address_info( + TEST_P2SH_ADDR, + ACCOUNT_INFO_ELECTRUM_URL, + None, + ) + .await; + + let info = result.expect("get_address_info(p2sh) should succeed"); + assert_eq!(info.address, TEST_P2SH_ADDR); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(!info.utxos.is_empty()); + assert!(info.transfers >= 1); + assert!(info.block_height > 0); + + for utxo in &info.utxos { + assert_eq!(utxo.address, TEST_P2SH_ADDR); + } + + println!("P2SH address info: balance={}, utxos={}, transfers={}, block_height={}", + info.balance, info.utxos.len(), info.transfers, info.block_height); + } + + #[tokio::test] + #[ignore] + async fn test_get_address_info_regtest_bech32() { + use crate::modules::onchain::get_address_info; + + let result = get_address_info( + TEST_REGTEST_BECH32_ADDR, + ACCOUNT_INFO_ELECTRUM_URL, + None, + ) + .await; + + let info = result.expect("get_address_info(regtest bech32) should succeed"); + assert_eq!(info.address, TEST_REGTEST_BECH32_ADDR); + let balance: u64 = info.balance; + assert!(balance >= 100_000, "Expected balance >= 100,000 sats, got {}", balance); + assert!(!info.utxos.is_empty()); + assert!(info.transfers >= 1); + assert!(info.block_height > 0); + + for utxo in &info.utxos { + assert_eq!(utxo.address, TEST_REGTEST_BECH32_ADDR); + } + + println!("Regtest bech32 address info: balance={}, utxos={}, transfers={}, block_height={}", + info.balance, info.utxos.len(), info.transfers, info.block_height); + } + + // --- Error / Edge Case Tests --- + + #[test] + fn test_get_account_info_invalid_key() { + use crate::modules::onchain::get_account_info; + + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(get_account_info( + "not_a_valid_xpub", + ACCOUNT_INFO_ELECTRUM_URL, + None, + None, + None, + )); + + assert!(result.is_err(), "Expected error for invalid key"); + } + + #[test] + fn test_get_account_info_network_mismatch() { + use crate::modules::onchain::get_account_info; + use crate::modules::onchain::{AccountInfoError, Network as OnchainNetwork}; + + let rt = tokio::runtime::Runtime::new().unwrap(); + // tpub is testnet, but we specify Bitcoin (mainnet) — should get NetworkMismatch + let result = rt.block_on(get_account_info( + TEST_TPUB, + ACCOUNT_INFO_ELECTRUM_URL, + Some(OnchainNetwork::Bitcoin), + None, + None, + )); + + assert!(result.is_err(), "Expected NetworkMismatch error"); + match result.unwrap_err() { + AccountInfoError::NetworkMismatch { .. } => {} + other => panic!("Expected NetworkMismatch, got: {:?}", other), + } + } + + #[test] + fn test_get_address_info_invalid_address() { + use crate::modules::onchain::get_address_info; + use crate::modules::onchain::AccountInfoError; + + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(get_address_info( + "not_a_valid_address", + ACCOUNT_INFO_ELECTRUM_URL, + None, + )); + + assert!(result.is_err(), "Expected error for invalid address"); + match result.unwrap_err() { + AccountInfoError::InvalidAddress { .. } => {} + other => panic!("Expected InvalidAddress, got: {:?}", other), + } + } + + #[tokio::test] + #[ignore] + async fn test_get_address_info_invalid_electrum() { + use crate::modules::onchain::get_address_info; + + let result = get_address_info( + TEST_LEGACY_ADDR, + "invalid://url", + None, + ) + .await; + + assert!(result.is_err(), "Expected error for invalid electrum URL"); + } + + // ======================================================================== + // Compose Transaction Tests (BDK-based, signer-agnostic) + // ======================================================================== + + fn test_wallet_params(fingerprint: Option) -> crate::modules::onchain::WalletParams { + crate::modules::onchain::WalletParams { + extended_key: TEST_VPUB.to_string(), + electrum_url: ACCOUNT_INFO_ELECTRUM_URL.to_string(), + fingerprint, + network: None, + account_type: None, + } + } + + #[tokio::test] + #[ignore] + async fn test_compose_basic_payment() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 5_000, + }], + fee_rates: vec![2.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 1); + + match &results[0] { + ComposeResult::Success { psbt, fee, total_spent, .. } => { + assert!(!psbt.is_empty(), "PSBT should not be empty"); + assert!(*fee > 0, "Fee should be > 0"); + assert!(*total_spent > 5_000, "total_spent should be > payment amount"); + + use base64::{engine::general_purpose, Engine as _}; + let decoded = general_purpose::STANDARD.decode(psbt); + assert!(decoded.is_ok(), "PSBT should be valid base64"); + } + ComposeResult::Error { error } => panic!("Compose failed: {}", error), + } + } + + #[tokio::test] + #[ignore] + async fn test_compose_send_max() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ComposeOutput::SendMax { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + }], + fee_rates: vec![1.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 1); + + match &results[0] { + ComposeResult::Success { fee, total_spent, .. } => { + assert!(*fee > 0, "Fee should be > 0"); + assert!(*total_spent > 0, "Should have funds to send"); + } + ComposeResult::Error { error } => panic!("SendMax compose failed: {}", error), + } + } + + #[tokio::test] + #[ignore] + async fn test_compose_send_max_with_payment() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ + ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 1_000, + }, + ComposeOutput::SendMax { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + }, + ], + fee_rates: vec![1.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 1); + + match &results[0] { + ComposeResult::Success { fee, total_spent, .. } => { + assert!(*fee > 0, "Fee should be > 0"); + assert!(*total_spent >= 1_000 + fee, "total_spent should cover payment + fee"); + } + ComposeResult::Error { error } => panic!("SendMax+Payment compose failed: {}", error), + } + } + + #[tokio::test] + #[ignore] + async fn test_compose_insufficient_funds() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 999_999_999_999, + }], + fee_rates: vec![2.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 1); + assert!(matches!(&results[0], ComposeResult::Error { .. })); + } + + #[tokio::test] + #[ignore] + async fn test_compose_multiple_fee_rates() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(None), + outputs: vec![ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 5_000, + }], + fee_rates: vec![1.0, 5.0, 20.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 3); + + let mut prev_fee = 0u64; + for (i, result) in results.iter().enumerate() { + match result { + ComposeResult::Success { fee, .. } => { + assert!(*fee > prev_fee, "Fee level {} ({} sats) should be > previous ({} sats)", i, fee, prev_fee); + prev_fee = *fee; + } + ComposeResult::Error { error } => panic!("Fee level {} failed: {}", i, error), + } + } + } + + #[tokio::test] + #[ignore] + async fn test_compose_with_fingerprint() { + use crate::modules::onchain::{compose_transaction, ComposeParams, ComposeOutput, ComposeResult}; + + let params = ComposeParams { + wallet: test_wallet_params(Some("73c5da0a".to_string())), + outputs: vec![ComposeOutput::Payment { + address: TEST_TESTNET_BECH32_ADDR.to_string(), + amount_sats: 5_000, + }], + fee_rates: vec![2.0], + coin_selection: None, + }; + + let results = compose_transaction(params).await; + assert_eq!(results.len(), 1); + assert!(matches!(&results[0], ComposeResult::Success { .. }), "Compose with fingerprint should succeed"); + } } diff --git a/src/modules/onchain/types.rs b/src/modules/onchain/types.rs index b2f8052..22d21c9 100644 --- a/src/modules/onchain/types.rs +++ b/src/modules/onchain/types.rs @@ -199,3 +199,190 @@ pub struct SweepableBalances { /// Total number of UTXOs across all wallet types pub total_utxos_count: u32, } + +// ============================================================================ +// Account info types +// ============================================================================ + +/// Account type classification for extended public keys. +/// +/// Determines the BIP standard, derivation path purpose, and script type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)] +pub enum AccountType { + /// BIP44 legacy (P2PKH) — xpub/tpub prefix + Legacy, + /// BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + WrappedSegwit, + /// BIP84 native segwit (P2WPKH) — zpub/vpub prefix + NativeSegwit, + /// BIP86 taproot (P2TR) + Taproot, +} + +/// A UTXO associated with an account or address. +#[derive(Debug, Clone, uniffi::Record)] +pub struct AccountUtxo { + /// Transaction ID (hex) + pub txid: String, + /// Output index + pub vout: u32, + /// Amount in satoshis + pub amount: u64, + /// Block height where the UTXO was confirmed (0 if unconfirmed) + pub block_height: u32, + /// Address holding this UTXO + pub address: String, + /// BIP32 derivation path (e.g., "m/84'/0'/0'/0/0") + pub path: String, + /// Number of confirmations (0 if unconfirmed) + pub confirmations: u32, + /// Whether this is a coinbase output + pub coinbase: bool, + /// Whether this UTXO is owned by the account + pub own: bool, + /// Whether this UTXO must be included in the transaction + pub required: Option, +} + +/// Information about a single address in an account. +#[derive(Debug, Clone, uniffi::Record)] +pub struct AddressInfo { + /// The Bitcoin address + pub address: String, + /// BIP32 derivation path + pub path: String, + /// Number of transfers (real count in `get_address_info`, 1/0 presence flag in `get_account_info`) + pub transfers: u32, +} + +/// Grouped address lists for an account. +#[derive(Debug, Clone, uniffi::Record)] +pub struct AccountAddresses { + /// Used receive addresses (have at least one transaction) + pub used: Vec, + /// Unused receive addresses (no transactions yet) + pub unused: Vec, + /// Change addresses + pub change: Vec, +} + +/// Full account structure with addresses and UTXOs. +#[derive(Debug, Clone, uniffi::Record)] +pub struct ComposeAccount { + /// Account derivation path (e.g., "m/84'/0'/0'") + pub path: String, + /// Categorized addresses + pub addresses: AccountAddresses, + /// Unspent transaction outputs + pub utxo: Vec, +} + +/// Result from querying an extended public key via Electrum. +#[derive(Debug, Clone, uniffi::Record)] +pub struct AccountInfoResult { + /// The account structure with addresses and UTXOs + pub account: ComposeAccount, + /// Total confirmed balance in satoshis + pub balance: u64, + /// Number of UTXOs + pub utxo_count: u32, + /// The detected or specified account type + pub account_type: AccountType, + /// The current blockchain tip height + pub block_height: u32, +} + +/// Result from querying a single Bitcoin address. +#[derive(Debug, Clone, uniffi::Record)] +pub struct SingleAddressInfoResult { + /// The queried address + pub address: String, + /// Total confirmed balance in satoshis + pub balance: u64, + /// UTXOs for this address + pub utxos: Vec, + /// Number of transactions involving this address + pub transfers: u32, + /// Current blockchain tip height + pub block_height: u32, +} + +// ============================================================================ +// Shared wallet parameters +// ============================================================================ + +/// Common parameters for creating and syncing a watch-only BDK wallet. +#[derive(Debug, Clone, uniffi::Record)] +pub struct WalletParams { + /// Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + pub extended_key: String, + /// Electrum server URL for wallet sync + pub electrum_url: String, + /// Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + pub fingerprint: Option, + /// Bitcoin network (auto-detected from key prefix if not specified) + pub network: Option, + /// Override account type for ambiguous key prefixes (xpub/tpub) + pub account_type: Option, +} + +// ============================================================================ +// Transaction compose types (signer-agnostic) +// ============================================================================ + +/// Coin selection strategy for transaction composition. +#[derive(Debug, Clone, uniffi::Enum)] +pub enum CoinSelection { + /// Branch-and-bound (default). Minimizes change by searching for exact matches. + BranchAndBound, + /// Selects largest UTXOs first. Useful for UTXO consolidation. + LargestFirst, + /// Selects oldest UTXOs first. Maximizes coin-age spending. + OldestFirst, +} + +/// Output specification for transaction composition. +#[derive(Debug, Clone, uniffi::Enum)] +pub enum ComposeOutput { + /// Payment to a specific address with a fixed amount (satoshis) + Payment { address: String, amount_sats: u64 }, + /// Send all remaining funds (after fees) to an address + SendMax { address: String }, + /// OP_RETURN data output (hex-encoded payload) + OpReturn { data_hex: String }, +} + +/// Parameters for composing a signer-agnostic transaction. +#[derive(Debug, Clone, uniffi::Record)] +pub struct ComposeParams { + /// Wallet configuration (key, server, network) + pub wallet: WalletParams, + /// Desired transaction outputs + pub outputs: Vec, + /// Fee rates to evaluate (sat/vB), one PSBT per rate + pub fee_rates: Vec, + /// UTXO selection strategy (defaults to BranchAndBound) + pub coin_selection: Option, +} + +/// Result of composing a transaction at a single fee rate. +#[derive(Debug, Clone, uniffi::Enum)] +pub enum ComposeResult { + /// Successfully built a signable PSBT + Success { + /// Base64-encoded PSBT ready for signing + psbt: String, + /// Total fee in satoshis + fee: u64, + /// Target fee rate in sat/vB (actual may differ slightly due to rounding) + fee_rate: f32, + /// Total value spent (payments + fee, excluding change). + /// Uses BDK's `sent - received` semantics, which may undercount for + /// self-transfers where the destination is also owned by the wallet. + total_spent: u64, + }, + /// Composition failed (e.g. insufficient funds) + Error { + error: String, + }, +} diff --git a/src/modules/trezor/README.md b/src/modules/trezor/README.md index 0aefe4a..01a522a 100644 --- a/src/modules/trezor/README.md +++ b/src/modules/trezor/README.md @@ -63,6 +63,13 @@ FFI-compatible types exposed via UniFFI: Error types with UniFFI support: - `TrezorError` - Main error enum with variants for transport, connection, device errors +#### `account_info.rs` +Helper that maps generic `AccountType` to Trezor's `TrezorScriptType` for transaction signing. +Blockchain query functions (account info, address info) and transaction composition now live in +the generic `onchain` module. + +- `account_type_to_script_type(account_type)` - Maps `AccountType` to `TrezorScriptType` for signing inputs + #### `implementation.rs` Core `TrezorManager` struct: - Manages device lifecycle (scan, connect, disconnect) diff --git a/src/modules/trezor/account_info.rs b/src/modules/trezor/account_info.rs new file mode 100644 index 0000000..e71bbe9 --- /dev/null +++ b/src/modules/trezor/account_info.rs @@ -0,0 +1,16 @@ +//! Trezor-specific account info helpers. +//! +//! Functions that bridge generic account types to Trezor's signing protocol. + +use crate::modules::onchain::AccountType; +use super::types::TrezorScriptType; + +/// Map AccountType to Trezor's ScriptType for transaction inputs. +pub fn account_type_to_script_type(account_type: AccountType) -> TrezorScriptType { + match account_type { + AccountType::Legacy => TrezorScriptType::SpendAddress, + AccountType::WrappedSegwit => TrezorScriptType::SpendP2shWitness, + AccountType::NativeSegwit => TrezorScriptType::SpendWitness, + AccountType::Taproot => TrezorScriptType::SpendTaproot, + } +} diff --git a/src/modules/trezor/errors.rs b/src/modules/trezor/errors.rs index 9b04ac6..32d0a0c 100644 --- a/src/modules/trezor/errors.rs +++ b/src/modules/trezor/errors.rs @@ -283,3 +283,4 @@ impl From for TrezorError { } } } + diff --git a/src/modules/trezor/mod.rs b/src/modules/trezor/mod.rs index 80c3786..b227f21 100644 --- a/src/modules/trezor/mod.rs +++ b/src/modules/trezor/mod.rs @@ -7,6 +7,7 @@ mod errors; mod types; mod implementation; mod callbacks; +pub mod account_info; #[cfg(test)] mod tests; @@ -14,3 +15,4 @@ pub use errors::*; pub use types::*; pub use implementation::*; pub use callbacks::*; +pub use account_info::account_type_to_script_type; diff --git a/src/modules/trezor/tests.rs b/src/modules/trezor/tests.rs index 6562c62..f4396f5 100644 --- a/src/modules/trezor/tests.rs +++ b/src/modules/trezor/tests.rs @@ -385,12 +385,14 @@ mod tests { let response = trezor_connect_rs::SignedTxResponse { signatures: vec!["sig1".to_string(), "sig2".to_string()], serialized_tx: "rawtx".to_string(), + txid: None, }; let result: TrezorSignedTx = response.into(); assert_eq!(result.signatures, vec!["sig1", "sig2"]); assert_eq!(result.serialized_tx, "rawtx"); + assert!(result.txid.is_none()); } // ======================================================================== @@ -580,4 +582,35 @@ mod tests { let err = TrezorError::NotConnected; assert_eq!(err.to_string(), "No device connected. Call trezor_connect first."); } + + #[test] + fn test_account_type_to_script_type() { + use crate::modules::trezor::account_info::account_type_to_script_type; + use crate::modules::onchain::AccountType; + + assert!(matches!(account_type_to_script_type(AccountType::Legacy), TrezorScriptType::SpendAddress)); + assert!(matches!(account_type_to_script_type(AccountType::WrappedSegwit), TrezorScriptType::SpendP2shWitness)); + assert!(matches!(account_type_to_script_type(AccountType::NativeSegwit), TrezorScriptType::SpendWitness)); + assert!(matches!(account_type_to_script_type(AccountType::Taproot), TrezorScriptType::SpendTaproot)); + } + + #[test] + fn test_script_type_reverse_conversion() { + use trezor_connect_rs::ScriptType; + + let cases = vec![ + (ScriptType::SpendAddress, TrezorScriptType::SpendAddress), + (ScriptType::SpendP2SHWitness, TrezorScriptType::SpendP2shWitness), + (ScriptType::SpendWitness, TrezorScriptType::SpendWitness), + (ScriptType::SpendTaproot, TrezorScriptType::SpendTaproot), + (ScriptType::SpendMultisig, TrezorScriptType::SpendMultisig), + (ScriptType::External, TrezorScriptType::External), + ]; + + for (tc_type, expected) in cases { + let result: TrezorScriptType = tc_type.into(); + assert_eq!(result, expected); + } + } + } diff --git a/src/modules/trezor/types.rs b/src/modules/trezor/types.rs index 13e62ef..bd9beb9 100644 --- a/src/modules/trezor/types.rs +++ b/src/modules/trezor/types.rs @@ -127,6 +127,19 @@ impl From for trezor_connect_rs::ScriptType { } } +impl From for TrezorScriptType { + fn from(t: trezor_connect_rs::ScriptType) -> Self { + match t { + trezor_connect_rs::ScriptType::SpendAddress => TrezorScriptType::SpendAddress, + trezor_connect_rs::ScriptType::SpendP2SHWitness => TrezorScriptType::SpendP2shWitness, + trezor_connect_rs::ScriptType::SpendWitness => TrezorScriptType::SpendWitness, + trezor_connect_rs::ScriptType::SpendTaproot => TrezorScriptType::SpendTaproot, + trezor_connect_rs::ScriptType::SpendMultisig => TrezorScriptType::SpendMultisig, + trezor_connect_rs::ScriptType::External => TrezorScriptType::External, + } + } +} + /// Bitcoin network / coin type for Trezor operations. #[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)] pub enum TrezorCoinType { @@ -140,6 +153,17 @@ pub enum TrezorCoinType { Regtest, } +impl TrezorCoinType { + /// Returns the coin name string expected by the Trezor protocol. + pub fn coin_name(&self) -> String { + match self { + TrezorCoinType::Bitcoin => "Bitcoin".to_string(), + TrezorCoinType::Testnet | TrezorCoinType::Signet => "Testnet".to_string(), + TrezorCoinType::Regtest => "Regtest".to_string(), + } + } +} + impl From for trezor_connect_rs::Network { fn from(c: TrezorCoinType) -> Self { match c { @@ -413,6 +437,8 @@ pub struct TrezorSignedTx { pub signatures: Vec, /// Serialized transaction (hex) pub serialized_tx: String, + /// Broadcast transaction ID (populated when push=true) + pub txid: Option, } impl From for trezor_connect_rs::SignTxInput { @@ -426,6 +452,12 @@ impl From for trezor_connect_rs::SignTxInput { sequence: input.sequence, orig_hash: input.orig_hash, orig_index: input.orig_index, + multisig: None, + script_pubkey: None, + script_sig: None, + witness: None, + ownership_proof: None, + commitment_data: None, } } } @@ -440,6 +472,8 @@ impl From for trezor_connect_rs::SignTxOutput { op_return_data: output.op_return_data, orig_hash: output.orig_hash, orig_index: output.orig_index, + multisig: None, + payment_req_index: None, } } } @@ -472,6 +506,7 @@ impl From for trezor_connect_rs::SignTxPrevTx { lock_time: tx.lock_time, inputs: tx.inputs.into_iter().map(|i| i.into()).collect(), outputs: tx.outputs.into_iter().map(|o| o.into()).collect(), + extra_data: None, } } } @@ -485,6 +520,12 @@ impl From for trezor_connect_rs::SignTxParams { lock_time: params.lock_time, version: params.version, prev_txs: params.prev_txs.into_iter().map(|t| t.into()).collect(), + push: None, + amount_unit: None, + serialize: None, + chunkify: None, + unlock_path: None, + payment_requests: vec![], } } } @@ -494,28 +535,7 @@ impl From for TrezorSignedTx { Self { signatures: response.signatures, serialized_tx: response.serialized_tx, + txid: response.txid, } } } - -/// Address information -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, uniffi::Record)] -pub struct AddressInfo { - /// Address string - pub address: String, - /// Derivation path - pub path: String, - /// Number of transfers - pub transfers: u32, -} - -/// Account addresses -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, uniffi::Record)] -pub struct AccountAddresses { - /// Used addresses - pub used: Vec, - /// Unused addresses - pub unused: Vec, - /// Change addresses - pub change: Vec, -}