diff --git a/.gitignore b/.gitignore index ceee4e1..9a259ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ target/ bindings/android/native-debug-symbols.zip +# The xcframework's static libraries exceed GitHub's 100 MB file limit. They are +# already shipped, compressed, inside BitkitCore.xcframework.zip, which is what +# Package.swift downloads from the GitHub release, so tracking them here would +# only duplicate that payload. +bindings/ios/BitkitCore.xcframework/**/libbitkitcore.a .idea/ .DS_Store # AI diff --git a/Cargo.lock b/Cargo.lock index 42e39c7..8bc65c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" dependencies = [ "android_log-sys", - "env_filter", + "env_filter 0.1.4", "log", ] @@ -101,12 +101,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -224,6 +268,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -270,6 +336,15 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "base85" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36915bbaca237c626689b5bd14d02f2ba7a5a359d30a2a08be697392e3718079" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "basic-toml" version = "0.1.10" @@ -289,7 +364,7 @@ dependencies = [ "bdk-macros", "bip39", "bitcoin 0.30.2", - "electrum-client", + "electrum-client 0.18.0", "getrandom 0.2.17", "js-sys", "log", @@ -356,6 +431,19 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bip85_extended" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f300582d6cf7cb4b1f59971f14b4353015a3538b3aa048e3de85c7ef80ac34c9" +dependencies = [ + "base64 0.22.1", + "base85", + "bip39", + "bitcoin 0.32.8", + "sha3", +] + [[package]] name = "bitcoin" version = "0.30.2" @@ -378,6 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", + "base64 0.21.7", "bech32 0.11.1", "bitcoin-internals", "bitcoin-io", @@ -475,7 +564,7 @@ dependencies = [ [[package]] name = "bitkitcore" -version = "0.4.1" +version = "0.5.1" dependencies = [ "android_logger", "async-trait", @@ -484,12 +573,13 @@ dependencies = [ "bip39", "bitcoin 0.32.8", "bitcoin-address-generator", + "boltz-client", "btleplug", "chrono", "hex", "jni", "lazy-regex", - "lightning-invoice", + "lightning-invoice 0.32.0", "lnurl-rs", "log", "once_cell", @@ -609,6 +699,50 @@ dependencies = [ "dbus", ] +[[package]] +name = "boltz-client" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "889efede8a28a19c3f6088ab5406bf1b8379a21a3470f24a5cef4cc6c7122346" +dependencies = [ + "async-trait", + "bip39", + "bip85_extended", + "bitcoin 0.32.8", + "boltz-client-macros", + "electrum-client 0.25.0", + "elements", + "env_logger", + "futures-util", + "getrandom 0.2.17", + "gloo-timers", + "hex", + "js-sys", + "lightning", + "lightning-invoice 0.34.1", + "log", + "reqwest", + "secp256k1 0.32.0-beta.2", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite-wasm", + "url", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "boltz-client-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bae3bbd82eb8c315b3b120d7cb1a8adef85003216315c42e23abead43ae3835" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "borsh" version = "1.6.0" @@ -757,6 +891,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -853,6 +989,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cobs" version = "0.3.0" @@ -862,6 +1007,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.7" @@ -1126,6 +1277,12 @@ dependencies = [ "parking_lot_core 0.9.12", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "dbus" version = "0.9.10" @@ -1150,6 +1307,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "der" version = "0.7.10" @@ -1198,6 +1387,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dnssec-prover" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4f825369fc7134da70ca4040fddc8e03b80a46d249ae38d9c1c39b7b4476bf" + [[package]] name = "document-features" version = "0.2.12" @@ -1216,6 +1411,12 @@ dependencies = [ "phf", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1285,6 +1486,36 @@ dependencies = [ "winapi", ] +[[package]] +name = "electrum-client" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1970c5d7bd9de6d4041cbfc3e46faa3e85fe7efcea2b0eb06750d3ae1ef577b7" +dependencies = [ + "bitcoin 0.32.8", + "byteorder", + "libc", + "log", + "rustls 0.23.37", + "serde", + "serde_json", + "webpki-roots 0.25.4", + "winapi", +] + +[[package]] +name = "elements" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1210978e2686f892318085f806866014d4788479a37f0a634db97fcb39454bde" +dependencies = [ + "bech32 0.11.1", + "bitcoin 0.32.8", + "secp256k1-zkp", + "serde", + "serde_json", +] + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -1334,6 +1565,29 @@ dependencies = [ "regex", ] +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter 2.0.0", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1466,6 +1720,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -1694,6 +1954,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "goblin" version = "0.8.2" @@ -1734,6 +2006,12 @@ dependencies = [ "ahash 0.7.8", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -2162,6 +2440,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.14.0" @@ -2177,6 +2461,31 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" +dependencies = [ + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "jni" version = "0.19.0" @@ -2212,6 +2521,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.91" @@ -2234,6 +2553,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + [[package]] name = "lazy-regex" version = "3.6.0" @@ -2284,6 +2612,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -2307,6 +2641,23 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "lightning" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c421e68d603b989c8f59a95a1860f1f5de90a6305ed5bcb6d9b35a3a11f450" +dependencies = [ + "bech32 0.11.1", + "bitcoin 0.32.8", + "dnssec-prover", + "hashbrown 0.13.2", + "libm", + "lightning-invoice 0.34.1", + "lightning-macros", + "lightning-types 0.3.2", + "possiblyrandom", +] + [[package]] name = "lightning-invoice" version = "0.32.0" @@ -2315,7 +2666,29 @@ checksum = "90ab9f6ea77e20e3129235e62a2e6bd64ed932363df104e864ee65ccffb54a8f" dependencies = [ "bech32 0.9.1", "bitcoin 0.32.8", - "lightning-types", + "lightning-types 0.1.0", +] + +[[package]] +name = "lightning-invoice" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d83bd798e04ab9eecc8bbef1fa17d3808859bcdc0406bd16c55d51c8834444" +dependencies = [ + "bech32 0.11.1", + "bitcoin 0.32.8", + "lightning-types 0.3.2", +] + +[[package]] +name = "lightning-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c717494cdc2c8bb85bee7113031248f5f6c64f8802b33c1c9e2d98e594aa71" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2329,6 +2702,15 @@ dependencies = [ "hex-conservative", ] +[[package]] +name = "lightning-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c211dfcff95ca308247da8b1e0e81604bc9e568239967cd2c34572558511e869" +dependencies = [ + "bitcoin 0.32.8", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2641,6 +3023,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2939,6 +3327,30 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "possiblyrandom" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c564dbf654befd49035528299f1208a40508f6e07efb11c163444e304e4484f" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "postcard" version = "1.1.3" @@ -2995,6 +3407,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3661,6 +4095,8 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -3718,6 +4154,7 @@ version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3882,6 +4319,16 @@ dependencies = [ "secp256k1-sys 0.10.1", ] +[[package]] +name = "secp256k1" +version = "0.32.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5fdc7d6e800869d3fd60ff857c479bf0a83ea7bf44b389e64461e844204994" +dependencies = [ + "rand 0.9.2", + "secp256k1-sys 0.12.0", +] + [[package]] name = "secp256k1-sys" version = "0.8.2" @@ -3900,6 +4347,38 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3be00697c88c00fe102af8dc316038cc2062eab8da646e7463f4c0e70ca9fd" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-zkp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" +dependencies = [ + "bitcoin-private", + "rand 0.8.5", + "secp256k1 0.29.1", + "secp256k1-zkp-sys", + "serde", +] + +[[package]] +name = "secp256k1-zkp-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f08b2d0b143a22e07f798ae4f0ab20d5590d7c68e0d090f2088a48a21d1654" +dependencies = [ + "cc", + "secp256k1-sys 0.10.1", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -4063,6 +4542,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -4080,6 +4570,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4529,6 +5029,42 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.37", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-tungstenite-wasm" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4585aa997e4afb43c64f9101c27411b8e1bf9dde49b22e3e47acdde3055b325c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "httparse", + "js-sys", + "rustls 0.23.37", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4728,6 +5264,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls 0.23.37", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" @@ -4924,12 +5479,24 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.22.0" @@ -5141,6 +5708,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + [[package]] name = "webpki-roots" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index fbe42a2..2c999b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.4.1" +version = "0.5.1" edition = "2021" [lib] @@ -36,6 +36,7 @@ uuid = { version = "1.16.0", features = ["v4"] } hex = "0.4.3" bip39 = "2.0" bdk = { version = "0.30.2", features = ["all-keys"] } +boltz-client = { version = "0.4.1", default-features = false, features = ["electrum", "ws"] } base64 = "0.22" log = "0.4" pubky = "0.6.0" diff --git a/Package.swift b/Package.swift index 1421230..6e625a6 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.4.1" -let checksum = "867bea0633b707c5da7eef38439b4a91310540cad07baa11be4ddf43da60b0f7" +let tag = "v0.5.1" +let checksum = "a4fca2044a5becdd2d55c59b4a7883d5b1da9bf95d802c1663e49ddf57f39632" let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip" let package = Package( diff --git a/bindings/android/gradle.properties b/bindings/android/gradle.properties index e48f767..3a8a812 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.4.1 +version=0.5.1 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 f5b8946..682c33c 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/armeabi-v7a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so index 31f15ed..6185efb 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/x86/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so index 1229d19..dd9ce75 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_64/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so index 41a56a8..ab0d144 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/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt index 99affbe..f8148b5 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 @@ -898,6 +898,9 @@ internal typealias UniffiForeignFutureStructVoidUniffiByValue = UniffiForeignFut internal interface UniffiForeignFutureCompleteVoid: com.sun.jna.Callback { public fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructVoidUniffiByValue,) } +internal interface UniffiCallbackInterfaceBoltzEventListenerMethod0: com.sun.jna.Callback { + public fun callback(`uniffiHandle`: Long,`event`: RustBufferByValue,`uniffiOutReturn`: Pointer,uniffiCallStatus: UniffiRustCallStatus,) +} internal interface UniffiCallbackInterfaceEventListenerMethod0: com.sun.jna.Callback { public fun callback(`uniffiHandle`: Long,`watcherId`: RustBufferByValue,`event`: RustBufferByValue,`uniffiOutReturn`: Pointer,uniffiCallStatus: UniffiRustCallStatus,) } @@ -941,6 +944,37 @@ internal interface UniffiCallbackInterfaceTrezorUiCallbackMethod1: com.sun.jna.C public fun callback(`uniffiHandle`: Long,`onDevice`: Byte,`uniffiOutReturn`: RustBuffer,uniffiCallStatus: UniffiRustCallStatus,) } @Structure.FieldOrder("onEvent", "uniffiFree") +internal open class UniffiVTableCallbackInterfaceBoltzEventListenerStruct( + @JvmField public var `onEvent`: UniffiCallbackInterfaceBoltzEventListenerMethod0?, + @JvmField public var `uniffiFree`: UniffiCallbackInterfaceFree?, +) : com.sun.jna.Structure() { + internal constructor(): this( + + `onEvent` = null, + + `uniffiFree` = null, + + ) + + internal class UniffiByValue( + `onEvent`: UniffiCallbackInterfaceBoltzEventListenerMethod0?, + `uniffiFree`: UniffiCallbackInterfaceFree?, + ): UniffiVTableCallbackInterfaceBoltzEventListener(`onEvent`,`uniffiFree`,), Structure.ByValue +} + +internal typealias UniffiVTableCallbackInterfaceBoltzEventListener = UniffiVTableCallbackInterfaceBoltzEventListenerStruct + +internal fun UniffiVTableCallbackInterfaceBoltzEventListener.uniffiSetValue(other: UniffiVTableCallbackInterfaceBoltzEventListener) { + `onEvent` = other.`onEvent` + `uniffiFree` = other.`uniffiFree` +} +internal fun UniffiVTableCallbackInterfaceBoltzEventListener.uniffiSetValue(other: UniffiVTableCallbackInterfaceBoltzEventListenerUniffiByValue) { + `onEvent` = other.`onEvent` + `uniffiFree` = other.`uniffiFree` +} + +internal typealias UniffiVTableCallbackInterfaceBoltzEventListenerUniffiByValue = UniffiVTableCallbackInterfaceBoltzEventListenerStruct.UniffiByValue +@Structure.FieldOrder("onEvent", "uniffiFree") internal open class UniffiVTableCallbackInterfaceEventListenerStruct( @JvmField public var `onEvent`: UniffiCallbackInterfaceEventListenerMethod0?, @JvmField public var `uniffiFree`: UniffiCallbackInterfaceFree?, @@ -1501,6 +1535,33 @@ internal typealias UniffiVTableCallbackInterfaceTrezorUiCallbackUniffiByValue = + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1597,6 +1658,39 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_blocktank_wipe_all() != 41797.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap() != 52516.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap() != 20570.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap() != 27933.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits() != 59203.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits() != 5900.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_get_swap() != 17473.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps() != 20926.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_list_swaps() != 16447.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap() != 24549.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_start_swap_updates() != 24371.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates() != 63683.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction() != 43422.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2053,6 +2147,9 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_wipe_all_transaction_details() != 65339.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event() != 11294.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_method_eventlistener_on_event() != 35531.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2135,6 +2232,39 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_blocktank_wipe_all( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_get_swap( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_list_swaps( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_start_swap_updates( + ): Short + @JvmStatic + external fun uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction( ): Short @JvmStatic @@ -2591,6 +2721,9 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_wipe_all_transaction_details( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_method_eventlistener_on_event( ): Short @JvmStatic @@ -2646,6 +2779,7 @@ internal object UniffiLib : Library { Native.register(UniffiLib::class.java, findLibraryName("bitkitcore")) // No need to check the contract version and checksums, since // we already did that with `IntegrityCheckingUniffiLib` above. + uniffiCallbackInterfaceBoltzEventListener.register(this) uniffiCallbackInterfaceEventListener.register(this) uniffiCallbackInterfaceTrezorTransportCallback.register(this) uniffiCallbackInterfaceTrezorUiCallback.register(this) @@ -2655,6 +2789,26 @@ internal object UniffiLib : Library { UniffiCleaner.create() } @JvmStatic + external fun uniffi_bitkitcore_fn_clone_boltzeventlistener( + `ptr`: Pointer?, + uniffiCallStatus: UniffiRustCallStatus, + ): Pointer? + @JvmStatic + external fun uniffi_bitkitcore_fn_free_boltzeventlistener( + `ptr`: Pointer?, + uniffiCallStatus: UniffiRustCallStatus, + ): Unit + @JvmStatic + external fun uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener( + `vtable`: UniffiVTableCallbackInterfaceBoltzEventListener, + ): Unit + @JvmStatic + external fun uniffi_bitkitcore_fn_method_boltzeventlistener_on_event( + `ptr`: Pointer?, + `event`: RustBufferByValue, + uniffiCallStatus: UniffiRustCallStatus, + ): Unit + @JvmStatic external fun uniffi_bitkitcore_fn_clone_eventlistener( `ptr`: Pointer?, uniffiCallStatus: UniffiRustCallStatus, @@ -2841,6 +2995,67 @@ internal object UniffiLib : Library { external fun uniffi_bitkitcore_fn_func_blocktank_wipe_all( ): Long @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap( + `swapId`: RustBufferByValue, + `mnemonic`: RustBufferByValue, + `bip39Passphrase`: RustBufferByValue, + `feeRateSatPerVb`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_create_reverse_swap( + `network`: RustBufferByValue, + `electrumUrl`: RustBufferByValue, + `amountSat`: Long, + `claimAddress`: RustBufferByValue, + `mnemonic`: RustBufferByValue, + `bip39Passphrase`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_create_submarine_swap( + `network`: RustBufferByValue, + `electrumUrl`: RustBufferByValue, + `invoice`: RustBufferByValue, + `mnemonic`: RustBufferByValue, + `bip39Passphrase`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_get_reverse_limits( + `network`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_get_submarine_limits( + `network`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_get_swap( + `swapId`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_list_pending_swaps( + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_list_swaps( + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap( + `swapId`: RustBufferByValue, + `refundAddress`: RustBufferByValue, + `mnemonic`: RustBufferByValue, + `bip39Passphrase`: RustBufferByValue, + `feeRateSatPerVb`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_start_swap_updates( + `network`: RustBufferByValue, + `listener`: Pointer?, + `mnemonic`: RustBufferByValue, + `bip39Passphrase`: RustBufferByValue, + `feeRateSatPerVb`: RustBufferByValue, + ): Long + @JvmStatic + external fun uniffi_bitkitcore_fn_func_boltz_stop_swap_updates( + ): Long + @JvmStatic external fun uniffi_bitkitcore_fn_func_broadcast_sweep_transaction( `psbt`: RustBufferByValue, `mnemonicPhrase`: RustBufferByValue, @@ -4221,12 +4436,14 @@ public object FfiConverterByteArray: FfiConverterRustBuffer { /** - * Callback interface for receiving watcher events. + * Callback interface for receiving Boltz swap lifecycle events. * - * Implement this trait in Swift/Kotlin/Python to receive typed notifications - * from xpub watchers. + * Implement this in Swift/Kotlin/Python and register it via + * `boltz_start_swap_updates` to receive typed notifications as swaps progress. + * Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + * event reports the resulting transaction id. */ -public open class EventListenerImpl: Disposable, EventListener { +public open class BoltzEventListenerImpl: Disposable, BoltzEventListener { public constructor(pointer: Pointer) { this.pointer = pointer @@ -4304,7 +4521,7 @@ public open class EventListenerImpl: Disposable, EventListener { override fun destroy() { pointer?.let { ptr -> uniffiRustCall { status -> - UniffiLib.uniffi_bitkitcore_fn_free_eventlistener(ptr, status) + UniffiLib.uniffi_bitkitcore_fn_free_boltzeventlistener(ptr, status) } } } @@ -4312,24 +4529,17 @@ public open class EventListenerImpl: Disposable, EventListener { public fun uniffiClonePointer(): Pointer { return uniffiRustCall { status -> - UniffiLib.uniffi_bitkitcore_fn_clone_eventlistener(pointer!!, status) + UniffiLib.uniffi_bitkitcore_fn_clone_boltzeventlistener(pointer!!, status) }!! } - /** - * Called when a watcher event occurs. - * - * `watcher_id` identifies which watcher produced the event. - * `event` is a typed enum — no JSON parsing needed. - */ - public override fun `onEvent`(`watcherId`: kotlin.String, `event`: WatcherEvent) { + public override fun `onEvent`(`event`: BoltzSwapEvent) { callWithPointer { uniffiRustCall { uniffiRustCallStatus -> - UniffiLib.uniffi_bitkitcore_fn_method_eventlistener_on_event( + UniffiLib.uniffi_bitkitcore_fn_method_boltzeventlistener_on_event( it, - FfiConverterString.lower(`watcherId`), - FfiConverterTypeWatcherEvent.lower(`event`), + FfiConverterTypeBoltzSwapEvent.lower(`event`), uniffiRustCallStatus, ) } @@ -4350,26 +4560,26 @@ public open class EventListenerImpl: Disposable, EventListener { -public object FfiConverterTypeEventListener: FfiConverter { - internal val handleMap = UniffiHandleMap() +public object FfiConverterTypeBoltzEventListener: FfiConverter { + internal val handleMap = UniffiHandleMap() - override fun lower(value: EventListener): Pointer { + override fun lower(value: BoltzEventListener): Pointer { return handleMap.insert(value).toPointer() } - override fun lift(value: Pointer): EventListener { - return EventListenerImpl(value) + override fun lift(value: Pointer): BoltzEventListener { + return BoltzEventListenerImpl(value) } - override fun read(buf: ByteBuffer): EventListener { + override fun read(buf: ByteBuffer): BoltzEventListener { // The Rust code always writes pointers as 8 bytes, and will // fail to compile if they don't fit. return lift(buf.getLong().toPointer()) } - override fun allocationSize(value: EventListener): ULong = 8UL + override fun allocationSize(value: BoltzEventListener): ULong = 8UL - override fun write(value: EventListener, buf: ByteBuffer) { + override fun write(value: BoltzEventListener, buf: ByteBuffer) { // The Rust code always expects pointers written as 8 bytes, // and will fail to compile if they don't fit. buf.putLong(lower(value).toLong()) @@ -4378,20 +4588,18 @@ public object FfiConverterTypeEventListener: FfiConverter uniffiObj.`onEvent`( - FfiConverterString.lift(`watcherId`), - FfiConverterTypeWatcherEvent.lift(`event`), + FfiConverterTypeBoltzSwapEvent.lift(`event`), ) } val writeReturn = { _: Unit -> @@ -4404,45 +4612,29 @@ internal object uniffiCallbackInterfaceEventListener { } internal object uniffiFree: UniffiCallbackInterfaceFree { override fun callback(handle: Long) { - FfiConverterTypeEventListener.handleMap.remove(handle) + FfiConverterTypeBoltzEventListener.handleMap.remove(handle) } } - internal val vtable = UniffiVTableCallbackInterfaceEventListener( + internal val vtable = UniffiVTableCallbackInterfaceBoltzEventListener( `onEvent`, uniffiFree, ) internal fun register(lib: UniffiLib) { - lib.uniffi_bitkitcore_fn_init_callback_vtable_eventlistener(vtable) + lib.uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(vtable) } } /** - * Callback interface for native Trezor transport operations - * - * This trait must be implemented by the native iOS/Android code. - * The implementation handles actual USB or Bluetooth communication. - * - * # Android Implementation - * Use Android USB Host API for USB devices: - * - Enumerate devices with vendorId 0x1209 (0x534c for older), productId 0x53c1 - * - Request USB permission, claim interface, get endpoints - * - Chunk size: 64 bytes for USB - * - * Use Android BLE API for Bluetooth: - * - Scan for Trezor BLE service UUID: 8c000001-a59b-4d58-a9ad-073df69fa1b1 - * - Connect and discover characteristics - * - Read from: 8c000002-a59b-4d58-a9ad-073df69fa1b1 - * - Write to: 8c000003-a59b-4d58-a9ad-073df69fa1b1 - * - Chunk size: 244 bytes for BLE + * Callback interface for receiving watcher events. * - * # iOS Implementation - * Use IOKit/CoreBluetooth with same service/characteristic UUIDs. + * Implement this trait in Swift/Kotlin/Python to receive typed notifications + * from xpub watchers. */ -public open class TrezorTransportCallbackImpl: Disposable, TrezorTransportCallback { +public open class EventListenerImpl: Disposable, EventListener { public constructor(pointer: Pointer) { this.pointer = pointer @@ -4520,7 +4712,7 @@ public open class TrezorTransportCallbackImpl: Disposable, TrezorTransportCallba override fun destroy() { pointer?.let { ptr -> uniffiRustCall { status -> - UniffiLib.uniffi_bitkitcore_fn_free_trezortransportcallback(ptr, status) + UniffiLib.uniffi_bitkitcore_fn_free_eventlistener(ptr, status) } } } @@ -4528,54 +4720,270 @@ public open class TrezorTransportCallbackImpl: Disposable, TrezorTransportCallba public fun uniffiClonePointer(): Pointer { return uniffiRustCall { status -> - UniffiLib.uniffi_bitkitcore_fn_clone_trezortransportcallback(pointer!!, status) + UniffiLib.uniffi_bitkitcore_fn_clone_eventlistener(pointer!!, status) }!! } /** - * Enumerate all connected Trezor devices + * Called when a watcher event occurs. + * + * `watcher_id` identifies which watcher produced the event. + * `event` is a typed enum — no JSON parsing needed. */ - public override fun `enumerateDevices`(): List { - return FfiConverterSequenceTypeNativeDeviceInfo.lift(callWithPointer { + public override fun `onEvent`(`watcherId`: kotlin.String, `event`: WatcherEvent) { + callWithPointer { uniffiRustCall { uniffiRustCallStatus -> - UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_enumerate_devices( + UniffiLib.uniffi_bitkitcore_fn_method_eventlistener_on_event( it, + FfiConverterString.lower(`watcherId`), + FfiConverterTypeWatcherEvent.lower(`event`), uniffiRustCallStatus, ) } - }) + } } - /** - * Open a connection to a device - */ - public override fun `openDevice`(`path`: kotlin.String): TrezorTransportWriteResult { - return FfiConverterTypeTrezorTransportWriteResult.lift(callWithPointer { - uniffiRustCall { uniffiRustCallStatus -> - UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_open_device( - it, - FfiConverterString.lower(`path`), - uniffiRustCallStatus, - ) - } - }) - } - /** - * Close the connection to a device - */ - public override fun `closeDevice`(`path`: kotlin.String): TrezorTransportWriteResult { - return FfiConverterTypeTrezorTransportWriteResult.lift(callWithPointer { - uniffiRustCall { uniffiRustCallStatus -> - UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_close_device( - it, - FfiConverterString.lower(`path`), - uniffiRustCallStatus, - ) - } - }) - } + + + + + + public companion object + +} + + + + + +public object FfiConverterTypeEventListener: FfiConverter { + internal val handleMap = UniffiHandleMap() + + override fun lower(value: EventListener): Pointer { + return handleMap.insert(value).toPointer() + } + + override fun lift(value: Pointer): EventListener { + return EventListenerImpl(value) + } + + override fun read(buf: ByteBuffer): EventListener { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(buf.getLong().toPointer()) + } + + override fun allocationSize(value: EventListener): ULong = 8UL + + override fun write(value: EventListener, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(lower(value).toLong()) + } +} + + +// Put the implementation in an object so we don't pollute the top-level namespace +internal object uniffiCallbackInterfaceEventListener { + internal object `onEvent`: UniffiCallbackInterfaceEventListenerMethod0 { + override fun callback ( + `uniffiHandle`: Long, + `watcherId`: RustBufferByValue, + `event`: RustBufferByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) { + val uniffiObj = FfiConverterTypeEventListener.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`onEvent`( + FfiConverterString.lift(`watcherId`), + FfiConverterTypeWatcherEvent.lift(`event`), + ) + } + val writeReturn = { _: Unit -> + @Suppress("UNUSED_EXPRESSION") + uniffiOutReturn + Unit + } + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + } + } + internal object uniffiFree: UniffiCallbackInterfaceFree { + override fun callback(handle: Long) { + FfiConverterTypeEventListener.handleMap.remove(handle) + } + } + + internal val vtable = UniffiVTableCallbackInterfaceEventListener( + `onEvent`, + uniffiFree, + ) + + internal fun register(lib: UniffiLib) { + lib.uniffi_bitkitcore_fn_init_callback_vtable_eventlistener(vtable) + } +} + + + +/** + * Callback interface for native Trezor transport operations + * + * This trait must be implemented by the native iOS/Android code. + * The implementation handles actual USB or Bluetooth communication. + * + * # Android Implementation + * Use Android USB Host API for USB devices: + * - Enumerate devices with vendorId 0x1209 (0x534c for older), productId 0x53c1 + * - Request USB permission, claim interface, get endpoints + * - Chunk size: 64 bytes for USB + * + * Use Android BLE API for Bluetooth: + * - Scan for Trezor BLE service UUID: 8c000001-a59b-4d58-a9ad-073df69fa1b1 + * - Connect and discover characteristics + * - Read from: 8c000002-a59b-4d58-a9ad-073df69fa1b1 + * - Write to: 8c000003-a59b-4d58-a9ad-073df69fa1b1 + * - Chunk size: 244 bytes for BLE + * + * # iOS Implementation + * Use IOKit/CoreBluetooth with same service/characteristic UUIDs. + */ +public open class TrezorTransportCallbackImpl: Disposable, TrezorTransportCallback { + + public constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiPointerDestroyer(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + public constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiPointerDestroyer(null)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed: kotlinx.atomicfu.AtomicBoolean = kotlinx.atomicfu.atomic(false) + private val callCounter: kotlinx.atomicfu.AtomicLong = kotlinx.atomicfu.atomic(1L) + + private val lock = kotlinx.atomicfu.locks.ReentrantLock() + + private fun synchronized(block: () -> T): T { + lock.lock() + try { + return block() + } finally { + lock.unlock() + } + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + override fun close() { + synchronized { this.destroy() } + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.value + if (c == 0L) { + throw IllegalStateException("${this::class::simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this::class::simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiPointerDestroyer(private val pointer: Pointer?) : Disposable { + override fun destroy() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.uniffi_bitkitcore_fn_free_trezortransportcallback(ptr, status) + } + } + } + } + + public fun uniffiClonePointer(): Pointer { + return uniffiRustCall { status -> + UniffiLib.uniffi_bitkitcore_fn_clone_trezortransportcallback(pointer!!, status) + }!! + } + + + /** + * Enumerate all connected Trezor devices + */ + public override fun `enumerateDevices`(): List { + return FfiConverterSequenceTypeNativeDeviceInfo.lift(callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_enumerate_devices( + it, + uniffiRustCallStatus, + ) + } + }) + } + + /** + * Open a connection to a device + */ + public override fun `openDevice`(`path`: kotlin.String): TrezorTransportWriteResult { + return FfiConverterTypeTrezorTransportWriteResult.lift(callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_open_device( + it, + FfiConverterString.lower(`path`), + uniffiRustCallStatus, + ) + } + }) + } + + /** + * Close the connection to a device + */ + public override fun `closeDevice`(`path`: kotlin.String): TrezorTransportWriteResult { + return FfiConverterTypeTrezorTransportWriteResult.lift(callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.uniffi_bitkitcore_fn_method_trezortransportcallback_close_device( + it, + FfiConverterString.lower(`path`), + uniffiRustCallStatus, + ) + } + }) + } /** * Read a chunk of data from the device @@ -5421,28 +5829,120 @@ public object FfiConverterTypeAddressInfo: FfiConverterRustBuffer { -public object FfiConverterTypeChannelLiquidityOptions: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ChannelLiquidityOptions { - return ChannelLiquidityOptions( - FfiConverterULong.read(buf), +public object FfiConverterTypeBoltzPairInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzPairInfo { + return BoltzPairInfo( + FfiConverterString.read(buf), + FfiConverterDouble.read(buf), FfiConverterULong.read(buf), FfiConverterULong.read(buf), + FfiConverterDouble.read(buf), FfiConverterULong.read(buf), ) } - override fun allocationSize(value: ChannelLiquidityOptions): ULong = ( - FfiConverterULong.allocationSize(value.`defaultLspBalanceSat`) + - FfiConverterULong.allocationSize(value.`minLspBalanceSat`) + - FfiConverterULong.allocationSize(value.`maxLspBalanceSat`) + - FfiConverterULong.allocationSize(value.`maxClientBalanceSat`) + override fun allocationSize(value: BoltzPairInfo): ULong = ( + FfiConverterString.allocationSize(value.`hash`) + + FfiConverterDouble.allocationSize(value.`rate`) + + FfiConverterULong.allocationSize(value.`minimalSat`) + + FfiConverterULong.allocationSize(value.`maximalSat`) + + FfiConverterDouble.allocationSize(value.`feePercentage`) + + FfiConverterULong.allocationSize(value.`minerFeesSat`) ) - override fun write(value: ChannelLiquidityOptions, buf: ByteBuffer) { - FfiConverterULong.write(value.`defaultLspBalanceSat`, buf) - FfiConverterULong.write(value.`minLspBalanceSat`, buf) - FfiConverterULong.write(value.`maxLspBalanceSat`, buf) - FfiConverterULong.write(value.`maxClientBalanceSat`, buf) + override fun write(value: BoltzPairInfo, buf: ByteBuffer) { + FfiConverterString.write(value.`hash`, buf) + FfiConverterDouble.write(value.`rate`, buf) + FfiConverterULong.write(value.`minimalSat`, buf) + FfiConverterULong.write(value.`maximalSat`, buf) + FfiConverterDouble.write(value.`feePercentage`, buf) + FfiConverterULong.write(value.`minerFeesSat`, buf) + } +} + + + + +public object FfiConverterTypeBoltzSwap: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzSwap { + return BoltzSwap( + FfiConverterString.read(buf), + FfiConverterTypeBoltzSwapType.read(buf), + FfiConverterTypeBoltzSwapStatus.read(buf), + FfiConverterTypeBoltzNetwork.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterOptionalULong.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: BoltzSwap): ULong = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterTypeBoltzSwapType.allocationSize(value.`swapType`) + + FfiConverterTypeBoltzSwapStatus.allocationSize(value.`status`) + + FfiConverterTypeBoltzNetwork.allocationSize(value.`network`) + + FfiConverterULong.allocationSize(value.`swapIndex`) + + FfiConverterULong.allocationSize(value.`amountSat`) + + FfiConverterOptionalULong.allocationSize(value.`onchainAmountSat`) + + FfiConverterOptionalString.allocationSize(value.`invoice`) + + FfiConverterOptionalString.allocationSize(value.`lockupAddress`) + + FfiConverterOptionalString.allocationSize(value.`onchainAddress`) + + FfiConverterULong.allocationSize(value.`timeoutBlockHeight`) + + FfiConverterULong.allocationSize(value.`createdAt`) + + FfiConverterOptionalString.allocationSize(value.`claimTxId`) + + FfiConverterOptionalString.allocationSize(value.`refundTxId`) + ) + + override fun write(value: BoltzSwap, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterTypeBoltzSwapType.write(value.`swapType`, buf) + FfiConverterTypeBoltzSwapStatus.write(value.`status`, buf) + FfiConverterTypeBoltzNetwork.write(value.`network`, buf) + FfiConverterULong.write(value.`swapIndex`, buf) + FfiConverterULong.write(value.`amountSat`, buf) + FfiConverterOptionalULong.write(value.`onchainAmountSat`, buf) + FfiConverterOptionalString.write(value.`invoice`, buf) + FfiConverterOptionalString.write(value.`lockupAddress`, buf) + FfiConverterOptionalString.write(value.`onchainAddress`, buf) + FfiConverterULong.write(value.`timeoutBlockHeight`, buf) + FfiConverterULong.write(value.`createdAt`, buf) + FfiConverterOptionalString.write(value.`claimTxId`, buf) + FfiConverterOptionalString.write(value.`refundTxId`, buf) + } +} + + + + +public object FfiConverterTypeChannelLiquidityOptions: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ChannelLiquidityOptions { + return ChannelLiquidityOptions( + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: ChannelLiquidityOptions): ULong = ( + FfiConverterULong.allocationSize(value.`defaultLspBalanceSat`) + + FfiConverterULong.allocationSize(value.`minLspBalanceSat`) + + FfiConverterULong.allocationSize(value.`maxLspBalanceSat`) + + FfiConverterULong.allocationSize(value.`maxClientBalanceSat`) + ) + + override fun write(value: ChannelLiquidityOptions, buf: ByteBuffer) { + FfiConverterULong.write(value.`defaultLspBalanceSat`, buf) + FfiConverterULong.write(value.`minLspBalanceSat`, buf) + FfiConverterULong.write(value.`maxLspBalanceSat`, buf) + FfiConverterULong.write(value.`maxClientBalanceSat`, buf) } } @@ -7466,6 +7966,37 @@ public object FfiConverterTypePubkyProfileLink: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ReverseSwapResponse { + return ReverseSwapResponse( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: ReverseSwapResponse): ULong = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`invoice`) + + FfiConverterString.allocationSize(value.`lockupAddress`) + + FfiConverterULong.allocationSize(value.`onchainAmountSat`) + + FfiConverterULong.allocationSize(value.`timeoutBlockHeight`) + ) + + override fun write(value: ReverseSwapResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`invoice`, buf) + FfiConverterString.write(value.`lockupAddress`, buf) + FfiConverterULong.write(value.`onchainAmountSat`, buf) + FfiConverterULong.write(value.`timeoutBlockHeight`, buf) + } +} + + + + public object FfiConverterTypeSingleAddressInfoResult: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): SingleAddressInfoResult { return SingleAddressInfoResult( @@ -7497,6 +8028,40 @@ public object FfiConverterTypeSingleAddressInfoResult: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SubmarineSwapResponse { + return SubmarineSwapResponse( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: SubmarineSwapResponse): ULong = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`address`) + + FfiConverterString.allocationSize(value.`bip21`) + + FfiConverterULong.allocationSize(value.`expectedAmountSat`) + + FfiConverterBoolean.allocationSize(value.`acceptZeroConf`) + + FfiConverterULong.allocationSize(value.`timeoutBlockHeight`) + ) + + override fun write(value: SubmarineSwapResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`address`, buf) + FfiConverterString.write(value.`bip21`, buf) + FfiConverterULong.write(value.`expectedAmountSat`, buf) + FfiConverterBoolean.write(value.`acceptZeroConf`, buf) + FfiConverterULong.write(value.`timeoutBlockHeight`, buf) + } +} + + + + public object FfiConverterTypeSupportedHardwareWallet: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): SupportedHardwareWallet { return SupportedHardwareWallet( @@ -9206,65 +9771,517 @@ public object FfiConverterTypeBlocktankError : FfiConverterRustBuffer { + is BlocktankException.BlocktankClient -> { + buf.putInt(2) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.InvalidBlocktank -> { + buf.putInt(3) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.InitializationException -> { + buf.putInt(4) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.InsertException -> { + buf.putInt(5) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.RetrievalException -> { + buf.putInt(6) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.DataException -> { + buf.putInt(7) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.ConnectionException -> { + buf.putInt(8) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.SerializationException -> { + buf.putInt(9) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.ChannelOpen -> { + buf.putInt(10) + FfiConverterTypeBtChannelOrderErrorType.write(value.`errorType`, buf) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.OrderState -> { + buf.putInt(11) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.InvalidParameter -> { + buf.putInt(12) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BlocktankException.DatabaseException -> { + buf.putInt(13) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + +public object BoltzExceptionErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(errorBuf: RustBufferByValue): BoltzException = FfiConverterTypeBoltzError.lift(errorBuf) +} + +public object FfiConverterTypeBoltzError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzException { + return when (buf.getInt()) { + 1 -> BoltzException.InitializationException( + FfiConverterString.read(buf), + ) + 2 -> BoltzException.ConnectionException( + FfiConverterString.read(buf), + ) + 3 -> BoltzException.DatabaseException( + FfiConverterString.read(buf), + ) + 4 -> BoltzException.ApiException( + FfiConverterString.read(buf), + ) + 5 -> BoltzException.SwapException( + FfiConverterString.read(buf), + ) + 6 -> BoltzException.BroadcastException( + FfiConverterString.read(buf), + ) + 7 -> BoltzException.InvalidInput( + FfiConverterString.read(buf), + ) + 8 -> BoltzException.SerializationException( + FfiConverterString.read(buf), + ) + 9 -> BoltzException.NotFound( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: BoltzException): ULong { + return when (value) { + is BoltzException.InitializationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.ConnectionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.DatabaseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.ApiException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.SwapException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.BroadcastException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.InvalidInput -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.SerializationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`errorDetails`) + ) + is BoltzException.NotFound -> ( + // 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: BoltzException, buf: ByteBuffer) { + when (value) { + is BoltzException.InitializationException -> { + buf.putInt(1) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.ConnectionException -> { + buf.putInt(2) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.DatabaseException -> { + buf.putInt(3) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.ApiException -> { + buf.putInt(4) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.SwapException -> { + buf.putInt(5) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.BroadcastException -> { + buf.putInt(6) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.InvalidInput -> { + buf.putInt(7) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.SerializationException -> { + buf.putInt(8) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzException.NotFound -> { + buf.putInt(9) + FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + + +public object FfiConverterTypeBoltzNetwork: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzNetwork = try { + BoltzNetwork.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: BoltzNetwork): ULong = 4UL + + override fun write(value: BoltzNetwork, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + +public object FfiConverterTypeBoltzSwapEvent : FfiConverterRustBuffer{ + override fun read(buf: ByteBuffer): BoltzSwapEvent { + return when(buf.getInt()) { + 1 -> BoltzSwapEvent.StatusUpdate( + FfiConverterString.read(buf), + FfiConverterTypeBoltzSwapStatus.read(buf), + ) + 2 -> BoltzSwapEvent.Claimed( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + 3 -> BoltzSwapEvent.Refunded( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + 4 -> BoltzSwapEvent.Error( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: BoltzSwapEvent): ULong = when(value) { + is BoltzSwapEvent.StatusUpdate -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`swapId`) + + FfiConverterTypeBoltzSwapStatus.allocationSize(value.`status`) + ) + } + is BoltzSwapEvent.Claimed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`swapId`) + + FfiConverterString.allocationSize(value.`txid`) + ) + } + is BoltzSwapEvent.Refunded -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`swapId`) + + FfiConverterString.allocationSize(value.`txid`) + ) + } + is BoltzSwapEvent.Error -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`swapId`) + + FfiConverterString.allocationSize(value.`message`) + ) + } + } + + override fun write(value: BoltzSwapEvent, buf: ByteBuffer) { + when(value) { + is BoltzSwapEvent.StatusUpdate -> { + buf.putInt(1) + FfiConverterString.write(value.`swapId`, buf) + FfiConverterTypeBoltzSwapStatus.write(value.`status`, buf) + Unit + } + is BoltzSwapEvent.Claimed -> { + buf.putInt(2) + FfiConverterString.write(value.`swapId`, buf) + FfiConverterString.write(value.`txid`, buf) + Unit + } + is BoltzSwapEvent.Refunded -> { + buf.putInt(3) + FfiConverterString.write(value.`swapId`, buf) + FfiConverterString.write(value.`txid`, buf) + Unit + } + is BoltzSwapEvent.Error -> { + buf.putInt(4) + FfiConverterString.write(value.`swapId`, buf) + FfiConverterString.write(value.`message`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + + +public object FfiConverterTypeBoltzSwapStatus : FfiConverterRustBuffer{ + override fun read(buf: ByteBuffer): BoltzSwapStatus { + return when(buf.getInt()) { + 1 -> BoltzSwapStatus.SwapCreated + 2 -> BoltzSwapStatus.InvoiceSet + 3 -> BoltzSwapStatus.TransactionMempool + 4 -> BoltzSwapStatus.TransactionConfirmed + 5 -> BoltzSwapStatus.InvoicePending + 6 -> BoltzSwapStatus.InvoicePaid + 7 -> BoltzSwapStatus.InvoiceSettled + 8 -> BoltzSwapStatus.InvoiceFailedToPay + 9 -> BoltzSwapStatus.InvoiceExpired + 10 -> BoltzSwapStatus.TransactionClaimPending + 11 -> BoltzSwapStatus.TransactionClaimed + 12 -> BoltzSwapStatus.TransactionRefunded + 13 -> BoltzSwapStatus.TransactionLockupFailed + 14 -> BoltzSwapStatus.TransactionFailed + 15 -> BoltzSwapStatus.SwapExpired + 16 -> BoltzSwapStatus.Unknown( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: BoltzSwapStatus): ULong = when(value) { + is BoltzSwapStatus.SwapCreated -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoiceSet -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionMempool -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionConfirmed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoicePending -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoicePaid -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoiceSettled -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoiceFailedToPay -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.InvoiceExpired -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionClaimPending -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionClaimed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionRefunded -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionLockupFailed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.TransactionFailed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.SwapExpired -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + ) + } + is BoltzSwapStatus.Unknown -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterString.allocationSize(value.`raw`) + ) + } + } + + override fun write(value: BoltzSwapStatus, buf: ByteBuffer) { + when(value) { + is BoltzSwapStatus.SwapCreated -> { + buf.putInt(1) + Unit + } + is BoltzSwapStatus.InvoiceSet -> { buf.putInt(2) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.InvalidBlocktank -> { + is BoltzSwapStatus.TransactionMempool -> { buf.putInt(3) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.InitializationException -> { + is BoltzSwapStatus.TransactionConfirmed -> { buf.putInt(4) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.InsertException -> { + is BoltzSwapStatus.InvoicePending -> { buf.putInt(5) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.RetrievalException -> { + is BoltzSwapStatus.InvoicePaid -> { buf.putInt(6) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.DataException -> { + is BoltzSwapStatus.InvoiceSettled -> { buf.putInt(7) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.ConnectionException -> { + is BoltzSwapStatus.InvoiceFailedToPay -> { buf.putInt(8) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.SerializationException -> { + is BoltzSwapStatus.InvoiceExpired -> { buf.putInt(9) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.ChannelOpen -> { + is BoltzSwapStatus.TransactionClaimPending -> { buf.putInt(10) - FfiConverterTypeBtChannelOrderErrorType.write(value.`errorType`, buf) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.OrderState -> { + is BoltzSwapStatus.TransactionClaimed -> { buf.putInt(11) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.InvalidParameter -> { + is BoltzSwapStatus.TransactionRefunded -> { buf.putInt(12) - FfiConverterString.write(value.`errorDetails`, buf) Unit } - is BlocktankException.DatabaseException -> { + is BoltzSwapStatus.TransactionLockupFailed -> { buf.putInt(13) - FfiConverterString.write(value.`errorDetails`, buf) + Unit + } + is BoltzSwapStatus.TransactionFailed -> { + buf.putInt(14) + Unit + } + is BoltzSwapStatus.SwapExpired -> { + buf.putInt(15) + Unit + } + is BoltzSwapStatus.Unknown -> { + buf.putInt(16) + FfiConverterString.write(value.`raw`, buf) Unit } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } @@ -9274,6 +10291,24 @@ public object FfiConverterTypeBlocktankError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzSwapType = try { + BoltzSwapType.entries[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: BoltzSwapType): ULong = 4UL + + override fun write(value: BoltzSwapType, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + public object BroadcastExceptionErrorHandler : UniffiRustCallStatusErrorHandler { override fun lift(errorBuf: RustBufferByValue): BroadcastException = FfiConverterTypeBroadcastError.lift(errorBuf) } @@ -9653,7 +10688,10 @@ public object FfiConverterTypeDbError : FfiConverterRustBuffer { 2 -> DbException.DbBlocktankException( FfiConverterTypeBlocktankError.read(buf), ) - 3 -> DbException.InitializationException( + 3 -> DbException.DbBoltzException( + FfiConverterTypeBoltzError.read(buf), + ) + 4 -> DbException.InitializationException( FfiConverterString.read(buf), ) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") @@ -9672,6 +10710,11 @@ public object FfiConverterTypeDbError : FfiConverterRustBuffer { 4UL + FfiConverterTypeBlocktankError.allocationSize(value.`errorDetails`) ) + is DbException.DbBoltzException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterTypeBoltzError.allocationSize(value.`errorDetails`) + ) is DbException.InitializationException -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL @@ -9692,8 +10735,13 @@ public object FfiConverterTypeDbError : FfiConverterRustBuffer { FfiConverterTypeBlocktankError.write(value.`errorDetails`, buf) Unit } - is DbException.InitializationException -> { + is DbException.DbBoltzException -> { buf.putInt(3) + FfiConverterTypeBoltzError.write(value.`errorDetails`, buf) + Unit + } + is DbException.InitializationException -> { + buf.putInt(4) FfiConverterString.write(value.`errorDetails`, buf) Unit } @@ -11252,6 +12300,35 @@ public object FfiConverterOptionalByteArray: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BoltzSwap? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeBoltzSwap.read(buf) + } + + override fun allocationSize(value: BoltzSwap?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeBoltzSwap.allocationSize(value) + } + } + + override fun write(value: BoltzSwap?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeBoltzSwap.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeClosedChannelDetails: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): ClosedChannelDetails? { if (buf.get().toInt() == 0) { @@ -12566,6 +13643,31 @@ public object FfiConverterSequenceTypeAddressInfo: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeBoltzSwap.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterTypeBoltzSwap.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeBoltzSwap.write(it, buf) + } + } +} + + + + public object FfiConverterSequenceTypeClosedChannelDetails: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List { val len = buf.getInt() @@ -13501,6 +14603,277 @@ public suspend fun `blocktankWipeAll`() { ) } +/** + * Claim a reverse swap's onchain funds to its claim address, returning the + * broadcast claim transaction id. Normally happens automatically via the + * updates stream; exposed for manual recovery. The claim key is re-derived from + * `mnemonic`. Claims are serialized per swap, so calling this while the updates + * stream is auto-claiming the same swap waits for that claim and returns its + * txid rather than broadcasting a second transaction. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzClaimReverseSwap`(`swapId`: kotlin.String, `mnemonic`: kotlin.String, `bip39Passphrase`: kotlin.String?, `feeRateSatPerVb`: kotlin.Double?): kotlin.String { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap( + FfiConverterString.lower(`swapId`), + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`bip39Passphrase`), + FfiConverterOptionalDouble.lower(`feeRateSatPerVb`), + ), + { 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 + BoltzExceptionErrorHandler, + ) +} + +/** + * Create a reverse swap (Lightning -> onchain). + * + * The caller pays the returned hold invoice from its Lightning node; + * `claim_address` is the onchain address the received funds are claimed to. + * The claim key and preimage are derived deterministically from `mnemonic` + * (only the derivation index is persisted, never the secrets) so the claim can + * be made automatically once Boltz locks the funds. `bip39_passphrase` must + * match the wallet's, or claims will derive the wrong key. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzCreateReverseSwap`(`network`: BoltzNetwork, `electrumUrl`: kotlin.String, `amountSat`: kotlin.ULong, `claimAddress`: kotlin.String, `mnemonic`: kotlin.String, `bip39Passphrase`: kotlin.String?): ReverseSwapResponse { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_reverse_swap( + FfiConverterTypeBoltzNetwork.lower(`network`), + FfiConverterString.lower(`electrumUrl`), + FfiConverterULong.lower(`amountSat`), + FfiConverterString.lower(`claimAddress`), + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`bip39Passphrase`), + ), + { 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 + { FfiConverterTypeReverseSwapResponse.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Create a submarine swap (onchain -> Lightning). + * + * `invoice` is a BOLT11 invoice the caller's Lightning node generated. The + * caller funds the returned lockup address from its onchain wallet. The refund + * key is derived deterministically from `mnemonic` (only the derivation index + * is persisted, never the key), and the swap is tracked if an updates stream is + * running. `bip39_passphrase` must match the wallet's, or refunds will derive + * the wrong key. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzCreateSubmarineSwap`(`network`: BoltzNetwork, `electrumUrl`: kotlin.String, `invoice`: kotlin.String, `mnemonic`: kotlin.String, `bip39Passphrase`: kotlin.String?): SubmarineSwapResponse { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_submarine_swap( + FfiConverterTypeBoltzNetwork.lower(`network`), + FfiConverterString.lower(`electrumUrl`), + FfiConverterString.lower(`invoice`), + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`bip39Passphrase`), + ), + { 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 + { FfiConverterTypeSubmarineSwapResponse.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Fetch fees and limits for reverse swaps (Lightning -> onchain). + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzGetReverseLimits`(`network`: BoltzNetwork): BoltzPairInfo { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_reverse_limits( + FfiConverterTypeBoltzNetwork.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 + { FfiConverterTypeBoltzPairInfo.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Fetch fees and limits for submarine swaps (onchain -> Lightning). + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzGetSubmarineLimits`(`network`: BoltzNetwork): BoltzPairInfo { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_submarine_limits( + FfiConverterTypeBoltzNetwork.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 + { FfiConverterTypeBoltzPairInfo.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Fetch a single swap by id. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzGetSwap`(`swapId`: kotlin.String): BoltzSwap? { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_swap( + FfiConverterString.lower(`swapId`), + ), + { 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 + { FfiConverterOptionalTypeBoltzSwap.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * List swaps that have not reached a terminal state (for recovery/resume). + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzListPendingSwaps`(): List { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_pending_swaps( + ), + { 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 + { FfiConverterSequenceTypeBoltzSwap.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * List every persisted swap, newest first. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzListSwaps`(): List { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_swaps( + ), + { 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 + { FfiConverterSequenceTypeBoltzSwap.lift(it) }, + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Refund a submarine swap's locked funds to `refund_address`, returning the + * broadcast refund transaction id. Used when Boltz fails to pay the invoice or + * the swap expires. The refund key is re-derived from `mnemonic`. Refunds are + * serialized per swap, so two concurrent calls cannot both broadcast: the second + * waits for the first and returns its txid. + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzRefundSubmarineSwap`(`swapId`: kotlin.String, `refundAddress`: kotlin.String, `mnemonic`: kotlin.String, `bip39Passphrase`: kotlin.String?, `feeRateSatPerVb`: kotlin.Double?): kotlin.String { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap( + FfiConverterString.lower(`swapId`), + FfiConverterString.lower(`refundAddress`), + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`bip39Passphrase`), + FfiConverterOptionalDouble.lower(`feeRateSatPerVb`), + ), + { 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 + BoltzExceptionErrorHandler, + ) +} + +/** + * Open a Boltz WebSocket for `network`, subscribe to all pending swaps, and + * drive their lifecycle (auto-claiming reverse swaps) until stopped. Replaces + * any previously running updates stream (only one network is tracked at a + * time). `mnemonic` is held in memory for the lifetime of the stream so + * confirmed reverse swaps can be auto-claimed; it is never persisted. Events + * are delivered to `listener`. + * + * `fee_rate_sat_per_vb` is the fee rate used for automatic claim transactions. + * Bitkit owns fee estimation and should pass its current recommended rate; when + * `None`, a conservative built-in default is used. To auto-claim at an updated + * fee rate, call this again (it restarts the stream). + */ +@Throws(BoltzException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `boltzStartSwapUpdates`(`network`: BoltzNetwork, `listener`: BoltzEventListener, `mnemonic`: kotlin.String, `bip39Passphrase`: kotlin.String?, `feeRateSatPerVb`: kotlin.Double?) { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_start_swap_updates( + FfiConverterTypeBoltzNetwork.lower(`network`), + FfiConverterTypeBoltzEventListener.lower(`listener`), + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`bip39Passphrase`), + FfiConverterOptionalDouble.lower(`feeRateSatPerVb`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_void(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_void(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_void(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + BoltzExceptionErrorHandler, + ) +} + +/** + * Stop the running Boltz updates stream, if any. + */ +public suspend fun `boltzStopSwapUpdates`() { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_boltz_stop_swap_updates( + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_void(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_void(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_void(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + UniffiNullRustCallStatusErrorHandler, + ) +} + @Throws(SweepException::class, kotlin.coroutines.cancellation.CancellationException::class) public suspend fun `broadcastSweepTransaction`(`psbt`: kotlin.String, `mnemonicPhrase`: kotlin.String, `network`: Network?, `bip39Passphrase`: kotlin.String?, `electrumUrl`: kotlin.String): SweepResult { return uniffiRustCallAsync( 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 bb0f867..85fe3be 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 @@ -112,6 +112,24 @@ public object NoPointer + +/** + * Callback interface for receiving Boltz swap lifecycle events. + * + * Implement this in Swift/Kotlin/Python and register it via + * `boltz_start_swap_updates` to receive typed notifications as swaps progress. + * Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + * event reports the resulting transaction id. + */ +public interface BoltzEventListener { + + public fun `onEvent`(`event`: BoltzSwapEvent) + + public companion object +} + + + /** * Callback interface for receiving watcher events. @@ -438,6 +456,94 @@ public data class AddressInfo ( +/** + * Fees and limits for a swap pair, used to size a swap and present costs. + */ +@kotlinx.serialization.Serializable +public data class BoltzPairInfo ( + /** + * Pair hash identifying the current terms (passed back to Boltz if needed). + */ + val `hash`: kotlin.String, + /** + * Exchange rate of the pair. + */ + val `rate`: kotlin.Double, + /** + * Minimum swap amount in satoshis. + */ + val `minimalSat`: kotlin.ULong, + /** + * Maximum swap amount in satoshis. + */ + val `maximalSat`: kotlin.ULong, + /** + * Boltz service fee as a percentage of the swap amount. + */ + val `feePercentage`: kotlin.Double, + /** + * Estimated absolute miner fees in satoshis. + */ + val `minerFeesSat`: kotlin.ULong +) { + public companion object +} + + + +/** + * A persisted swap and its current state, returned by the listing/query APIs. + */ +@kotlinx.serialization.Serializable +public data class BoltzSwap ( + val `id`: kotlin.String, + val `swapType`: BoltzSwapType, + val `status`: BoltzSwapStatus, + val `network`: BoltzNetwork, + /** + * Deterministic BIP85 index used to derive this swap's key and preimage + * from the wallet seed. The recovery handle: given the seed and this index + * (or by scanning indices), the swap's secrets can be reconstructed. + */ + val `swapIndex`: kotlin.ULong, + /** + * For submarine swaps: the amount to lock onchain. For reverse swaps: the + * Lightning invoice amount. + */ + val `amountSat`: kotlin.ULong, + /** + * For reverse swaps: the onchain amount that will be received. + */ + val `onchainAmountSat`: kotlin.ULong?, + /** + * Lightning invoice associated with the swap (the hold invoice for reverse + * swaps, the invoice Boltz pays for submarine swaps). + */ + val `invoice`: kotlin.String?, + /** + * Onchain lockup address. + */ + val `lockupAddress`: kotlin.String?, + /** + * The address funds are claimed to (reverse) or refunded to (submarine). + */ + val `onchainAddress`: kotlin.String?, + val `timeoutBlockHeight`: kotlin.ULong, + val `createdAt`: kotlin.ULong, + /** + * Txid of the claim transaction once broadcast (reverse swaps). + */ + val `claimTxId`: kotlin.String?, + /** + * Txid of the refund transaction once broadcast (submarine swaps). + */ + val `refundTxId`: kotlin.String? +) { + public companion object +} + + + @kotlinx.serialization.Serializable public data class ChannelLiquidityOptions ( val `defaultLspBalanceSat`: kotlin.ULong, @@ -1475,6 +1581,37 @@ public data class PubkyProfileLink ( +/** + * Result of creating a reverse swap (Lightning -> onchain). + * + * The caller pays `invoice` from its Lightning node; once Boltz locks funds at + * `lockup_address`, the module claims them to the provided onchain address. + */ +@kotlinx.serialization.Serializable +public data class ReverseSwapResponse ( + val `id`: kotlin.String, + /** + * Hold invoice the caller must pay via Lightning. + */ + val `invoice`: kotlin.String, + /** + * Address Boltz locks the onchain funds to. + */ + val `lockupAddress`: kotlin.String, + /** + * Amount in satoshis that will be received onchain (after Boltz fees). + */ + val `onchainAmountSat`: kotlin.ULong, + /** + * Onchain timeout height for the swap. + */ + val `timeoutBlockHeight`: kotlin.ULong +) { + public companion object +} + + + /** * Result from querying a single Bitcoin address. */ @@ -1506,6 +1643,41 @@ public data class SingleAddressInfoResult ( +/** + * Result of creating a submarine swap (onchain -> Lightning). + * + * The caller funds `address` with `expected_amount_sat` from its onchain + * wallet; Boltz then pays the Lightning invoice supplied at creation. + */ +@kotlinx.serialization.Serializable +public data class SubmarineSwapResponse ( + val `id`: kotlin.String, + /** + * Onchain lockup address to send funds to. + */ + val `address`: kotlin.String, + /** + * BIP21 URI for the lockup payment. + */ + val `bip21`: kotlin.String, + /** + * Exact amount in satoshis the caller must send to `address`. + */ + val `expectedAmountSat`: kotlin.ULong, + /** + * Whether Boltz will accept a zero-conf lockup. + */ + val `acceptZeroConf`: kotlin.Boolean, + /** + * Onchain timeout height after which a refund is possible. + */ + val `timeoutBlockHeight`: kotlin.ULong +) { + public companion object +} + + + /** * A hardware-wallet model Bitkit supports, with the transports it can connect over. * @@ -3068,6 +3240,300 @@ public sealed class BlocktankException: kotlin.Exception() { +/** + * Errors surfaced by the Boltz swaps module. + */ +public sealed class BoltzException: kotlin.Exception() { + + public class InitializationException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class ConnectionException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class DatabaseException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class ApiException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class SwapException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class BroadcastException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class InvalidInput( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class SerializationException( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + + public class NotFound( + public val `errorDetails`: kotlin.String, + ) : BoltzException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + +} + + + + +/** + * Bitcoin network selection for Boltz swaps. Maps to the networks Boltz + * operates on (mainnet, testnet, regtest). + */ + +@kotlinx.serialization.Serializable +public enum class BoltzNetwork { + + MAINNET, + TESTNET, + REGTEST; + public companion object +} + + + + + + +/** + * Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] + * as swaps progress through their lifecycle. + */ +@kotlinx.serialization.Serializable +public sealed class BoltzSwapEvent { + + /** + * The swap transitioned to a new status. + */@kotlinx.serialization.Serializable + public data class StatusUpdate( + val `swapId`: kotlin.String, + val `status`: BoltzSwapStatus, + ) : BoltzSwapEvent() { + } + + /** + * A reverse swap was claimed onchain. `txid` is the claim transaction. + */@kotlinx.serialization.Serializable + public data class Claimed( + val `swapId`: kotlin.String, + val `txid`: kotlin.String, + ) : BoltzSwapEvent() { + } + + /** + * A submarine swap was refunded onchain. `txid` is the refund transaction. + */@kotlinx.serialization.Serializable + public data class Refunded( + val `swapId`: kotlin.String, + val `txid`: kotlin.String, + ) : BoltzSwapEvent() { + } + + /** + * An error occurred while processing the swap (e.g. an auto-claim failed). + */@kotlinx.serialization.Serializable + public data class Error( + val `swapId`: kotlin.String, + val `message`: kotlin.String, + ) : BoltzSwapEvent() { + } + +} + + + + + + +/** + * Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so + * new server-side states don't break the bindings. + * + * See . + */ +@kotlinx.serialization.Serializable +public sealed class BoltzSwapStatus { + + /** + * `swap.created` — initial state. + */ + @kotlinx.serialization.Serializable + public data object SwapCreated : BoltzSwapStatus() + + + /** + * `invoice.set` — invoice attached to a submarine swap. + */ + @kotlinx.serialization.Serializable + public data object InvoiceSet : BoltzSwapStatus() + + + /** + * `transaction.mempool` — a lockup transaction is in the mempool. + */ + @kotlinx.serialization.Serializable + public data object TransactionMempool : BoltzSwapStatus() + + + /** + * `transaction.confirmed` — a lockup transaction confirmed. + */ + @kotlinx.serialization.Serializable + public data object TransactionConfirmed : BoltzSwapStatus() + + + /** + * `invoice.pending` — Boltz is paying the submarine swap invoice. + */ + @kotlinx.serialization.Serializable + public data object InvoicePending : BoltzSwapStatus() + + + /** + * `invoice.paid` — submarine swap invoice paid by Boltz. + */ + @kotlinx.serialization.Serializable + public data object InvoicePaid : BoltzSwapStatus() + + + /** + * `invoice.settled` — reverse swap invoice settled (preimage revealed). + */ + @kotlinx.serialization.Serializable + public data object InvoiceSettled : BoltzSwapStatus() + + + /** + * `invoice.failedToPay` — submarine swap invoice could not be paid; refund. + */ + @kotlinx.serialization.Serializable + public data object InvoiceFailedToPay : BoltzSwapStatus() + + + /** + * `invoice.expired` — reverse swap invoice expired before payment. + */ + @kotlinx.serialization.Serializable + public data object InvoiceExpired : BoltzSwapStatus() + + + /** + * `transaction.claim.pending` — Boltz ready for a cooperative claim. + */ + @kotlinx.serialization.Serializable + public data object TransactionClaimPending : BoltzSwapStatus() + + + /** + * `transaction.claimed` — onchain funds claimed. + */ + @kotlinx.serialization.Serializable + public data object TransactionClaimed : BoltzSwapStatus() + + + /** + * `transaction.refunded` — onchain funds refunded. + */ + @kotlinx.serialization.Serializable + public data object TransactionRefunded : BoltzSwapStatus() + + + /** + * `transaction.lockupFailed` — wrong amount locked; can refund. + */ + @kotlinx.serialization.Serializable + public data object TransactionLockupFailed : BoltzSwapStatus() + + + /** + * `transaction.failed` — Boltz failed to lock the agreed funds. + */ + @kotlinx.serialization.Serializable + public data object TransactionFailed : BoltzSwapStatus() + + + /** + * `swap.expired` — swap expired without completing. + */ + @kotlinx.serialization.Serializable + public data object SwapExpired : BoltzSwapStatus() + + + /** + * Any status not yet modelled. `raw` holds the verbatim Boltz status. + */@kotlinx.serialization.Serializable + public data class Unknown( + val `raw`: kotlin.String, + ) : BoltzSwapStatus() { + } + +} + + + + + + +/** + * The direction of a Boltz swap. + * + * - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, + * Boltz pays a Lightning invoice). + * - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold + * invoice, Boltz locks onchain funds the user then claims). + */ + +@kotlinx.serialization.Serializable +public enum class BoltzSwapType { + + SUBMARINE, + REVERSE; + public companion object +} + + + + + + + public sealed class BroadcastException: kotlin.Exception() { public class InvalidHex( @@ -3361,6 +3827,13 @@ public sealed class DbException: kotlin.Exception() { get() = "errorDetails=${ `errorDetails` }" } + public class DbBoltzException( + public val `errorDetails`: BoltzException, + ) : DbException() { + override val message: String + get() = "errorDetails=${ `errorDetails` }" + } + public class InitializationException( public val `errorDetails`: kotlin.String, ) : DbException() { @@ -4490,6 +4963,10 @@ public enum class WordCount { + + + + diff --git a/bindings/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 6e923f9..f477d89 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 608cd28..d81406d 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h @@ -250,6 +250,13 @@ typedef struct UniffiForeignFutureStructVoid { typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid ); +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +typedef void (*UniffiCallbackInterfaceBoltzEventListenerMethod0)(uint64_t, RustBuffer, void* _Nonnull, + RustCallStatus *_Nonnull uniffiCallStatus + ); + #endif #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 @@ -348,6 +355,14 @@ typedef void (*UniffiCallbackInterfaceTrezorUiCallbackMethod1)(uint64_t, int8_t, RustCallStatus *_Nonnull uniffiCallStatus ); +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +typedef struct UniffiVTableCallbackInterfaceBoltzEventListener { + UniffiCallbackInterfaceBoltzEventListenerMethod0 _Nonnull onEvent; + UniffiCallbackInterfaceFree _Nonnull uniffiFree; +} UniffiVTableCallbackInterfaceBoltzEventListener; + #endif #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER @@ -383,6 +398,26 @@ typedef struct UniffiVTableCallbackInterfaceTrezorUiCallback { UniffiCallbackInterfaceFree _Nonnull uniffiFree; } UniffiVTableCallbackInterfaceTrezorUiCallback; +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +void*_Nonnull uniffi_bitkitcore_fn_clone_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_free_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(const UniffiVTableCallbackInterfaceBoltzEventListener* _Nonnull vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +void uniffi_bitkitcore_fn_method_boltzeventlistener_on_event(void*_Nonnull ptr, RustBuffer event, RustCallStatus *_Nonnull out_status +); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER @@ -561,6 +596,64 @@ uint64_t uniffi_bitkitcore_fn_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BLOCKTANK_WIPE_ALL uint64_t uniffi_bitkitcore_fn_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap(RustBuffer swap_id, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_reverse_swap(RustBuffer network, RustBuffer electrum_url, uint64_t amount_sat, RustBuffer claim_address, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_submarine_swap(RustBuffer network, RustBuffer electrum_url, RustBuffer invoice, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_reverse_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_submarine_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_get_swap(RustBuffer swap_id +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap(RustBuffer swap_id, RustBuffer refund_address, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_start_swap_updates(RustBuffer network, void*_Nonnull listener, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -1699,6 +1792,72 @@ uint16_t uniffi_bitkitcore_checksum_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BLOCKTANK_WIPE_ALL uint16_t uniffi_bitkitcore_checksum_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_start_swap_updates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -2611,6 +2770,12 @@ uint16_t uniffi_bitkitcore_checksum_func_wipe_all_databases(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WIPE_ALL_TRANSACTION_DETAILS uint16_t uniffi_bitkitcore_checksum_func_wipe_all_transaction_details(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +uint16_t uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_EVENTLISTENER_ON_EVENT diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a deleted file mode 100644 index ca8fede..0000000 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a and /dev/null differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h index 608cd28..d81406d 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h @@ -250,6 +250,13 @@ typedef struct UniffiForeignFutureStructVoid { typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid ); +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +typedef void (*UniffiCallbackInterfaceBoltzEventListenerMethod0)(uint64_t, RustBuffer, void* _Nonnull, + RustCallStatus *_Nonnull uniffiCallStatus + ); + #endif #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 @@ -348,6 +355,14 @@ typedef void (*UniffiCallbackInterfaceTrezorUiCallbackMethod1)(uint64_t, int8_t, RustCallStatus *_Nonnull uniffiCallStatus ); +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +typedef struct UniffiVTableCallbackInterfaceBoltzEventListener { + UniffiCallbackInterfaceBoltzEventListenerMethod0 _Nonnull onEvent; + UniffiCallbackInterfaceFree _Nonnull uniffiFree; +} UniffiVTableCallbackInterfaceBoltzEventListener; + #endif #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER @@ -383,6 +398,26 @@ typedef struct UniffiVTableCallbackInterfaceTrezorUiCallback { UniffiCallbackInterfaceFree _Nonnull uniffiFree; } UniffiVTableCallbackInterfaceTrezorUiCallback; +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +void*_Nonnull uniffi_bitkitcore_fn_clone_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_free_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(const UniffiVTableCallbackInterfaceBoltzEventListener* _Nonnull vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +void uniffi_bitkitcore_fn_method_boltzeventlistener_on_event(void*_Nonnull ptr, RustBuffer event, RustCallStatus *_Nonnull out_status +); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER @@ -561,6 +596,64 @@ uint64_t uniffi_bitkitcore_fn_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BLOCKTANK_WIPE_ALL uint64_t uniffi_bitkitcore_fn_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap(RustBuffer swap_id, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_reverse_swap(RustBuffer network, RustBuffer electrum_url, uint64_t amount_sat, RustBuffer claim_address, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_submarine_swap(RustBuffer network, RustBuffer electrum_url, RustBuffer invoice, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_reverse_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_submarine_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_get_swap(RustBuffer swap_id +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap(RustBuffer swap_id, RustBuffer refund_address, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_start_swap_updates(RustBuffer network, void*_Nonnull listener, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -1699,6 +1792,72 @@ uint16_t uniffi_bitkitcore_checksum_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BLOCKTANK_WIPE_ALL uint16_t uniffi_bitkitcore_checksum_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_start_swap_updates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -2611,6 +2770,12 @@ uint16_t uniffi_bitkitcore_checksum_func_wipe_all_databases(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WIPE_ALL_TRANSACTION_DETAILS uint16_t uniffi_bitkitcore_checksum_func_wipe_all_transaction_details(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +uint16_t uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_EVENTLISTENER_ON_EVENT diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a deleted file mode 100644 index 34b9ab4..0000000 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a and /dev/null differ diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index 267fcaa..c463ba6 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -585,6 +585,195 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer { +/** + * Callback interface for receiving Boltz swap lifecycle events. + * + * Implement this in Swift/Kotlin/Python and register it via + * `boltz_start_swap_updates` to receive typed notifications as swaps progress. + * Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + * event reports the resulting transaction id. + */ +public protocol BoltzEventListener: AnyObject, Sendable { + + func onEvent(event: BoltzSwapEvent) + +} +/** + * Callback interface for receiving Boltz swap lifecycle events. + * + * Implement this in Swift/Kotlin/Python and register it via + * `boltz_start_swap_updates` to receive typed notifications as swaps progress. + * Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + * event reports the resulting transaction id. + */ +open class BoltzEventListenerImpl: BoltzEventListener, @unchecked Sendable { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noPointer: NoPointer) { + self.pointer = nil + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_bitkitcore_fn_clone_boltzeventlistener(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_bitkitcore_fn_free_boltzeventlistener(pointer, $0) } + } + + + + +open func onEvent(event: BoltzSwapEvent) {try! rustCall() { + uniffi_bitkitcore_fn_method_boltzeventlistener_on_event(self.uniffiClonePointer(), + FfiConverterTypeBoltzSwapEvent_lower(event),$0 + ) +} +} + + +} + + +// Put the implementation in a struct so we don't pollute the top-level namespace +fileprivate struct UniffiCallbackInterfaceBoltzEventListener { + + // Create the VTable using a series of closures. + // Swift automatically converts these into C callback functions. + // + // This creates 1-element array, since this seems to be the only way to construct a const + // pointer that we can pass to the Rust code. + static let vtable: [UniffiVTableCallbackInterfaceBoltzEventListener] = [UniffiVTableCallbackInterfaceBoltzEventListener( + onEvent: { ( + uniffiHandle: UInt64, + event: RustBuffer, + uniffiOutReturn: UnsafeMutableRawPointer, + uniffiCallStatus: UnsafeMutablePointer + ) in + let makeCall = { + () throws -> () in + guard let uniffiObj = try? FfiConverterTypeBoltzEventListener.handleMap.get(handle: uniffiHandle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return uniffiObj.onEvent( + event: try FfiConverterTypeBoltzSwapEvent_lift(event) + ) + } + + + let writeReturn = { () } + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + }, + uniffiFree: { (uniffiHandle: UInt64) -> () in + let result = try? FfiConverterTypeBoltzEventListener.handleMap.remove(handle: uniffiHandle) + if result == nil { + print("Uniffi callback interface BoltzEventListener: handle missing in uniffiFree") + } + } + )] +} + +private func uniffiCallbackInitBoltzEventListener() { + uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(UniffiCallbackInterfaceBoltzEventListener.vtable) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzEventListener: FfiConverter { + fileprivate static let handleMap = UniffiHandleMap() + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = BoltzEventListener + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> BoltzEventListener { + return BoltzEventListenerImpl(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: BoltzEventListener) -> UnsafeMutableRawPointer { + guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else { + fatalError("Cast to UnsafeMutableRawPointer failed") + } + return ptr + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzEventListener { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: BoltzEventListener, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzEventListener_lift(_ pointer: UnsafeMutableRawPointer) throws -> BoltzEventListener { + return try FfiConverterTypeBoltzEventListener.lift(pointer) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzEventListener_lower(_ value: BoltzEventListener) -> UnsafeMutableRawPointer { + return FfiConverterTypeBoltzEventListener.lower(value) +} + + + + + + /** * Callback interface for receiving watcher events. * @@ -2364,75 +2553,130 @@ public func FfiConverterTypeAddressInfo_lower(_ value: AddressInfo) -> RustBuffe } -public struct ChannelLiquidityOptions { - public var defaultLspBalanceSat: UInt64 - public var minLspBalanceSat: UInt64 - public var maxLspBalanceSat: UInt64 - public var maxClientBalanceSat: UInt64 +/** + * Fees and limits for a swap pair, used to size a swap and present costs. + */ +public struct BoltzPairInfo { + /** + * Pair hash identifying the current terms (passed back to Boltz if needed). + */ + public var hash: String + /** + * Exchange rate of the pair. + */ + public var rate: Double + /** + * Minimum swap amount in satoshis. + */ + public var minimalSat: UInt64 + /** + * Maximum swap amount in satoshis. + */ + public var maximalSat: UInt64 + /** + * Boltz service fee as a percentage of the swap amount. + */ + public var feePercentage: Double + /** + * Estimated absolute miner fees in satoshis. + */ + public var minerFeesSat: UInt64 // Default memberwise initializers are never public by default, so we // declare one manually. - public init(defaultLspBalanceSat: UInt64, minLspBalanceSat: UInt64, maxLspBalanceSat: UInt64, maxClientBalanceSat: UInt64) { - self.defaultLspBalanceSat = defaultLspBalanceSat - self.minLspBalanceSat = minLspBalanceSat - self.maxLspBalanceSat = maxLspBalanceSat - self.maxClientBalanceSat = maxClientBalanceSat + public init( + /** + * Pair hash identifying the current terms (passed back to Boltz if needed). + */hash: String, + /** + * Exchange rate of the pair. + */rate: Double, + /** + * Minimum swap amount in satoshis. + */minimalSat: UInt64, + /** + * Maximum swap amount in satoshis. + */maximalSat: UInt64, + /** + * Boltz service fee as a percentage of the swap amount. + */feePercentage: Double, + /** + * Estimated absolute miner fees in satoshis. + */minerFeesSat: UInt64) { + self.hash = hash + self.rate = rate + self.minimalSat = minimalSat + self.maximalSat = maximalSat + self.feePercentage = feePercentage + self.minerFeesSat = minerFeesSat } } #if compiler(>=6) -extension ChannelLiquidityOptions: Sendable {} +extension BoltzPairInfo: Sendable {} #endif -extension ChannelLiquidityOptions: Equatable, Hashable { - public static func ==(lhs: ChannelLiquidityOptions, rhs: ChannelLiquidityOptions) -> Bool { - if lhs.defaultLspBalanceSat != rhs.defaultLspBalanceSat { +extension BoltzPairInfo: Equatable, Hashable { + public static func ==(lhs: BoltzPairInfo, rhs: BoltzPairInfo) -> Bool { + if lhs.hash != rhs.hash { return false } - if lhs.minLspBalanceSat != rhs.minLspBalanceSat { + if lhs.rate != rhs.rate { return false } - if lhs.maxLspBalanceSat != rhs.maxLspBalanceSat { + if lhs.minimalSat != rhs.minimalSat { return false } - if lhs.maxClientBalanceSat != rhs.maxClientBalanceSat { + if lhs.maximalSat != rhs.maximalSat { + return false + } + if lhs.feePercentage != rhs.feePercentage { + return false + } + if lhs.minerFeesSat != rhs.minerFeesSat { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(defaultLspBalanceSat) - hasher.combine(minLspBalanceSat) - hasher.combine(maxLspBalanceSat) - hasher.combine(maxClientBalanceSat) + hasher.combine(hash) + hasher.combine(rate) + hasher.combine(minimalSat) + hasher.combine(maximalSat) + hasher.combine(feePercentage) + hasher.combine(minerFeesSat) } } -extension ChannelLiquidityOptions: Codable {} +extension BoltzPairInfo: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeChannelLiquidityOptions: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelLiquidityOptions { +public struct FfiConverterTypeBoltzPairInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzPairInfo { return - try ChannelLiquidityOptions( - defaultLspBalanceSat: FfiConverterUInt64.read(from: &buf), - minLspBalanceSat: FfiConverterUInt64.read(from: &buf), - maxLspBalanceSat: FfiConverterUInt64.read(from: &buf), - maxClientBalanceSat: FfiConverterUInt64.read(from: &buf) + try BoltzPairInfo( + hash: FfiConverterString.read(from: &buf), + rate: FfiConverterDouble.read(from: &buf), + minimalSat: FfiConverterUInt64.read(from: &buf), + maximalSat: FfiConverterUInt64.read(from: &buf), + feePercentage: FfiConverterDouble.read(from: &buf), + minerFeesSat: FfiConverterUInt64.read(from: &buf) ) } - public static func write(_ value: ChannelLiquidityOptions, into buf: inout [UInt8]) { - FfiConverterUInt64.write(value.defaultLspBalanceSat, into: &buf) - FfiConverterUInt64.write(value.minLspBalanceSat, into: &buf) - FfiConverterUInt64.write(value.maxLspBalanceSat, into: &buf) - FfiConverterUInt64.write(value.maxClientBalanceSat, into: &buf) + public static func write(_ value: BoltzPairInfo, into buf: inout [UInt8]) { + FfiConverterString.write(value.hash, into: &buf) + FfiConverterDouble.write(value.rate, into: &buf) + FfiConverterUInt64.write(value.minimalSat, into: &buf) + FfiConverterUInt64.write(value.maximalSat, into: &buf) + FfiConverterDouble.write(value.feePercentage, into: &buf) + FfiConverterUInt64.write(value.minerFeesSat, into: &buf) } } @@ -2440,35 +2684,350 @@ public struct FfiConverterTypeChannelLiquidityOptions: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeChannelLiquidityOptions_lift(_ buf: RustBuffer) throws -> ChannelLiquidityOptions { - return try FfiConverterTypeChannelLiquidityOptions.lift(buf) +public func FfiConverterTypeBoltzPairInfo_lift(_ buf: RustBuffer) throws -> BoltzPairInfo { + return try FfiConverterTypeBoltzPairInfo.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeChannelLiquidityOptions_lower(_ value: ChannelLiquidityOptions) -> RustBuffer { - return FfiConverterTypeChannelLiquidityOptions.lower(value) +public func FfiConverterTypeBoltzPairInfo_lower(_ value: BoltzPairInfo) -> RustBuffer { + return FfiConverterTypeBoltzPairInfo.lower(value) } -public struct ChannelLiquidityParams { - public var clientBalanceSat: UInt64 - public var existingChannelsTotalSat: UInt64 - public var minChannelSizeSat: UInt64 - public var maxChannelSizeSat: UInt64 - public var satsPerEur: UInt64 +/** + * A persisted swap and its current state, returned by the listing/query APIs. + */ +public struct BoltzSwap { + public var id: String + public var swapType: BoltzSwapType + public var status: BoltzSwapStatus + public var network: BoltzNetwork + /** + * Deterministic BIP85 index used to derive this swap's key and preimage + * from the wallet seed. The recovery handle: given the seed and this index + * (or by scanning indices), the swap's secrets can be reconstructed. + */ + public var swapIndex: UInt64 + /** + * For submarine swaps: the amount to lock onchain. For reverse swaps: the + * Lightning invoice amount. + */ + public var amountSat: UInt64 + /** + * For reverse swaps: the onchain amount that will be received. + */ + public var onchainAmountSat: UInt64? + /** + * Lightning invoice associated with the swap (the hold invoice for reverse + * swaps, the invoice Boltz pays for submarine swaps). + */ + public var invoice: String? + /** + * Onchain lockup address. + */ + public var lockupAddress: String? + /** + * The address funds are claimed to (reverse) or refunded to (submarine). + */ + public var onchainAddress: String? + public var timeoutBlockHeight: UInt64 + public var createdAt: UInt64 + /** + * Txid of the claim transaction once broadcast (reverse swaps). + */ + public var claimTxId: String? + /** + * Txid of the refund transaction once broadcast (submarine swaps). + */ + public var refundTxId: String? // Default memberwise initializers are never public by default, so we // declare one manually. - public init(clientBalanceSat: UInt64, existingChannelsTotalSat: UInt64, minChannelSizeSat: UInt64, maxChannelSizeSat: UInt64, satsPerEur: UInt64) { - self.clientBalanceSat = clientBalanceSat - self.existingChannelsTotalSat = existingChannelsTotalSat - self.minChannelSizeSat = minChannelSizeSat - self.maxChannelSizeSat = maxChannelSizeSat - self.satsPerEur = satsPerEur - } -} + public init(id: String, swapType: BoltzSwapType, status: BoltzSwapStatus, network: BoltzNetwork, + /** + * Deterministic BIP85 index used to derive this swap's key and preimage + * from the wallet seed. The recovery handle: given the seed and this index + * (or by scanning indices), the swap's secrets can be reconstructed. + */swapIndex: UInt64, + /** + * For submarine swaps: the amount to lock onchain. For reverse swaps: the + * Lightning invoice amount. + */amountSat: UInt64, + /** + * For reverse swaps: the onchain amount that will be received. + */onchainAmountSat: UInt64?, + /** + * Lightning invoice associated with the swap (the hold invoice for reverse + * swaps, the invoice Boltz pays for submarine swaps). + */invoice: String?, + /** + * Onchain lockup address. + */lockupAddress: String?, + /** + * The address funds are claimed to (reverse) or refunded to (submarine). + */onchainAddress: String?, timeoutBlockHeight: UInt64, createdAt: UInt64, + /** + * Txid of the claim transaction once broadcast (reverse swaps). + */claimTxId: String?, + /** + * Txid of the refund transaction once broadcast (submarine swaps). + */refundTxId: String?) { + self.id = id + self.swapType = swapType + self.status = status + self.network = network + self.swapIndex = swapIndex + self.amountSat = amountSat + self.onchainAmountSat = onchainAmountSat + self.invoice = invoice + self.lockupAddress = lockupAddress + self.onchainAddress = onchainAddress + self.timeoutBlockHeight = timeoutBlockHeight + self.createdAt = createdAt + self.claimTxId = claimTxId + self.refundTxId = refundTxId + } +} + +#if compiler(>=6) +extension BoltzSwap: Sendable {} +#endif + + +extension BoltzSwap: Equatable, Hashable { + public static func ==(lhs: BoltzSwap, rhs: BoltzSwap) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.swapType != rhs.swapType { + return false + } + if lhs.status != rhs.status { + return false + } + if lhs.network != rhs.network { + return false + } + if lhs.swapIndex != rhs.swapIndex { + return false + } + if lhs.amountSat != rhs.amountSat { + return false + } + if lhs.onchainAmountSat != rhs.onchainAmountSat { + return false + } + if lhs.invoice != rhs.invoice { + return false + } + if lhs.lockupAddress != rhs.lockupAddress { + return false + } + if lhs.onchainAddress != rhs.onchainAddress { + return false + } + if lhs.timeoutBlockHeight != rhs.timeoutBlockHeight { + return false + } + if lhs.createdAt != rhs.createdAt { + return false + } + if lhs.claimTxId != rhs.claimTxId { + return false + } + if lhs.refundTxId != rhs.refundTxId { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(swapType) + hasher.combine(status) + hasher.combine(network) + hasher.combine(swapIndex) + hasher.combine(amountSat) + hasher.combine(onchainAmountSat) + hasher.combine(invoice) + hasher.combine(lockupAddress) + hasher.combine(onchainAddress) + hasher.combine(timeoutBlockHeight) + hasher.combine(createdAt) + hasher.combine(claimTxId) + hasher.combine(refundTxId) + } +} + +extension BoltzSwap: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzSwap: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwap { + return + try BoltzSwap( + id: FfiConverterString.read(from: &buf), + swapType: FfiConverterTypeBoltzSwapType.read(from: &buf), + status: FfiConverterTypeBoltzSwapStatus.read(from: &buf), + network: FfiConverterTypeBoltzNetwork.read(from: &buf), + swapIndex: FfiConverterUInt64.read(from: &buf), + amountSat: FfiConverterUInt64.read(from: &buf), + onchainAmountSat: FfiConverterOptionUInt64.read(from: &buf), + invoice: FfiConverterOptionString.read(from: &buf), + lockupAddress: FfiConverterOptionString.read(from: &buf), + onchainAddress: FfiConverterOptionString.read(from: &buf), + timeoutBlockHeight: FfiConverterUInt64.read(from: &buf), + createdAt: FfiConverterUInt64.read(from: &buf), + claimTxId: FfiConverterOptionString.read(from: &buf), + refundTxId: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: BoltzSwap, into buf: inout [UInt8]) { + FfiConverterString.write(value.id, into: &buf) + FfiConverterTypeBoltzSwapType.write(value.swapType, into: &buf) + FfiConverterTypeBoltzSwapStatus.write(value.status, into: &buf) + FfiConverterTypeBoltzNetwork.write(value.network, into: &buf) + FfiConverterUInt64.write(value.swapIndex, into: &buf) + FfiConverterUInt64.write(value.amountSat, into: &buf) + FfiConverterOptionUInt64.write(value.onchainAmountSat, into: &buf) + FfiConverterOptionString.write(value.invoice, into: &buf) + FfiConverterOptionString.write(value.lockupAddress, into: &buf) + FfiConverterOptionString.write(value.onchainAddress, into: &buf) + FfiConverterUInt64.write(value.timeoutBlockHeight, into: &buf) + FfiConverterUInt64.write(value.createdAt, into: &buf) + FfiConverterOptionString.write(value.claimTxId, into: &buf) + FfiConverterOptionString.write(value.refundTxId, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwap_lift(_ buf: RustBuffer) throws -> BoltzSwap { + return try FfiConverterTypeBoltzSwap.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwap_lower(_ value: BoltzSwap) -> RustBuffer { + return FfiConverterTypeBoltzSwap.lower(value) +} + + +public struct ChannelLiquidityOptions { + public var defaultLspBalanceSat: UInt64 + public var minLspBalanceSat: UInt64 + public var maxLspBalanceSat: UInt64 + public var maxClientBalanceSat: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(defaultLspBalanceSat: UInt64, minLspBalanceSat: UInt64, maxLspBalanceSat: UInt64, maxClientBalanceSat: UInt64) { + self.defaultLspBalanceSat = defaultLspBalanceSat + self.minLspBalanceSat = minLspBalanceSat + self.maxLspBalanceSat = maxLspBalanceSat + self.maxClientBalanceSat = maxClientBalanceSat + } +} + +#if compiler(>=6) +extension ChannelLiquidityOptions: Sendable {} +#endif + + +extension ChannelLiquidityOptions: Equatable, Hashable { + public static func ==(lhs: ChannelLiquidityOptions, rhs: ChannelLiquidityOptions) -> Bool { + if lhs.defaultLspBalanceSat != rhs.defaultLspBalanceSat { + return false + } + if lhs.minLspBalanceSat != rhs.minLspBalanceSat { + return false + } + if lhs.maxLspBalanceSat != rhs.maxLspBalanceSat { + return false + } + if lhs.maxClientBalanceSat != rhs.maxClientBalanceSat { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(defaultLspBalanceSat) + hasher.combine(minLspBalanceSat) + hasher.combine(maxLspBalanceSat) + hasher.combine(maxClientBalanceSat) + } +} + +extension ChannelLiquidityOptions: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeChannelLiquidityOptions: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChannelLiquidityOptions { + return + try ChannelLiquidityOptions( + defaultLspBalanceSat: FfiConverterUInt64.read(from: &buf), + minLspBalanceSat: FfiConverterUInt64.read(from: &buf), + maxLspBalanceSat: FfiConverterUInt64.read(from: &buf), + maxClientBalanceSat: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: ChannelLiquidityOptions, into buf: inout [UInt8]) { + FfiConverterUInt64.write(value.defaultLspBalanceSat, into: &buf) + FfiConverterUInt64.write(value.minLspBalanceSat, into: &buf) + FfiConverterUInt64.write(value.maxLspBalanceSat, into: &buf) + FfiConverterUInt64.write(value.maxClientBalanceSat, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChannelLiquidityOptions_lift(_ buf: RustBuffer) throws -> ChannelLiquidityOptions { + return try FfiConverterTypeChannelLiquidityOptions.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChannelLiquidityOptions_lower(_ value: ChannelLiquidityOptions) -> RustBuffer { + return FfiConverterTypeChannelLiquidityOptions.lower(value) +} + + +public struct ChannelLiquidityParams { + public var clientBalanceSat: UInt64 + public var existingChannelsTotalSat: UInt64 + public var minChannelSizeSat: UInt64 + public var maxChannelSizeSat: UInt64 + public var satsPerEur: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(clientBalanceSat: UInt64, existingChannelsTotalSat: UInt64, minChannelSizeSat: UInt64, maxChannelSizeSat: UInt64, satsPerEur: UInt64) { + self.clientBalanceSat = clientBalanceSat + self.existingChannelsTotalSat = existingChannelsTotalSat + self.minChannelSizeSat = minChannelSizeSat + self.maxChannelSizeSat = maxChannelSizeSat + self.satsPerEur = satsPerEur + } +} #if compiler(>=6) extension ChannelLiquidityParams: Sendable {} @@ -8838,87 +9397,213 @@ public func FfiConverterTypePubkyProfileLink_lower(_ value: PubkyProfileLink) -> /** - * Result from querying a single Bitcoin address. + * Result of creating a reverse swap (Lightning -> onchain). + * + * The caller pays `invoice` from its Lightning node; once Boltz locks funds at + * `lockup_address`, the module claims them to the provided onchain address. */ -public struct SingleAddressInfoResult { - /** - * The queried address - */ - public var address: String +public struct ReverseSwapResponse { + public var id: String /** - * Total confirmed balance in satoshis + * Hold invoice the caller must pay via Lightning. */ - public var balance: UInt64 + public var invoice: String /** - * UTXOs for this address + * Address Boltz locks the onchain funds to. */ - public var utxos: [AccountUtxo] + public var lockupAddress: String /** - * Number of transactions involving this address + * Amount in satoshis that will be received onchain (after Boltz fees). */ - public var transfers: UInt32 + public var onchainAmountSat: UInt64 /** - * Current blockchain tip height + * Onchain timeout height for the swap. */ - public var blockHeight: UInt32 + public var timeoutBlockHeight: UInt64 // Default memberwise initializers are never public by default, so we // declare one manually. - public init( - /** - * The queried address - */address: String, + public init(id: String, /** - * Total confirmed balance in satoshis - */balance: UInt64, + * Hold invoice the caller must pay via Lightning. + */invoice: String, /** - * UTXOs for this address - */utxos: [AccountUtxo], + * Address Boltz locks the onchain funds to. + */lockupAddress: String, /** - * Number of transactions involving this address - */transfers: UInt32, + * Amount in satoshis that will be received onchain (after Boltz fees). + */onchainAmountSat: UInt64, /** - * Current blockchain tip height - */blockHeight: UInt32) { - self.address = address - self.balance = balance - self.utxos = utxos - self.transfers = transfers - self.blockHeight = blockHeight + * Onchain timeout height for the swap. + */timeoutBlockHeight: UInt64) { + self.id = id + self.invoice = invoice + self.lockupAddress = lockupAddress + self.onchainAmountSat = onchainAmountSat + self.timeoutBlockHeight = timeoutBlockHeight } } #if compiler(>=6) -extension SingleAddressInfoResult: Sendable {} +extension ReverseSwapResponse: Sendable {} #endif -extension SingleAddressInfoResult: Equatable, Hashable { - public static func ==(lhs: SingleAddressInfoResult, rhs: SingleAddressInfoResult) -> Bool { - if lhs.address != rhs.address { +extension ReverseSwapResponse: Equatable, Hashable { + public static func ==(lhs: ReverseSwapResponse, rhs: ReverseSwapResponse) -> Bool { + if lhs.id != rhs.id { return false } - if lhs.balance != rhs.balance { + if lhs.invoice != rhs.invoice { return false } - if lhs.utxos != rhs.utxos { + if lhs.lockupAddress != rhs.lockupAddress { return false } - if lhs.transfers != rhs.transfers { + if lhs.onchainAmountSat != rhs.onchainAmountSat { return false } - if lhs.blockHeight != rhs.blockHeight { + if lhs.timeoutBlockHeight != rhs.timeoutBlockHeight { 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) + hasher.combine(id) + hasher.combine(invoice) + hasher.combine(lockupAddress) + hasher.combine(onchainAmountSat) + hasher.combine(timeoutBlockHeight) + } +} + +extension ReverseSwapResponse: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeReverseSwapResponse: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ReverseSwapResponse { + return + try ReverseSwapResponse( + id: FfiConverterString.read(from: &buf), + invoice: FfiConverterString.read(from: &buf), + lockupAddress: FfiConverterString.read(from: &buf), + onchainAmountSat: FfiConverterUInt64.read(from: &buf), + timeoutBlockHeight: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: ReverseSwapResponse, into buf: inout [UInt8]) { + FfiConverterString.write(value.id, into: &buf) + FfiConverterString.write(value.invoice, into: &buf) + FfiConverterString.write(value.lockupAddress, into: &buf) + FfiConverterUInt64.write(value.onchainAmountSat, into: &buf) + FfiConverterUInt64.write(value.timeoutBlockHeight, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeReverseSwapResponse_lift(_ buf: RustBuffer) throws -> ReverseSwapResponse { + return try FfiConverterTypeReverseSwapResponse.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeReverseSwapResponse_lower(_ value: ReverseSwapResponse) -> RustBuffer { + return FfiConverterTypeReverseSwapResponse.lower(value) +} + + +/** + * 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) } } @@ -8966,6 +9651,146 @@ public func FfiConverterTypeSingleAddressInfoResult_lower(_ value: SingleAddress } +/** + * Result of creating a submarine swap (onchain -> Lightning). + * + * The caller funds `address` with `expected_amount_sat` from its onchain + * wallet; Boltz then pays the Lightning invoice supplied at creation. + */ +public struct SubmarineSwapResponse { + public var id: String + /** + * Onchain lockup address to send funds to. + */ + public var address: String + /** + * BIP21 URI for the lockup payment. + */ + public var bip21: String + /** + * Exact amount in satoshis the caller must send to `address`. + */ + public var expectedAmountSat: UInt64 + /** + * Whether Boltz will accept a zero-conf lockup. + */ + public var acceptZeroConf: Bool + /** + * Onchain timeout height after which a refund is possible. + */ + public var timeoutBlockHeight: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, + /** + * Onchain lockup address to send funds to. + */address: String, + /** + * BIP21 URI for the lockup payment. + */bip21: String, + /** + * Exact amount in satoshis the caller must send to `address`. + */expectedAmountSat: UInt64, + /** + * Whether Boltz will accept a zero-conf lockup. + */acceptZeroConf: Bool, + /** + * Onchain timeout height after which a refund is possible. + */timeoutBlockHeight: UInt64) { + self.id = id + self.address = address + self.bip21 = bip21 + self.expectedAmountSat = expectedAmountSat + self.acceptZeroConf = acceptZeroConf + self.timeoutBlockHeight = timeoutBlockHeight + } +} + +#if compiler(>=6) +extension SubmarineSwapResponse: Sendable {} +#endif + + +extension SubmarineSwapResponse: Equatable, Hashable { + public static func ==(lhs: SubmarineSwapResponse, rhs: SubmarineSwapResponse) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.address != rhs.address { + return false + } + if lhs.bip21 != rhs.bip21 { + return false + } + if lhs.expectedAmountSat != rhs.expectedAmountSat { + return false + } + if lhs.acceptZeroConf != rhs.acceptZeroConf { + return false + } + if lhs.timeoutBlockHeight != rhs.timeoutBlockHeight { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(address) + hasher.combine(bip21) + hasher.combine(expectedAmountSat) + hasher.combine(acceptZeroConf) + hasher.combine(timeoutBlockHeight) + } +} + +extension SubmarineSwapResponse: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSubmarineSwapResponse: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SubmarineSwapResponse { + return + try SubmarineSwapResponse( + id: FfiConverterString.read(from: &buf), + address: FfiConverterString.read(from: &buf), + bip21: FfiConverterString.read(from: &buf), + expectedAmountSat: FfiConverterUInt64.read(from: &buf), + acceptZeroConf: FfiConverterBool.read(from: &buf), + timeoutBlockHeight: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: SubmarineSwapResponse, into buf: inout [UInt8]) { + FfiConverterString.write(value.id, into: &buf) + FfiConverterString.write(value.address, into: &buf) + FfiConverterString.write(value.bip21, into: &buf) + FfiConverterUInt64.write(value.expectedAmountSat, into: &buf) + FfiConverterBool.write(value.acceptZeroConf, into: &buf) + FfiConverterUInt64.write(value.timeoutBlockHeight, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSubmarineSwapResponse_lift(_ buf: RustBuffer) throws -> SubmarineSwapResponse { + return try FfiConverterTypeSubmarineSwapResponse.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSubmarineSwapResponse_lower(_ value: SubmarineSwapResponse) -> RustBuffer { + return FfiConverterTypeSubmarineSwapResponse.lower(value) +} + + /** * A hardware-wallet model Bitkit supports, with the transports it can connect over. * @@ -14601,36 +15426,712 @@ public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { FfiConverterString.write(errorDetails, into: &buf) - case let .ConnectionError(errorDetails): - writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - + case let .ConnectionError(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SerializationError(errorDetails): + writeInt(&buf, Int32(9)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ChannelOpen(errorType,errorDetails): + writeInt(&buf, Int32(10)) + FfiConverterTypeBtChannelOrderErrorType.write(errorType, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .OrderState(errorDetails): + writeInt(&buf, Int32(11)) + FfiConverterString.write(errorDetails, into: &buf) + + + 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) + } +} + + + + + +/** + * Errors surfaced by the Boltz swaps module. + */ +public enum BoltzError: Swift.Error { + + + + case InitializationError(errorDetails: String + ) + case ConnectionError(errorDetails: String + ) + case DatabaseError(errorDetails: String + ) + case ApiError(errorDetails: String + ) + case SwapError(errorDetails: String + ) + case BroadcastError(errorDetails: String + ) + case InvalidInput(errorDetails: String + ) + case SerializationError(errorDetails: String + ) + case NotFound(errorDetails: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzError: FfiConverterRustBuffer { + typealias SwiftType = BoltzError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .InitializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .DatabaseError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .ApiError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .SwapError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .BroadcastError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .InvalidInput( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .SerializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .NotFound( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BoltzError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InitializationError(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ConnectionError(errorDetails): + writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DatabaseError(errorDetails): + writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ApiError(errorDetails): + writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SwapError(errorDetails): + writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .BroadcastError(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidInput(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SerializationError(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .NotFound(errorDetails): + writeInt(&buf, Int32(9)) + FfiConverterString.write(errorDetails, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzError_lift(_ buf: RustBuffer) throws -> BoltzError { + return try FfiConverterTypeBoltzError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzError_lower(_ value: BoltzError) -> RustBuffer { + return FfiConverterTypeBoltzError.lower(value) +} + + +extension BoltzError: Equatable, Hashable {} + +extension BoltzError: Codable {} + + + + +extension BoltzError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Bitcoin network selection for Boltz swaps. Maps to the networks Boltz + * operates on (mainnet, testnet, regtest). + */ + +public enum BoltzNetwork { + + case mainnet + case testnet + case regtest +} + + +#if compiler(>=6) +extension BoltzNetwork: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzNetwork: FfiConverterRustBuffer { + typealias SwiftType = BoltzNetwork + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzNetwork { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .mainnet + + case 2: return .testnet + + case 3: return .regtest + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BoltzNetwork, into buf: inout [UInt8]) { + switch value { + + + case .mainnet: + writeInt(&buf, Int32(1)) + + + case .testnet: + writeInt(&buf, Int32(2)) + + + case .regtest: + writeInt(&buf, Int32(3)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzNetwork_lift(_ buf: RustBuffer) throws -> BoltzNetwork { + return try FfiConverterTypeBoltzNetwork.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzNetwork_lower(_ value: BoltzNetwork) -> RustBuffer { + return FfiConverterTypeBoltzNetwork.lower(value) +} + + +extension BoltzNetwork: Equatable, Hashable {} + +extension BoltzNetwork: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] + * as swaps progress through their lifecycle. + */ + +public enum BoltzSwapEvent { + + /** + * The swap transitioned to a new status. + */ + case statusUpdate(swapId: String, status: BoltzSwapStatus + ) + /** + * A reverse swap was claimed onchain. `txid` is the claim transaction. + */ + case claimed(swapId: String, txid: String + ) + /** + * A submarine swap was refunded onchain. `txid` is the refund transaction. + */ + case refunded(swapId: String, txid: String + ) + /** + * An error occurred while processing the swap (e.g. an auto-claim failed). + */ + case error(swapId: String, message: String + ) +} + + +#if compiler(>=6) +extension BoltzSwapEvent: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzSwapEvent: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapEvent + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapEvent { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .statusUpdate(swapId: try FfiConverterString.read(from: &buf), status: try FfiConverterTypeBoltzSwapStatus.read(from: &buf) + ) + + case 2: return .claimed(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) + ) + + case 3: return .refunded(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) + ) + + case 4: return .error(swapId: try FfiConverterString.read(from: &buf), message: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BoltzSwapEvent, into buf: inout [UInt8]) { + switch value { + + + case let .statusUpdate(swapId,status): + writeInt(&buf, Int32(1)) + FfiConverterString.write(swapId, into: &buf) + FfiConverterTypeBoltzSwapStatus.write(status, into: &buf) + + + case let .claimed(swapId,txid): + writeInt(&buf, Int32(2)) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(txid, into: &buf) + + + case let .refunded(swapId,txid): + writeInt(&buf, Int32(3)) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(txid, into: &buf) + + + case let .error(swapId,message): + writeInt(&buf, Int32(4)) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(message, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwapEvent_lift(_ buf: RustBuffer) throws -> BoltzSwapEvent { + return try FfiConverterTypeBoltzSwapEvent.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwapEvent_lower(_ value: BoltzSwapEvent) -> RustBuffer { + return FfiConverterTypeBoltzSwapEvent.lower(value) +} + + +extension BoltzSwapEvent: Equatable, Hashable {} + +extension BoltzSwapEvent: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so + * new server-side states don't break the bindings. + * + * See . + */ + +public enum BoltzSwapStatus { + + /** + * `swap.created` — initial state. + */ + case swapCreated + /** + * `invoice.set` — invoice attached to a submarine swap. + */ + case invoiceSet + /** + * `transaction.mempool` — a lockup transaction is in the mempool. + */ + case transactionMempool + /** + * `transaction.confirmed` — a lockup transaction confirmed. + */ + case transactionConfirmed + /** + * `invoice.pending` — Boltz is paying the submarine swap invoice. + */ + case invoicePending + /** + * `invoice.paid` — submarine swap invoice paid by Boltz. + */ + case invoicePaid + /** + * `invoice.settled` — reverse swap invoice settled (preimage revealed). + */ + case invoiceSettled + /** + * `invoice.failedToPay` — submarine swap invoice could not be paid; refund. + */ + case invoiceFailedToPay + /** + * `invoice.expired` — reverse swap invoice expired before payment. + */ + case invoiceExpired + /** + * `transaction.claim.pending` — Boltz ready for a cooperative claim. + */ + case transactionClaimPending + /** + * `transaction.claimed` — onchain funds claimed. + */ + case transactionClaimed + /** + * `transaction.refunded` — onchain funds refunded. + */ + case transactionRefunded + /** + * `transaction.lockupFailed` — wrong amount locked; can refund. + */ + case transactionLockupFailed + /** + * `transaction.failed` — Boltz failed to lock the agreed funds. + */ + case transactionFailed + /** + * `swap.expired` — swap expired without completing. + */ + case swapExpired + /** + * Any status not yet modelled. `raw` holds the verbatim Boltz status. + */ + case unknown(raw: String + ) +} + + +#if compiler(>=6) +extension BoltzSwapStatus: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzSwapStatus: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapStatus + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapStatus { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .swapCreated + + case 2: return .invoiceSet + + case 3: return .transactionMempool + + case 4: return .transactionConfirmed + + case 5: return .invoicePending + + case 6: return .invoicePaid + + case 7: return .invoiceSettled + + case 8: return .invoiceFailedToPay + + case 9: return .invoiceExpired + + case 10: return .transactionClaimPending + + case 11: return .transactionClaimed + + case 12: return .transactionRefunded + + case 13: return .transactionLockupFailed + + case 14: return .transactionFailed + + case 15: return .swapExpired + + case 16: return .unknown(raw: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BoltzSwapStatus, into buf: inout [UInt8]) { + switch value { + + + case .swapCreated: + writeInt(&buf, Int32(1)) + + + case .invoiceSet: + writeInt(&buf, Int32(2)) + + + case .transactionMempool: + writeInt(&buf, Int32(3)) + + + case .transactionConfirmed: + writeInt(&buf, Int32(4)) + + + case .invoicePending: + writeInt(&buf, Int32(5)) + + + case .invoicePaid: + writeInt(&buf, Int32(6)) + + + case .invoiceSettled: + writeInt(&buf, Int32(7)) + + + case .invoiceFailedToPay: + writeInt(&buf, Int32(8)) + + + case .invoiceExpired: + writeInt(&buf, Int32(9)) + + + case .transactionClaimPending: + writeInt(&buf, Int32(10)) + + + case .transactionClaimed: + writeInt(&buf, Int32(11)) + + + case .transactionRefunded: + writeInt(&buf, Int32(12)) + + + case .transactionLockupFailed: + writeInt(&buf, Int32(13)) + + + case .transactionFailed: + writeInt(&buf, Int32(14)) + + + case .swapExpired: + writeInt(&buf, Int32(15)) + + + case let .unknown(raw): + writeInt(&buf, Int32(16)) + FfiConverterString.write(raw, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwapStatus_lift(_ buf: RustBuffer) throws -> BoltzSwapStatus { + return try FfiConverterTypeBoltzSwapStatus.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBoltzSwapStatus_lower(_ value: BoltzSwapStatus) -> RustBuffer { + return FfiConverterTypeBoltzSwapStatus.lower(value) +} + + +extension BoltzSwapStatus: Equatable, Hashable {} + +extension BoltzSwapStatus: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * The direction of a Boltz swap. + * + * - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, + * Boltz pays a Lightning invoice). + * - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold + * invoice, Boltz locks onchain funds the user then claims). + */ + +public enum BoltzSwapType { + + case submarine + case reverse +} + + +#if compiler(>=6) +extension BoltzSwapType: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBoltzSwapType: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapType { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .submarine + + case 2: return .reverse + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BoltzSwapType, into buf: inout [UInt8]) { + switch value { - case let .SerializationError(errorDetails): - writeInt(&buf, Int32(9)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .ChannelOpen(errorType,errorDetails): - writeInt(&buf, Int32(10)) - FfiConverterTypeBtChannelOrderErrorType.write(errorType, into: &buf) - FfiConverterString.write(errorDetails, into: &buf) - + case .submarine: + writeInt(&buf, Int32(1)) - case let .OrderState(errorDetails): - writeInt(&buf, Int32(11)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .InvalidParameter(errorDetails): - writeInt(&buf, Int32(12)) - FfiConverterString.write(errorDetails, into: &buf) - + case .reverse: + writeInt(&buf, Int32(2)) - case let .DatabaseError(errorDetails): - writeInt(&buf, Int32(13)) - FfiConverterString.write(errorDetails, into: &buf) - } } } @@ -14639,30 +16140,23 @@ 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 FfiConverterTypeBoltzSwapType_lift(_ buf: RustBuffer) throws -> BoltzSwapType { + return try FfiConverterTypeBoltzSwapType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBlocktankError_lower(_ value: BlocktankError) -> RustBuffer { - return FfiConverterTypeBlocktankError.lower(value) +public func FfiConverterTypeBoltzSwapType_lower(_ value: BoltzSwapType) -> RustBuffer { + return FfiConverterTypeBoltzSwapType.lower(value) } -extension BlocktankError: Equatable, Hashable {} - -extension BlocktankError: Codable {} - +extension BoltzSwapType: Equatable, Hashable {} +extension BoltzSwapType: Codable {} -extension BlocktankError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -15782,6 +17276,8 @@ public enum DbError: Swift.Error { ) case DbBlocktankError(errorDetails: BlocktankError ) + case DbBoltzError(errorDetails: BoltzError + ) case InitializationError(errorDetails: String ) } @@ -15806,7 +17302,10 @@ public struct FfiConverterTypeDbError: FfiConverterRustBuffer { case 2: return .DbBlocktankError( errorDetails: try FfiConverterTypeBlocktankError.read(from: &buf) ) - case 3: return .InitializationError( + case 3: return .DbBoltzError( + errorDetails: try FfiConverterTypeBoltzError.read(from: &buf) + ) + case 4: return .InitializationError( errorDetails: try FfiConverterString.read(from: &buf) ) @@ -15831,8 +17330,13 @@ public struct FfiConverterTypeDbError: FfiConverterRustBuffer { FfiConverterTypeBlocktankError.write(errorDetails, into: &buf) - case let .InitializationError(errorDetails): + case let .DbBoltzError(errorDetails): writeInt(&buf, Int32(3)) + FfiConverterTypeBoltzError.write(errorDetails, into: &buf) + + + case let .InitializationError(errorDetails): + writeInt(&buf, Int32(4)) FfiConverterString.write(errorDetails, into: &buf) } @@ -18559,6 +20063,30 @@ fileprivate struct FfiConverterOptionData: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeBoltzSwap: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwap? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeBoltzSwap.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 FfiConverterTypeBoltzSwap.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -19668,6 +21196,31 @@ fileprivate struct FfiConverterSequenceTypeAddressInfo: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeBoltzSwap: FfiConverterRustBuffer { + typealias SwiftType = [BoltzSwap] + + public static func write(_ value: [BoltzSwap], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeBoltzSwap.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [BoltzSwap] { + let len: Int32 = try readInt(&buf) + var seq = [BoltzSwap]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeBoltzSwap.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -20589,6 +22142,227 @@ public func blocktankWipeAll()async throws { errorHandler: FfiConverterTypeBlocktankError_lift ) } +/** + * Claim a reverse swap's onchain funds to its claim address, returning the + * broadcast claim transaction id. Normally happens automatically via the + * updates stream; exposed for manual recovery. The claim key is re-derived from + * `mnemonic`. Claims are serialized per swap, so calling this while the updates + * stream is auto-claiming the same swap waits for that claim and returns its + * txid rather than broadcasting a second transaction. + */ +public func boltzClaimReverseSwap(swapId: String, mnemonic: String, bip39Passphrase: String?, feeRateSatPerVb: Double?)async throws -> String { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap(FfiConverterString.lower(swapId),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(bip39Passphrase),FfiConverterOptionDouble.lower(feeRateSatPerVb) + ) + }, + 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: FfiConverterTypeBoltzError_lift + ) +} +/** + * Create a reverse swap (Lightning -> onchain). + * + * The caller pays the returned hold invoice from its Lightning node; + * `claim_address` is the onchain address the received funds are claimed to. + * The claim key and preimage are derived deterministically from `mnemonic` + * (only the derivation index is persisted, never the secrets) so the claim can + * be made automatically once Boltz locks the funds. `bip39_passphrase` must + * match the wallet's, or claims will derive the wrong key. + */ +public func boltzCreateReverseSwap(network: BoltzNetwork, electrumUrl: String, amountSat: UInt64, claimAddress: String, mnemonic: String, bip39Passphrase: String?)async throws -> ReverseSwapResponse { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_create_reverse_swap(FfiConverterTypeBoltzNetwork_lower(network),FfiConverterString.lower(electrumUrl),FfiConverterUInt64.lower(amountSat),FfiConverterString.lower(claimAddress),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(bip39Passphrase) + ) + }, + 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: FfiConverterTypeReverseSwapResponse_lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Create a submarine swap (onchain -> Lightning). + * + * `invoice` is a BOLT11 invoice the caller's Lightning node generated. The + * caller funds the returned lockup address from its onchain wallet. The refund + * key is derived deterministically from `mnemonic` (only the derivation index + * is persisted, never the key), and the swap is tracked if an updates stream is + * running. `bip39_passphrase` must match the wallet's, or refunds will derive + * the wrong key. + */ +public func boltzCreateSubmarineSwap(network: BoltzNetwork, electrumUrl: String, invoice: String, mnemonic: String, bip39Passphrase: String?)async throws -> SubmarineSwapResponse { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_create_submarine_swap(FfiConverterTypeBoltzNetwork_lower(network),FfiConverterString.lower(electrumUrl),FfiConverterString.lower(invoice),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(bip39Passphrase) + ) + }, + 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: FfiConverterTypeSubmarineSwapResponse_lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Fetch fees and limits for reverse swaps (Lightning -> onchain). + */ +public func boltzGetReverseLimits(network: BoltzNetwork)async throws -> BoltzPairInfo { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_get_reverse_limits(FfiConverterTypeBoltzNetwork_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: FfiConverterTypeBoltzPairInfo_lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Fetch fees and limits for submarine swaps (onchain -> Lightning). + */ +public func boltzGetSubmarineLimits(network: BoltzNetwork)async throws -> BoltzPairInfo { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_get_submarine_limits(FfiConverterTypeBoltzNetwork_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: FfiConverterTypeBoltzPairInfo_lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Fetch a single swap by id. + */ +public func boltzGetSwap(swapId: String)async throws -> BoltzSwap? { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_get_swap(FfiConverterString.lower(swapId) + ) + }, + 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: FfiConverterOptionTypeBoltzSwap.lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * List swaps that have not reached a terminal state (for recovery/resume). + */ +public func boltzListPendingSwaps()async throws -> [BoltzSwap] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_list_pending_swaps( + ) + }, + 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: FfiConverterSequenceTypeBoltzSwap.lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * List every persisted swap, newest first. + */ +public func boltzListSwaps()async throws -> [BoltzSwap] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_list_swaps( + ) + }, + 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: FfiConverterSequenceTypeBoltzSwap.lift, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Refund a submarine swap's locked funds to `refund_address`, returning the + * broadcast refund transaction id. Used when Boltz fails to pay the invoice or + * the swap expires. The refund key is re-derived from `mnemonic`. Refunds are + * serialized per swap, so two concurrent calls cannot both broadcast: the second + * waits for the first and returns its txid. + */ +public func boltzRefundSubmarineSwap(swapId: String, refundAddress: String, mnemonic: String, bip39Passphrase: String?, feeRateSatPerVb: Double?)async throws -> String { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap(FfiConverterString.lower(swapId),FfiConverterString.lower(refundAddress),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(bip39Passphrase),FfiConverterOptionDouble.lower(feeRateSatPerVb) + ) + }, + 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: FfiConverterTypeBoltzError_lift + ) +} +/** + * Open a Boltz WebSocket for `network`, subscribe to all pending swaps, and + * drive their lifecycle (auto-claiming reverse swaps) until stopped. Replaces + * any previously running updates stream (only one network is tracked at a + * time). `mnemonic` is held in memory for the lifetime of the stream so + * confirmed reverse swaps can be auto-claimed; it is never persisted. Events + * are delivered to `listener`. + * + * `fee_rate_sat_per_vb` is the fee rate used for automatic claim transactions. + * Bitkit owns fee estimation and should pass its current recommended rate; when + * `None`, a conservative built-in default is used. To auto-claim at an updated + * fee rate, call this again (it restarts the stream). + */ +public func boltzStartSwapUpdates(network: BoltzNetwork, listener: BoltzEventListener, mnemonic: String, bip39Passphrase: String?, feeRateSatPerVb: Double?)async throws { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_start_swap_updates(FfiConverterTypeBoltzNetwork_lower(network),FfiConverterTypeBoltzEventListener_lower(listener),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(bip39Passphrase),FfiConverterOptionDouble.lower(feeRateSatPerVb) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_void, + completeFunc: ffi_bitkitcore_rust_future_complete_void, + freeFunc: ffi_bitkitcore_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeBoltzError_lift + ) +} +/** + * Stop the running Boltz updates stream, if any. + */ +public func boltzStopSwapUpdates()async { + return + try! await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_boltz_stop_swap_updates( + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_void, + completeFunc: ffi_bitkitcore_rust_future_complete_void, + freeFunc: ffi_bitkitcore_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: nil + + ) +} public func broadcastSweepTransaction(psbt: String, mnemonicPhrase: String, network: Network?, bip39Passphrase: String?, electrumUrl: String)async throws -> SweepResult { return try await uniffiRustCallAsync( @@ -22454,6 +24228,39 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_blocktank_wipe_all() != 41797) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap() != 52516) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap() != 20570) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap() != 27933) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits() != 59203) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits() != 5900) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_get_swap() != 17473) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps() != 20926) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_list_swaps() != 16447) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap() != 24549) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_start_swap_updates() != 24371) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates() != 63683) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction() != 43422) { return InitializationResult.apiChecksumMismatch } @@ -22910,6 +24717,9 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_wipe_all_transaction_details() != 65339) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event() != 11294) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_method_eventlistener_on_event() != 35531) { return InitializationResult.apiChecksumMismatch } @@ -22953,6 +24763,7 @@ private let initializationResult: InitializationResult = { return InitializationResult.apiChecksumMismatch } + uniffiCallbackInitBoltzEventListener() uniffiCallbackInitEventListener() uniffiCallbackInitTrezorTransportCallback() uniffiCallbackInitTrezorUiCallback() diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index 608cd28..d81406d 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -250,6 +250,13 @@ typedef struct UniffiForeignFutureStructVoid { typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid ); +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 +typedef void (*UniffiCallbackInterfaceBoltzEventListenerMethod0)(uint64_t, RustBuffer, void* _Nonnull, + RustCallStatus *_Nonnull uniffiCallStatus + ); + #endif #ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 #define UNIFFI_FFIDEF_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 @@ -348,6 +355,14 @@ typedef void (*UniffiCallbackInterfaceTrezorUiCallbackMethod1)(uint64_t, int8_t, RustCallStatus *_Nonnull uniffiCallStatus ); +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER +typedef struct UniffiVTableCallbackInterfaceBoltzEventListener { + UniffiCallbackInterfaceBoltzEventListenerMethod0 _Nonnull onEvent; + UniffiCallbackInterfaceFree _Nonnull uniffiFree; +} UniffiVTableCallbackInterfaceBoltzEventListener; + #endif #ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER #define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_EVENT_LISTENER @@ -383,6 +398,26 @@ typedef struct UniffiVTableCallbackInterfaceTrezorUiCallback { UniffiCallbackInterfaceFree _Nonnull uniffiFree; } UniffiVTableCallbackInterfaceTrezorUiCallback; +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_BOLTZEVENTLISTENER +void*_Nonnull uniffi_bitkitcore_fn_clone_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_free_boltzeventlistener(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_INIT_CALLBACK_VTABLE_BOLTZEVENTLISTENER +void uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(const UniffiVTableCallbackInterfaceBoltzEventListener* _Nonnull vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_BOLTZEVENTLISTENER_ON_EVENT +void uniffi_bitkitcore_fn_method_boltzeventlistener_on_event(void*_Nonnull ptr, RustBuffer event, RustCallStatus *_Nonnull out_status +); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_EVENTLISTENER @@ -561,6 +596,64 @@ uint64_t uniffi_bitkitcore_fn_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BLOCKTANK_WIPE_ALL uint64_t uniffi_bitkitcore_fn_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap(RustBuffer swap_id, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_reverse_swap(RustBuffer network, RustBuffer electrum_url, uint64_t amount_sat, RustBuffer claim_address, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_create_submarine_swap(RustBuffer network, RustBuffer electrum_url, RustBuffer invoice, RustBuffer mnemonic, RustBuffer bip39_passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_reverse_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint64_t uniffi_bitkitcore_fn_func_boltz_get_submarine_limits(RustBuffer network +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_GET_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_get_swap(RustBuffer swap_id +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_LIST_SWAPS +uint64_t uniffi_bitkitcore_fn_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint64_t uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap(RustBuffer swap_id, RustBuffer refund_address, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_START_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_start_swap_updates(RustBuffer network, void*_Nonnull listener, RustBuffer mnemonic, RustBuffer bip39_passphrase, RustBuffer fee_rate_sat_per_vb +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint64_t uniffi_bitkitcore_fn_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -1699,6 +1792,72 @@ uint16_t uniffi_bitkitcore_checksum_func_blocktank_remove_all_orders(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BLOCKTANK_WIPE_ALL uint16_t uniffi_bitkitcore_checksum_func_blocktank_wipe_all(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CLAIM_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_REVERSE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_CREATE_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_REVERSE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SUBMARINE_LIMITS +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_GET_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_get_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_PENDING_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_LIST_SWAPS +uint16_t uniffi_bitkitcore_checksum_func_boltz_list_swaps(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_REFUND_SUBMARINE_SWAP +uint16_t uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_START_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_start_swap_updates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BOLTZ_STOP_SWAP_UPDATES +uint16_t uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_BROADCAST_SWEEP_TRANSACTION @@ -2611,6 +2770,12 @@ uint16_t uniffi_bitkitcore_checksum_func_wipe_all_databases(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WIPE_ALL_TRANSACTION_DETAILS uint16_t uniffi_bitkitcore_checksum_func_wipe_all_transaction_details(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_BOLTZEVENTLISTENER_ON_EVENT +uint16_t uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_EVENTLISTENER_ON_EVENT diff --git a/bindings/python/bitkitcore/bitkitcore.py b/bindings/python/bitkitcore/bitkitcore.py index fa24beb..d27c3a0 100644 --- a/bindings/python/bitkitcore/bitkitcore.py +++ b/bindings/python/bitkitcore/bitkitcore.py @@ -485,6 +485,28 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_blocktank_wipe_all() != 41797: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap() != 52516: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap() != 20570: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap() != 27933: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits() != 59203: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits() != 5900: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_get_swap() != 17473: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps() != 20926: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_list_swaps() != 16447: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap() != 24549: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_start_swap_updates() != 24371: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates() != 63683: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction() != 43422: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_calculate_channel_liquidity_options() != 51013: @@ -789,6 +811,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_wipe_all_transaction_details() != 65339: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event() != 11294: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_method_eventlistener_on_event() != 35531: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_method_trezortransportcallback_enumerate_devices() != 18766: @@ -923,6 +947,9 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): ] _UNIFFI_FOREIGN_FUTURE_COMPLETE_VOID = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructVoid, ) +_UNIFFI_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiRustBuffer,ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) _UNIFFI_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiRustBuffer,_UniffiRustBuffer,ctypes.c_void_p, ctypes.POINTER(_UniffiRustCallStatus), ) @@ -965,6 +992,11 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): _UNIFFI_CALLBACK_INTERFACE_TREZOR_UI_CALLBACK_METHOD1 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,ctypes.c_int8,ctypes.POINTER(_UniffiRustBuffer), ctypes.POINTER(_UniffiRustCallStatus), ) +class _UniffiVTableCallbackInterfaceBoltzEventListener(ctypes.Structure): + _fields_ = [ + ("on_event", _UNIFFI_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0), + ("uniffi_free", _UNIFFI_CALLBACK_INTERFACE_FREE), + ] class _UniffiVTableCallbackInterfaceEventListener(ctypes.Structure): _fields_ = [ ("on_event", _UNIFFI_CALLBACK_INTERFACE_EVENT_LISTENER_METHOD0), @@ -991,6 +1023,26 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): ("on_passphrase_request", _UNIFFI_CALLBACK_INTERFACE_TREZOR_UI_CALLBACK_METHOD1), ("uniffi_free", _UNIFFI_CALLBACK_INTERFACE_FREE), ] +_UniffiLib.uniffi_bitkitcore_fn_clone_boltzeventlistener.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_bitkitcore_fn_clone_boltzeventlistener.restype = ctypes.c_void_p +_UniffiLib.uniffi_bitkitcore_fn_free_boltzeventlistener.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_bitkitcore_fn_free_boltzeventlistener.restype = None +_UniffiLib.uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener.argtypes = ( + ctypes.POINTER(_UniffiVTableCallbackInterfaceBoltzEventListener), +) +_UniffiLib.uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener.restype = None +_UniffiLib.uniffi_bitkitcore_fn_method_boltzeventlistener_on_event.argtypes = ( + ctypes.c_void_p, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_bitkitcore_fn_method_boltzeventlistener_on_event.restype = None _UniffiLib.uniffi_bitkitcore_fn_clone_eventlistener.argtypes = ( ctypes.c_void_p, ctypes.POINTER(_UniffiRustCallStatus), @@ -1177,6 +1229,67 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_fn_func_blocktank_wipe_all.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_fn_func_blocktank_wipe_all.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_reverse_swap.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.c_uint64, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_reverse_swap.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_submarine_swap.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_submarine_swap.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_reverse_limits.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_reverse_limits.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_submarine_limits.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_submarine_limits.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_swap.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_swap.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_pending_swaps.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_pending_swaps.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_swaps.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_swaps.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_start_swap_updates.argtypes = ( + _UniffiRustBuffer, + ctypes.c_void_p, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_start_swap_updates.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_stop_swap_updates.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_fn_func_boltz_stop_swap_updates.restype = ctypes.c_uint64 _UniffiLib.uniffi_bitkitcore_fn_func_broadcast_sweep_transaction.argtypes = ( _UniffiRustBuffer, _UniffiRustBuffer, @@ -2280,6 +2393,39 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_blocktank_wipe_all.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_blocktank_wipe_all.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_claim_reverse_swap.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_create_reverse_swap.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_create_submarine_swap.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_reverse_limits.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_submarine_limits.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_swap.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_get_swap.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_list_pending_swaps.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_list_swaps.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_list_swaps.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_refund_submarine_swap.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_start_swap_updates.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_start_swap_updates.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_boltz_stop_swap_updates.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_broadcast_sweep_transaction.restype = ctypes.c_uint16 @@ -2736,6 +2882,9 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_wipe_all_transaction_details.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_wipe_all_transaction_details.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_method_boltzeventlistener_on_event.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_method_eventlistener_on_event.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_method_eventlistener_on_event.restype = ctypes.c_uint16 @@ -2969,6 +3118,8 @@ def write(value, buf): + + class AccountAddresses: """ Grouped address lists for an account. @@ -3347,6 +3498,258 @@ def write(value, buf): _UniffiConverterUInt32.write(value.transfers, buf) +class BoltzPairInfo: + """ + Fees and limits for a swap pair, used to size a swap and present costs. + """ + + hash: "str" + """ + Pair hash identifying the current terms (passed back to Boltz if needed). + """ + + rate: "float" + """ + Exchange rate of the pair. + """ + + minimal_sat: "int" + """ + Minimum swap amount in satoshis. + """ + + maximal_sat: "int" + """ + Maximum swap amount in satoshis. + """ + + fee_percentage: "float" + """ + Boltz service fee as a percentage of the swap amount. + """ + + miner_fees_sat: "int" + """ + Estimated absolute miner fees in satoshis. + """ + + def __init__(self, *, hash: "str", rate: "float", minimal_sat: "int", maximal_sat: "int", fee_percentage: "float", miner_fees_sat: "int"): + self.hash = hash + self.rate = rate + self.minimal_sat = minimal_sat + self.maximal_sat = maximal_sat + self.fee_percentage = fee_percentage + self.miner_fees_sat = miner_fees_sat + + def __str__(self): + return "BoltzPairInfo(hash={}, rate={}, minimal_sat={}, maximal_sat={}, fee_percentage={}, miner_fees_sat={})".format(self.hash, self.rate, self.minimal_sat, self.maximal_sat, self.fee_percentage, self.miner_fees_sat) + + def __eq__(self, other): + if self.hash != other.hash: + return False + if self.rate != other.rate: + return False + if self.minimal_sat != other.minimal_sat: + return False + if self.maximal_sat != other.maximal_sat: + return False + if self.fee_percentage != other.fee_percentage: + return False + if self.miner_fees_sat != other.miner_fees_sat: + return False + return True + +class _UniffiConverterTypeBoltzPairInfo(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return BoltzPairInfo( + hash=_UniffiConverterString.read(buf), + rate=_UniffiConverterDouble.read(buf), + minimal_sat=_UniffiConverterUInt64.read(buf), + maximal_sat=_UniffiConverterUInt64.read(buf), + fee_percentage=_UniffiConverterDouble.read(buf), + miner_fees_sat=_UniffiConverterUInt64.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.hash) + _UniffiConverterDouble.check_lower(value.rate) + _UniffiConverterUInt64.check_lower(value.minimal_sat) + _UniffiConverterUInt64.check_lower(value.maximal_sat) + _UniffiConverterDouble.check_lower(value.fee_percentage) + _UniffiConverterUInt64.check_lower(value.miner_fees_sat) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.hash, buf) + _UniffiConverterDouble.write(value.rate, buf) + _UniffiConverterUInt64.write(value.minimal_sat, buf) + _UniffiConverterUInt64.write(value.maximal_sat, buf) + _UniffiConverterDouble.write(value.fee_percentage, buf) + _UniffiConverterUInt64.write(value.miner_fees_sat, buf) + + +class BoltzSwap: + """ + A persisted swap and its current state, returned by the listing/query APIs. + """ + + id: "str" + swap_type: "BoltzSwapType" + status: "BoltzSwapStatus" + network: "BoltzNetwork" + swap_index: "int" + """ + Deterministic BIP85 index used to derive this swap's key and preimage + from the wallet seed. The recovery handle: given the seed and this index + (or by scanning indices), the swap's secrets can be reconstructed. + """ + + amount_sat: "int" + """ + For submarine swaps: the amount to lock onchain. For reverse swaps: the + Lightning invoice amount. + """ + + onchain_amount_sat: "typing.Optional[int]" + """ + For reverse swaps: the onchain amount that will be received. + """ + + invoice: "typing.Optional[str]" + """ + Lightning invoice associated with the swap (the hold invoice for reverse + swaps, the invoice Boltz pays for submarine swaps). + """ + + lockup_address: "typing.Optional[str]" + """ + Onchain lockup address. + """ + + onchain_address: "typing.Optional[str]" + """ + The address funds are claimed to (reverse) or refunded to (submarine). + """ + + timeout_block_height: "int" + created_at: "int" + claim_tx_id: "typing.Optional[str]" + """ + Txid of the claim transaction once broadcast (reverse swaps). + """ + + refund_tx_id: "typing.Optional[str]" + """ + Txid of the refund transaction once broadcast (submarine swaps). + """ + + def __init__(self, *, id: "str", swap_type: "BoltzSwapType", status: "BoltzSwapStatus", network: "BoltzNetwork", swap_index: "int", amount_sat: "int", onchain_amount_sat: "typing.Optional[int]", invoice: "typing.Optional[str]", lockup_address: "typing.Optional[str]", onchain_address: "typing.Optional[str]", timeout_block_height: "int", created_at: "int", claim_tx_id: "typing.Optional[str]", refund_tx_id: "typing.Optional[str]"): + self.id = id + self.swap_type = swap_type + self.status = status + self.network = network + self.swap_index = swap_index + self.amount_sat = amount_sat + self.onchain_amount_sat = onchain_amount_sat + self.invoice = invoice + self.lockup_address = lockup_address + self.onchain_address = onchain_address + self.timeout_block_height = timeout_block_height + self.created_at = created_at + self.claim_tx_id = claim_tx_id + self.refund_tx_id = refund_tx_id + + def __str__(self): + return "BoltzSwap(id={}, swap_type={}, status={}, network={}, swap_index={}, amount_sat={}, onchain_amount_sat={}, invoice={}, lockup_address={}, onchain_address={}, timeout_block_height={}, created_at={}, claim_tx_id={}, refund_tx_id={})".format(self.id, self.swap_type, self.status, self.network, self.swap_index, self.amount_sat, self.onchain_amount_sat, self.invoice, self.lockup_address, self.onchain_address, self.timeout_block_height, self.created_at, self.claim_tx_id, self.refund_tx_id) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.swap_type != other.swap_type: + return False + if self.status != other.status: + return False + if self.network != other.network: + return False + if self.swap_index != other.swap_index: + return False + if self.amount_sat != other.amount_sat: + return False + if self.onchain_amount_sat != other.onchain_amount_sat: + return False + if self.invoice != other.invoice: + return False + if self.lockup_address != other.lockup_address: + return False + if self.onchain_address != other.onchain_address: + return False + if self.timeout_block_height != other.timeout_block_height: + return False + if self.created_at != other.created_at: + return False + if self.claim_tx_id != other.claim_tx_id: + return False + if self.refund_tx_id != other.refund_tx_id: + return False + return True + +class _UniffiConverterTypeBoltzSwap(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return BoltzSwap( + id=_UniffiConverterString.read(buf), + swap_type=_UniffiConverterTypeBoltzSwapType.read(buf), + status=_UniffiConverterTypeBoltzSwapStatus.read(buf), + network=_UniffiConverterTypeBoltzNetwork.read(buf), + swap_index=_UniffiConverterUInt64.read(buf), + amount_sat=_UniffiConverterUInt64.read(buf), + onchain_amount_sat=_UniffiConverterOptionalUInt64.read(buf), + invoice=_UniffiConverterOptionalString.read(buf), + lockup_address=_UniffiConverterOptionalString.read(buf), + onchain_address=_UniffiConverterOptionalString.read(buf), + timeout_block_height=_UniffiConverterUInt64.read(buf), + created_at=_UniffiConverterUInt64.read(buf), + claim_tx_id=_UniffiConverterOptionalString.read(buf), + refund_tx_id=_UniffiConverterOptionalString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterTypeBoltzSwapType.check_lower(value.swap_type) + _UniffiConverterTypeBoltzSwapStatus.check_lower(value.status) + _UniffiConverterTypeBoltzNetwork.check_lower(value.network) + _UniffiConverterUInt64.check_lower(value.swap_index) + _UniffiConverterUInt64.check_lower(value.amount_sat) + _UniffiConverterOptionalUInt64.check_lower(value.onchain_amount_sat) + _UniffiConverterOptionalString.check_lower(value.invoice) + _UniffiConverterOptionalString.check_lower(value.lockup_address) + _UniffiConverterOptionalString.check_lower(value.onchain_address) + _UniffiConverterUInt64.check_lower(value.timeout_block_height) + _UniffiConverterUInt64.check_lower(value.created_at) + _UniffiConverterOptionalString.check_lower(value.claim_tx_id) + _UniffiConverterOptionalString.check_lower(value.refund_tx_id) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterTypeBoltzSwapType.write(value.swap_type, buf) + _UniffiConverterTypeBoltzSwapStatus.write(value.status, buf) + _UniffiConverterTypeBoltzNetwork.write(value.network, buf) + _UniffiConverterUInt64.write(value.swap_index, buf) + _UniffiConverterUInt64.write(value.amount_sat, buf) + _UniffiConverterOptionalUInt64.write(value.onchain_amount_sat, buf) + _UniffiConverterOptionalString.write(value.invoice, buf) + _UniffiConverterOptionalString.write(value.lockup_address, buf) + _UniffiConverterOptionalString.write(value.onchain_address, buf) + _UniffiConverterUInt64.write(value.timeout_block_height, buf) + _UniffiConverterUInt64.write(value.created_at, buf) + _UniffiConverterOptionalString.write(value.claim_tx_id, buf) + _UniffiConverterOptionalString.write(value.refund_tx_id, buf) + + class ChannelLiquidityOptions: default_lsp_balance_sat: "int" min_lsp_balance_sat: "int" @@ -7451,74 +7854,154 @@ def write(value, buf): _UniffiConverterString.write(value.url, buf) -class SingleAddressInfoResult: - """ - Result from querying a single Bitcoin address. +class ReverseSwapResponse: """ + Result of creating a reverse swap (Lightning -> onchain). - address: "str" - """ - The queried address + The caller pays `invoice` from its Lightning node; once Boltz locks funds at + `lockup_address`, the module claims them to the provided onchain address. """ - balance: "int" + id: "str" + invoice: "str" """ - Total confirmed balance in satoshis + Hold invoice the caller must pay via Lightning. """ - utxos: "typing.List[AccountUtxo]" + lockup_address: "str" """ - UTXOs for this address + Address Boltz locks the onchain funds to. """ - transfers: "int" + onchain_amount_sat: "int" """ - Number of transactions involving this address + Amount in satoshis that will be received onchain (after Boltz fees). """ - block_height: "int" + timeout_block_height: "int" """ - Current blockchain tip height + Onchain timeout height for the swap. """ - 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 __init__(self, *, id: "str", invoice: "str", lockup_address: "str", onchain_amount_sat: "int", timeout_block_height: "int"): + self.id = id + self.invoice = invoice + self.lockup_address = lockup_address + self.onchain_amount_sat = onchain_amount_sat + self.timeout_block_height = timeout_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) + return "ReverseSwapResponse(id={}, invoice={}, lockup_address={}, onchain_amount_sat={}, timeout_block_height={})".format(self.id, self.invoice, self.lockup_address, self.onchain_amount_sat, self.timeout_block_height) def __eq__(self, other): - if self.address != other.address: + if self.id != other.id: return False - if self.balance != other.balance: + if self.invoice != other.invoice: return False - if self.utxos != other.utxos: + if self.lockup_address != other.lockup_address: return False - if self.transfers != other.transfers: + if self.onchain_amount_sat != other.onchain_amount_sat: return False - if self.block_height != other.block_height: + if self.timeout_block_height != other.timeout_block_height: return False return True -class _UniffiConverterTypeSingleAddressInfoResult(_UniffiConverterRustBuffer): +class _UniffiConverterTypeReverseSwapResponse(_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), + return ReverseSwapResponse( + id=_UniffiConverterString.read(buf), + invoice=_UniffiConverterString.read(buf), + lockup_address=_UniffiConverterString.read(buf), + onchain_amount_sat=_UniffiConverterUInt64.read(buf), + timeout_block_height=_UniffiConverterUInt64.read(buf), ) @staticmethod def check_lower(value): - _UniffiConverterString.check_lower(value.address) - _UniffiConverterUInt64.check_lower(value.balance) + _UniffiConverterString.check_lower(value.id) + _UniffiConverterString.check_lower(value.invoice) + _UniffiConverterString.check_lower(value.lockup_address) + _UniffiConverterUInt64.check_lower(value.onchain_amount_sat) + _UniffiConverterUInt64.check_lower(value.timeout_block_height) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterString.write(value.invoice, buf) + _UniffiConverterString.write(value.lockup_address, buf) + _UniffiConverterUInt64.write(value.onchain_amount_sat, buf) + _UniffiConverterUInt64.write(value.timeout_block_height, buf) + + +class SingleAddressInfoResult: + """ + Result from querying a single Bitcoin address. + """ + + 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) @@ -7532,6 +8015,97 @@ def write(value, buf): _UniffiConverterUInt32.write(value.block_height, buf) +class SubmarineSwapResponse: + """ + Result of creating a submarine swap (onchain -> Lightning). + + The caller funds `address` with `expected_amount_sat` from its onchain + wallet; Boltz then pays the Lightning invoice supplied at creation. + """ + + id: "str" + address: "str" + """ + Onchain lockup address to send funds to. + """ + + bip21: "str" + """ + BIP21 URI for the lockup payment. + """ + + expected_amount_sat: "int" + """ + Exact amount in satoshis the caller must send to `address`. + """ + + accept_zero_conf: "bool" + """ + Whether Boltz will accept a zero-conf lockup. + """ + + timeout_block_height: "int" + """ + Onchain timeout height after which a refund is possible. + """ + + def __init__(self, *, id: "str", address: "str", bip21: "str", expected_amount_sat: "int", accept_zero_conf: "bool", timeout_block_height: "int"): + self.id = id + self.address = address + self.bip21 = bip21 + self.expected_amount_sat = expected_amount_sat + self.accept_zero_conf = accept_zero_conf + self.timeout_block_height = timeout_block_height + + def __str__(self): + return "SubmarineSwapResponse(id={}, address={}, bip21={}, expected_amount_sat={}, accept_zero_conf={}, timeout_block_height={})".format(self.id, self.address, self.bip21, self.expected_amount_sat, self.accept_zero_conf, self.timeout_block_height) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.address != other.address: + return False + if self.bip21 != other.bip21: + return False + if self.expected_amount_sat != other.expected_amount_sat: + return False + if self.accept_zero_conf != other.accept_zero_conf: + return False + if self.timeout_block_height != other.timeout_block_height: + return False + return True + +class _UniffiConverterTypeSubmarineSwapResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return SubmarineSwapResponse( + id=_UniffiConverterString.read(buf), + address=_UniffiConverterString.read(buf), + bip21=_UniffiConverterString.read(buf), + expected_amount_sat=_UniffiConverterUInt64.read(buf), + accept_zero_conf=_UniffiConverterBool.read(buf), + timeout_block_height=_UniffiConverterUInt64.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterString.check_lower(value.address) + _UniffiConverterString.check_lower(value.bip21) + _UniffiConverterUInt64.check_lower(value.expected_amount_sat) + _UniffiConverterBool.check_lower(value.accept_zero_conf) + _UniffiConverterUInt64.check_lower(value.timeout_block_height) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterString.write(value.address, buf) + _UniffiConverterString.write(value.bip21, buf) + _UniffiConverterUInt64.write(value.expected_amount_sat, buf) + _UniffiConverterBool.write(value.accept_zero_conf, buf) + _UniffiConverterUInt64.write(value.timeout_block_height, buf) + + class SupportedHardwareWallet: """ A hardware-wallet model Bitkit supports, with the transports it can connect over. @@ -11639,19 +12213,27 @@ def write(value, buf): _UniffiConverterString.write(value.error_details, buf) -# BroadcastError +# BoltzError # 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): +class BoltzError(Exception): + """ + Errors surfaced by the Boltz swaps module. + """ + pass -_UniffiTempBroadcastError = BroadcastError +_UniffiTempBoltzError = BoltzError -class BroadcastError: # type: ignore - class InvalidHex(_UniffiTempBroadcastError): +class BoltzError: # type: ignore + """ + Errors surfaced by the Boltz swaps module. + """ + + class InitializationError(_UniffiTempBoltzError): def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -11659,9 +12241,9 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "BroadcastError.InvalidHex({})".format(str(self)) - _UniffiTempBroadcastError.InvalidHex = InvalidHex # type: ignore - class InvalidTransaction(_UniffiTempBroadcastError): + return "BoltzError.InitializationError({})".format(str(self)) + _UniffiTempBoltzError.InitializationError = InitializationError # type: ignore + class ConnectionError(_UniffiTempBoltzError): def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -11669,9 +12251,9 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "BroadcastError.InvalidTransaction({})".format(str(self)) - _UniffiTempBroadcastError.InvalidTransaction = InvalidTransaction # type: ignore - class ElectrumError(_UniffiTempBroadcastError): + return "BoltzError.ConnectionError({})".format(str(self)) + _UniffiTempBoltzError.ConnectionError = ConnectionError # type: ignore + class DatabaseError(_UniffiTempBoltzError): def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -11679,9 +12261,9 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "BroadcastError.ElectrumError({})".format(str(self)) - _UniffiTempBroadcastError.ElectrumError = ElectrumError # type: ignore - class TaskError(_UniffiTempBroadcastError): + return "BoltzError.DatabaseError({})".format(str(self)) + _UniffiTempBoltzError.DatabaseError = DatabaseError # type: ignore + class ApiError(_UniffiTempBoltzError): def __init__(self, error_details): super().__init__(", ".join([ "error_details={!r}".format(error_details), @@ -11689,90 +12271,1122 @@ def __init__(self, error_details): self.error_details = error_details def __repr__(self): - return "BroadcastError.TaskError({})".format(str(self)) - _UniffiTempBroadcastError.TaskError = TaskError # type: ignore + return "BoltzError.ApiError({})".format(str(self)) + _UniffiTempBoltzError.ApiError = ApiError # type: ignore + class SwapError(_UniffiTempBoltzError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details -BroadcastError = _UniffiTempBroadcastError # type: ignore -del _UniffiTempBroadcastError + def __repr__(self): + return "BoltzError.SwapError({})".format(str(self)) + _UniffiTempBoltzError.SwapError = SwapError # type: ignore + class BroadcastError(_UniffiTempBoltzError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + def __repr__(self): + return "BoltzError.BroadcastError({})".format(str(self)) + _UniffiTempBoltzError.BroadcastError = BroadcastError # type: ignore + class InvalidInput(_UniffiTempBoltzError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details -class _UniffiConverterTypeBroadcastError(_UniffiConverterRustBuffer): + def __repr__(self): + return "BoltzError.InvalidInput({})".format(str(self)) + _UniffiTempBoltzError.InvalidInput = InvalidInput # type: ignore + class SerializationError(_UniffiTempBoltzError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BoltzError.SerializationError({})".format(str(self)) + _UniffiTempBoltzError.SerializationError = SerializationError # type: ignore + class NotFound(_UniffiTempBoltzError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "BoltzError.NotFound({})".format(str(self)) + _UniffiTempBoltzError.NotFound = NotFound # type: ignore + +BoltzError = _UniffiTempBoltzError # type: ignore +del _UniffiTempBoltzError + + +class _UniffiConverterTypeBoltzError(_UniffiConverterRustBuffer): @staticmethod def read(buf): variant = buf.read_i32() if variant == 1: - return BroadcastError.InvalidHex( + return BoltzError.InitializationError( _UniffiConverterString.read(buf), ) if variant == 2: - return BroadcastError.InvalidTransaction( + return BoltzError.ConnectionError( _UniffiConverterString.read(buf), ) if variant == 3: - return BroadcastError.ElectrumError( + return BoltzError.DatabaseError( _UniffiConverterString.read(buf), ) if variant == 4: - return BroadcastError.TaskError( + return BoltzError.ApiError( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return BoltzError.SwapError( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return BoltzError.BroadcastError( + _UniffiConverterString.read(buf), + ) + if variant == 7: + return BoltzError.InvalidInput( + _UniffiConverterString.read(buf), + ) + if variant == 8: + return BoltzError.SerializationError( + _UniffiConverterString.read(buf), + ) + if variant == 9: + return BoltzError.NotFound( _UniffiConverterString.read(buf), ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod def check_lower(value): - if isinstance(value, BroadcastError.InvalidHex): + if isinstance(value, BoltzError.InitializationError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, BroadcastError.InvalidTransaction): + if isinstance(value, BoltzError.ConnectionError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, BroadcastError.ElectrumError): + if isinstance(value, BoltzError.DatabaseError): _UniffiConverterString.check_lower(value.error_details) return - if isinstance(value, BroadcastError.TaskError): + if isinstance(value, BoltzError.ApiError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BoltzError.SwapError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BoltzError.BroadcastError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BoltzError.InvalidInput): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BoltzError.SerializationError): + _UniffiConverterString.check_lower(value.error_details) + return + if isinstance(value, BoltzError.NotFound): _UniffiConverterString.check_lower(value.error_details) return @staticmethod def write(value, buf): - if isinstance(value, BroadcastError.InvalidHex): + if isinstance(value, BoltzError.InitializationError): buf.write_i32(1) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BroadcastError.InvalidTransaction): + if isinstance(value, BoltzError.ConnectionError): buf.write_i32(2) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BroadcastError.ElectrumError): + if isinstance(value, BoltzError.DatabaseError): buf.write_i32(3) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, BroadcastError.TaskError): + if isinstance(value, BoltzError.ApiError): buf.write_i32(4) _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BoltzError.SwapError): + buf.write_i32(5) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BoltzError.BroadcastError): + buf.write_i32(6) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BoltzError.InvalidInput): + buf.write_i32(7) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BoltzError.SerializationError): + buf.write_i32(8) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BoltzError.NotFound): + buf.write_i32(9) + _UniffiConverterString.write(value.error_details, buf) -class BtBolt11InvoiceState(enum.Enum): - PENDING = 0 - - HOLDING = 1 +class BoltzNetwork(enum.Enum): + """ + Bitcoin network selection for Boltz swaps. Maps to the networks Boltz + operates on (mainnet, testnet, regtest). + """ + + MAINNET = 0 - PAID = 2 + TESTNET = 1 - CANCELED = 3 + REGTEST = 2 -class _UniffiConverterTypeBtBolt11InvoiceState(_UniffiConverterRustBuffer): +class _UniffiConverterTypeBoltzNetwork(_UniffiConverterRustBuffer): @staticmethod def read(buf): variant = buf.read_i32() if variant == 1: - return BtBolt11InvoiceState.PENDING + return BoltzNetwork.MAINNET if variant == 2: - return BtBolt11InvoiceState.HOLDING + return BoltzNetwork.TESTNET if variant == 3: - return BtBolt11InvoiceState.PAID + return BoltzNetwork.REGTEST + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == BoltzNetwork.MAINNET: + return + if value == BoltzNetwork.TESTNET: + return + if value == BoltzNetwork.REGTEST: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == BoltzNetwork.MAINNET: + buf.write_i32(1) + if value == BoltzNetwork.TESTNET: + buf.write_i32(2) + if value == BoltzNetwork.REGTEST: + buf.write_i32(3) + + + + + + + +class BoltzSwapEvent: + """ + Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] + as swaps progress through their lifecycle. + """ + + def __init__(self): + raise RuntimeError("BoltzSwapEvent cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class STATUS_UPDATE: + """ + The swap transitioned to a new status. + """ + + swap_id: "str" + status: "BoltzSwapStatus" + + def __init__(self,swap_id: "str", status: "BoltzSwapStatus"): + self.swap_id = swap_id + self.status = status + + def __str__(self): + return "BoltzSwapEvent.STATUS_UPDATE(swap_id={}, status={})".format(self.swap_id, self.status) + + def __eq__(self, other): + if not other.is_STATUS_UPDATE(): + return False + if self.swap_id != other.swap_id: + return False + if self.status != other.status: + return False + return True + + class CLAIMED: + """ + A reverse swap was claimed onchain. `txid` is the claim transaction. + """ + + swap_id: "str" + txid: "str" + + def __init__(self,swap_id: "str", txid: "str"): + self.swap_id = swap_id + self.txid = txid + + def __str__(self): + return "BoltzSwapEvent.CLAIMED(swap_id={}, txid={})".format(self.swap_id, self.txid) + + def __eq__(self, other): + if not other.is_CLAIMED(): + return False + if self.swap_id != other.swap_id: + return False + if self.txid != other.txid: + return False + return True + + class REFUNDED: + """ + A submarine swap was refunded onchain. `txid` is the refund transaction. + """ + + swap_id: "str" + txid: "str" + + def __init__(self,swap_id: "str", txid: "str"): + self.swap_id = swap_id + self.txid = txid + + def __str__(self): + return "BoltzSwapEvent.REFUNDED(swap_id={}, txid={})".format(self.swap_id, self.txid) + + def __eq__(self, other): + if not other.is_REFUNDED(): + return False + if self.swap_id != other.swap_id: + return False + if self.txid != other.txid: + return False + return True + + class ERROR: + """ + An error occurred while processing the swap (e.g. an auto-claim failed). + """ + + swap_id: "str" + message: "str" + + def __init__(self,swap_id: "str", message: "str"): + self.swap_id = swap_id + self.message = message + + def __str__(self): + return "BoltzSwapEvent.ERROR(swap_id={}, message={})".format(self.swap_id, self.message) + + def __eq__(self, other): + if not other.is_ERROR(): + return False + if self.swap_id != other.swap_id: + return False + if self.message != other.message: + 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_STATUS_UPDATE(self) -> bool: + return isinstance(self, BoltzSwapEvent.STATUS_UPDATE) + def is_status_update(self) -> bool: + return isinstance(self, BoltzSwapEvent.STATUS_UPDATE) + def is_CLAIMED(self) -> bool: + return isinstance(self, BoltzSwapEvent.CLAIMED) + def is_claimed(self) -> bool: + return isinstance(self, BoltzSwapEvent.CLAIMED) + def is_REFUNDED(self) -> bool: + return isinstance(self, BoltzSwapEvent.REFUNDED) + def is_refunded(self) -> bool: + return isinstance(self, BoltzSwapEvent.REFUNDED) + def is_ERROR(self) -> bool: + return isinstance(self, BoltzSwapEvent.ERROR) + def is_error(self) -> bool: + return isinstance(self, BoltzSwapEvent.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. +BoltzSwapEvent.STATUS_UPDATE = type("BoltzSwapEvent.STATUS_UPDATE", (BoltzSwapEvent.STATUS_UPDATE, BoltzSwapEvent,), {}) # type: ignore +BoltzSwapEvent.CLAIMED = type("BoltzSwapEvent.CLAIMED", (BoltzSwapEvent.CLAIMED, BoltzSwapEvent,), {}) # type: ignore +BoltzSwapEvent.REFUNDED = type("BoltzSwapEvent.REFUNDED", (BoltzSwapEvent.REFUNDED, BoltzSwapEvent,), {}) # type: ignore +BoltzSwapEvent.ERROR = type("BoltzSwapEvent.ERROR", (BoltzSwapEvent.ERROR, BoltzSwapEvent,), {}) # type: ignore + + + + +class _UniffiConverterTypeBoltzSwapEvent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return BoltzSwapEvent.STATUS_UPDATE( + _UniffiConverterString.read(buf), + _UniffiConverterTypeBoltzSwapStatus.read(buf), + ) + if variant == 2: + return BoltzSwapEvent.CLAIMED( + _UniffiConverterString.read(buf), + _UniffiConverterString.read(buf), + ) + if variant == 3: + return BoltzSwapEvent.REFUNDED( + _UniffiConverterString.read(buf), + _UniffiConverterString.read(buf), + ) + if variant == 4: + return BoltzSwapEvent.ERROR( + _UniffiConverterString.read(buf), + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_STATUS_UPDATE(): + _UniffiConverterString.check_lower(value.swap_id) + _UniffiConverterTypeBoltzSwapStatus.check_lower(value.status) + return + if value.is_CLAIMED(): + _UniffiConverterString.check_lower(value.swap_id) + _UniffiConverterString.check_lower(value.txid) + return + if value.is_REFUNDED(): + _UniffiConverterString.check_lower(value.swap_id) + _UniffiConverterString.check_lower(value.txid) + return + if value.is_ERROR(): + _UniffiConverterString.check_lower(value.swap_id) + _UniffiConverterString.check_lower(value.message) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_STATUS_UPDATE(): + buf.write_i32(1) + _UniffiConverterString.write(value.swap_id, buf) + _UniffiConverterTypeBoltzSwapStatus.write(value.status, buf) + if value.is_CLAIMED(): + buf.write_i32(2) + _UniffiConverterString.write(value.swap_id, buf) + _UniffiConverterString.write(value.txid, buf) + if value.is_REFUNDED(): + buf.write_i32(3) + _UniffiConverterString.write(value.swap_id, buf) + _UniffiConverterString.write(value.txid, buf) + if value.is_ERROR(): + buf.write_i32(4) + _UniffiConverterString.write(value.swap_id, buf) + _UniffiConverterString.write(value.message, buf) + + + + + + + +class BoltzSwapStatus: + """ + Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so + new server-side states don't break the bindings. + + See . + """ + + def __init__(self): + raise RuntimeError("BoltzSwapStatus cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class SWAP_CREATED: + """ + `swap.created` — initial state. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.SWAP_CREATED()".format() + + def __eq__(self, other): + if not other.is_SWAP_CREATED(): + return False + return True + + class INVOICE_SET: + """ + `invoice.set` — invoice attached to a submarine swap. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_SET()".format() + + def __eq__(self, other): + if not other.is_INVOICE_SET(): + return False + return True + + class TRANSACTION_MEMPOOL: + """ + `transaction.mempool` — a lockup transaction is in the mempool. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_MEMPOOL()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_MEMPOOL(): + return False + return True + + class TRANSACTION_CONFIRMED: + """ + `transaction.confirmed` — a lockup transaction confirmed. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_CONFIRMED()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_CONFIRMED(): + return False + return True + + class INVOICE_PENDING: + """ + `invoice.pending` — Boltz is paying the submarine swap invoice. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_PENDING()".format() + + def __eq__(self, other): + if not other.is_INVOICE_PENDING(): + return False + return True + + class INVOICE_PAID: + """ + `invoice.paid` — submarine swap invoice paid by Boltz. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_PAID()".format() + + def __eq__(self, other): + if not other.is_INVOICE_PAID(): + return False + return True + + class INVOICE_SETTLED: + """ + `invoice.settled` — reverse swap invoice settled (preimage revealed). + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_SETTLED()".format() + + def __eq__(self, other): + if not other.is_INVOICE_SETTLED(): + return False + return True + + class INVOICE_FAILED_TO_PAY: + """ + `invoice.failedToPay` — submarine swap invoice could not be paid; refund. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_FAILED_TO_PAY()".format() + + def __eq__(self, other): + if not other.is_INVOICE_FAILED_TO_PAY(): + return False + return True + + class INVOICE_EXPIRED: + """ + `invoice.expired` — reverse swap invoice expired before payment. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.INVOICE_EXPIRED()".format() + + def __eq__(self, other): + if not other.is_INVOICE_EXPIRED(): + return False + return True + + class TRANSACTION_CLAIM_PENDING: + """ + `transaction.claim.pending` — Boltz ready for a cooperative claim. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_CLAIM_PENDING()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_CLAIM_PENDING(): + return False + return True + + class TRANSACTION_CLAIMED: + """ + `transaction.claimed` — onchain funds claimed. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_CLAIMED()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_CLAIMED(): + return False + return True + + class TRANSACTION_REFUNDED: + """ + `transaction.refunded` — onchain funds refunded. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_REFUNDED()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_REFUNDED(): + return False + return True + + class TRANSACTION_LOCKUP_FAILED: + """ + `transaction.lockupFailed` — wrong amount locked; can refund. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_LOCKUP_FAILED(): + return False + return True + + class TRANSACTION_FAILED: + """ + `transaction.failed` — Boltz failed to lock the agreed funds. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.TRANSACTION_FAILED()".format() + + def __eq__(self, other): + if not other.is_TRANSACTION_FAILED(): + return False + return True + + class SWAP_EXPIRED: + """ + `swap.expired` — swap expired without completing. + """ + + + def __init__(self,): + pass + + def __str__(self): + return "BoltzSwapStatus.SWAP_EXPIRED()".format() + + def __eq__(self, other): + if not other.is_SWAP_EXPIRED(): + return False + return True + + class UNKNOWN: + """ + Any status not yet modelled. `raw` holds the verbatim Boltz status. + """ + + raw: "str" + + def __init__(self,raw: "str"): + self.raw = raw + + def __str__(self): + return "BoltzSwapStatus.UNKNOWN(raw={})".format(self.raw) + + def __eq__(self, other): + if not other.is_UNKNOWN(): + return False + if self.raw != other.raw: + 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_SWAP_CREATED(self) -> bool: + return isinstance(self, BoltzSwapStatus.SWAP_CREATED) + def is_swap_created(self) -> bool: + return isinstance(self, BoltzSwapStatus.SWAP_CREATED) + def is_INVOICE_SET(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_SET) + def is_invoice_set(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_SET) + def is_TRANSACTION_MEMPOOL(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_MEMPOOL) + def is_transaction_mempool(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_MEMPOOL) + def is_TRANSACTION_CONFIRMED(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CONFIRMED) + def is_transaction_confirmed(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CONFIRMED) + def is_INVOICE_PENDING(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_PENDING) + def is_invoice_pending(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_PENDING) + def is_INVOICE_PAID(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_PAID) + def is_invoice_paid(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_PAID) + def is_INVOICE_SETTLED(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_SETTLED) + def is_invoice_settled(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_SETTLED) + def is_INVOICE_FAILED_TO_PAY(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_FAILED_TO_PAY) + def is_invoice_failed_to_pay(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_FAILED_TO_PAY) + def is_INVOICE_EXPIRED(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_EXPIRED) + def is_invoice_expired(self) -> bool: + return isinstance(self, BoltzSwapStatus.INVOICE_EXPIRED) + def is_TRANSACTION_CLAIM_PENDING(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CLAIM_PENDING) + def is_transaction_claim_pending(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CLAIM_PENDING) + def is_TRANSACTION_CLAIMED(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CLAIMED) + def is_transaction_claimed(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_CLAIMED) + def is_TRANSACTION_REFUNDED(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_REFUNDED) + def is_transaction_refunded(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_REFUNDED) + def is_TRANSACTION_LOCKUP_FAILED(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED) + def is_transaction_lockup_failed(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED) + def is_TRANSACTION_FAILED(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_FAILED) + def is_transaction_failed(self) -> bool: + return isinstance(self, BoltzSwapStatus.TRANSACTION_FAILED) + def is_SWAP_EXPIRED(self) -> bool: + return isinstance(self, BoltzSwapStatus.SWAP_EXPIRED) + def is_swap_expired(self) -> bool: + return isinstance(self, BoltzSwapStatus.SWAP_EXPIRED) + def is_UNKNOWN(self) -> bool: + return isinstance(self, BoltzSwapStatus.UNKNOWN) + def is_unknown(self) -> bool: + return isinstance(self, BoltzSwapStatus.UNKNOWN) + + +# 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. +BoltzSwapStatus.SWAP_CREATED = type("BoltzSwapStatus.SWAP_CREATED", (BoltzSwapStatus.SWAP_CREATED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_SET = type("BoltzSwapStatus.INVOICE_SET", (BoltzSwapStatus.INVOICE_SET, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_MEMPOOL = type("BoltzSwapStatus.TRANSACTION_MEMPOOL", (BoltzSwapStatus.TRANSACTION_MEMPOOL, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_CONFIRMED = type("BoltzSwapStatus.TRANSACTION_CONFIRMED", (BoltzSwapStatus.TRANSACTION_CONFIRMED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_PENDING = type("BoltzSwapStatus.INVOICE_PENDING", (BoltzSwapStatus.INVOICE_PENDING, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_PAID = type("BoltzSwapStatus.INVOICE_PAID", (BoltzSwapStatus.INVOICE_PAID, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_SETTLED = type("BoltzSwapStatus.INVOICE_SETTLED", (BoltzSwapStatus.INVOICE_SETTLED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_FAILED_TO_PAY = type("BoltzSwapStatus.INVOICE_FAILED_TO_PAY", (BoltzSwapStatus.INVOICE_FAILED_TO_PAY, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.INVOICE_EXPIRED = type("BoltzSwapStatus.INVOICE_EXPIRED", (BoltzSwapStatus.INVOICE_EXPIRED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_CLAIM_PENDING = type("BoltzSwapStatus.TRANSACTION_CLAIM_PENDING", (BoltzSwapStatus.TRANSACTION_CLAIM_PENDING, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_CLAIMED = type("BoltzSwapStatus.TRANSACTION_CLAIMED", (BoltzSwapStatus.TRANSACTION_CLAIMED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_REFUNDED = type("BoltzSwapStatus.TRANSACTION_REFUNDED", (BoltzSwapStatus.TRANSACTION_REFUNDED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED = type("BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED", (BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.TRANSACTION_FAILED = type("BoltzSwapStatus.TRANSACTION_FAILED", (BoltzSwapStatus.TRANSACTION_FAILED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.SWAP_EXPIRED = type("BoltzSwapStatus.SWAP_EXPIRED", (BoltzSwapStatus.SWAP_EXPIRED, BoltzSwapStatus,), {}) # type: ignore +BoltzSwapStatus.UNKNOWN = type("BoltzSwapStatus.UNKNOWN", (BoltzSwapStatus.UNKNOWN, BoltzSwapStatus,), {}) # type: ignore + + + + +class _UniffiConverterTypeBoltzSwapStatus(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return BoltzSwapStatus.SWAP_CREATED( + ) + if variant == 2: + return BoltzSwapStatus.INVOICE_SET( + ) + if variant == 3: + return BoltzSwapStatus.TRANSACTION_MEMPOOL( + ) + if variant == 4: + return BoltzSwapStatus.TRANSACTION_CONFIRMED( + ) + if variant == 5: + return BoltzSwapStatus.INVOICE_PENDING( + ) + if variant == 6: + return BoltzSwapStatus.INVOICE_PAID( + ) + if variant == 7: + return BoltzSwapStatus.INVOICE_SETTLED( + ) + if variant == 8: + return BoltzSwapStatus.INVOICE_FAILED_TO_PAY( + ) + if variant == 9: + return BoltzSwapStatus.INVOICE_EXPIRED( + ) + if variant == 10: + return BoltzSwapStatus.TRANSACTION_CLAIM_PENDING( + ) + if variant == 11: + return BoltzSwapStatus.TRANSACTION_CLAIMED( + ) + if variant == 12: + return BoltzSwapStatus.TRANSACTION_REFUNDED( + ) + if variant == 13: + return BoltzSwapStatus.TRANSACTION_LOCKUP_FAILED( + ) + if variant == 14: + return BoltzSwapStatus.TRANSACTION_FAILED( + ) + if variant == 15: + return BoltzSwapStatus.SWAP_EXPIRED( + ) + if variant == 16: + return BoltzSwapStatus.UNKNOWN( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_SWAP_CREATED(): + return + if value.is_INVOICE_SET(): + return + if value.is_TRANSACTION_MEMPOOL(): + return + if value.is_TRANSACTION_CONFIRMED(): + return + if value.is_INVOICE_PENDING(): + return + if value.is_INVOICE_PAID(): + return + if value.is_INVOICE_SETTLED(): + return + if value.is_INVOICE_FAILED_TO_PAY(): + return + if value.is_INVOICE_EXPIRED(): + return + if value.is_TRANSACTION_CLAIM_PENDING(): + return + if value.is_TRANSACTION_CLAIMED(): + return + if value.is_TRANSACTION_REFUNDED(): + return + if value.is_TRANSACTION_LOCKUP_FAILED(): + return + if value.is_TRANSACTION_FAILED(): + return + if value.is_SWAP_EXPIRED(): + return + if value.is_UNKNOWN(): + _UniffiConverterString.check_lower(value.raw) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_SWAP_CREATED(): + buf.write_i32(1) + if value.is_INVOICE_SET(): + buf.write_i32(2) + if value.is_TRANSACTION_MEMPOOL(): + buf.write_i32(3) + if value.is_TRANSACTION_CONFIRMED(): + buf.write_i32(4) + if value.is_INVOICE_PENDING(): + buf.write_i32(5) + if value.is_INVOICE_PAID(): + buf.write_i32(6) + if value.is_INVOICE_SETTLED(): + buf.write_i32(7) + if value.is_INVOICE_FAILED_TO_PAY(): + buf.write_i32(8) + if value.is_INVOICE_EXPIRED(): + buf.write_i32(9) + if value.is_TRANSACTION_CLAIM_PENDING(): + buf.write_i32(10) + if value.is_TRANSACTION_CLAIMED(): + buf.write_i32(11) + if value.is_TRANSACTION_REFUNDED(): + buf.write_i32(12) + if value.is_TRANSACTION_LOCKUP_FAILED(): + buf.write_i32(13) + if value.is_TRANSACTION_FAILED(): + buf.write_i32(14) + if value.is_SWAP_EXPIRED(): + buf.write_i32(15) + if value.is_UNKNOWN(): + buf.write_i32(16) + _UniffiConverterString.write(value.raw, buf) + + + + + + + +class BoltzSwapType(enum.Enum): + """ + The direction of a Boltz swap. + + - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, + Boltz pays a Lightning invoice). + - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold + invoice, Boltz locks onchain funds the user then claims). + """ + + SUBMARINE = 0 + + REVERSE = 1 + + + +class _UniffiConverterTypeBoltzSwapType(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return BoltzSwapType.SUBMARINE + if variant == 2: + return BoltzSwapType.REVERSE + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == BoltzSwapType.SUBMARINE: + return + if value == BoltzSwapType.REVERSE: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == BoltzSwapType.SUBMARINE: + buf.write_i32(1) + if value == BoltzSwapType.REVERSE: + buf.write_i32(2) + + + + +# 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, BroadcastError.ElectrumError): + buf.write_i32(3) + _UniffiConverterString.write(value.error_details, buf) + if isinstance(value, BroadcastError.TaskError): + buf.write_i32(4) + _UniffiConverterString.write(value.error_details, buf) + + + + + +class BtBolt11InvoiceState(enum.Enum): + PENDING = 0 + + HOLDING = 1 + + PAID = 2 + + CANCELED = 3 + + + +class _UniffiConverterTypeBtBolt11InvoiceState(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return BtBolt11InvoiceState.PENDING + if variant == 2: + return BtBolt11InvoiceState.HOLDING + if variant == 3: + return BtBolt11InvoiceState.PAID if variant == 4: return BtBolt11InvoiceState.CANCELED raise InternalError("Raw enum value doesn't match any cases") @@ -12594,6 +14208,16 @@ def __init__(self, error_details): def __repr__(self): return "DbError.DbBlocktankError({})".format(str(self)) _UniffiTempDbError.DbBlocktankError = DbBlocktankError # type: ignore + class DbBoltzError(_UniffiTempDbError): + def __init__(self, error_details): + super().__init__(", ".join([ + "error_details={!r}".format(error_details), + ])) + self.error_details = error_details + + def __repr__(self): + return "DbError.DbBoltzError({})".format(str(self)) + _UniffiTempDbError.DbBoltzError = DbBoltzError # type: ignore class InitializationError(_UniffiTempDbError): def __init__(self, error_details): super().__init__(", ".join([ @@ -12622,6 +14246,10 @@ def read(buf): _UniffiConverterTypeBlocktankError.read(buf), ) if variant == 3: + return DbError.DbBoltzError( + _UniffiConverterTypeBoltzError.read(buf), + ) + if variant == 4: return DbError.InitializationError( _UniffiConverterString.read(buf), ) @@ -12635,6 +14263,9 @@ def check_lower(value): if isinstance(value, DbError.DbBlocktankError): _UniffiConverterTypeBlocktankError.check_lower(value.error_details) return + if isinstance(value, DbError.DbBoltzError): + _UniffiConverterTypeBoltzError.check_lower(value.error_details) + return if isinstance(value, DbError.InitializationError): _UniffiConverterString.check_lower(value.error_details) return @@ -12647,8 +14278,11 @@ def write(value, buf): if isinstance(value, DbError.DbBlocktankError): buf.write_i32(2) _UniffiConverterTypeBlocktankError.write(value.error_details, buf) - if isinstance(value, DbError.InitializationError): + if isinstance(value, DbError.DbBoltzError): buf.write_i32(3) + _UniffiConverterTypeBoltzError.write(value.error_details, buf) + if isinstance(value, DbError.InitializationError): + buf.write_i32(4) _UniffiConverterString.write(value.error_details, buf) @@ -15651,6 +17285,33 @@ def read(cls, buf): +class _UniffiConverterOptionalTypeBoltzSwap(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterTypeBoltzSwap.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterTypeBoltzSwap.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterTypeBoltzSwap.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + class _UniffiConverterOptionalTypeClosedChannelDetails(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -16883,6 +18544,31 @@ def read(cls, buf): +class _UniffiConverterSequenceTypeBoltzSwap(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeBoltzSwap.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeBoltzSwap.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeBoltzSwap.read(buf) for i in range(count) + ] + + + class _UniffiConverterSequenceTypeClosedChannelDetails(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -17589,57 +19275,197 @@ def check_lower(cls, value): for item in value: _UniffiConverterTypeTrezorTransportType.check_lower(item) - @classmethod - def write(cls, value, buf): - items = len(value) - buf.write_i32(items) - for item in value: - _UniffiConverterTypeTrezorTransportType.write(item, buf) + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeTrezorTransportType.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeTrezorTransportType.read(buf) for i in range(count) + ] + + + +class _UniffiConverterMapStringString(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, items): + for (key, value) in items.items(): + _UniffiConverterString.check_lower(key) + _UniffiConverterString.check_lower(value) + + @classmethod + def write(cls, items, buf): + buf.write_i32(len(items)) + for (key, value) in items.items(): + _UniffiConverterString.write(key, buf) + _UniffiConverterString.write(value, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative map size") + + # It would be nice to use a dict comprehension, + # but in Python 3.7 and before the evaluation order is not according to spec, + # so we we're reading the value before the key. + # This loop makes the order explicit: first reading the key, then the value. + d = {} + for i in range(count): + key = _UniffiConverterString.read(buf) + val = _UniffiConverterString.read(buf) + d[key] = val + return d + +# objects. +class BoltzEventListenerProtocol(typing.Protocol): + """ + Callback interface for receiving Boltz swap lifecycle events. + + Implement this in Swift/Kotlin/Python and register it via + `boltz_start_swap_updates` to receive typed notifications as swaps progress. + Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + event reports the resulting transaction id. + """ + + def on_event(self, event: "BoltzSwapEvent"): + raise NotImplementedError +# BoltzEventListener is a foreign trait so treated like a callback interface, where the +# primary use-case is the trait being implemented locally. +# It is a base-class local implementations might subclass. + + +class BoltzEventListener(): + """ + Callback interface for receiving Boltz swap lifecycle events. + + Implement this in Swift/Kotlin/Python and register it via + `boltz_start_swap_updates` to receive typed notifications as swaps progress. + Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + event reports the resulting transaction id. + """ + + def on_event(self, event: "BoltzSwapEvent"): + raise NotImplementedError +# `BoltzEventListenerImpl` is the implementation for a Rust implemented version. +class BoltzEventListenerImpl(): + """ + Callback interface for receiving Boltz swap lifecycle events. + + Implement this in Swift/Kotlin/Python and register it via + `boltz_start_swap_updates` to receive typed notifications as swaps progress. + Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] + event reports the resulting transaction id. + """ + + _pointer: ctypes.c_void_p + + def __init__(self, *args, **kwargs): + raise ValueError("This class has no default constructor") + + def __del__(self): + # In case of partial initialization of instances. + pointer = getattr(self, "_pointer", None) + if pointer is not None: + _uniffi_rust_call(_UniffiLib.uniffi_bitkitcore_fn_free_boltzeventlistener, pointer) + + def _uniffi_clone_pointer(self): + return _uniffi_rust_call(_UniffiLib.uniffi_bitkitcore_fn_clone_boltzeventlistener, self._pointer) + + # Used by alternative constructors or any methods which return this type. + @classmethod + def _make_instance_(cls, pointer): + # Lightly yucky way to bypass the usual __init__ logic + # and just create a new instance with the required pointer. + inst = cls.__new__(cls) + inst._pointer = pointer + return inst + + + def on_event(self, event: "BoltzSwapEvent") -> None: + _UniffiConverterTypeBoltzSwapEvent.check_lower(event) + + _uniffi_rust_call(_UniffiLib.uniffi_bitkitcore_fn_method_boltzeventlistener_on_event,self._uniffi_clone_pointer(), + _UniffiConverterTypeBoltzSwapEvent.lower(event)) + + + + + + +# Put all the bits inside a class to keep the top-level namespace clean +class _UniffiTraitImplBoltzEventListener: + # For each method, generate a callback function to pass to Rust + + @_UNIFFI_CALLBACK_INTERFACE_BOLTZ_EVENT_LISTENER_METHOD0 + def on_event( + uniffi_handle, + event, + uniffi_out_return, + uniffi_call_status_ptr, + ): + uniffi_obj = _UniffiConverterTypeBoltzEventListener._handle_map.get(uniffi_handle) + def make_call(): + args = (_UniffiConverterTypeBoltzSwapEvent.lift(event), ) + method = uniffi_obj.on_event + return method(*args) + + + write_return_value = lambda v: None + _uniffi_trait_interface_call( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + ) + + @_UNIFFI_CALLBACK_INTERFACE_FREE + def _uniffi_free(uniffi_handle): + _UniffiConverterTypeBoltzEventListener._handle_map.remove(uniffi_handle) + + # Generate the FFI VTable. This has a field for each callback interface method. + _uniffi_vtable = _UniffiVTableCallbackInterfaceBoltzEventListener( + on_event, + _uniffi_free + ) + # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever, + # or else bad things will happen when Rust tries to access it. + _UniffiLib.uniffi_bitkitcore_fn_init_callback_vtable_boltzeventlistener(ctypes.byref(_uniffi_vtable)) + - @classmethod - def read(cls, buf): - count = buf.read_i32() - if count < 0: - raise InternalError("Unexpected negative sequence length") - return [ - _UniffiConverterTypeTrezorTransportType.read(buf) for i in range(count) - ] +class _UniffiConverterTypeBoltzEventListener: + _handle_map = _UniffiHandleMap() + @staticmethod + def lift(value: int): + return BoltzEventListenerImpl._make_instance_(value) + @staticmethod + def check_lower(value: BoltzEventListener): + pass -class _UniffiConverterMapStringString(_UniffiConverterRustBuffer): - @classmethod - def check_lower(cls, items): - for (key, value) in items.items(): - _UniffiConverterString.check_lower(key) - _UniffiConverterString.check_lower(value) + @staticmethod + def lower(value: BoltzEventListenerProtocol): + return _UniffiConverterTypeBoltzEventListener._handle_map.insert(value) @classmethod - def write(cls, items, buf): - buf.write_i32(len(items)) - for (key, value) in items.items(): - _UniffiConverterString.write(key, buf) - _UniffiConverterString.write(value, buf) + def read(cls, buf: _UniffiRustBuffer): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) @classmethod - def read(cls, buf): - count = buf.read_i32() - if count < 0: - raise InternalError("Unexpected negative map size") - - # It would be nice to use a dict comprehension, - # but in Python 3.7 and before the evaluation order is not according to spec, - # so we we're reading the value before the key. - # This loop makes the order explicit: first reading the key, then the value. - d = {} - for i in range(count): - key = _UniffiConverterString.read(buf) - val = _UniffiConverterString.read(buf) - d[key] = val - return d - -# objects. + def write(cls, value: BoltzEventListenerProtocol, buf: _UniffiRustBuffer): + buf.write_u64(cls.lower(value)) class EventListenerProtocol(typing.Protocol): """ Callback interface for receiving watcher events. @@ -19087,6 +20913,324 @@ async def blocktank_wipe_all() -> None: # Error FFI converter _UniffiConverterTypeBlocktankError, + ) +async def boltz_claim_reverse_swap(swap_id: "str",mnemonic: "str",bip39_passphrase: "typing.Optional[str]",fee_rate_sat_per_vb: "typing.Optional[float]") -> "str": + + """ + Claim a reverse swap's onchain funds to its claim address, returning the + broadcast claim transaction id. Normally happens automatically via the + updates stream; exposed for manual recovery. The claim key is re-derived from + `mnemonic`. Claims are serialized per swap, so calling this while the updates + stream is auto-claiming the same swap waits for that claim and returns its + txid rather than broadcasting a second transaction. + """ + + _UniffiConverterString.check_lower(swap_id) + + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(bip39_passphrase) + + _UniffiConverterOptionalDouble.check_lower(fee_rate_sat_per_vb) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_claim_reverse_swap( + _UniffiConverterString.lower(swap_id), + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(bip39_passphrase), + _UniffiConverterOptionalDouble.lower(fee_rate_sat_per_vb)), + _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 +_UniffiConverterTypeBoltzError, + + ) +async def boltz_create_reverse_swap(network: "BoltzNetwork",electrum_url: "str",amount_sat: "int",claim_address: "str",mnemonic: "str",bip39_passphrase: "typing.Optional[str]") -> "ReverseSwapResponse": + + """ + Create a reverse swap (Lightning -> onchain). + + The caller pays the returned hold invoice from its Lightning node; + `claim_address` is the onchain address the received funds are claimed to. + The claim key and preimage are derived deterministically from `mnemonic` + (only the derivation index is persisted, never the secrets) so the claim can + be made automatically once Boltz locks the funds. `bip39_passphrase` must + match the wallet's, or claims will derive the wrong key. + """ + + _UniffiConverterTypeBoltzNetwork.check_lower(network) + + _UniffiConverterString.check_lower(electrum_url) + + _UniffiConverterUInt64.check_lower(amount_sat) + + _UniffiConverterString.check_lower(claim_address) + + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(bip39_passphrase) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_reverse_swap( + _UniffiConverterTypeBoltzNetwork.lower(network), + _UniffiConverterString.lower(electrum_url), + _UniffiConverterUInt64.lower(amount_sat), + _UniffiConverterString.lower(claim_address), + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(bip39_passphrase)), + _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 + _UniffiConverterTypeReverseSwapResponse.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_create_submarine_swap(network: "BoltzNetwork",electrum_url: "str",invoice: "str",mnemonic: "str",bip39_passphrase: "typing.Optional[str]") -> "SubmarineSwapResponse": + + """ + Create a submarine swap (onchain -> Lightning). + + `invoice` is a BOLT11 invoice the caller's Lightning node generated. The + caller funds the returned lockup address from its onchain wallet. The refund + key is derived deterministically from `mnemonic` (only the derivation index + is persisted, never the key), and the swap is tracked if an updates stream is + running. `bip39_passphrase` must match the wallet's, or refunds will derive + the wrong key. + """ + + _UniffiConverterTypeBoltzNetwork.check_lower(network) + + _UniffiConverterString.check_lower(electrum_url) + + _UniffiConverterString.check_lower(invoice) + + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(bip39_passphrase) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_create_submarine_swap( + _UniffiConverterTypeBoltzNetwork.lower(network), + _UniffiConverterString.lower(electrum_url), + _UniffiConverterString.lower(invoice), + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(bip39_passphrase)), + _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 + _UniffiConverterTypeSubmarineSwapResponse.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_get_reverse_limits(network: "BoltzNetwork") -> "BoltzPairInfo": + + """ + Fetch fees and limits for reverse swaps (Lightning -> onchain). + """ + + _UniffiConverterTypeBoltzNetwork.check_lower(network) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_reverse_limits( + _UniffiConverterTypeBoltzNetwork.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 + _UniffiConverterTypeBoltzPairInfo.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_get_submarine_limits(network: "BoltzNetwork") -> "BoltzPairInfo": + + """ + Fetch fees and limits for submarine swaps (onchain -> Lightning). + """ + + _UniffiConverterTypeBoltzNetwork.check_lower(network) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_submarine_limits( + _UniffiConverterTypeBoltzNetwork.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 + _UniffiConverterTypeBoltzPairInfo.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_get_swap(swap_id: "str") -> "typing.Optional[BoltzSwap]": + + """ + Fetch a single swap by id. + """ + + _UniffiConverterString.check_lower(swap_id) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_get_swap( + _UniffiConverterString.lower(swap_id)), + _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 + _UniffiConverterOptionalTypeBoltzSwap.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_list_pending_swaps() -> "typing.List[BoltzSwap]": + + """ + List swaps that have not reached a terminal state (for recovery/resume). + """ + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_pending_swaps(), + _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 + _UniffiConverterSequenceTypeBoltzSwap.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_list_swaps() -> "typing.List[BoltzSwap]": + + """ + List every persisted swap, newest first. + """ + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_list_swaps(), + _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 + _UniffiConverterSequenceTypeBoltzSwap.lift, + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_refund_submarine_swap(swap_id: "str",refund_address: "str",mnemonic: "str",bip39_passphrase: "typing.Optional[str]",fee_rate_sat_per_vb: "typing.Optional[float]") -> "str": + + """ + Refund a submarine swap's locked funds to `refund_address`, returning the + broadcast refund transaction id. Used when Boltz fails to pay the invoice or + the swap expires. The refund key is re-derived from `mnemonic`. Refunds are + serialized per swap, so two concurrent calls cannot both broadcast: the second + waits for the first and returns its txid. + """ + + _UniffiConverterString.check_lower(swap_id) + + _UniffiConverterString.check_lower(refund_address) + + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(bip39_passphrase) + + _UniffiConverterOptionalDouble.check_lower(fee_rate_sat_per_vb) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_refund_submarine_swap( + _UniffiConverterString.lower(swap_id), + _UniffiConverterString.lower(refund_address), + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(bip39_passphrase), + _UniffiConverterOptionalDouble.lower(fee_rate_sat_per_vb)), + _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 +_UniffiConverterTypeBoltzError, + + ) +async def boltz_start_swap_updates(network: "BoltzNetwork",listener: "BoltzEventListener",mnemonic: "str",bip39_passphrase: "typing.Optional[str]",fee_rate_sat_per_vb: "typing.Optional[float]") -> None: + + """ + Open a Boltz WebSocket for `network`, subscribe to all pending swaps, and + drive their lifecycle (auto-claiming reverse swaps) until stopped. Replaces + any previously running updates stream (only one network is tracked at a + time). `mnemonic` is held in memory for the lifetime of the stream so + confirmed reverse swaps can be auto-claimed; it is never persisted. Events + are delivered to `listener`. + + `fee_rate_sat_per_vb` is the fee rate used for automatic claim transactions. + Bitkit owns fee estimation and should pass its current recommended rate; when + `None`, a conservative built-in default is used. To auto-claim at an updated + fee rate, call this again (it restarts the stream). + """ + + _UniffiConverterTypeBoltzNetwork.check_lower(network) + + _UniffiConverterTypeBoltzEventListener.check_lower(listener) + + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(bip39_passphrase) + + _UniffiConverterOptionalDouble.check_lower(fee_rate_sat_per_vb) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_start_swap_updates( + _UniffiConverterTypeBoltzNetwork.lower(network), + _UniffiConverterTypeBoltzEventListener.lower(listener), + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(bip39_passphrase), + _UniffiConverterOptionalDouble.lower(fee_rate_sat_per_vb)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_void, + _UniffiLib.ffi_bitkitcore_rust_future_complete_void, + _UniffiLib.ffi_bitkitcore_rust_future_free_void, + # lift function + lambda val: None, + + + # Error FFI converter +_UniffiConverterTypeBoltzError, + + ) +async def boltz_stop_swap_updates() -> None: + + """ + Stop the running Boltz updates stream, if any. + """ + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_boltz_stop_swap_updates(), + _UniffiLib.ffi_bitkitcore_rust_future_poll_void, + _UniffiLib.ffi_bitkitcore_rust_future_complete_void, + _UniffiLib.ffi_bitkitcore_rust_future_free_void, + # lift function + lambda val: None, + + + # Error FFI converter + + None, + ) async def broadcast_sweep_transaction(psbt: "str",mnemonic_phrase: "str",network: "typing.Optional[Network]",bip39_passphrase: "typing.Optional[str]",electrum_url: "str") -> "SweepResult": @@ -21516,6 +23660,11 @@ def wipe_all_transaction_details() -> None: "AddressType", "BitcoinNetworkEnum", "BlocktankError", + "BoltzError", + "BoltzNetwork", + "BoltzSwapEvent", + "BoltzSwapStatus", + "BoltzSwapType", "BroadcastError", "BtBolt11InvoiceState", "BtChannelOrderErrorType", @@ -21557,6 +23706,8 @@ def wipe_all_transaction_details() -> None: "AccountUtxo", "ActivityTags", "AddressInfo", + "BoltzPairInfo", + "BoltzSwap", "ChannelLiquidityOptions", "ChannelLiquidityParams", "ClosedChannelDetails", @@ -21613,7 +23764,9 @@ def wipe_all_transaction_details() -> None: "PubkyAuthDetails", "PubkyProfile", "PubkyProfileLink", + "ReverseSwapResponse", "SingleAddressInfoResult", + "SubmarineSwapResponse", "SupportedHardwareWallet", "SweepResult", "SweepTransactionPreview", @@ -21660,6 +23813,17 @@ def wipe_all_transaction_details() -> None: "blocktank_remove_all_cjit_entries", "blocktank_remove_all_orders", "blocktank_wipe_all", + "boltz_claim_reverse_swap", + "boltz_create_reverse_swap", + "boltz_create_submarine_swap", + "boltz_get_reverse_limits", + "boltz_get_submarine_limits", + "boltz_get_swap", + "boltz_list_pending_swaps", + "boltz_list_swaps", + "boltz_refund_submarine_swap", + "boltz_start_swap_updates", + "boltz_stop_swap_updates", "broadcast_sweep_transaction", "calculate_channel_liquidity_options", "cancel_pubky_auth", @@ -21812,6 +23976,7 @@ def wipe_all_transaction_details() -> None: "wipe_all_closed_channels", "wipe_all_databases", "wipe_all_transaction_details", + "BoltzEventListener", "EventListener", "TrezorTransportCallback", "TrezorUiCallback", diff --git a/bindings/python/bitkitcore/libbitkitcore.dylib b/bindings/python/bitkitcore/libbitkitcore.dylib index ee3e21d..fb3d347 100755 Binary files a/bindings/python/bitkitcore/libbitkitcore.dylib and b/bindings/python/bitkitcore/libbitkitcore.dylib differ diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 550ab36..47fe4d0 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -2,7 +2,7 @@ setup( name="bitkitcore", - version="0.4.1", + version="0.5.1", packages=find_packages(), package_data={ "bitkitcore": ["*.so", "*.dylib", "*.dll"], diff --git a/src/lib.rs b/src/lib.rs index f8d162c..cf7ab49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,10 @@ use crate::modules::blocktank::{ IBt0ConfMinTxFeeWindow, IBtBolt11Invoice, IBtEstimateFeeResponse, IBtEstimateFeeResponse2, IBtInfo, IBtOrder, ICJitEntry, IGift, }; +use crate::modules::boltz::{ + self, BoltzDB, BoltzError, BoltzEventListener, BoltzNetwork, BoltzPairInfo, BoltzSwap, + ReverseSwapResponse, SubmarineSwapResponse, +}; use crate::modules::pubky::{PubkyAuthDetails, PubkyAuthKind, PubkyError, PubkyProfile}; use crate::modules::trezor::account_type_to_script_type; pub use crate::modules::trezor::{ @@ -61,6 +65,7 @@ use crate::onchain::{ start_watcher, stop_all_watchers, stop_watcher, EventListener, WatcherParams, }; pub use modules::activity; +pub use modules::boltz as boltz_swaps; pub use modules::lnurl; pub use modules::onchain; pub use modules::scanner::{DecodingError, LnurlPayData, Scanner}; @@ -79,6 +84,7 @@ pub struct DatabaseConnections { pub struct AsyncDatabaseConnections { blocktank_db: Option, + boltz_db: Option>, } // Two separate global states for sync and async connections static DB: OnceCell> = OnceCell::new(); @@ -454,7 +460,12 @@ pub fn init_db(base_path: String) -> Result { DB.get_or_init(|| StdMutex::new(DatabaseConnections { activity_db: None })); // Initialize async database state - ASYNC_DB.get_or_init(|| TokioMutex::new(AsyncDatabaseConnections { blocktank_db: None })); + ASYNC_DB.get_or_init(|| { + TokioMutex::new(AsyncDatabaseConnections { + blocktank_db: None, + boltz_db: None, + }) + }); // Create runtime for async operations let rt = ensure_runtime(); @@ -462,6 +473,7 @@ pub fn init_db(base_path: String) -> Result { let activity_db = ActivityDB::new(&format!("{}/activity.db", base_path))?; let blocktank_db = rt .block_on(async { BlocktankDB::new(&format!("{}/blocktank.db", base_path), None).await })?; + let boltz_db = rt.block_on(async { BoltzDB::new(&format!("{}/boltz.db", base_path)).await })?; // Initialize sync database { @@ -475,6 +487,7 @@ pub fn init_db(base_path: String) -> Result { rt.block_on(async { let mut guard = async_db.lock().await; guard.blocktank_db = Some(blocktank_db); + guard.boltz_db = Some(Arc::new(boltz_db)); }); } @@ -2713,3 +2726,262 @@ pub fn onchain_stop_watcher(watcher_id: String) -> Result<(), AccountInfoError> pub fn onchain_stop_all_watchers() { stop_all_watchers(); } + +// ============================================================================ +// Boltz swaps +// ============================================================================ + +/// Clone the shared Boltz database handle. The handle is held only briefly +/// while cloning, so long-running swap operations don't block other callers. +async fn get_boltz_db() -> Result, BoltzError> { + let cell = ASYNC_DB.get().ok_or(BoltzError::ConnectionError { + error_details: "Database not initialized. Call init_db first.".to_string(), + })?; + let guard = cell.lock().await; + guard.boltz_db.clone().ok_or(BoltzError::ConnectionError { + error_details: "Database not initialized. Call init_db first.".to_string(), + }) +} + +fn boltz_runtime_err(e: tokio::task::JoinError) -> BoltzError { + BoltzError::ConnectionError { + error_details: format!("Runtime error: {}", e), + } +} + +/// Fetch fees and limits for submarine swaps (onchain -> Lightning). +#[uniffi::export] +pub async fn boltz_get_submarine_limits( + network: BoltzNetwork, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { boltz::get_submarine_limits(network).await }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Fetch fees and limits for reverse swaps (Lightning -> onchain). +#[uniffi::export] +pub async fn boltz_get_reverse_limits(network: BoltzNetwork) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { boltz::get_reverse_limits(network).await }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Create a submarine swap (onchain -> Lightning). +/// +/// `invoice` is a BOLT11 invoice the caller's Lightning node generated. The +/// caller funds the returned lockup address from its onchain wallet. The refund +/// key is derived deterministically from `mnemonic` (only the derivation index +/// is persisted, never the key), and the swap is tracked if an updates stream is +/// running. `bip39_passphrase` must match the wallet's, or refunds will derive +/// the wrong key. +#[uniffi::export] +pub async fn boltz_create_submarine_swap( + network: BoltzNetwork, + electrum_url: String, + invoice: String, + mnemonic: String, + bip39_passphrase: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + let response = db + .create_submarine_swap(network, electrum_url, invoice, mnemonic, bip39_passphrase) + .await?; + boltz::subscribe_if_active(network, &response.id).await; + Ok(response) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Create a reverse swap (Lightning -> onchain). +/// +/// The caller pays the returned hold invoice from its Lightning node; +/// `claim_address` is the onchain address the received funds are claimed to. +/// The claim key and preimage are derived deterministically from `mnemonic` +/// (only the derivation index is persisted, never the secrets) so the claim can +/// be made automatically once Boltz locks the funds. `bip39_passphrase` must +/// match the wallet's, or claims will derive the wrong key. +#[uniffi::export] +pub async fn boltz_create_reverse_swap( + network: BoltzNetwork, + electrum_url: String, + amount_sat: u64, + claim_address: String, + mnemonic: String, + bip39_passphrase: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + let response = db + .create_reverse_swap( + network, + electrum_url, + amount_sat, + claim_address, + mnemonic, + bip39_passphrase, + ) + .await?; + boltz::subscribe_if_active(network, &response.id).await; + Ok(response) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// List every persisted swap, newest first. +#[uniffi::export] +pub async fn boltz_list_swaps() -> Result, BoltzError> { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + Ok(db + .list_swaps() + .await? + .iter() + .map(|r| r.to_boltz_swap()) + .collect()) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// List swaps that have not reached a terminal state (for recovery/resume). +#[uniffi::export] +pub async fn boltz_list_pending_swaps() -> Result, BoltzError> { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + Ok(db + .list_pending_swaps() + .await? + .iter() + .map(|r| r.to_boltz_swap()) + .collect()) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Fetch a single swap by id. +#[uniffi::export] +pub async fn boltz_get_swap(swap_id: String) -> Result, BoltzError> { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + Ok(db.get_swap(&swap_id).await?.map(|r| r.to_boltz_swap())) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Claim a reverse swap's onchain funds to its claim address, returning the +/// broadcast claim transaction id. Normally happens automatically via the +/// updates stream; exposed for manual recovery. The claim key is re-derived from +/// `mnemonic`. Claims are serialized per swap, so calling this while the updates +/// stream is auto-claiming the same swap waits for that claim and returns its +/// txid rather than broadcasting a second transaction. +#[uniffi::export] +pub async fn boltz_claim_reverse_swap( + swap_id: String, + mnemonic: String, + bip39_passphrase: Option, + fee_rate_sat_per_vb: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + let outcome = boltz::claim_reverse_swap_guarded( + &db, + &swap_id, + &mnemonic, + bip39_passphrase.as_deref(), + fee_rate_sat_per_vb, + ) + .await?; + Ok(outcome.txid()) + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Refund a submarine swap's locked funds to `refund_address`, returning the +/// broadcast refund transaction id. Used when Boltz fails to pay the invoice or +/// the swap expires. The refund key is re-derived from `mnemonic`. Refunds are +/// serialized per swap, so two concurrent calls cannot both broadcast: the second +/// waits for the first and returns its txid. +#[uniffi::export] +pub async fn boltz_refund_submarine_swap( + swap_id: String, + refund_address: String, + mnemonic: String, + bip39_passphrase: Option, + fee_rate_sat_per_vb: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + boltz::refund_submarine_swap_guarded( + &db, + &swap_id, + refund_address, + &mnemonic, + bip39_passphrase.as_deref(), + fee_rate_sat_per_vb, + ) + .await + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Open a Boltz WebSocket for `network`, subscribe to all pending swaps, and +/// drive their lifecycle (auto-claiming reverse swaps) until stopped. Replaces +/// any previously running updates stream (only one network is tracked at a +/// time). `mnemonic` is held in memory for the lifetime of the stream so +/// confirmed reverse swaps can be auto-claimed; it is never persisted. Events +/// are delivered to `listener`. +/// +/// `fee_rate_sat_per_vb` is the fee rate used for automatic claim transactions. +/// Bitkit owns fee estimation and should pass its current recommended rate; when +/// `None`, a conservative built-in default is used. To auto-claim at an updated +/// fee rate, call this again (it restarts the stream). +#[uniffi::export] +pub async fn boltz_start_swap_updates( + network: BoltzNetwork, + listener: Arc, + mnemonic: String, + bip39_passphrase: Option, + fee_rate_sat_per_vb: Option, +) -> Result<(), BoltzError> { + let rt = ensure_runtime(); + rt.spawn(async move { + let db = get_boltz_db().await?; + boltz::start_swap_updates( + db, + network, + listener, + mnemonic, + bip39_passphrase, + fee_rate_sat_per_vb, + ) + .await + }) + .await + .unwrap_or_else(|e| Err(boltz_runtime_err(e))) +} + +/// Stop the running Boltz updates stream, if any. +#[uniffi::export] +pub async fn boltz_stop_swap_updates() { + let rt = ensure_runtime(); + let _ = rt + .spawn(async move { boltz::stop_swap_updates().await }) + .await; +} diff --git a/src/modules/activity/types.rs b/src/modules/activity/types.rs index b2acdb6..2941849 100644 --- a/src/modules/activity/types.rs +++ b/src/modules/activity/types.rs @@ -1,5 +1,6 @@ use crate::activity::ActivityError; use crate::modules::blocktank::BlocktankError; +use crate::modules::boltz::BoltzError; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -307,6 +308,9 @@ pub enum DbError { #[error("DB Blocktank Error: {error_details}")] DbBlocktankError { error_details: BlocktankError }, + #[error("DB Boltz Error: {error_details}")] + DbBoltzError { error_details: BoltzError }, + #[error("Initialization Error: {error_details}")] InitializationError { error_details: String }, } @@ -326,3 +330,11 @@ impl From for DbError { } } } + +impl From for DbError { + fn from(error: BoltzError) -> Self { + DbError::DbBoltzError { + error_details: error, + } + } +} diff --git a/src/modules/boltz/README.md b/src/modules/boltz/README.md new file mode 100644 index 0000000..82b926c --- /dev/null +++ b/src/modules/boltz/README.md @@ -0,0 +1,308 @@ +# Boltz Module + +This module integrates [Boltz](https://boltz.exchange) submarine and reverse +swaps so funds can move between onchain Bitcoin and Lightning channels: + +- **Submarine swap** (onchain → Lightning): you lock onchain BTC, Boltz pays a + Lightning invoice your node generated. Use this to **add** Lightning balance + from onchain funds. +- **Reverse swap** (Lightning → onchain): you pay a Boltz hold invoice over + Lightning, Boltz locks onchain BTC, and this module claims it to your onchain + address. Use this to **drain** Lightning balance to onchain. + +The dangerous cryptography (MuSig2 Taproot cooperative signing, swap scripts, +claim/refund transaction construction) is handled by the +[`boltz-client`](https://crates.io/crates/boltz-client) crate. This module adds +persistence, lifecycle tracking, automatic claiming, and the FFI surface. + +## Responsibility split with the app + +bitkit-core does **not** own the Lightning node. The app (via LDK Node) and +bitkit-core cooperate: + +| Step | Owner | +|------|-------| +| Generate the BOLT11 invoice (submarine) | **App** — `node.bolt11Payment().receive(...)` | +| Pay the hold invoice (reverse) | **App** — `node.bolt11Payment().send(...)` | +| Provide an onchain claim address (reverse) | **App** — `node.onchainPayment().newAddress()` | +| Send the onchain lockup (submarine) | **App** — `node.onchainPayment().sendToAddress(...)` | +| Call Boltz, derive keys & preimage, track status | **bitkit-core** | +| Build, sign and broadcast claim/refund transactions | **bitkit-core** | + +The app passes its **wallet mnemonic** (and BIP39 passphrase, if any) to the +create/claim/refund/start-updates calls. + +## Keys, secrets & recovery + +Swap keys are **derived deterministically from the wallet seed**, never randomly +generated and never stored. Each swap uses a unique index under Boltz's BIP85 +scheme (`m/26589'/0'/0'/{index}`); for reverse swaps the preimage is +`sha256(swapKey)`. `boltz.db` persists only that **index** (plus status and +metadata) — it holds **no key material**, so a leaked database cannot move funds. + +This makes swaps recoverable two independent ways: + +1. **Same device / restored `boltz.db`** — the index is on disk; combine it with + the in-memory seed to re-derive keys. Use `boltzListPendingSwaps` after + `boltzStartSwapUpdates` on startup. +2. **Seed only (`boltz.db` lost)** — because keys derive from the seed, an + in-flight swap can still be recovered: the same BIP85 swap mnemonic can be + registered with Boltz's rescue API + (`https://boltz.exchange/rescue/external?mode=rescue-key`) to re-enumerate + swaps and re-derive their keys by scanning indices. + +> **The BIP39 passphrase must match the wallet's.** Keys derived under the wrong +> passphrase (or a typo'd mnemonic) will not control the locked funds. Pass the +> exact same `mnemonic`/`bip39Passphrase` used by the wallet to every Boltz call. + +Secrets exist in process memory only while a swap is being created, claimed or +refunded; the background updates stream additionally holds the mnemonic in memory +for its lifetime (dropped on `boltzStopSwapUpdates`) so it can auto-claim. As +elsewhere in the app, `boltz.db` relies on the platform's app-sandbox/filesystem +encryption at rest. + +## Lifecycle + +Status strings mirror the [Boltz lifecycle](https://api.docs.boltz.exchange/lifecycle.html) +and are surfaced as the typed `BoltzSwapStatus` enum (unknown future states fall +back to `Unknown { raw }`). Register a `BoltzEventListener` via +`boltzStartSwapUpdates` to receive `BoltzSwapEvent`s over a managed WebSocket. + +**Reverse swaps are claimed automatically**: once Boltz's lockup reaches +`transaction.confirmed`, this module builds and broadcasts the claim transaction +(cooperative key-path first, script-path fallback) and emits +`BoltzSwapEvent.Claimed { txid }`. Claiming on confirmation (not mempool) avoids +revealing the preimage before the lockup is final; call `boltzClaimReverseSwap` +manually if you accept the 0-conf risk. The auto-claim fee rate is the +`feeRateSatPerVb` passed to `boltzStartSwapUpdates` — **Bitkit owns fee +estimation** and should pass its current recommended rate so the claim confirms +before the swap times out; restart the stream to apply an updated rate. + +**Submarine refunds are manual** (the module needs a destination address): on +`invoice.failedToPay` / `transaction.lockupFailed` / `swap.expired`, call +`boltzRefundSubmarineSwap` with an onchain address. + +## FFI surface + +``` +boltzGetSubmarineLimits(network) -> BoltzPairInfo +boltzGetReverseLimits(network) -> BoltzPairInfo +boltzCreateSubmarineSwap(network, electrumUrl, invoice, mnemonic, bip39Passphrase?) -> SubmarineSwapResponse +boltzCreateReverseSwap(network, electrumUrl, amountSat, claimAddress, mnemonic, bip39Passphrase?) -> ReverseSwapResponse +boltzListSwaps() -> [BoltzSwap] +boltzListPendingSwaps() -> [BoltzSwap] +boltzGetSwap(swapId) -> BoltzSwap? +boltzClaimReverseSwap(swapId, mnemonic, bip39Passphrase?, feeRateSatPerVb?) -> String (txid) +boltzRefundSubmarineSwap(swapId, refundAddress, mnemonic, bip39Passphrase?, feeRateSatPerVb?) -> String (txid) +boltzStartSwapUpdates(network, listener, mnemonic, bip39Passphrase?, feeRateSatPerVb?) // managed WebSocket +boltzStopSwapUpdates() +``` + +`network` is `BoltzNetwork.{Mainnet, Testnet, Regtest}`. `electrumUrl` accepts +`ssl://host:port`, `tcp://host:port`, or a bare `host:port` (treated as TLS) and +is stored per-swap for later claim/refund broadcasting. `mnemonic` is the wallet +mnemonic; pass the same `bip39Passphrase` the wallet uses (omit/`null` if none). + +`boltzClaimReverseSwap` and `boltzRefundSubmarineSwap` are **idempotent**: if the +swap already has a recorded claim/refund tx, the existing txid is returned +without re-broadcasting. + +**Only one updates stream runs at a time.** `boltzStartSwapUpdates` stops any +previous stream, so a single network is tracked at once; call it again to switch +networks. + +## Usage Examples + +### Reverse swap — Lightning → onchain + +#### iOS (Swift) +```swift +import BitkitCore + +// 1. Register a listener once (auto-claims reverse swaps). +final class SwapListener: BoltzEventListener { + func onEvent(event: BoltzSwapEvent) { + switch event { + case .statusUpdate(let swapId, let status): + print("swap \(swapId): \(status)") + case .claimed(let swapId, let txid): + print("reverse swap \(swapId) claimed in \(txid)") + case .refunded(let swapId, let txid): + print("swap \(swapId) refunded in \(txid)") + case .error(let swapId, let message): + print("swap \(swapId) error: \(message)") + } + } +} +// `mnemonic` is the wallet's seed phrase; pass the wallet's BIP39 passphrase too +// (or nil). It's held in memory for the stream's lifetime to auto-claim. +try await boltzStartSwapUpdates( + network: .mainnet, + listener: SwapListener(), + mnemonic: wallet.mnemonic, + bip39Passphrase: nil, + feeRateSatPerVb: feeService.currentSatPerVb() // Bitkit-provided fee for auto-claims +) + +func drainToOnchain(amountSat: UInt64) async throws { + // 2. A fresh onchain address from the LDK node receives the funds. + let claimAddress = try lightningService.node.onchainPayment().newAddress() + + // 3. Create the swap and pay its hold invoice over Lightning. + let swap = try await boltzCreateReverseSwap( + network: .mainnet, + electrumUrl: "ssl://electrum.blockstream.info:50002", + amountSat: amountSat, + claimAddress: claimAddress, + mnemonic: wallet.mnemonic, + bip39Passphrase: nil + ) + _ = try lightningService.node.bolt11Payment().send(invoice: swap.invoice, sendingParameters: nil) + + // 4. Once Boltz locks & confirms onchain, the module auto-claims and the + // listener reports `.claimed`. Nothing else to do. +} +``` + +#### Android (Kotlin) +```kotlin +import com.synonym.bitkitcore.* + +class SwapListener : BoltzEventListener { + override fun onEvent(event: BoltzSwapEvent) { + when (event) { + is BoltzSwapEvent.StatusUpdate -> println("swap ${event.swapId}: ${event.status}") + is BoltzSwapEvent.Claimed -> println("reverse swap ${event.swapId} claimed in ${event.txid}") + is BoltzSwapEvent.Refunded -> println("swap ${event.swapId} refunded in ${event.txid}") + is BoltzSwapEvent.Error -> println("swap ${event.swapId} error: ${event.message}") + } + } +} + +suspend fun drainToOnchain(amountSat: ULong) { + // mnemonic = wallet seed phrase; pass the wallet's BIP39 passphrase or null. + // The last arg is Bitkit's fee rate (sat/vB) for auto-claims. + boltzStartSwapUpdates(BoltzNetwork.MAINNET, SwapListener(), wallet.mnemonic, null, feeService.currentSatPerVb()) + + val claimAddress = lightningService.node.onchainPayment().newAddress() + val swap = boltzCreateReverseSwap( + network = BoltzNetwork.MAINNET, + electrumUrl = "ssl://electrum.blockstream.info:50002", + amountSat = amountSat, + claimAddress = claimAddress, + mnemonic = wallet.mnemonic, + bip39Passphrase = null, + ) + lightningService.node.bolt11Payment().send(swap.invoice, null) + // Auto-claimed on confirmation; listener emits Claimed. +} +``` + +#### Python +```python +from bitkitcore import ( + boltz_create_reverse_swap, boltz_start_swap_updates, + BoltzNetwork, BoltzEventListener, +) + +class SwapListener(BoltzEventListener): + def on_event(self, event): + print(event) + +await boltz_start_swap_updates(BoltzNetwork.MAINNET, SwapListener(), wallet_mnemonic, None, 5.0) +swap = await boltz_create_reverse_swap( + network=BoltzNetwork.MAINNET, + electrum_url="ssl://electrum.blockstream.info:50002", + amount_sat=50_000, + claim_address=claim_address, # from your onchain wallet + mnemonic=wallet_mnemonic, + bip39_passphrase=None, +) +# Pay swap.invoice over Lightning; the module claims onchain automatically. +``` + +### Submarine swap — onchain → Lightning + +#### iOS (Swift) +```swift +func topUpLightning(amountSat: UInt64) async throws { + // 1. Your LDK node issues the invoice Boltz will pay. + let invoice = try lightningService.node.bolt11Payment() + .receive(amountMsat: amountSat * 1000, description: "Boltz top-up", expirySecs: 3600) + + // 2. Create the swap; Boltz returns the lockup address & exact amount. + let swap = try await boltzCreateSubmarineSwap( + network: .mainnet, + electrumUrl: "ssl://electrum.blockstream.info:50002", + invoice: invoice, + mnemonic: wallet.mnemonic, + bip39Passphrase: nil + ) + + // 3. Fund the lockup from your onchain wallet. + _ = try lightningService.node.onchainPayment() + .sendToAddress(address: swap.address, amountSats: swap.expectedAmountSat) + + // 4. Boltz pays the invoice on confirmation (.invoicePaid → .transactionClaimed). + // If it fails, refund onchain (key re-derived from the mnemonic): + // try await boltzRefundSubmarineSwap( + // swapId: swap.id, refundAddress: addr, + // mnemonic: wallet.mnemonic, bip39Passphrase: nil, feeRateSatPerVb: nil) +} +``` + +#### Android (Kotlin) +```kotlin +suspend fun topUpLightning(amountSat: ULong) { + val invoice = lightningService.node.bolt11Payment() + .receive(amountSat * 1000u, "Boltz top-up", 3600u) + + val swap = boltzCreateSubmarineSwap( + network = BoltzNetwork.MAINNET, + electrumUrl = "ssl://electrum.blockstream.info:50002", + invoice = invoice, + mnemonic = wallet.mnemonic, + bip39Passphrase = null, + ) + lightningService.node.onchainPayment().sendToAddress(swap.address, swap.expectedAmountSat) + // On failure: boltzRefundSubmarineSwap(swap.id, refundAddress, wallet.mnemonic, null, null) +} +``` + +## Recovery after restart + +On startup (after `initDb`), resume tracking and surface anything actionable: + +```kotlin +// re-subscribes all pending swaps; holds the mnemonic to auto-claim +boltzStartSwapUpdates(BoltzNetwork.MAINNET, SwapListener(), wallet.mnemonic, null, feeService.currentSatPerVb()) +val pending = boltzListPendingSwaps() // for UI / manual refunds +``` + +`boltzListPendingSwaps` returns every non-terminal swap; combined with the wallet +seed (keys are re-derived from each swap's index), an interrupted reverse swap can +still be claimed and a failed submarine swap refunded. If `boltz.db` itself was +lost, recover via Boltz's rescue API using the seed (see *Keys, secrets & +recovery* above). + +## Testing + +```bash +cargo test modules::boltz # unit tests (offline) +cargo test modules::boltz -- --ignored --nocapture # live E2E (testnet) +BOLTZ_LIVE_NETWORK=mainnet cargo test modules::boltz -- --ignored --nocapture +``` + +The offline tests cover status mapping, DB round-trip/recovery, monotonic index +reservation, and deterministic key/preimage derivation (same seed+index → +same key; distinct indices/passphrases → distinct keys). The **claim/refund +broadcast paths are not yet covered by an automated test** — they require a +regtest Boltz + Electrum stack; the live test exercises swap creation and +cryptographically validates the locally-derived redeem script and invoice +against Boltz's response, but does not broadcast. A regtest end-to-end test that +actually claims and refunds is the recommended follow-up. + +The live test creates a real reverse swap and asserts the locally-derived redeem +script and invoice match Boltz's response (the guarantee that a later claim is +valid). The swap is never paid and simply expires — no funds move. It skips +gracefully if the Boltz endpoint is temporarily unavailable. diff --git a/src/modules/boltz/api.rs b/src/modules/boltz/api.rs new file mode 100644 index 0000000..1720fa8 --- /dev/null +++ b/src/modules/boltz/api.rs @@ -0,0 +1,246 @@ +use crate::modules::boltz::client::build_boltz_client; +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::models::{derive_swap_keypair, BoltzDB, SwapRecord}; +use crate::modules::boltz::types::{ + BoltzNetwork, BoltzPairInfo, BoltzSwapType, ReverseSwapResponse, SubmarineSwapResponse, +}; +use boltz_client::swaps::boltz::{CreateReverseRequest, CreateSubmarineRequest}; +use boltz_client::util::secrets::Preimage; + +/// Raw status string a freshly created swap starts in. +const STATUS_CREATED: &str = "swap.created"; + +/// Fetch fees and limits for submarine swaps (onchain -> Lightning) on `network`. +pub async fn get_submarine_limits(network: BoltzNetwork) -> Result { + let client = build_boltz_client(network); + let pairs = client + .get_submarine_pairs() + .await + .map_err(map_api_err("fetch submarine pairs"))?; + let pair = pairs.get_btc_to_btc_pair().ok_or(BoltzError::ApiError { + error_details: "BTC submarine pair unavailable".to_string(), + })?; + Ok(BoltzPairInfo { + hash: pair.hash, + rate: pair.rate, + minimal_sat: pair.limits.minimal, + maximal_sat: pair.limits.maximal, + fee_percentage: pair.fees.percentage, + miner_fees_sat: pair.fees.miner_fees, + }) +} + +/// Fetch fees and limits for reverse swaps (Lightning -> onchain) on `network`. +pub async fn get_reverse_limits(network: BoltzNetwork) -> Result { + let client = build_boltz_client(network); + let pairs = client + .get_reverse_pairs() + .await + .map_err(map_api_err("fetch reverse pairs"))?; + let pair = pairs.get_btc_to_btc_pair().ok_or(BoltzError::ApiError { + error_details: "BTC reverse pair unavailable".to_string(), + })?; + Ok(BoltzPairInfo { + hash: pair.hash, + rate: pair.rate, + minimal_sat: pair.limits.minimal, + maximal_sat: pair.limits.maximal, + fee_percentage: pair.fees.percentage, + miner_fees_sat: pair.fees.miner_fees.lockup + pair.fees.miner_fees.claim, + }) +} + +impl BoltzDB { + /// Create a submarine swap (onchain -> Lightning). + /// + /// `invoice` is the BOLT11 invoice the caller's Lightning node generated for + /// the amount it wants to receive. Boltz returns a lockup address the caller + /// funds from its onchain wallet. The refund key is derived deterministically + /// from `mnemonic` at a freshly reserved index (persisted, not the key), so + /// the swap can be refunded — and recovered from the seed — if Boltz fails to + /// pay the invoice. + pub async fn create_submarine_swap( + &self, + network: BoltzNetwork, + electrum_url: String, + invoice: String, + mnemonic: String, + bip39_passphrase: Option, + ) -> Result { + if invoice.trim().is_empty() { + return Err(BoltzError::InvalidInput { + error_details: "invoice must not be empty".to_string(), + }); + } + + let swap_index = self.reserve_swap_index().await?; + let keypair = + derive_swap_keypair(&mnemonic, bip39_passphrase.as_deref(), network, swap_index)?; + let refund_public_key = bitcoin::PublicKey::new(keypair.public_key()); + + let client = build_boltz_client(network); + let request = CreateSubmarineRequest { + from: "BTC".to_string(), + to: "BTC".to_string(), + invoice: invoice.clone(), + refund_public_key, + pair_hash: None, + referral_id: None, + webhook: None, + }; + let response = client + .post_swap_req(&request) + .await + .map_err(map_api_err("create submarine swap"))?; + + // Prove Boltz's response before persisting it or handing the caller a + // lockup address to fund: this rebuilds the swap script from our own + // refund key and the invoice's preimage hash and checks it hashes to the + // address Boltz returned. Without it, a malicious or buggy server could + // hand back an address we cannot refund from. + response + .validate(&invoice, &refund_public_key, network.as_chain()) + .map_err(|e| BoltzError::SwapError { + error_details: format!("Boltz submarine response failed validation: {}", e), + })?; + + let record = SwapRecord { + id: response.id.clone(), + swap_type: BoltzSwapType::Submarine, + status: STATUS_CREATED.to_string(), + network, + electrum_url, + swap_index, + invoice: Some(invoice), + lockup_address: Some(response.address.clone()), + onchain_address: None, + amount_sat: response.expected_amount, + onchain_amount_sat: None, + timeout_block_height: response.timeout_block_height, + create_response_json: serde_json::to_string(&response)?, + claim_tx_id: None, + refund_tx_id: None, + created_at: now_secs(), + }; + self.insert_swap(&record).await?; + + Ok(SubmarineSwapResponse { + id: response.id, + address: response.address, + bip21: response.bip21, + expected_amount_sat: response.expected_amount, + accept_zero_conf: response.accept_zero_conf, + timeout_block_height: response.timeout_block_height, + }) + } + + /// Create a reverse swap (Lightning -> onchain). + /// + /// `amount_sat` is the Lightning amount the caller will pay; `claim_address` + /// is the onchain address the received funds are claimed to (typically a + /// fresh address from the caller's onchain wallet). The claim key and the + /// preimage are derived deterministically from `mnemonic` at a freshly + /// reserved index (the preimage is `sha256(swap_key)`), so the claim can be + /// made — and recovered from the seed — once Boltz locks the onchain funds. + pub async fn create_reverse_swap( + &self, + network: BoltzNetwork, + electrum_url: String, + amount_sat: u64, + claim_address: String, + mnemonic: String, + bip39_passphrase: Option, + ) -> Result { + if amount_sat == 0 { + return Err(BoltzError::InvalidInput { + error_details: "amount_sat must be greater than 0".to_string(), + }); + } + if claim_address.trim().is_empty() { + return Err(BoltzError::InvalidInput { + error_details: "claim_address must not be empty".to_string(), + }); + } + + let swap_index = self.reserve_swap_index().await?; + let keypair = + derive_swap_keypair(&mnemonic, bip39_passphrase.as_deref(), network, swap_index)?; + let claim_public_key = bitcoin::PublicKey::new(keypair.public_key()); + let preimage = Preimage::from_swap_key(&keypair); + + let client = build_boltz_client(network); + let request = CreateReverseRequest { + from: "BTC".to_string(), + to: "BTC".to_string(), + claim_public_key, + invoice: None, + invoice_amount: Some(amount_sat), + preimage_hash: Some(preimage.sha256), + description: None, + description_hash: None, + address: None, + address_signature: None, + referral_id: None, + webhook: None, + }; + let response = client + .post_reverse_req(request) + .await + .map_err(map_api_err("create reverse swap"))?; + + // Prove Boltz's response before persisting it or handing the caller an + // invoice to pay: this checks the invoice commits to our preimage hash + // and that the swap script rebuilt from our own claim key hashes to the + // lockup address Boltz returned. Without it, the caller could pay an + // invoice for funds it has no key to claim. + response + .validate(&preimage, &claim_public_key, network.as_chain()) + .map_err(|e| BoltzError::SwapError { + error_details: format!("Boltz reverse response failed validation: {}", e), + })?; + + let invoice = response.invoice.clone().ok_or(BoltzError::ApiError { + error_details: "Reverse swap response missing invoice".to_string(), + })?; + let timeout_block_height = response.timeout_block_height as u64; + let onchain_amount = response.onchain_amount; + + let record = SwapRecord { + id: response.id.clone(), + swap_type: BoltzSwapType::Reverse, + status: STATUS_CREATED.to_string(), + network, + electrum_url, + swap_index, + invoice: Some(invoice.clone()), + lockup_address: Some(response.lockup_address.clone()), + onchain_address: Some(claim_address), + amount_sat, + onchain_amount_sat: Some(onchain_amount), + timeout_block_height, + create_response_json: serde_json::to_string(&response)?, + claim_tx_id: None, + refund_tx_id: None, + created_at: now_secs(), + }; + self.insert_swap(&record).await?; + + Ok(ReverseSwapResponse { + id: response.id, + invoice, + lockup_address: response.lockup_address, + onchain_amount_sat: onchain_amount, + timeout_block_height, + }) + } +} + +fn now_secs() -> u64 { + chrono::Utc::now().timestamp().max(0) as u64 +} + +fn map_api_err(context: &'static str) -> impl Fn(boltz_client::error::Error) -> BoltzError { + move |e| BoltzError::ApiError { + error_details: format!("Failed to {}: {}", context, e), + } +} diff --git a/src/modules/boltz/claim.rs b/src/modules/boltz/claim.rs new file mode 100644 index 0000000..7d65bdb --- /dev/null +++ b/src/modules/boltz/claim.rs @@ -0,0 +1,126 @@ +use crate::modules::boltz::client::{build_boltz_client, build_chain_client}; +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::guard::lock_swap; +use crate::modules::boltz::models::{BoltzDB, SwapRecord}; +use boltz_client::swaps::{SwapScript, SwapTransactionParams, TransactionOptions}; +use boltz_client::util::fees::Fee; + +/// Default claim fee rate in sat/vByte used when the caller doesn't specify one. +pub(crate) const DEFAULT_FEERATE_SAT_PER_VB: f64 = 2.0; + +/// Result of a guarded claim attempt. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ClaimOutcome { + /// A claim transaction was built and broadcast by this call. + Broadcast(String), + /// The swap already had a claim txid recorded, so nothing was broadcast. + AlreadyClaimed(String), +} + +impl ClaimOutcome { + /// The claim transaction id, however it was arrived at. + pub fn txid(self) -> String { + match self { + ClaimOutcome::Broadcast(txid) | ClaimOutcome::AlreadyClaimed(txid) => txid, + } + } +} + +/// Claim a reverse swap, serialized against any other claim of the same swap. +/// +/// This is the only path that should broadcast a claim. It holds the swap's lock +/// across the whole read-broadcast-record sequence, so the automatic claim from +/// the updates stream and a manual recovery call cannot both broadcast: whichever +/// arrives second re-reads the swap under the lock, finds the first one's txid, +/// and returns it as [`ClaimOutcome::AlreadyClaimed`]. +pub async fn claim_reverse_swap_guarded( + db: &BoltzDB, + swap_id: &str, + mnemonic: &str, + bip39_passphrase: Option<&str>, + fee_rate_sat_per_vb: Option, +) -> Result { + let _guard = lock_swap(swap_id).await; + + // Re-read under the lock. The record the caller checked may be stale: a + // concurrent claim can have completed while we waited to acquire. + let record = db + .get_swap(swap_id) + .await? + .ok_or_else(|| BoltzError::NotFound { + error_details: format!("Swap {} not found", swap_id), + })?; + if let Some(existing) = record.claim_tx_id { + return Ok(ClaimOutcome::AlreadyClaimed(existing)); + } + + let txid = claim_reverse_swap(&record, mnemonic, bip39_passphrase, fee_rate_sat_per_vb).await?; + db.set_claim_tx(swap_id, &txid).await?; + Ok(ClaimOutcome::Broadcast(txid)) +} + +/// Claim a reverse swap's onchain funds to the address captured at creation, +/// revealing the preimage so Boltz can settle the Lightning invoice. +/// +/// A cooperative (key-path) claim is attempted first for a smaller, cheaper +/// transaction; if Boltz declines to cooperate it falls back to the script-path +/// spend, which is always available while the lockup is unspent. Returns the +/// broadcast claim transaction id. +pub async fn claim_reverse_swap( + record: &SwapRecord, + mnemonic: &str, + bip39_passphrase: Option<&str>, + fee_rate_sat_per_vb: Option, +) -> Result { + let reverse_resp = record.reverse_response()?; + let keypair = record.keypair(mnemonic, bip39_passphrase)?; + let preimage = record.preimage(mnemonic, bip39_passphrase)?; + let our_pubkey = bitcoin::PublicKey::new(keypair.public_key()); + + let claim_address = record + .onchain_address + .clone() + .ok_or_else(|| BoltzError::InvalidInput { + error_details: "Reverse swap is missing a claim address".to_string(), + })?; + + let chain = record.network.as_chain(); + let swap_script = SwapScript::reverse_from_swap_resp(chain, &reverse_resp, our_pubkey)?; + let chain_client = build_chain_client(record.network, &record.electrum_url)?; + let boltz_client = build_boltz_client(record.network); + let fee = Fee::Relative(fee_rate_sat_per_vb.unwrap_or(DEFAULT_FEERATE_SAT_PER_VB)); + + let make_params = |cooperative: bool| SwapTransactionParams { + keys: keypair, + output_address: claim_address.clone(), + fee, + swap_id: record.id.clone(), + chain_client: &chain_client, + boltz_client: &boltz_client, + options: Some(TransactionOptions::default().with_cooperative(cooperative)), + }; + + // Try the cooperative key-path claim first; fall back to the script path. + let tx = match swap_script + .construct_claim(&preimage, make_params(true)) + .await + { + Ok(tx) => tx, + Err(coop_err) => swap_script + .construct_claim(&preimage, make_params(false)) + .await + .map_err(|script_err| BoltzError::SwapError { + error_details: format!( + "Claim failed (cooperative: {}; script-path: {})", + coop_err, script_err + ), + })?, + }; + + chain_client + .broadcast_tx(&tx) + .await + .map_err(|e| BoltzError::BroadcastError { + error_details: format!("Failed to broadcast claim transaction: {}", e), + }) +} diff --git a/src/modules/boltz/client.rs b/src/modules/boltz/client.rs new file mode 100644 index 0000000..719eb01 --- /dev/null +++ b/src/modules/boltz/client.rs @@ -0,0 +1,70 @@ +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::types::BoltzNetwork; +use boltz_client::network::electrum::ElectrumBitcoinClient; +use boltz_client::swaps::boltz::BoltzApiClientV2; +use boltz_client::swaps::ChainClient; + +/// Electrum socket timeout, in seconds, used when building swap chain clients. +const ELECTRUM_TIMEOUT_SECS: u8 = 30; + +/// Build a Boltz REST client for the given network. +pub fn build_boltz_client(network: BoltzNetwork) -> BoltzApiClientV2 { + BoltzApiClientV2::default(network.as_client_network()) +} + +/// Build an Electrum-backed [`ChainClient`] for broadcasting and fetching swap +/// UTXOs. `electrum_url` accepts the same `ssl://`/`tcp://` scheme conventions +/// used elsewhere in the crate; a bare `host:port` is treated as TLS. +pub fn build_chain_client( + network: BoltzNetwork, + electrum_url: &str, +) -> Result { + let (host_port, tls) = parse_electrum_url(electrum_url); + let client = ElectrumBitcoinClient::new( + network.as_bitcoin_chain(), + &host_port, + tls, + tls, + ELECTRUM_TIMEOUT_SECS, + ) + .map_err(|e| BoltzError::ConnectionError { + error_details: format!("Failed to connect to Electrum server: {}", e), + })?; + Ok(ChainClient::new().with_bitcoin(client)) +} + +/// Split an Electrum URL into a `host:port` and a TLS flag. `ssl://` and +/// `tls://` mean TLS; `tcp://` means plaintext; a scheme-less URL defaults to +/// TLS (the safe default for public servers). +fn parse_electrum_url(url: &str) -> (String, bool) { + if let Some(rest) = url.strip_prefix("ssl://") { + (rest.to_string(), true) + } else if let Some(rest) = url.strip_prefix("tls://") { + (rest.to_string(), true) + } else if let Some(rest) = url.strip_prefix("tcp://") { + (rest.to_string(), false) + } else { + (url.to_string(), true) + } +} + +#[cfg(test)] +mod tests { + use super::parse_electrum_url; + + #[test] + fn parses_schemes() { + assert_eq!( + parse_electrum_url("ssl://electrum.example.com:50002"), + ("electrum.example.com:50002".to_string(), true) + ); + assert_eq!( + parse_electrum_url("tcp://10.0.0.1:50001"), + ("10.0.0.1:50001".to_string(), false) + ); + assert_eq!( + parse_electrum_url("electrum.example.com:50002"), + ("electrum.example.com:50002".to_string(), true) + ); + } +} diff --git a/src/modules/boltz/db.rs b/src/modules/boltz/db.rs new file mode 100644 index 0000000..ab2c9b6 --- /dev/null +++ b/src/modules/boltz/db.rs @@ -0,0 +1,231 @@ +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::models::{ + BoltzDB, SwapRecord, CREATE_META_TABLE, CREATE_SWAPS_TABLE, SCHEMA_VERSION, +}; +use crate::modules::boltz::types::{BoltzNetwork, BoltzSwapStatus, BoltzSwapType}; +use rusqlite::{params, Connection, OptionalExtension, Row}; + +/// Counter key in `swap_meta` for the next deterministic swap index. +const NEXT_SWAP_INDEX_KEY: &str = "next_swap_index"; + +impl BoltzDB { + /// Open (or create) the swaps database at `db_path` and run migrations. + pub async fn new(db_path: &str) -> Result { + let conn = Connection::open(db_path).map_err(|e| BoltzError::InitializationError { + error_details: format!("Error opening database: {}", e), + })?; + let db = BoltzDB { + conn: tokio::sync::Mutex::new(conn), + }; + db.initialize().await?; + Ok(db) + } + + async fn initialize(&self) -> Result<(), BoltzError> { + let conn = self.conn.lock().await; + conn.execute(CREATE_SWAPS_TABLE, []) + .map_err(|e| BoltzError::InitializationError { + error_details: format!("Failed to create swaps table: {}", e), + })?; + conn.execute(CREATE_META_TABLE, []) + .map_err(|e| BoltzError::InitializationError { + error_details: format!("Failed to create swap_meta table: {}", e), + })?; + conn.pragma_update(None, "user_version", SCHEMA_VERSION) + .map_err(|e| BoltzError::InitializationError { + error_details: format!("Failed to set schema version: {}", e), + })?; + Ok(()) + } + + /// Atomically reserve the next deterministic swap index. The connection + /// mutex serializes all access, so the read-then-write below cannot + /// interleave with another reservation. Indices are monotonic and never + /// reused, so each swap derives a unique key even if a creation later fails. + pub async fn reserve_swap_index(&self) -> Result { + let conn = self.conn.lock().await; + let current: Option = conn + .query_row( + "SELECT value FROM swap_meta WHERE key = ?1", + params![NEXT_SWAP_INDEX_KEY], + |row| row.get(0), + ) + .optional()?; + let index = current.unwrap_or(0); + conn.execute( + "INSERT INTO swap_meta (key, value) VALUES (?1, ?2) + ON CONFLICT(key) DO UPDATE SET value = ?2", + params![NEXT_SWAP_INDEX_KEY, index + 1], + )?; + Ok(index as u64) + } + + /// Insert a newly-created swap. + pub async fn insert_swap(&self, record: &SwapRecord) -> Result<(), BoltzError> { + let conn = self.conn.lock().await; + conn.execute( + "INSERT INTO swaps ( + id, swap_type, status, network, electrum_url, swap_index, + invoice, lockup_address, onchain_address, amount_sat, onchain_amount_sat, + timeout_block_height, create_response_json, claim_tx_id, refund_tx_id, created_at + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)", + params![ + record.id, + record.swap_type.as_str(), + record.status, + record.network.as_str(), + record.electrum_url, + record.swap_index as i64, + record.invoice, + record.lockup_address, + record.onchain_address, + record.amount_sat as i64, + record.onchain_amount_sat.map(|v| v as i64), + record.timeout_block_height as i64, + record.create_response_json, + record.claim_tx_id, + record.refund_tx_id, + record.created_at as i64, + ], + ) + .map_err(|e| BoltzError::DatabaseError { + error_details: format!("Failed to insert swap: {}", e), + })?; + Ok(()) + } + + /// Update the raw status string of a swap. + pub async fn update_status(&self, swap_id: &str, status: &str) -> Result<(), BoltzError> { + let conn = self.conn.lock().await; + conn.execute( + "UPDATE swaps SET status = ?1 WHERE id = ?2", + params![status, swap_id], + )?; + Ok(()) + } + + /// Record the broadcast claim transaction id for a swap and mark it claimed. + /// + /// The `status` column is normally advanced only by the live updates stream, so + /// claiming (manual or automatic) would otherwise leave a swap showing its + /// pre-claim status. Setting the terminal `transaction.claimed` status here keeps + /// the persisted state truthful and drops the swap from the pending set. + pub async fn set_claim_tx(&self, swap_id: &str, txid: &str) -> Result<(), BoltzError> { + let conn = self.conn.lock().await; + conn.execute( + "UPDATE swaps SET claim_tx_id = ?1, status = 'transaction.claimed' WHERE id = ?2", + params![txid, swap_id], + )?; + Ok(()) + } + + /// Record the broadcast refund transaction id for a swap and mark it refunded. + pub async fn set_refund_tx(&self, swap_id: &str, txid: &str) -> Result<(), BoltzError> { + let conn = self.conn.lock().await; + conn.execute( + "UPDATE swaps SET refund_tx_id = ?1, status = 'transaction.refunded' WHERE id = ?2", + params![txid, swap_id], + )?; + Ok(()) + } + + /// Fetch a single swap by id. + pub async fn get_swap(&self, swap_id: &str) -> Result, BoltzError> { + let conn = self.conn.lock().await; + let record = conn + .query_row( + "SELECT id, swap_type, status, network, electrum_url, swap_index, + invoice, lockup_address, onchain_address, amount_sat, onchain_amount_sat, + timeout_block_height, create_response_json, claim_tx_id, refund_tx_id, + created_at + FROM swaps WHERE id = ?1", + params![swap_id], + row_to_record, + ) + .optional()?; + record.transpose() + } + + /// List every persisted swap, newest first. + pub async fn list_swaps(&self) -> Result, BoltzError> { + self.query_swaps( + "SELECT id, swap_type, status, network, electrum_url, swap_index, + invoice, lockup_address, onchain_address, amount_sat, onchain_amount_sat, + timeout_block_height, create_response_json, claim_tx_id, refund_tx_id, created_at + FROM swaps ORDER BY created_at DESC", + ) + .await + } + + /// List swaps that have not reached a terminal state, for recovery and for + /// resubscribing to status updates after a restart. + pub async fn list_pending_swaps(&self) -> Result, BoltzError> { + Ok(self + .list_swaps() + .await? + .into_iter() + .filter(|r| !BoltzSwapStatus::from_raw(&r.status).is_terminal()) + .collect()) + } + + async fn query_swaps(&self, sql: &str) -> Result, BoltzError> { + let conn = self.conn.lock().await; + let mut stmt = conn.prepare(sql)?; + let rows = stmt.query_map([], row_to_record)?; + let mut records = Vec::new(); + for row in rows { + records.push(row??); + } + Ok(records) + } +} + +/// Map a SQLite row to a [`SwapRecord`]. The outer `rusqlite::Result` covers +/// column-access failures; the inner `Result` covers +/// decoding of persisted enum strings (`swap_type`, `network`). No secrets are +/// stored, so there is no key material to decode here. +fn row_to_record(row: &Row) -> rusqlite::Result> { + let swap_type_str: String = row.get(1)?; + let network_str: String = row.get(3)?; + let amount_sat: i64 = row.get(9)?; + let onchain_amount_sat: Option = row.get(10)?; + let timeout_block_height: i64 = row.get(11)?; + let created_at: i64 = row.get(15)?; + let swap_index: i64 = row.get(5)?; + + let swap_type = match BoltzSwapType::from_str(&swap_type_str) { + Some(t) => t, + None => { + return Ok(Err(BoltzError::DatabaseError { + error_details: format!("Unknown swap_type: {}", swap_type_str), + })) + } + }; + let network = match BoltzNetwork::from_str(&network_str) { + Some(n) => n, + None => { + return Ok(Err(BoltzError::DatabaseError { + error_details: format!("Unknown network: {}", network_str), + })) + } + }; + + Ok(Ok(SwapRecord { + id: row.get(0)?, + swap_type, + status: row.get(2)?, + network, + electrum_url: row.get(4)?, + swap_index: swap_index as u64, + invoice: row.get(6)?, + lockup_address: row.get(7)?, + onchain_address: row.get(8)?, + amount_sat: amount_sat as u64, + onchain_amount_sat: onchain_amount_sat.map(|v| v as u64), + timeout_block_height: timeout_block_height as u64, + create_response_json: row.get(12)?, + claim_tx_id: row.get(13)?, + refund_tx_id: row.get(14)?, + created_at: created_at as u64, + })) +} diff --git a/src/modules/boltz/errors.rs b/src/modules/boltz/errors.rs new file mode 100644 index 0000000..f4d7a89 --- /dev/null +++ b/src/modules/boltz/errors.rs @@ -0,0 +1,56 @@ +use thiserror::Error; + +/// Errors surfaced by the Boltz swaps module. +#[derive(uniffi::Error, Debug, Error)] +pub enum BoltzError { + #[error("Database initialization failed: {error_details}")] + InitializationError { error_details: String }, + + #[error("Database connection error: {error_details}")] + ConnectionError { error_details: String }, + + #[error("Database error: {error_details}")] + DatabaseError { error_details: String }, + + #[error("Boltz API error: {error_details}")] + ApiError { error_details: String }, + + #[error("Swap error: {error_details}")] + SwapError { error_details: String }, + + #[error("Broadcast error: {error_details}")] + BroadcastError { error_details: String }, + + #[error("Invalid input: {error_details}")] + InvalidInput { error_details: String }, + + #[error("Serialization error: {error_details}")] + SerializationError { error_details: String }, + + #[error("Swap not found: {error_details}")] + NotFound { error_details: String }, +} + +impl From for BoltzError { + fn from(err: boltz_client::error::Error) -> Self { + BoltzError::SwapError { + error_details: err.to_string(), + } + } +} + +impl From for BoltzError { + fn from(err: serde_json::Error) -> Self { + BoltzError::SerializationError { + error_details: err.to_string(), + } + } +} + +impl From for BoltzError { + fn from(err: rusqlite::Error) -> Self { + BoltzError::DatabaseError { + error_details: err.to_string(), + } + } +} diff --git a/src/modules/boltz/guard.rs b/src/modules/boltz/guard.rs new file mode 100644 index 0000000..d782ad6 --- /dev/null +++ b/src/modules/boltz/guard.rs @@ -0,0 +1,32 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::{Arc, Mutex as StdMutex}; +use tokio::sync::{Mutex as TokioMutex, OwnedMutexGuard}; + +/// Per-swap locks that serialize claim and refund attempts. +/// +/// Claiming reads the swap, broadcasts, then records the txid. Those steps span +/// `.await` points (Boltz and Electrum round trips), so two callers can +/// interleave: the automatic claim driven by the updates stream and a manual +/// `boltz_claim_reverse_swap` recovery call can both observe no recorded txid +/// and both broadcast. Holding the swap's lock across the whole +/// read-broadcast-record sequence makes it atomic, so the second caller sees the +/// first caller's txid and returns it instead of broadcasting again. +/// +/// The map only ever holds one small entry per swap that has had a claim or +/// refund attempted, so it is left to grow rather than reference-counted down; +/// pruning would race with a caller that is waiting to acquire the same lock. +static SWAP_LOCKS: Lazy>>>> = + Lazy::new(|| StdMutex::new(HashMap::new())); + +/// Acquire the lock for `swap_id`, waiting for any in-flight claim or refund on +/// the same swap to finish. The returned guard releases the lock when dropped. +pub(crate) async fn lock_swap(swap_id: &str) -> OwnedMutexGuard<()> { + let lock = { + let mut locks = SWAP_LOCKS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + locks.entry(swap_id.to_string()).or_default().clone() + }; + lock.lock_owned().await +} diff --git a/src/modules/boltz/listener.rs b/src/modules/boltz/listener.rs new file mode 100644 index 0000000..47d7be9 --- /dev/null +++ b/src/modules/boltz/listener.rs @@ -0,0 +1,224 @@ +use crate::modules::boltz::claim::{claim_reverse_swap_guarded, ClaimOutcome}; +use crate::modules::boltz::client::build_boltz_client; +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::models::{BoltzDB, SwapRecord}; +use crate::modules::boltz::types::{BoltzNetwork, BoltzSwapEvent, BoltzSwapStatus, BoltzSwapType}; +use boltz_client::swaps::boltz::{BoltzWsApi, BoltzWsConfig, SwapStatus}; +use once_cell::sync::OnceCell; +use std::sync::Arc; +use tokio::sync::broadcast::error::RecvError; +use tokio::sync::Mutex as TokioMutex; +use tokio::task::JoinHandle; + +/// Callback interface for receiving Boltz swap lifecycle events. +/// +/// Implement this in Swift/Kotlin/Python and register it via +/// `boltz_start_swap_updates` to receive typed notifications as swaps progress. +/// Reverse swaps are claimed automatically; the [`BoltzSwapEvent::Claimed`] +/// event reports the resulting transaction id. +#[uniffi::export(with_foreign)] +pub trait BoltzEventListener: Send + Sync { + fn on_event(&self, event: BoltzSwapEvent); +} + +struct UpdatesHandle { + ws: Arc, + network: BoltzNetwork, + ws_task: JoinHandle<()>, + process_task: JoinHandle<()>, +} + +/// Configuration the background stream needs to perform automatic +/// reverse-swap claims. The wallet credentials (used to re-derive swap keys) +/// are held in memory only for the lifetime of the updates stream (dropped on +/// [`stop_swap_updates`]); never persisted. +#[derive(Clone)] +struct AutoClaimConfig { + mnemonic: String, + bip39_passphrase: Option, + /// Fee rate, in sat/vByte, used for the automatic claim transaction. Supplied + /// by the caller (Bitkit owns fee estimation); falls back to + /// [`crate::modules::boltz::claim::DEFAULT_FEERATE_SAT_PER_VB`] when `None`. + fee_rate_sat_per_vb: Option, +} + +static SWAP_UPDATES: OnceCell>> = OnceCell::new(); + +fn updates_cell() -> &'static TokioMutex> { + SWAP_UPDATES.get_or_init(|| TokioMutex::new(None)) +} + +/// Open a Boltz WebSocket for `network`, subscribe to every pending swap, and +/// drive their lifecycle until [`stop_swap_updates`] is called. +/// +/// Any previously running updates stream is stopped first; only one stream (for +/// one network) runs at a time. `mnemonic` is held in memory for the lifetime of +/// the stream so confirmed reverse swaps can be auto-claimed (their keys are +/// re-derived on demand). `fee_rate_sat_per_vb` is the fee rate used for those +/// automatic claims (Bitkit supplies the current rate; `None` falls back to a +/// conservative default). Must be invoked from within a Tokio runtime context +/// (it spawns background tasks). +pub async fn start_swap_updates( + db: Arc, + network: BoltzNetwork, + listener: Arc, + mnemonic: String, + bip39_passphrase: Option, + fee_rate_sat_per_vb: Option, +) -> Result<(), BoltzError> { + stop_swap_updates().await; + let config = AutoClaimConfig { + mnemonic, + bip39_passphrase, + fee_rate_sat_per_vb, + }; + + let boltz_client = build_boltz_client(network); + let ws = Arc::new(BoltzWsApi::new( + boltz_client.get_ws_url(), + BoltzWsConfig::default(), + )); + + let ws_task = tokio::spawn(ws.clone().run_ws_loop()); + + // Subscribe to every non-terminal swap for this network. + let pending = db.list_pending_swaps().await?; + for record in pending.iter().filter(|r| r.network == network) { + if let Err(e) = ws.subscribe_swap(&record.id).await { + log::warn!("Failed to subscribe to swap {}: {}", record.id, e); + } + } + + let process_task = { + let ws = ws.clone(); + let db = db.clone(); + let listener = listener.clone(); + let config = config.clone(); + tokio::spawn(async move { + let mut updates = ws.updates(); + loop { + match updates.recv().await { + Ok(status) => process_status(&db, listener.as_ref(), &config, status).await, + Err(RecvError::Lagged(_)) => continue, + Err(RecvError::Closed) => break, + } + } + }) + }; + + let mut guard = updates_cell().lock().await; + *guard = Some(UpdatesHandle { + ws, + network, + ws_task, + process_task, + }); + Ok(()) +} + +/// Stop the running updates stream, if any, and tear down its tasks. +pub async fn stop_swap_updates() { + let mut guard = updates_cell().lock().await; + if let Some(handle) = guard.take() { + handle.process_task.abort(); + handle.ws_task.abort(); + // Dropping the last `Arc` triggers its shutdown. + drop(handle.ws); + } +} + +/// If an updates stream is running for `network`, subscribe it to `swap_id` so +/// newly created swaps are tracked without restarting the stream. +pub async fn subscribe_if_active(network: BoltzNetwork, swap_id: &str) { + let guard = updates_cell().lock().await; + if let Some(handle) = guard.as_ref() { + if handle.network == network { + if let Err(e) = handle.ws.subscribe_swap(swap_id).await { + log::warn!("Failed to subscribe to swap {}: {}", swap_id, e); + } + } + } +} + +/// Handle a single status update: persist it, notify the listener, and trigger +/// an automatic claim for reverse swaps whose lockup is now spendable. +async fn process_status( + db: &Arc, + listener: &dyn BoltzEventListener, + config: &AutoClaimConfig, + status: SwapStatus, +) { + let swap_id = status.id.clone(); + let raw = status.status.clone(); + + if let Err(e) = db.update_status(&swap_id, &raw).await { + log::warn!("Failed to persist status for swap {}: {}", swap_id, e); + } + + listener.on_event(BoltzSwapEvent::StatusUpdate { + swap_id: swap_id.clone(), + status: BoltzSwapStatus::from_raw(&raw), + }); + + let record = match db.get_swap(&swap_id).await { + Ok(Some(record)) => record, + Ok(None) => return, + Err(e) => { + log::warn!("Failed to load swap {} after update: {}", swap_id, e); + return; + } + }; + + if should_auto_claim(&record, &raw) { + auto_claim(db, listener, config, &record).await; + } +} + +/// A reverse swap is auto-claimed once Boltz's lockup *confirms*, provided it +/// hasn't already been claimed. +/// +/// Claiming reveals the preimage, which lets Boltz settle the Lightning +/// invoice. Doing that against an unconfirmed (mempool-only) lockup risks the +/// preimage leaking before the lockup confirms — if the lockup were then +/// replaced, the user could be debited on Lightning without receiving onchain +/// funds. We therefore wait for confirmation here; a caller that accepts the +/// 0-conf risk can still claim early via `boltz_claim_reverse_swap`. +fn should_auto_claim(record: &SwapRecord, raw_status: &str) -> bool { + record.swap_type == BoltzSwapType::Reverse + && record.claim_tx_id.is_none() + && raw_status == "transaction.confirmed" +} + +/// Claim through the guarded path, which serializes against a concurrent manual +/// claim and persists the txid while still holding the swap's lock. If that +/// manual claim won the race, this call broadcasts nothing and stays quiet: +/// its caller already received the txid, so re-emitting [`BoltzSwapEvent::Claimed`] +/// would double-report the same claim. +async fn auto_claim( + db: &Arc, + listener: &dyn BoltzEventListener, + config: &AutoClaimConfig, + record: &SwapRecord, +) { + match claim_reverse_swap_guarded( + db, + &record.id, + &config.mnemonic, + config.bip39_passphrase.as_deref(), + config.fee_rate_sat_per_vb, + ) + .await + { + Ok(ClaimOutcome::Broadcast(txid)) => listener.on_event(BoltzSwapEvent::Claimed { + swap_id: record.id.clone(), + txid, + }), + Ok(ClaimOutcome::AlreadyClaimed(txid)) => { + log::info!("Swap {} was already claimed by tx {}", record.id, txid); + } + Err(e) => listener.on_event(BoltzSwapEvent::Error { + swap_id: record.id.clone(), + message: e.to_string(), + }), + } +} diff --git a/src/modules/boltz/mod.rs b/src/modules/boltz/mod.rs new file mode 100644 index 0000000..0ace725 --- /dev/null +++ b/src/modules/boltz/mod.rs @@ -0,0 +1,22 @@ +mod api; +mod claim; +mod client; +mod db; +mod errors; +mod guard; +mod listener; +mod models; +mod refund; +#[cfg(test)] +mod tests; +mod types; + +pub use api::{get_reverse_limits, get_submarine_limits}; +pub use claim::{claim_reverse_swap_guarded, ClaimOutcome}; +pub use errors::BoltzError; +pub use listener::{ + start_swap_updates, stop_swap_updates, subscribe_if_active, BoltzEventListener, +}; +pub use models::{BoltzDB, SwapRecord}; +pub use refund::refund_submarine_swap_guarded; +pub use types::*; diff --git a/src/modules/boltz/models.rs b/src/modules/boltz/models.rs new file mode 100644 index 0000000..b3115e4 --- /dev/null +++ b/src/modules/boltz/models.rs @@ -0,0 +1,155 @@ +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::types::{BoltzNetwork, BoltzSwap, BoltzSwapStatus, BoltzSwapType}; +use boltz_client::swaps::boltz::{CreateReverseResponse, CreateSubmarineResponse}; +use boltz_client::util::secrets::{Preimage, SwapMasterKey}; +use boltz_client::Keypair; +use rusqlite::Connection; +use tokio::sync::Mutex; + +/// SQLite-backed store for Boltz swaps. +/// +/// Wraps a single connection behind an async mutex (mirroring the blocktank +/// module). No swap secrets are persisted: each swap's key and (for reverse +/// swaps) preimage are *re-derived on demand* from the wallet mnemonic and the +/// swap's deterministic [`SwapRecord::swap_index`] via BIP85 +/// ([`derive_swap_keypair`]). The database therefore holds no key material — +/// only the index needed to reconstruct it given the seed. +pub struct BoltzDB { + pub(crate) conn: Mutex, +} + +/// Re-derive a swap's secp256k1 keypair from the wallet mnemonic. +/// +/// Keys are derived via Boltz's BIP85 scheme: the wallet mnemonic yields a +/// per-wallet swap master key, and each swap uses a unique `index` under it +/// (`m/26589'/0'/0'/{index}`). The same `(mnemonic, passphrase, index)` always +/// reproduces the same key, so swaps are recoverable from the seed alone — the +/// derived swap mnemonic can also be registered with Boltz's rescue API. +/// +/// The key value is independent of `network`; the parameter is accepted for +/// symmetry with the rest of the API and to match how the swap was created. +pub(crate) fn derive_swap_keypair( + mnemonic: &str, + passphrase: Option<&str>, + network: BoltzNetwork, + index: u64, +) -> Result { + let master = + SwapMasterKey::new(mnemonic, passphrase, network.as_client_network()).map_err(|e| { + BoltzError::InvalidInput { + error_details: format!("Invalid mnemonic or key derivation failed: {}", e), + } + })?; + master.derive_swapkey(index).map_err(BoltzError::from) +} + +pub const CREATE_SWAPS_TABLE: &str = "CREATE TABLE IF NOT EXISTS swaps ( + id TEXT PRIMARY KEY, + swap_type TEXT NOT NULL, + status TEXT NOT NULL, + network TEXT NOT NULL, + electrum_url TEXT NOT NULL, + swap_index INTEGER NOT NULL, + invoice TEXT, + lockup_address TEXT, + onchain_address TEXT, + amount_sat INTEGER NOT NULL, + onchain_amount_sat INTEGER, + timeout_block_height INTEGER NOT NULL, + create_response_json TEXT NOT NULL, + claim_tx_id TEXT, + refund_tx_id TEXT, + created_at INTEGER NOT NULL +)"; + +/// Single-row counter table backing monotonic [`BoltzDB::reserve_swap_index`] +/// allocation. Keeping the counter in its own table (rather than `MAX(index)+1`) +/// guarantees an index is never reused even if a later swap creation fails after +/// the index was reserved. +pub const CREATE_META_TABLE: &str = "CREATE TABLE IF NOT EXISTS swap_meta ( + key TEXT PRIMARY KEY, + value INTEGER NOT NULL +)"; + +/// Current `boltz.db` schema version, written to `PRAGMA user_version` so future +/// changes have a migration anchor. +pub const SCHEMA_VERSION: i64 = 1; + +/// Internal, fully-detailed representation of a persisted swap, including +/// secrets. This is never exposed across the FFI boundary — use +/// [`SwapRecord::to_boltz_swap`] to produce the public [`BoltzSwap`]. +#[derive(Debug, Clone)] +pub struct SwapRecord { + pub id: String, + pub swap_type: BoltzSwapType, + /// Raw Boltz status string (mapped to [`BoltzSwapStatus`] on the way out). + pub status: String, + pub network: BoltzNetwork, + pub electrum_url: String, + /// Deterministic BIP85 derivation index for this swap's key (and, for + /// reverse swaps, its preimage). The secrets themselves are never stored; + /// they are re-derived from the wallet mnemonic and this index. + pub swap_index: u64, + pub invoice: Option, + pub lockup_address: Option, + pub onchain_address: Option, + pub amount_sat: u64, + pub onchain_amount_sat: Option, + pub timeout_block_height: u64, + /// Serialized `CreateSubmarineResponse` or `CreateReverseResponse`, used to + /// reconstruct the swap script for claims/refunds. + pub create_response_json: String, + pub claim_tx_id: Option, + pub refund_tx_id: Option, + pub created_at: u64, +} + +impl SwapRecord { + /// Re-derive the client keypair from the wallet mnemonic and this swap's + /// [`SwapRecord::swap_index`]. + pub fn keypair(&self, mnemonic: &str, passphrase: Option<&str>) -> Result { + derive_swap_keypair(mnemonic, passphrase, self.network, self.swap_index) + } + + /// Re-derive the preimage (reverse swaps only) from the swap key. The + /// preimage is `sha256(swap_private_key)`, so it is reproducible from the + /// seed without ever being stored. + pub fn preimage( + &self, + mnemonic: &str, + passphrase: Option<&str>, + ) -> Result { + let keypair = self.keypair(mnemonic, passphrase)?; + Ok(Preimage::from_swap_key(&keypair)) + } + + /// Deserialize the stored submarine swap creation response. + pub fn submarine_response(&self) -> Result { + serde_json::from_str(&self.create_response_json).map_err(BoltzError::from) + } + + /// Deserialize the stored reverse swap creation response. + pub fn reverse_response(&self) -> Result { + serde_json::from_str(&self.create_response_json).map_err(BoltzError::from) + } + + /// Project to the public FFI type. + pub fn to_boltz_swap(&self) -> BoltzSwap { + BoltzSwap { + id: self.id.clone(), + swap_type: self.swap_type, + status: BoltzSwapStatus::from_raw(&self.status), + network: self.network, + swap_index: self.swap_index, + amount_sat: self.amount_sat, + onchain_amount_sat: self.onchain_amount_sat, + invoice: self.invoice.clone(), + lockup_address: self.lockup_address.clone(), + onchain_address: self.onchain_address.clone(), + timeout_block_height: self.timeout_block_height, + created_at: self.created_at, + claim_tx_id: self.claim_tx_id.clone(), + refund_tx_id: self.refund_tx_id.clone(), + } + } +} diff --git a/src/modules/boltz/refund.rs b/src/modules/boltz/refund.rs new file mode 100644 index 0000000..a33f3da --- /dev/null +++ b/src/modules/boltz/refund.rs @@ -0,0 +1,102 @@ +use crate::modules::boltz::claim::DEFAULT_FEERATE_SAT_PER_VB; +use crate::modules::boltz::client::{build_boltz_client, build_chain_client}; +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::guard::lock_swap; +use crate::modules::boltz::models::{BoltzDB, SwapRecord}; +use boltz_client::swaps::{SwapScript, SwapTransactionParams, TransactionOptions}; +use boltz_client::util::fees::Fee; + +/// Refund a submarine swap, serialized against any other refund of the same swap. +/// +/// The counterpart to [`crate::modules::boltz::claim::claim_reverse_swap_guarded`]: +/// it holds the swap's lock across the read-broadcast-record sequence so two +/// concurrent recovery calls cannot both broadcast a refund. If the swap already +/// has a refund txid recorded, that txid is returned without re-broadcasting. +pub async fn refund_submarine_swap_guarded( + db: &BoltzDB, + swap_id: &str, + refund_address: String, + mnemonic: &str, + bip39_passphrase: Option<&str>, + fee_rate_sat_per_vb: Option, +) -> Result { + let _guard = lock_swap(swap_id).await; + + // Re-read under the lock: a concurrent refund may have completed while we waited. + let record = db + .get_swap(swap_id) + .await? + .ok_or_else(|| BoltzError::NotFound { + error_details: format!("Swap {} not found", swap_id), + })?; + if let Some(existing) = record.refund_tx_id { + return Ok(existing); + } + + let txid = refund_submarine_swap( + &record, + refund_address, + mnemonic, + bip39_passphrase, + fee_rate_sat_per_vb, + ) + .await?; + db.set_refund_tx(swap_id, &txid).await?; + Ok(txid) +} + +/// Refund a submarine swap's locked onchain funds back to `refund_address`. +/// +/// Used when Boltz fails to pay the invoice (`invoice.failedToPay`), the wrong +/// amount was locked (`transaction.lockupFailed`), or the swap expired. A +/// cooperative refund is attempted first; if unavailable it falls back to the +/// script-path refund, which becomes spendable after the swap's onchain +/// timeout. Returns the broadcast refund transaction id. +pub async fn refund_submarine_swap( + record: &SwapRecord, + refund_address: String, + mnemonic: &str, + bip39_passphrase: Option<&str>, + fee_rate_sat_per_vb: Option, +) -> Result { + let submarine_resp = record.submarine_response()?; + let keypair = record.keypair(mnemonic, bip39_passphrase)?; + let our_pubkey = bitcoin::PublicKey::new(keypair.public_key()); + + let chain = record.network.as_chain(); + let swap_script = SwapScript::submarine_from_swap_resp(chain, &submarine_resp, our_pubkey)?; + let chain_client = build_chain_client(record.network, &record.electrum_url)?; + let boltz_client = build_boltz_client(record.network); + let fee = Fee::Relative(fee_rate_sat_per_vb.unwrap_or(DEFAULT_FEERATE_SAT_PER_VB)); + + let make_params = |cooperative: bool| SwapTransactionParams { + keys: keypair, + output_address: refund_address.clone(), + fee, + swap_id: record.id.clone(), + chain_client: &chain_client, + boltz_client: &boltz_client, + options: Some(TransactionOptions::default().with_cooperative(cooperative)), + }; + + // Prefer a cooperative refund; fall back to the timeout script path. + let tx = match swap_script.construct_refund(make_params(true)).await { + Ok(tx) => tx, + Err(coop_err) => swap_script + .construct_refund(make_params(false)) + .await + .map_err(|script_err| BoltzError::SwapError { + error_details: format!( + "Refund failed (cooperative: {}; script-path: {})", + coop_err, script_err + ), + })?, + }; + + chain_client + .broadcast_tx(&tx) + .await + .map_err(|e| BoltzError::BroadcastError { + error_details: format!("Failed to broadcast refund transaction: {}", e), + }) +} diff --git a/src/modules/boltz/tests.rs b/src/modules/boltz/tests.rs new file mode 100644 index 0000000..e296ea0 --- /dev/null +++ b/src/modules/boltz/tests.rs @@ -0,0 +1,372 @@ +use crate::modules::boltz::api::{get_reverse_limits, get_submarine_limits}; +use crate::modules::boltz::claim::{claim_reverse_swap_guarded, ClaimOutcome}; +use crate::modules::boltz::errors::BoltzError; +use crate::modules::boltz::guard::lock_swap; +use crate::modules::boltz::models::{derive_swap_keypair, BoltzDB, SwapRecord}; +use crate::modules::boltz::refund::refund_submarine_swap_guarded; +use crate::modules::boltz::types::{BoltzNetwork, BoltzSwapStatus, BoltzSwapType}; +use boltz_client::util::secrets::Preimage; + +/// A throwaway BIP39 mnemonic used only by the offline tests. +const TEST_MNEMONIC: &str = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + +fn sample_record(id: &str, swap_type: BoltzSwapType) -> SwapRecord { + SwapRecord { + id: id.to_string(), + swap_type, + status: "swap.created".to_string(), + network: BoltzNetwork::Testnet, + electrum_url: "ssl://electrum.example.com:50002".to_string(), + swap_index: 0, + invoice: Some("lnbc1...".to_string()), + lockup_address: Some("bc1qexample".to_string()), + onchain_address: Some("bc1qclaim".to_string()), + amount_sat: 100_000, + onchain_amount_sat: Some(99_000), + timeout_block_height: 800_000, + create_response_json: "{}".to_string(), + claim_tx_id: None, + refund_tx_id: None, + created_at: 1_700_000_000, + } +} + +#[test] +fn status_round_trips_through_raw() { + let cases = [ + ("swap.created", BoltzSwapStatus::SwapCreated), + ("transaction.mempool", BoltzSwapStatus::TransactionMempool), + ( + "transaction.claim.pending", + BoltzSwapStatus::TransactionClaimPending, + ), + ("invoice.failedToPay", BoltzSwapStatus::InvoiceFailedToPay), + ("transaction.claimed", BoltzSwapStatus::TransactionClaimed), + ]; + for (raw, expected) in cases { + let mapped = BoltzSwapStatus::from_raw(raw); + assert_eq!(mapped, expected); + assert_eq!(mapped.as_raw(), raw); + } +} + +#[test] +fn unknown_status_preserves_raw() { + let mapped = BoltzSwapStatus::from_raw("some.future.state"); + assert_eq!( + mapped, + BoltzSwapStatus::Unknown { + raw: "some.future.state".to_string() + } + ); + assert_eq!(mapped.as_raw(), "some.future.state"); +} + +#[test] +fn terminal_states_are_terminal() { + assert!(BoltzSwapStatus::TransactionClaimed.is_terminal()); + assert!(BoltzSwapStatus::TransactionRefunded.is_terminal()); + assert!(BoltzSwapStatus::SwapExpired.is_terminal()); + assert!(!BoltzSwapStatus::TransactionMempool.is_terminal()); + assert!(!BoltzSwapStatus::SwapCreated.is_terminal()); +} + +#[test] +fn derivation_is_deterministic_and_index_unique() { + // Same (mnemonic, index) reproduces the same key and preimage — the + // property that makes swaps recoverable from the seed alone. + let k0a = derive_swap_keypair(TEST_MNEMONIC, None, BoltzNetwork::Mainnet, 0).unwrap(); + let k0b = derive_swap_keypair(TEST_MNEMONIC, None, BoltzNetwork::Mainnet, 0).unwrap(); + assert_eq!(k0a.secret_bytes(), k0b.secret_bytes()); + assert_eq!( + Preimage::from_swap_key(&k0a).bytes, + Preimage::from_swap_key(&k0b).bytes + ); + + // Different indices yield different keys (no key reuse across swaps). + let k1 = derive_swap_keypair(TEST_MNEMONIC, None, BoltzNetwork::Mainnet, 1).unwrap(); + assert_ne!(k0a.secret_bytes(), k1.secret_bytes()); + + // A different passphrase yields a different key (passphrase must match). + let k0p = derive_swap_keypair(TEST_MNEMONIC, Some("pass"), BoltzNetwork::Mainnet, 0).unwrap(); + assert_ne!(k0a.secret_bytes(), k0p.secret_bytes()); +} + +#[test] +fn rejects_invalid_mnemonic() { + assert!(derive_swap_keypair("not a valid mnemonic", None, BoltzNetwork::Mainnet, 0).is_err()); +} + +#[tokio::test] +async fn reserve_swap_index_is_monotonic_and_persists() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + + assert_eq!(db.reserve_swap_index().await.unwrap(), 0); + assert_eq!(db.reserve_swap_index().await.unwrap(), 1); + assert_eq!(db.reserve_swap_index().await.unwrap(), 2); + drop(db); + + // The counter survives reopening the database — no index is ever reused. + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + assert_eq!(db.reserve_swap_index().await.unwrap(), 3); +} + +#[tokio::test] +async fn db_round_trip_and_recovery() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + + let mut reverse = sample_record("rev-1", BoltzSwapType::Reverse); + reverse.swap_index = 3; + let submarine = sample_record("sub-1", BoltzSwapType::Submarine); + db.insert_swap(&reverse).await.unwrap(); + db.insert_swap(&submarine).await.unwrap(); + + // get_swap reconstructs the record faithfully (no secrets are stored, only + // the derivation index needed to reconstruct them from the seed). + let loaded = db.get_swap("rev-1").await.unwrap().expect("swap exists"); + assert_eq!(loaded.swap_type, BoltzSwapType::Reverse); + assert_eq!(loaded.swap_index, 3); + assert_eq!(loaded.amount_sat, 100_000); + assert_eq!(loaded.onchain_amount_sat, Some(99_000)); + + // Both swaps are pending until they reach a terminal state. + assert_eq!(db.list_swaps().await.unwrap().len(), 2); + assert_eq!(db.list_pending_swaps().await.unwrap().len(), 2); + + // A non-terminal status persists and leaves the swap in the pending set. + db.update_status("rev-1", "transaction.mempool") + .await + .unwrap(); + let loaded = db.get_swap("rev-1").await.unwrap().unwrap(); + assert_eq!(loaded.status, "transaction.mempool"); + assert_eq!(db.list_pending_swaps().await.unwrap().len(), 2); + + // Recording the claim tx id stores it and advances the swap to the terminal + // claimed status, which drops it from the pending set. + db.set_claim_tx("rev-1", "abc123").await.unwrap(); + let loaded = db.get_swap("rev-1").await.unwrap().unwrap(); + assert_eq!(loaded.status, "transaction.claimed"); + assert_eq!(loaded.claim_tx_id, Some("abc123".to_string())); + let pending = db.list_pending_swaps().await.unwrap(); + assert_eq!(pending.len(), 1); + assert_eq!(pending[0].id, "sub-1"); + + // Recording the refund tx id does the same for the submarine swap. + db.set_refund_tx("sub-1", "def456").await.unwrap(); + let loaded = db.get_swap("sub-1").await.unwrap().unwrap(); + assert_eq!(loaded.status, "transaction.refunded"); + assert_eq!(loaded.refund_tx_id, Some("def456".to_string())); + assert!(db.list_pending_swaps().await.unwrap().is_empty()); + + // Terminal swaps are still listed, just no longer pending. + assert_eq!(db.list_swaps().await.unwrap().len(), 2); +} + +#[tokio::test] +async fn guarded_claim_returns_recorded_txid_without_rebroadcasting() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + + let mut record = sample_record("rev-claimed", BoltzSwapType::Reverse); + record.claim_tx_id = Some("already-broadcast".to_string()); + db.insert_swap(&record).await.unwrap(); + + // The record's electrum_url is unreachable, so reaching the broadcast path at + // all would fail the call rather than return the recorded txid. + let outcome = claim_reverse_swap_guarded(&db, "rev-claimed", TEST_MNEMONIC, None, None) + .await + .unwrap(); + assert!(matches!(outcome, ClaimOutcome::AlreadyClaimed(_))); + assert_eq!(outcome.txid(), "already-broadcast"); +} + +#[tokio::test] +async fn guarded_refund_returns_recorded_txid_without_rebroadcasting() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + + let mut record = sample_record("sub-refunded", BoltzSwapType::Submarine); + record.refund_tx_id = Some("already-refunded".to_string()); + db.insert_swap(&record).await.unwrap(); + + let txid = refund_submarine_swap_guarded( + &db, + "sub-refunded", + "bc1qrefund".to_string(), + TEST_MNEMONIC, + None, + None, + ) + .await + .unwrap(); + assert_eq!(txid, "already-refunded"); +} + +#[tokio::test] +async fn guarded_claim_reports_a_missing_swap() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + + let err = claim_reverse_swap_guarded(&db, "nope", TEST_MNEMONIC, None, None) + .await + .unwrap_err(); + assert!(matches!(err, BoltzError::NotFound { .. })); +} + +/// The lock is what stops an auto-claim and a manual claim of the same swap from +/// both broadcasting, so assert it actually excludes: holders of one swap's lock +/// never overlap, while a different swap's lock stays free. +#[tokio::test] +async fn swap_lock_excludes_per_swap_and_not_across_swaps() { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + + let live = Arc::new(AtomicUsize::new(0)); + let peak = Arc::new(AtomicUsize::new(0)); + + let mut handles = Vec::new(); + for _ in 0..4 { + let live = live.clone(); + let peak = peak.clone(); + handles.push(tokio::spawn(async move { + let _guard = lock_swap("contended").await; + let in_section = live.fetch_add(1, Ordering::SeqCst) + 1; + peak.fetch_max(in_section, Ordering::SeqCst); + // Yield while holding the lock: a claim awaits here, and this is + // exactly where an unguarded second caller would slip through. + tokio::task::yield_now().await; + live.fetch_sub(1, Ordering::SeqCst); + })); + } + for handle in handles { + handle.await.unwrap(); + } + assert_eq!( + peak.load(Ordering::SeqCst), + 1, + "two claims of the same swap must never run concurrently" + ); + + // Holding one swap's lock must not stall an unrelated swap. + let held = lock_swap("swap-a").await; + let other = tokio::time::timeout(std::time::Duration::from_secs(5), lock_swap("swap-b")).await; + assert!(other.is_ok(), "locks must be independent across swaps"); + drop(held); +} + +/// Returns true if a Boltz API error indicates the endpoint is temporarily +/// unreachable (gateway/timeout), so the live test can skip rather than fail. +fn api_unavailable(err: &crate::modules::boltz::BoltzError) -> bool { + let msg = err.to_string(); + msg.contains("502") + || msg.contains("503") + || msg.contains("504") + || msg.contains("Bad Gateway") + || msg.contains("timed out") + || msg.contains("error sending request") +} + +/// Live end-to-end test against the Boltz API. Ignored by default (needs +/// network); run with: +/// `cargo test modules::boltz -- --ignored --nocapture` +/// +/// Targets testnet by default; set `BOLTZ_LIVE_NETWORK=mainnet` (or `regtest`) +/// to override. A reverse swap is ideal for an unattended live test: Boltz +/// generates the hold invoice itself, so no Lightning node or onchain funds are +/// required, and an unpaid swap simply expires. The test asserts the +/// locally-derived redeem script and invoice match what Boltz returned — the +/// core cryptographic guarantee that a later claim/refund will be valid. If the +/// endpoint is temporarily down it skips instead of failing. +#[tokio::test] +#[ignore = "hits the live Boltz API"] +async fn live_reverse_swap() { + let network = match std::env::var("BOLTZ_LIVE_NETWORK").as_deref() { + Ok("mainnet") => BoltzNetwork::Mainnet, + Ok("regtest") => BoltzNetwork::Regtest, + _ => BoltzNetwork::Testnet, + }; + println!("live test network: {}", network.as_str()); + + // 1. Real fees/limits round-trip for both directions. + let reverse = match get_reverse_limits(network).await { + Ok(limits) => limits, + Err(e) if api_unavailable(&e) => { + println!("SKIP: Boltz API unavailable ({e})"); + return; + } + Err(e) => panic!("fetch reverse limits: {e:?}"), + }; + let submarine = get_submarine_limits(network) + .await + .expect("fetch submarine limits"); + println!( + "submarine: min={} max={} fee%={}", + submarine.minimal_sat, submarine.maximal_sat, submarine.fee_percentage + ); + println!( + "reverse: min={} max={} fee%={}", + reverse.minimal_sat, reverse.maximal_sat, reverse.fee_percentage + ); + assert!(reverse.maximal_sat > reverse.minimal_sat); + + // 2. Create a real reverse swap at the minimum amount. + let dir = tempfile::tempdir().unwrap(); + let db = BoltzDB::new(dir.path().join("boltz.db").to_str().unwrap()) + .await + .unwrap(); + // Any valid testnet address — only stored locally as the claim destination, + // not sent to Boltz (we never broadcast in this test). + let claim_address = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(); + let response = db + .create_reverse_swap( + network, + "ssl://blockstream.info:993".to_string(), + reverse.minimal_sat, + claim_address, + TEST_MNEMONIC.to_string(), + None, + ) + .await + .expect("create reverse swap"); + + println!("swap id: {}", response.id); + println!("hold invoice: {}", response.invoice); + println!("lockup address: {}", response.lockup_address); + assert!( + response.invoice.starts_with("lnbc") || response.invoice.starts_with("lntb"), + "expected a BOLT11 invoice, got: {}", + response.invoice + ); + assert!(!response.lockup_address.is_empty()); + + // 3. Reconstruct the swap from persisted secrets and verify the invoice's + // payment hash and the redeem script match — proving our claim key, + // preimage and locally-built swap script are correct. + let record = db.get_swap(&response.id).await.unwrap().expect("persisted"); + let keypair = record.keypair(TEST_MNEMONIC, None).unwrap(); + let preimage = record.preimage(TEST_MNEMONIC, None).unwrap(); + let our_pubkey = bitcoin::PublicKey::new(keypair.public_key()); + let reverse_resp = record.reverse_response().unwrap(); + reverse_resp + .validate(&preimage, &our_pubkey, network.as_chain()) + .expect("invoice + redeem script validate against our keys"); + + // The swap is left to expire on testnet; nothing is broadcast. + println!("reverse swap created and cryptographically validated ✅"); +} + +#[tokio::test] +async fn get_missing_swap_returns_none() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("boltz.db"); + let db = BoltzDB::new(path.to_str().unwrap()).await.unwrap(); + assert!(db.get_swap("nope").await.unwrap().is_none()); +} diff --git a/src/modules/boltz/types.rs b/src/modules/boltz/types.rs new file mode 100644 index 0000000..46c6935 --- /dev/null +++ b/src/modules/boltz/types.rs @@ -0,0 +1,282 @@ +use boltz_client::network::{BitcoinChain, Chain, Network as BoltzClientNetwork}; +use serde::{Deserialize, Serialize}; + +/// Bitcoin network selection for Boltz swaps. Maps to the networks Boltz +/// operates on (mainnet, testnet, regtest). +#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum, Serialize, Deserialize)] +pub enum BoltzNetwork { + Mainnet, + Testnet, + Regtest, +} + +impl BoltzNetwork { + pub(crate) fn as_client_network(self) -> BoltzClientNetwork { + match self { + BoltzNetwork::Mainnet => BoltzClientNetwork::Mainnet, + BoltzNetwork::Testnet => BoltzClientNetwork::Testnet, + BoltzNetwork::Regtest => BoltzClientNetwork::Regtest, + } + } + + pub(crate) fn as_bitcoin_chain(self) -> BitcoinChain { + match self { + BoltzNetwork::Mainnet => BitcoinChain::Bitcoin, + BoltzNetwork::Testnet => BitcoinChain::BitcoinTestnet, + BoltzNetwork::Regtest => BitcoinChain::BitcoinRegtest, + } + } + + pub(crate) fn as_chain(self) -> Chain { + Chain::Bitcoin(self.as_bitcoin_chain()) + } + + pub(crate) fn as_str(self) -> &'static str { + match self { + BoltzNetwork::Mainnet => "mainnet", + BoltzNetwork::Testnet => "testnet", + BoltzNetwork::Regtest => "regtest", + } + } + + pub(crate) fn from_str(s: &str) -> Option { + match s { + "mainnet" => Some(BoltzNetwork::Mainnet), + "testnet" => Some(BoltzNetwork::Testnet), + "regtest" => Some(BoltzNetwork::Regtest), + _ => None, + } + } +} + +/// The direction of a Boltz swap. +/// +/// - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, +/// Boltz pays a Lightning invoice). +/// - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold +/// invoice, Boltz locks onchain funds the user then claims). +#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum, Serialize, Deserialize)] +pub enum BoltzSwapType { + Submarine, + Reverse, +} + +impl BoltzSwapType { + pub(crate) fn as_str(self) -> &'static str { + match self { + BoltzSwapType::Submarine => "submarine", + BoltzSwapType::Reverse => "reverse", + } + } + + pub(crate) fn from_str(s: &str) -> Option { + match s { + "submarine" => Some(BoltzSwapType::Submarine), + "reverse" => Some(BoltzSwapType::Reverse), + _ => None, + } + } +} + +/// Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so +/// new server-side states don't break the bindings. +/// +/// See . +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum, Serialize, Deserialize)] +pub enum BoltzSwapStatus { + /// `swap.created` — initial state. + SwapCreated, + /// `invoice.set` — invoice attached to a submarine swap. + InvoiceSet, + /// `transaction.mempool` — a lockup transaction is in the mempool. + TransactionMempool, + /// `transaction.confirmed` — a lockup transaction confirmed. + TransactionConfirmed, + /// `invoice.pending` — Boltz is paying the submarine swap invoice. + InvoicePending, + /// `invoice.paid` — submarine swap invoice paid by Boltz. + InvoicePaid, + /// `invoice.settled` — reverse swap invoice settled (preimage revealed). + InvoiceSettled, + /// `invoice.failedToPay` — submarine swap invoice could not be paid; refund. + InvoiceFailedToPay, + /// `invoice.expired` — reverse swap invoice expired before payment. + InvoiceExpired, + /// `transaction.claim.pending` — Boltz ready for a cooperative claim. + TransactionClaimPending, + /// `transaction.claimed` — onchain funds claimed. + TransactionClaimed, + /// `transaction.refunded` — onchain funds refunded. + TransactionRefunded, + /// `transaction.lockupFailed` — wrong amount locked; can refund. + TransactionLockupFailed, + /// `transaction.failed` — Boltz failed to lock the agreed funds. + TransactionFailed, + /// `swap.expired` — swap expired without completing. + SwapExpired, + /// Any status not yet modelled. `raw` holds the verbatim Boltz status. + Unknown { raw: String }, +} + +impl BoltzSwapStatus { + /// Map a raw Boltz status string to a typed status. + pub fn from_raw(raw: &str) -> BoltzSwapStatus { + match raw { + "swap.created" => BoltzSwapStatus::SwapCreated, + "invoice.set" => BoltzSwapStatus::InvoiceSet, + "transaction.mempool" => BoltzSwapStatus::TransactionMempool, + "transaction.confirmed" => BoltzSwapStatus::TransactionConfirmed, + "invoice.pending" => BoltzSwapStatus::InvoicePending, + "invoice.paid" => BoltzSwapStatus::InvoicePaid, + "invoice.settled" => BoltzSwapStatus::InvoiceSettled, + "invoice.failedToPay" => BoltzSwapStatus::InvoiceFailedToPay, + "invoice.expired" => BoltzSwapStatus::InvoiceExpired, + "transaction.claim.pending" => BoltzSwapStatus::TransactionClaimPending, + "transaction.claimed" => BoltzSwapStatus::TransactionClaimed, + "transaction.refunded" => BoltzSwapStatus::TransactionRefunded, + "transaction.lockupFailed" => BoltzSwapStatus::TransactionLockupFailed, + "transaction.failed" => BoltzSwapStatus::TransactionFailed, + "swap.expired" => BoltzSwapStatus::SwapExpired, + other => BoltzSwapStatus::Unknown { + raw: other.to_string(), + }, + } + } + + /// The raw Boltz status string this typed status was derived from. + pub fn as_raw(&self) -> String { + match self { + BoltzSwapStatus::SwapCreated => "swap.created".to_string(), + BoltzSwapStatus::InvoiceSet => "invoice.set".to_string(), + BoltzSwapStatus::TransactionMempool => "transaction.mempool".to_string(), + BoltzSwapStatus::TransactionConfirmed => "transaction.confirmed".to_string(), + BoltzSwapStatus::InvoicePending => "invoice.pending".to_string(), + BoltzSwapStatus::InvoicePaid => "invoice.paid".to_string(), + BoltzSwapStatus::InvoiceSettled => "invoice.settled".to_string(), + BoltzSwapStatus::InvoiceFailedToPay => "invoice.failedToPay".to_string(), + BoltzSwapStatus::InvoiceExpired => "invoice.expired".to_string(), + BoltzSwapStatus::TransactionClaimPending => "transaction.claim.pending".to_string(), + BoltzSwapStatus::TransactionClaimed => "transaction.claimed".to_string(), + BoltzSwapStatus::TransactionRefunded => "transaction.refunded".to_string(), + BoltzSwapStatus::TransactionLockupFailed => "transaction.lockupFailed".to_string(), + BoltzSwapStatus::TransactionFailed => "transaction.failed".to_string(), + BoltzSwapStatus::SwapExpired => "swap.expired".to_string(), + BoltzSwapStatus::Unknown { raw } => raw.clone(), + } + } + + /// Whether the swap has reached a terminal state and no further action is + /// possible or required. + pub fn is_terminal(&self) -> bool { + matches!( + self, + BoltzSwapStatus::TransactionClaimed + | BoltzSwapStatus::TransactionRefunded + | BoltzSwapStatus::InvoiceSettled + | BoltzSwapStatus::TransactionFailed + | BoltzSwapStatus::InvoiceExpired + | BoltzSwapStatus::SwapExpired + ) + } +} + +/// Fees and limits for a swap pair, used to size a swap and present costs. +#[derive(Debug, Clone, uniffi::Record)] +pub struct BoltzPairInfo { + /// Pair hash identifying the current terms (passed back to Boltz if needed). + pub hash: String, + /// Exchange rate of the pair. + pub rate: f64, + /// Minimum swap amount in satoshis. + pub minimal_sat: u64, + /// Maximum swap amount in satoshis. + pub maximal_sat: u64, + /// Boltz service fee as a percentage of the swap amount. + pub fee_percentage: f64, + /// Estimated absolute miner fees in satoshis. + pub miner_fees_sat: u64, +} + +/// Result of creating a submarine swap (onchain -> Lightning). +/// +/// The caller funds `address` with `expected_amount_sat` from its onchain +/// wallet; Boltz then pays the Lightning invoice supplied at creation. +#[derive(Debug, Clone, uniffi::Record)] +pub struct SubmarineSwapResponse { + pub id: String, + /// Onchain lockup address to send funds to. + pub address: String, + /// BIP21 URI for the lockup payment. + pub bip21: String, + /// Exact amount in satoshis the caller must send to `address`. + pub expected_amount_sat: u64, + /// Whether Boltz will accept a zero-conf lockup. + pub accept_zero_conf: bool, + /// Onchain timeout height after which a refund is possible. + pub timeout_block_height: u64, +} + +/// Result of creating a reverse swap (Lightning -> onchain). +/// +/// The caller pays `invoice` from its Lightning node; once Boltz locks funds at +/// `lockup_address`, the module claims them to the provided onchain address. +#[derive(Debug, Clone, uniffi::Record)] +pub struct ReverseSwapResponse { + pub id: String, + /// Hold invoice the caller must pay via Lightning. + pub invoice: String, + /// Address Boltz locks the onchain funds to. + pub lockup_address: String, + /// Amount in satoshis that will be received onchain (after Boltz fees). + pub onchain_amount_sat: u64, + /// Onchain timeout height for the swap. + pub timeout_block_height: u64, +} + +/// A persisted swap and its current state, returned by the listing/query APIs. +#[derive(Debug, Clone, uniffi::Record)] +pub struct BoltzSwap { + pub id: String, + pub swap_type: BoltzSwapType, + pub status: BoltzSwapStatus, + pub network: BoltzNetwork, + /// Deterministic BIP85 index used to derive this swap's key and preimage + /// from the wallet seed. The recovery handle: given the seed and this index + /// (or by scanning indices), the swap's secrets can be reconstructed. + pub swap_index: u64, + /// For submarine swaps: the amount to lock onchain. For reverse swaps: the + /// Lightning invoice amount. + pub amount_sat: u64, + /// For reverse swaps: the onchain amount that will be received. + pub onchain_amount_sat: Option, + /// Lightning invoice associated with the swap (the hold invoice for reverse + /// swaps, the invoice Boltz pays for submarine swaps). + pub invoice: Option, + /// Onchain lockup address. + pub lockup_address: Option, + /// The address funds are claimed to (reverse) or refunded to (submarine). + pub onchain_address: Option, + pub timeout_block_height: u64, + pub created_at: u64, + /// Txid of the claim transaction once broadcast (reverse swaps). + pub claim_tx_id: Option, + /// Txid of the refund transaction once broadcast (submarine swaps). + pub refund_tx_id: Option, +} + +/// Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] +/// as swaps progress through their lifecycle. +#[derive(Debug, Clone, uniffi::Enum)] +pub enum BoltzSwapEvent { + /// The swap transitioned to a new status. + StatusUpdate { + swap_id: String, + status: BoltzSwapStatus, + }, + /// A reverse swap was claimed onchain. `txid` is the claim transaction. + Claimed { swap_id: String, txid: String }, + /// A submarine swap was refunded onchain. `txid` is the refund transaction. + Refunded { swap_id: String, txid: String }, + /// An error occurred while processing the swap (e.g. an auto-claim failed). + Error { swap_id: String, message: String }, +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 3bc1337..beb8686 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,5 +1,6 @@ pub mod activity; pub mod blocktank; +pub mod boltz; pub mod lnurl; pub mod onchain; pub mod pubky;