From be3120c9355bc5d4fbde77e639608949f17609ba Mon Sep 17 00:00:00 2001 From: yse Date: Thu, 2 May 2024 00:41:11 +0200 Subject: [PATCH 01/29] feat: remove PrepareSendResponse fields feat: add `PrepareSendRequest` object fix: rename to `claim_tx_feerate` fix: fix reverse submarine test feat: improve prepare_send and prepare_receive interfaces fix: use v2 to broadcast lowball tx feat: finalize prepare_send and prepare_receive feat: remove manual BTC pair from response fix: remove `pair_hash` from PrepareReceive rebasing updating Cargo.lock fix: update lbtc pair method names fixing bindings fix: re-add uniffi_macros add comment to elements dependency fix: switch back to v1 fee calculation fix: revert to v1 pair hash fix: simplify migrations fix: rename `FeesExpired` to `InvalidOrExpiredFees` Log error when list_ongoing_swaps() fails Log swap ID when list_ongoing_swaps() succeeds Migrations: add missing ongoing_receive_swaps field Clarify swap type (reverse vs submarine) in logs --- cli/Cargo.lock | 190 +- cli/src/commands.rs | 4 +- lib/Cargo.lock | 214 +- .../lib/src/main/kotlin/breez_liquid_sdk.kt | 1925 +++++++++++++++++ .../include/breez_liquid_sdk.h | 11 +- lib/bindings/src/breez_liquid_sdk.udl | 9 +- lib/bindings/src/lib.rs | 2 +- lib/core/Cargo.toml | 4 +- lib/core/src/bindings.rs | 2 +- lib/core/src/error.rs | 6 + lib/core/src/frb/bridge.io.rs | 41 +- lib/core/src/frb/bridge.rs | 94 +- lib/core/src/model.rs | 25 +- lib/core/src/persist/migrations.rs | 18 +- lib/core/src/persist/mod.rs | 29 +- lib/core/src/sdk.rs | 334 ++- packages/dart/lib/src/error.dart | 4 + packages/dart/lib/src/error.freezed.dart | 94 + packages/dart/lib/src/frb_generated.dart | 121 +- packages/dart/lib/src/frb_generated.io.dart | 86 +- packages/dart/lib/src/model.dart | 36 +- packages/dart/pubspec.yaml | 2 +- packages/flutter/example/lib/main.dart | 8 - packages/flutter/ffigen.yaml | 2 +- ...utter_breez_liquid_bindings_generated.dart | 53 +- .../breezliquidsdk/BreezLiquidSDKMapper.kt | 69 +- 26 files changed, 2740 insertions(+), 643 deletions(-) create mode 100644 lib/bindings/bindings-android/lib/src/main/kotlin/breez_liquid_sdk.kt diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 5f1cbfc..daadbd2 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -127,47 +127,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -175,9 +176,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "atomic" @@ -198,9 +199,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -231,9 +232,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bech32" @@ -262,13 +263,15 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.29.2" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ "bech32 0.9.1", - "bitcoin_hashes 0.11.0", - "secp256k1 0.24.3", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", ] [[package]] @@ -308,6 +311,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", +] + [[package]] name = "bitcoin_hashes" version = "0.13.0" @@ -343,7 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/SatoshiPortal/boltz-rust?rev=6f45fff8b87c7530c847eb05f018906c48785a6c#6f45fff8b87c7530c847eb05f018906c48785a6c" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=410d3a95e528fce36c02e8d414d5b647a31cc28f#410d3a95e528fce36c02e8d414d5b647a31cc28f" dependencies = [ "bip39", "bitcoin 0.31.2", @@ -383,6 +395,7 @@ dependencies = [ "anyhow", "bip39", "boltz-client", + "elements", "flutter_rust_bridge", "log", "lwk_common", @@ -427,9 +440,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -511,9 +524,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console_error_panic_hook" @@ -599,9 +612,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "delegate-attr" @@ -762,9 +775,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fd-lock" @@ -779,9 +792,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -957,9 +970,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1001,9 +1014,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -1231,6 +1244,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -1254,9 +1273,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libsqlite3-sys" @@ -1271,25 +1290,25 @@ dependencies = [ [[package]] name = "lightning" -version = "0.0.118" +version = "0.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cec5fa9382154fe9671e8df93095b800c7d77abc66e2a5ef839d672521c5e" +checksum = "0d9b36ae12b379905bfc429ce5d4e8ca4a55c8dd3de73074309bd0bcc053bcac" dependencies = [ - "bitcoin 0.29.2", + "bitcoin 0.30.2", + "hex-conservative", ] [[package]] name = "lightning-invoice" -version = "0.26.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb24878b0f4ef75f020976c886d9ad1503867802329cc963e0ab4623ea3b25c" +checksum = "106fdb897e69df697480f45bf0a564b425af488fb0f7407e770a770c39b19a21" dependencies = [ "bech32 0.9.1", - "bitcoin 0.29.2", - "bitcoin_hashes 0.11.0", + "bitcoin 0.30.2", "lightning", "num-traits", - "secp256k1 0.24.3", + "secp256k1 0.27.0", ] [[package]] @@ -1485,9 +1504,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1657,9 +1676,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1850,9 +1869,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" @@ -1904,9 +1923,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "51f344d206c5e1b010eec27349b815a4805f70a778895959d70b74b9b529b30a" [[package]] name = "rustls-webpki" @@ -1965,9 +1984,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -1996,12 +2015,12 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes 0.11.0", - "secp256k1-sys 0.6.1", + "bitcoin_hashes 0.12.0", + "secp256k1-sys 0.8.1", ] [[package]] @@ -2018,9 +2037,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" dependencies = [ "cc", ] @@ -2057,11 +2076,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -2070,9 +2089,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2080,9 +2099,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -2108,9 +2127,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -2168,9 +2187,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2196,9 +2215,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -2255,18 +2274,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", @@ -2325,16 +2344,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2461,7 +2479,7 @@ version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "flate2", "log", "native-tls", @@ -2822,18 +2840,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 570150e..e827442 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -117,12 +117,12 @@ pub(crate) fn handle_command( } Command::SendPayment { bolt11, delay } => { let prepare_response = - sdk.prepare_send_payment(PrepareSendRequest { invoice: bolt11 })?; + sdk.prepare_send_payment(&PrepareSendRequest { invoice: bolt11 })?; wait_confirmation!( format!( "Fees: {} sat. Are the fees acceptable? (y/N) ", - prepare_response.total_fees + prepare_response.fees_sat ), "Payment send halted" ); diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 8fcd36e..b8d1ab6 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -127,47 +127,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -175,9 +176,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" dependencies = [ "backtrace", ] @@ -227,7 +228,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -281,9 +282,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -314,9 +315,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "basic-toml" @@ -385,13 +386,15 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.29.2" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ "bech32 0.9.1", - "bitcoin_hashes 0.11.0", - "secp256k1 0.24.3", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", ] [[package]] @@ -431,6 +434,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", +] + [[package]] name = "bitcoin_hashes" version = "0.13.0" @@ -466,7 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/SatoshiPortal/boltz-rust?rev=6f45fff8b87c7530c847eb05f018906c48785a6c#6f45fff8b87c7530c847eb05f018906c48785a6c" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=410d3a95e528fce36c02e8d414d5b647a31cc28f#410d3a95e528fce36c02e8d414d5b647a31cc28f" dependencies = [ "bip39", "bitcoin 0.31.2", @@ -490,6 +502,7 @@ dependencies = [ "anyhow", "bip39", "boltz-client", + "elements", "flutter_rust_bridge", "log", "lwk_common", @@ -581,9 +594,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -675,7 +688,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -695,9 +708,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console_error_panic_hook" @@ -795,7 +808,7 @@ checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -961,7 +974,7 @@ checksum = "d852460bc16316c4491a60e1652612f717764d436f3a97f8f1cc7c3b54d9a0dc" dependencies = [ "hex", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1065,7 +1078,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1110,9 +1123,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1438,6 +1451,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -1461,9 +1480,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libsqlite3-sys" @@ -1478,25 +1497,25 @@ dependencies = [ [[package]] name = "lightning" -version = "0.0.118" +version = "0.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cec5fa9382154fe9671e8df93095b800c7d77abc66e2a5ef839d672521c5e" +checksum = "0d9b36ae12b379905bfc429ce5d4e8ca4a55c8dd3de73074309bd0bcc053bcac" dependencies = [ - "bitcoin 0.29.2", + "bitcoin 0.30.2", + "hex-conservative", ] [[package]] name = "lightning-invoice" -version = "0.26.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb24878b0f4ef75f020976c886d9ad1503867802329cc963e0ab4623ea3b25c" +checksum = "106fdb897e69df697480f45bf0a564b425af488fb0f7407e770a770c39b19a21" dependencies = [ "bech32 0.9.1", - "bitcoin 0.29.2", - "bitcoin_hashes 0.11.0", + "bitcoin 0.30.2", "lightning", "num-traits", - "secp256k1 0.24.3", + "secp256k1 0.27.0", ] [[package]] @@ -1698,9 +1717,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1765,7 +1784,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1863,9 +1882,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1941,9 +1960,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -2173,9 +2192,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" @@ -2227,9 +2246,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "51f344d206c5e1b010eec27349b815a4805f70a778895959d70b74b9b529b30a" [[package]] name = "rustls-webpki" @@ -2254,9 +2273,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -2299,7 +2318,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2310,7 +2329,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2325,12 +2344,12 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes 0.11.0", - "secp256k1-sys 0.6.1", + "bitcoin_hashes 0.12.0", + "secp256k1-sys 0.8.1", ] [[package]] @@ -2347,9 +2366,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" dependencies = [ "cc", ] @@ -2386,11 +2405,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -2399,9 +2418,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2409,18 +2428,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -2446,13 +2465,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2578,9 +2597,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -2658,22 +2677,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2727,7 +2746,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2742,16 +2761,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2788,7 +2806,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -3031,7 +3049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c" dependencies = [ "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -3041,7 +3059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae7e5a6c33b1dec3f255f57ec0b6af0f0b2bb3021868be1d5eec7a38e2905ebc" dependencies = [ "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -3108,7 +3126,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.60", + "syn 2.0.61", "toml", "uniffi_meta 0.27.1", ] @@ -3236,7 +3254,7 @@ version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "flate2", "log", "native-tls", @@ -3330,7 +3348,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", "wasm-bindgen-shared", ] @@ -3364,7 +3382,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3624,22 +3642,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] diff --git a/lib/bindings/bindings-android/lib/src/main/kotlin/breez_liquid_sdk.kt b/lib/bindings/bindings-android/lib/src/main/kotlin/breez_liquid_sdk.kt new file mode 100644 index 0000000..7dc3358 --- /dev/null +++ b/lib/bindings/bindings-android/lib/src/main/kotlin/breez_liquid_sdk.kt @@ -0,0 +1,1925 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package breez_liquid_sdk; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.IntegerType +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Callback +import com.sun.jna.ptr.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + @JvmField var len: Long = 0 + @JvmField var data: Pointer? = null + + class ByValue: RustBuffer(), Structure.ByValue + class ByReference: RustBuffer(), Structure.ByReference + + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + + companion object { + internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.ffi_breez_liquid_sdk_bindings_rustbuffer_alloc(size.toLong(), status) + }.also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + + internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity.toLong() + buf.len = len.toLong() + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status -> + UniffiLib.INSTANCE.ffi_breez_liquid_sdk_bindings_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): ULong + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position().toLong()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer: FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + +@Structure.FieldOrder("code", "error_buf") +internal open class UniffiRustCallStatus : Structure() { + @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue: UniffiRustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == UNIFFI_CALL_SUCCESS + } + + fun isError(): Boolean { + return code == UNIFFI_CALL_ERROR + } + + fun isPanic(): Boolean { + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface UniffiRustCallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler, callback: (UniffiRustCallStatus) -> U): U { + var status = UniffiRustCallStatus(); + val return_value = callback(status) + uniffiCheckCallStatus(errorHandler, status) + return return_value +} + +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler, status: UniffiRustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback); +} + +internal inline fun uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } + } +} +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap { + private val map = ConcurrentHashMap() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "breez_liquid_sdk_bindings" +} + +private inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// Define FFI callback types +internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { + fun callback(`data`: Long,`pollResult`: Byte,) +} +internal interface UniffiForeignFutureFree : com.sun.jna.Callback { + fun callback(`handle`: Long,) +} +internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { + fun callback(`handle`: Long,) +} +@Structure.FieldOrder("handle", "free") +internal open class UniffiForeignFuture( + @JvmField internal var `handle`: Long = 0.toLong(), + @JvmField internal var `free`: UniffiForeignFutureFree? = null, +) : Structure() { + class UniffiByValue( + `handle`: Long = 0.toLong(), + `free`: UniffiForeignFutureFree? = null, + ): UniffiForeignFuture(`handle`,`free`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFuture) { + `handle` = other.`handle` + `free` = other.`free` + } + +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU8(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU8.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI8(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI8.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU16(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU16.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI16(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI16.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF32( + @JvmField internal var `returnValue`: Float = 0.0f, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Float = 0.0f, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructF32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF64( + @JvmField internal var `returnValue`: Double = 0.0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Double = 0.0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructF64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructPointer( + @JvmField internal var `returnValue`: Pointer = Pointer.NULL, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Pointer = Pointer.NULL, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructPointer(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructPointer.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructRustBuffer( + @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructRustBuffer(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructRustBuffer.UniffiByValue,) +} +@Structure.FieldOrder("callStatus") +internal open class UniffiForeignFutureStructVoid( + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructVoid(`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructVoid.UniffiByValue,) +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface UniffiLib : Library { + companion object { + internal val INSTANCE: UniffiLib by lazy { + loadIndirect(componentName = "breez_liquid_sdk") + .also { lib: UniffiLib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + } + } + + // The Cleaner for the whole library + internal val CLEANER: UniffiCleaner by lazy { + UniffiCleaner.create() + } + } + + fun uniffi_breez_liquid_sdk_bindings_fn_clone_bindingliquidsdk(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_breez_liquid_sdk_bindings_fn_free_bindingliquidsdk(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_backup(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_get_info(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_prepare_receive_payment(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_prepare_send_payment(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_receive_payment(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_restore(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_send_payment(`ptr`: Pointer,`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_breez_liquid_sdk_bindings_fn_func_connect(`req`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun ffi_breez_liquid_sdk_bindings_rustbuffer_alloc(`size`: Long,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun ffi_breez_liquid_sdk_bindings_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun ffi_breez_liquid_sdk_bindings_rustbuffer_free(`buf`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Long,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_u8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_u8(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_u8(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_u8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Byte + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_i8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_i8(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_i8(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_i8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Byte + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_u16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_u16(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_u16(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_u16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Short + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_i16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_i16(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_i16(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_i16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Short + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_u32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_u32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_u32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_u32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Int + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_i32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_i32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_i32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_i32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Int + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_u64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_u64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_u64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_u64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Long + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_i64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_i64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_i64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_i64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Long + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_f32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_f32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_f32(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_f32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Float + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_f64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_f64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_f64(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_f64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Double + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_pointer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_pointer(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_pointer(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_pointer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_rust_buffer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_rust_buffer(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_rust_buffer(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_rust_buffer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun ffi_breez_liquid_sdk_bindings_rust_future_poll_void(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_cancel_void(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_free_void(`handle`: Long, + ): Unit + fun ffi_breez_liquid_sdk_bindings_rust_future_complete_void(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_breez_liquid_sdk_bindings_checksum_func_connect( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_backup( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_get_info( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_prepare_receive_payment( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_prepare_send_payment( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_receive_payment( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_restore( + ): Short + fun uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_send_payment( + ): Short + fun ffi_breez_liquid_sdk_bindings_uniffi_contract_version( + ): Int + +} + +private fun uniffiCheckContractApiVersion(lib: UniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 26 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_breez_liquid_sdk_bindings_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: UniffiLib) { + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_func_connect() != 18922.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_backup() != 4460.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_get_info() != 5563.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_prepare_receive_payment() != 57331.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_prepare_send_payment() != 35897.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_receive_payment() != 8257.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_restore() != 42575.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_breez_liquid_sdk_bindings_checksum_method_bindingliquidsdk_send_payment() != 37911.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// Async support + +// Public interface members begin here. + + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ +object NoPointer + +public object FfiConverterULong: FfiConverter { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8UL + + override fun write(value: ULong, buf: ByteBuffer) { + buf.putLong(value.toLong()) + } +} + +public object FfiConverterBoolean: FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1UL + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} + +public object FfiConverterString: FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len.toInt()) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +// The cleaner interface for Object finalization code to run. +// This is the entry point to any implementation that we're using. +// +// The cleaner registers objects and returns cleanables, so now we are +// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the +// different implmentations available at compile time. +interface UniffiCleaner { + interface Cleanable { + fun clean() + } + + fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable + + companion object +} + +// The fallback Jna cleaner, which is available for both Android, and the JVM. +private class UniffiJnaCleaner : UniffiCleaner { + private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) +} + +private class UniffiJnaCleanable( + private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + +// We decide at uniffi binding generation time whether we were +// using Android or not. +// There are further runtime checks to chose the correct implementation +// of the cleaner. +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + try { + // For safety's sake: if the library hasn't been run in android_cleaner = true + // mode, but is being run on Android, then we still need to think about + // Android API versions. + // So we check if java.lang.ref.Cleaner is there, and use that… + java.lang.Class.forName("java.lang.ref.Cleaner") + JavaLangRefCleaner() + } catch (e: ClassNotFoundException) { + // … otherwise, fallback to the JNA cleaner. + UniffiJnaCleaner() + } + +private class JavaLangRefCleaner : UniffiCleaner { + val cleaner = java.lang.ref.Cleaner.create() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) +} + +private class JavaLangRefCleanable( + val cleanable: java.lang.ref.Cleaner.Cleanable +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} +public interface BindingLiquidSdkInterface { + + fun `backup`() + + fun `getInfo`(`req`: GetInfoRequest): GetInfoResponse + + fun `prepareReceivePayment`(`req`: PrepareReceiveRequest): PrepareReceiveResponse + + fun `prepareSendPayment`(`req`: PrepareSendRequest): PrepareSendResponse + + fun `receivePayment`(`req`: PrepareReceiveResponse): ReceivePaymentResponse + + fun `restore`(`req`: RestoreRequest) + + fun `sendPayment`(`req`: PrepareSendResponse): SendPaymentResponse + + companion object +} + +open class BindingLiquidSdk: Disposable, AutoCloseable, BindingLiquidSdkInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(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. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + 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() + } + } + } + + @Synchronized + override fun close() { + 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.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.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 UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_free_bindingliquidsdk(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_clone_bindingliquidsdk(pointer!!, status) + } + } + + + @Throws(LiquidSdkException::class)override fun `backup`() + = + callWithPointer { + uniffiRustCallWithError(LiquidSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_backup( + it, _status) +} + } + + + + + @Throws(LiquidSdkException::class)override fun `getInfo`(`req`: GetInfoRequest): GetInfoResponse { + return FfiConverterTypeGetInfoResponse.lift( + callWithPointer { + uniffiRustCallWithError(LiquidSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_get_info( + it, FfiConverterTypeGetInfoRequest.lower(`req`),_status) +} + } + ) + } + + + + @Throws(PaymentException::class)override fun `prepareReceivePayment`(`req`: PrepareReceiveRequest): PrepareReceiveResponse { + return FfiConverterTypePrepareReceiveResponse.lift( + callWithPointer { + uniffiRustCallWithError(PaymentException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_prepare_receive_payment( + it, FfiConverterTypePrepareReceiveRequest.lower(`req`),_status) +} + } + ) + } + + + + @Throws(PaymentException::class)override fun `prepareSendPayment`(`req`: PrepareSendRequest): PrepareSendResponse { + return FfiConverterTypePrepareSendResponse.lift( + callWithPointer { + uniffiRustCallWithError(PaymentException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_prepare_send_payment( + it, FfiConverterTypePrepareSendRequest.lower(`req`),_status) +} + } + ) + } + + + + @Throws(PaymentException::class)override fun `receivePayment`(`req`: PrepareReceiveResponse): ReceivePaymentResponse { + return FfiConverterTypeReceivePaymentResponse.lift( + callWithPointer { + uniffiRustCallWithError(PaymentException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_receive_payment( + it, FfiConverterTypePrepareReceiveResponse.lower(`req`),_status) +} + } + ) + } + + + + @Throws(LiquidSdkException::class)override fun `restore`(`req`: RestoreRequest) + = + callWithPointer { + uniffiRustCallWithError(LiquidSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_restore( + it, FfiConverterTypeRestoreRequest.lower(`req`),_status) +} + } + + + + + @Throws(PaymentException::class)override fun `sendPayment`(`req`: PrepareSendResponse): SendPaymentResponse { + return FfiConverterTypeSendPaymentResponse.lift( + callWithPointer { + uniffiRustCallWithError(PaymentException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_method_bindingliquidsdk_send_payment( + it, FfiConverterTypePrepareSendResponse.lower(`req`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypeBindingLiquidSdk: FfiConverter { + + override fun lower(value: BindingLiquidSdk): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): BindingLiquidSdk { + return BindingLiquidSdk(value) + } + + override fun read(buf: ByteBuffer): BindingLiquidSdk { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: BindingLiquidSdk) = 8UL + + override fun write(value: BindingLiquidSdk, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + + +data class ConnectRequest ( + var `mnemonic`: kotlin.String, + var `network`: Network, + var `dataDir`: kotlin.String? = null +) { + + companion object +} + +public object FfiConverterTypeConnectRequest: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ConnectRequest { + return ConnectRequest( + FfiConverterString.read(buf), + FfiConverterTypeNetwork.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: ConnectRequest) = ( + FfiConverterString.allocationSize(value.`mnemonic`) + + FfiConverterTypeNetwork.allocationSize(value.`network`) + + FfiConverterOptionalString.allocationSize(value.`dataDir`) + ) + + override fun write(value: ConnectRequest, buf: ByteBuffer) { + FfiConverterString.write(value.`mnemonic`, buf) + FfiConverterTypeNetwork.write(value.`network`, buf) + FfiConverterOptionalString.write(value.`dataDir`, buf) + } +} + + + +data class GetInfoRequest ( + var `withScan`: kotlin.Boolean +) { + + companion object +} + +public object FfiConverterTypeGetInfoRequest: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): GetInfoRequest { + return GetInfoRequest( + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: GetInfoRequest) = ( + FfiConverterBoolean.allocationSize(value.`withScan`) + ) + + override fun write(value: GetInfoRequest, buf: ByteBuffer) { + FfiConverterBoolean.write(value.`withScan`, buf) + } +} + + + +data class GetInfoResponse ( + var `balanceSat`: kotlin.ULong, + var `pubkey`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeGetInfoResponse: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): GetInfoResponse { + return GetInfoResponse( + FfiConverterULong.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: GetInfoResponse) = ( + FfiConverterULong.allocationSize(value.`balanceSat`) + + FfiConverterString.allocationSize(value.`pubkey`) + ) + + override fun write(value: GetInfoResponse, buf: ByteBuffer) { + FfiConverterULong.write(value.`balanceSat`, buf) + FfiConverterString.write(value.`pubkey`, buf) + } +} + + + +data class PrepareReceiveRequest ( + var `payerAmountSat`: kotlin.ULong +) { + + companion object +} + +public object FfiConverterTypePrepareReceiveRequest: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrepareReceiveRequest { + return PrepareReceiveRequest( + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: PrepareReceiveRequest) = ( + FfiConverterULong.allocationSize(value.`payerAmountSat`) + ) + + override fun write(value: PrepareReceiveRequest, buf: ByteBuffer) { + FfiConverterULong.write(value.`payerAmountSat`, buf) + } +} + + + +data class PrepareReceiveResponse ( + var `pairHash`: kotlin.String, + var `payerAmountSat`: kotlin.ULong, + var `feesSat`: kotlin.ULong +) { + + companion object +} + +public object FfiConverterTypePrepareReceiveResponse: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrepareReceiveResponse { + return PrepareReceiveResponse( + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: PrepareReceiveResponse) = ( + FfiConverterString.allocationSize(value.`pairHash`) + + FfiConverterULong.allocationSize(value.`payerAmountSat`) + + FfiConverterULong.allocationSize(value.`feesSat`) + ) + + override fun write(value: PrepareReceiveResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`pairHash`, buf) + FfiConverterULong.write(value.`payerAmountSat`, buf) + FfiConverterULong.write(value.`feesSat`, buf) + } +} + + + +data class PrepareSendRequest ( + var `invoice`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypePrepareSendRequest: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrepareSendRequest { + return PrepareSendRequest( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PrepareSendRequest) = ( + FfiConverterString.allocationSize(value.`invoice`) + ) + + override fun write(value: PrepareSendRequest, buf: ByteBuffer) { + FfiConverterString.write(value.`invoice`, buf) + } +} + + + +data class PrepareSendResponse ( + var `invoice`: kotlin.String, + var `feesSat`: kotlin.ULong +) { + + companion object +} + +public object FfiConverterTypePrepareSendResponse: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrepareSendResponse { + return PrepareSendResponse( + FfiConverterString.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: PrepareSendResponse) = ( + FfiConverterString.allocationSize(value.`invoice`) + + FfiConverterULong.allocationSize(value.`feesSat`) + ) + + override fun write(value: PrepareSendResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`invoice`, buf) + FfiConverterULong.write(value.`feesSat`, buf) + } +} + + + +data class ReceivePaymentResponse ( + var `id`: kotlin.String, + var `invoice`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeReceivePaymentResponse: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ReceivePaymentResponse { + return ReceivePaymentResponse( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: ReceivePaymentResponse) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`invoice`) + ) + + override fun write(value: ReceivePaymentResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`invoice`, buf) + } +} + + + +data class RestoreRequest ( + var `backupPath`: kotlin.String? = null +) { + + companion object +} + +public object FfiConverterTypeRestoreRequest: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RestoreRequest { + return RestoreRequest( + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: RestoreRequest) = ( + FfiConverterOptionalString.allocationSize(value.`backupPath`) + ) + + override fun write(value: RestoreRequest, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`backupPath`, buf) + } +} + + + +data class SendPaymentResponse ( + var `txid`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeSendPaymentResponse: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SendPaymentResponse { + return SendPaymentResponse( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SendPaymentResponse) = ( + FfiConverterString.allocationSize(value.`txid`) + ) + + override fun write(value: SendPaymentResponse, buf: ByteBuffer) { + FfiConverterString.write(value.`txid`, buf) + } +} + + + + + +sealed class LiquidSdkException(message: String): Exception(message) { + + class Generic(message: String) : LiquidSdkException(message) + + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): LiquidSdkException = FfiConverterTypeLiquidSdkError.lift(error_buf) + } +} + +public object FfiConverterTypeLiquidSdkError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): LiquidSdkException { + + return when(buf.getInt()) { + 1 -> LiquidSdkException.Generic(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + } + + override fun allocationSize(value: LiquidSdkException): ULong { + return 4UL + } + + override fun write(value: LiquidSdkException, buf: ByteBuffer) { + when(value) { + is LiquidSdkException.Generic -> { + buf.putInt(1) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} + + + + +enum class Network { + + LIQUID, + LIQUID_TESTNET; + companion object +} + + +public object FfiConverterTypeNetwork: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + Network.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: Network) = 4UL + + override fun write(value: Network, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + + + +sealed class PaymentException(message: String): Exception(message) { + + class AmountOutOfRange(message: String) : PaymentException(message) + + class FeesExpired(message: String) : PaymentException(message) + + class AlreadyClaimed(message: String) : PaymentException(message) + + class Generic(message: String) : PaymentException(message) + + class InvalidInvoice(message: String) : PaymentException(message) + + class InvalidPreimage(message: String) : PaymentException(message) + + class LwkException(message: String) : PaymentException(message) + + class PairsNotFound(message: String) : PaymentException(message) + + class PersistException(message: String) : PaymentException(message) + + class SendException(message: String) : PaymentException(message) + + class SignerException(message: String) : PaymentException(message) + + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): PaymentException = FfiConverterTypePaymentError.lift(error_buf) + } +} + +public object FfiConverterTypePaymentError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PaymentException { + + return when(buf.getInt()) { + 1 -> PaymentException.AmountOutOfRange(FfiConverterString.read(buf)) + 2 -> PaymentException.FeesExpired(FfiConverterString.read(buf)) + 3 -> PaymentException.AlreadyClaimed(FfiConverterString.read(buf)) + 4 -> PaymentException.Generic(FfiConverterString.read(buf)) + 5 -> PaymentException.InvalidInvoice(FfiConverterString.read(buf)) + 6 -> PaymentException.InvalidPreimage(FfiConverterString.read(buf)) + 7 -> PaymentException.LwkException(FfiConverterString.read(buf)) + 8 -> PaymentException.PairsNotFound(FfiConverterString.read(buf)) + 9 -> PaymentException.PersistException(FfiConverterString.read(buf)) + 10 -> PaymentException.SendException(FfiConverterString.read(buf)) + 11 -> PaymentException.SignerException(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + } + + override fun allocationSize(value: PaymentException): ULong { + return 4UL + } + + override fun write(value: PaymentException, buf: ByteBuffer) { + when(value) { + is PaymentException.AmountOutOfRange -> { + buf.putInt(1) + Unit + } + is PaymentException.FeesExpired -> { + buf.putInt(2) + Unit + } + is PaymentException.AlreadyClaimed -> { + buf.putInt(3) + Unit + } + is PaymentException.Generic -> { + buf.putInt(4) + Unit + } + is PaymentException.InvalidInvoice -> { + buf.putInt(5) + Unit + } + is PaymentException.InvalidPreimage -> { + buf.putInt(6) + Unit + } + is PaymentException.LwkException -> { + buf.putInt(7) + Unit + } + is PaymentException.PairsNotFound -> { + buf.putInt(8) + Unit + } + is PaymentException.PersistException -> { + buf.putInt(9) + Unit + } + is PaymentException.SendException -> { + buf.putInt(10) + Unit + } + is PaymentException.SignerException -> { + buf.putInt(11) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} + + + + +public object FfiConverterOptionalString: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write(value: kotlin.String?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + @Throws(LiquidSdkException::class) fun `connect`(`req`: ConnectRequest): BindingLiquidSdk { + return FfiConverterTypeBindingLiquidSdk.lift( + uniffiRustCallWithError(LiquidSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_breez_liquid_sdk_bindings_fn_func_connect( + FfiConverterTypeConnectRequest.lower(`req`),_status) +} + ) + } + + + diff --git a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h index a87a1b0..6c9997e 100644 --- a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h +++ b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h @@ -15,11 +15,13 @@ void store_dart_post_cobject(DartPostCObjectFnType ptr); typedef struct _Dart_Handle* Dart_Handle; /** - * Claim tx feerate for Receive, in sats per vbyte. + * Claim tx feerate, in sats per vbyte. * Since the Liquid blocks are consistently empty for now, we hardcode the minimum feerate. */ #define LIQUID_CLAIM_TX_FEERATE 0.1 +#define LIQUID_MIN_CLAIM_ABSOLUTE_FEES 134 + typedef struct wire_cst_list_prim_u_8_strict { uint8_t *ptr; int32_t len; @@ -44,7 +46,6 @@ typedef struct wire_cst_prepare_send_request { } wire_cst_prepare_send_request; typedef struct wire_cst_prepare_receive_response { - struct wire_cst_list_prim_u_8_strict *pair_hash; uint64_t payer_amount_sat; uint64_t fees_sat; } wire_cst_prepare_receive_response; @@ -54,12 +55,8 @@ typedef struct wire_cst_restore_request { } wire_cst_restore_request; typedef struct wire_cst_prepare_send_response { - struct wire_cst_list_prim_u_8_strict *id; - uint64_t payer_amount_sat; - uint64_t receiver_amount_sat; - uint64_t total_fees; - struct wire_cst_list_prim_u_8_strict *funding_address; struct wire_cst_list_prim_u_8_strict *invoice; + uint64_t fees_sat; } wire_cst_prepare_send_response; typedef struct wire_cst_payment { diff --git a/lib/bindings/src/breez_liquid_sdk.udl b/lib/bindings/src/breez_liquid_sdk.udl index fec19f5..79fb619 100644 --- a/lib/bindings/src/breez_liquid_sdk.udl +++ b/lib/bindings/src/breez_liquid_sdk.udl @@ -6,6 +6,8 @@ enum LiquidSdkError { [Error] enum PaymentError { "AmountOutOfRange", + "InvalidOrExpiredFees", + "InsufficientFunds", "AlreadyClaimed", "Generic", "InvalidInvoice", @@ -42,12 +44,8 @@ dictionary PrepareSendRequest { }; dictionary PrepareSendResponse { - string id; - u64 payer_amount_sat; - u64 receiver_amount_sat; - u64 total_fees; - string funding_address; string invoice; + u64 fees_sat; }; dictionary SendPaymentResponse { @@ -59,7 +57,6 @@ dictionary PrepareReceiveRequest { }; dictionary PrepareReceiveResponse { - string pair_hash; u64 payer_amount_sat; u64 fees_sat; }; diff --git a/lib/bindings/src/lib.rs b/lib/bindings/src/lib.rs index 16dc285..274578e 100644 --- a/lib/bindings/src/lib.rs +++ b/lib/bindings/src/lib.rs @@ -21,7 +21,7 @@ impl BindingLiquidSdk { &self, req: PrepareSendRequest, ) -> Result { - self.sdk.prepare_send_payment(req) + self.sdk.prepare_send_payment(&req) } pub fn send_payment( diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index 2e8770a..f91e7ac 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["lib", "cdylib", "staticlib"] [dependencies] anyhow = { workspace = true } bip39 = { version = "2.0.0", features = ["serde"] } -boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "6f45fff8b87c7530c847eb05f018906c48785a6c" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "410d3a95e528fce36c02e8d414d5b647a31cc28f" } flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" lwk_common = "0.3.0" @@ -21,6 +21,8 @@ rusqlite_migration = "1.0" serde = { version = "1.0.197", features = ["derive"] } thiserror = { workspace = true } openssl = { version = "0.10", features = ["vendored"] } +# TODO Remove once fully migrated to v2 API +elements = "0.24.1" [dev-dependencies] tempdir = "0.3.7" diff --git a/lib/core/src/bindings.rs b/lib/core/src/bindings.rs index f1e6a11..6506909 100644 --- a/lib/core/src/bindings.rs +++ b/lib/core/src/bindings.rs @@ -26,7 +26,7 @@ pub fn prepare_send_payment(req: PrepareSendRequest) -> Result Result { diff --git a/lib/core/src/error.rs b/lib/core/src/error.rs index afe54a6..e18b732 100644 --- a/lib/core/src/error.rs +++ b/lib/core/src/error.rs @@ -27,6 +27,12 @@ pub enum PaymentError { #[error("Invoice amount is out of range")] AmountOutOfRange, + #[error("The provided fees have expired")] + InvalidOrExpiredFees, + + #[error("Cannot pay: not enough funds")] + InsufficientFunds, + #[error("The specified funds have already been claimed")] AlreadyClaimed, diff --git a/lib/core/src/frb/bridge.io.rs b/lib/core/src/frb/bridge.io.rs index 772f1df..7438471 100644 --- a/lib/core/src/frb/bridge.io.rs +++ b/lib/core/src/frb/bridge.io.rs @@ -179,30 +179,32 @@ impl CstDecode for wire_cst_payment_error { fn cst_decode(self) -> crate::error::PaymentError { match self.tag { 0 => crate::error::PaymentError::AmountOutOfRange, - 1 => crate::error::PaymentError::AlreadyClaimed, - 2 => { + 1 => crate::error::PaymentError::InvalidOrExpiredFees, + 2 => crate::error::PaymentError::InsufficientFunds, + 3 => crate::error::PaymentError::AlreadyClaimed, + 4 => { let ans = unsafe { self.kind.Generic }; crate::error::PaymentError::Generic { err: ans.err.cst_decode(), } } - 3 => crate::error::PaymentError::InvalidInvoice, - 4 => crate::error::PaymentError::InvalidPreimage, - 5 => { + 5 => crate::error::PaymentError::InvalidInvoice, + 6 => crate::error::PaymentError::InvalidPreimage, + 7 => { let ans = unsafe { self.kind.LwkError }; crate::error::PaymentError::LwkError { err: ans.err.cst_decode(), } } - 6 => crate::error::PaymentError::PairsNotFound, - 7 => crate::error::PaymentError::PersistError, - 8 => { + 8 => crate::error::PaymentError::PairsNotFound, + 9 => crate::error::PaymentError::PersistError, + 10 => { let ans = unsafe { self.kind.SendError }; crate::error::PaymentError::SendError { err: ans.err.cst_decode(), } } - 9 => { + 11 => { let ans = unsafe { self.kind.SignerError }; crate::error::PaymentError::SignerError { err: ans.err.cst_decode(), @@ -224,7 +226,6 @@ impl CstDecode for wire_cst_prepare_receiv // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::PrepareReceiveResponse { crate::model::PrepareReceiveResponse { - pair_hash: self.pair_hash.cst_decode(), payer_amount_sat: self.payer_amount_sat.cst_decode(), fees_sat: self.fees_sat.cst_decode(), } @@ -242,12 +243,8 @@ impl CstDecode for wire_cst_prepare_send_resp // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::PrepareSendResponse { crate::model::PrepareSendResponse { - id: self.id.cst_decode(), - payer_amount_sat: self.payer_amount_sat.cst_decode(), - receiver_amount_sat: self.receiver_amount_sat.cst_decode(), - total_fees: self.total_fees.cst_decode(), - funding_address: self.funding_address.cst_decode(), invoice: self.invoice.cst_decode(), + fees_sat: self.fees_sat.cst_decode(), } } } @@ -360,7 +357,6 @@ impl Default for wire_cst_prepare_receive_request { impl NewWithNullPtr for wire_cst_prepare_receive_response { fn new_with_null_ptr() -> Self { Self { - pair_hash: core::ptr::null_mut(), payer_amount_sat: Default::default(), fees_sat: Default::default(), } @@ -386,12 +382,8 @@ impl Default for wire_cst_prepare_send_request { impl NewWithNullPtr for wire_cst_prepare_send_response { fn new_with_null_ptr() -> Self { Self { - id: core::ptr::null_mut(), - payer_amount_sat: Default::default(), - receiver_amount_sat: Default::default(), - total_fees: Default::default(), - funding_address: core::ptr::null_mut(), invoice: core::ptr::null_mut(), + fees_sat: Default::default(), } } } @@ -702,7 +694,6 @@ pub struct wire_cst_prepare_receive_request { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_receive_response { - pair_hash: *mut wire_cst_list_prim_u_8_strict, payer_amount_sat: u64, fees_sat: u64, } @@ -714,12 +705,8 @@ pub struct wire_cst_prepare_send_request { #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_prepare_send_response { - id: *mut wire_cst_list_prim_u_8_strict, - payer_amount_sat: u64, - receiver_amount_sat: u64, - total_fees: u64, - funding_address: *mut wire_cst_list_prim_u_8_strict, invoice: *mut wire_cst_list_prim_u_8_strict, + fees_sat: u64, } #[repr(C)] #[derive(Clone, Copy)] diff --git a/lib/core/src/frb/bridge.rs b/lib/core/src/frb/bridge.rs index 32ffddb..aeb0441 100644 --- a/lib/core/src/frb/bridge.rs +++ b/lib/core/src/frb/bridge.rs @@ -464,33 +464,39 @@ impl SseDecode for crate::error::PaymentError { return crate::error::PaymentError::AmountOutOfRange; } 1 => { - return crate::error::PaymentError::AlreadyClaimed; + return crate::error::PaymentError::InvalidOrExpiredFees; } 2 => { + return crate::error::PaymentError::InsufficientFunds; + } + 3 => { + return crate::error::PaymentError::AlreadyClaimed; + } + 4 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::Generic { err: var_err }; } - 3 => { + 5 => { return crate::error::PaymentError::InvalidInvoice; } - 4 => { + 6 => { return crate::error::PaymentError::InvalidPreimage; } - 5 => { + 7 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::LwkError { err: var_err }; } - 6 => { + 8 => { return crate::error::PaymentError::PairsNotFound; } - 7 => { + 9 => { return crate::error::PaymentError::PersistError; } - 8 => { + 10 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::SendError { err: var_err }; } - 9 => { + 11 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::SignerError { err: var_err }; } @@ -528,11 +534,9 @@ impl SseDecode for crate::model::PrepareReceiveRequest { impl SseDecode for crate::model::PrepareReceiveResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut var_pairHash = ::sse_decode(deserializer); let mut var_payerAmountSat = ::sse_decode(deserializer); let mut var_feesSat = ::sse_decode(deserializer); return crate::model::PrepareReceiveResponse { - pair_hash: var_pairHash, payer_amount_sat: var_payerAmountSat, fees_sat: var_feesSat, }; @@ -552,19 +556,11 @@ impl SseDecode for crate::model::PrepareSendRequest { impl SseDecode for crate::model::PrepareSendResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut var_id = ::sse_decode(deserializer); - let mut var_payerAmountSat = ::sse_decode(deserializer); - let mut var_receiverAmountSat = ::sse_decode(deserializer); - let mut var_totalFees = ::sse_decode(deserializer); - let mut var_fundingAddress = ::sse_decode(deserializer); let mut var_invoice = ::sse_decode(deserializer); + let mut var_feesSat = ::sse_decode(deserializer); return crate::model::PrepareSendResponse { - id: var_id, - payer_amount_sat: var_payerAmountSat, - receiver_amount_sat: var_receiverAmountSat, - total_fees: var_totalFees, - funding_address: var_fundingAddress, invoice: var_invoice, + fees_sat: var_feesSat, }; } } @@ -768,22 +764,24 @@ impl flutter_rust_bridge::IntoDart for crate::error::PaymentError { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { crate::error::PaymentError::AmountOutOfRange => [0.into_dart()].into_dart(), - crate::error::PaymentError::AlreadyClaimed => [1.into_dart()].into_dart(), + crate::error::PaymentError::InvalidOrExpiredFees => [1.into_dart()].into_dart(), + crate::error::PaymentError::InsufficientFunds => [2.into_dart()].into_dart(), + crate::error::PaymentError::AlreadyClaimed => [3.into_dart()].into_dart(), crate::error::PaymentError::Generic { err } => { - [2.into_dart(), err.into_into_dart().into_dart()].into_dart() + [4.into_dart(), err.into_into_dart().into_dart()].into_dart() } - crate::error::PaymentError::InvalidInvoice => [3.into_dart()].into_dart(), - crate::error::PaymentError::InvalidPreimage => [4.into_dart()].into_dart(), + crate::error::PaymentError::InvalidInvoice => [5.into_dart()].into_dart(), + crate::error::PaymentError::InvalidPreimage => [6.into_dart()].into_dart(), crate::error::PaymentError::LwkError { err } => { - [5.into_dart(), err.into_into_dart().into_dart()].into_dart() + [7.into_dart(), err.into_into_dart().into_dart()].into_dart() } - crate::error::PaymentError::PairsNotFound => [6.into_dart()].into_dart(), - crate::error::PaymentError::PersistError => [7.into_dart()].into_dart(), + crate::error::PaymentError::PairsNotFound => [8.into_dart()].into_dart(), + crate::error::PaymentError::PersistError => [9.into_dart()].into_dart(), crate::error::PaymentError::SendError { err } => { - [8.into_dart(), err.into_into_dart().into_dart()].into_dart() + [10.into_dart(), err.into_into_dart().into_dart()].into_dart() } crate::error::PaymentError::SignerError { err } => { - [9.into_dart(), err.into_into_dart().into_dart()].into_dart() + [11.into_dart(), err.into_into_dart().into_dart()].into_dart() } } } @@ -832,7 +830,6 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ - self.pair_hash.into_into_dart().into_dart(), self.payer_amount_sat.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(), ] @@ -871,12 +868,8 @@ impl flutter_rust_bridge::IntoIntoDart impl flutter_rust_bridge::IntoDart for crate::model::PrepareSendResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ - self.id.into_into_dart().into_dart(), - self.payer_amount_sat.into_into_dart().into_dart(), - self.receiver_amount_sat.into_into_dart().into_dart(), - self.total_fees.into_into_dart().into_dart(), - self.funding_address.into_into_dart().into_dart(), self.invoice.into_into_dart().into_dart(), + self.fees_sat.into_into_dart().into_dart(), ] .into_dart() } @@ -1100,35 +1093,41 @@ impl SseEncode for crate::error::PaymentError { crate::error::PaymentError::AmountOutOfRange => { ::sse_encode(0, serializer); } - crate::error::PaymentError::AlreadyClaimed => { + crate::error::PaymentError::InvalidOrExpiredFees => { ::sse_encode(1, serializer); } - crate::error::PaymentError::Generic { err } => { + crate::error::PaymentError::InsufficientFunds => { ::sse_encode(2, serializer); + } + crate::error::PaymentError::AlreadyClaimed => { + ::sse_encode(3, serializer); + } + crate::error::PaymentError::Generic { err } => { + ::sse_encode(4, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::InvalidInvoice => { - ::sse_encode(3, serializer); + ::sse_encode(5, serializer); } crate::error::PaymentError::InvalidPreimage => { - ::sse_encode(4, serializer); + ::sse_encode(6, serializer); } crate::error::PaymentError::LwkError { err } => { - ::sse_encode(5, serializer); + ::sse_encode(7, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::PairsNotFound => { - ::sse_encode(6, serializer); + ::sse_encode(8, serializer); } crate::error::PaymentError::PersistError => { - ::sse_encode(7, serializer); + ::sse_encode(9, serializer); } crate::error::PaymentError::SendError { err } => { - ::sse_encode(8, serializer); + ::sse_encode(10, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::SignerError { err } => { - ::sse_encode(9, serializer); + ::sse_encode(11, serializer); ::sse_encode(err, serializer); } } @@ -1163,7 +1162,6 @@ impl SseEncode for crate::model::PrepareReceiveRequest { impl SseEncode for crate::model::PrepareReceiveResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - ::sse_encode(self.pair_hash, serializer); ::sse_encode(self.payer_amount_sat, serializer); ::sse_encode(self.fees_sat, serializer); } @@ -1179,12 +1177,8 @@ impl SseEncode for crate::model::PrepareSendRequest { impl SseEncode for crate::model::PrepareSendResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - ::sse_encode(self.id, serializer); - ::sse_encode(self.payer_amount_sat, serializer); - ::sse_encode(self.receiver_amount_sat, serializer); - ::sse_encode(self.total_fees, serializer); - ::sse_encode(self.funding_address, serializer); ::sse_encode(self.invoice, serializer); + ::sse_encode(self.fees_sat, serializer); } } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 9f9e631..3b02843 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -86,7 +86,6 @@ pub struct PrepareReceiveRequest { #[derive(Debug, Serialize)] pub struct PrepareReceiveResponse { - pub pair_hash: String, pub payer_amount_sat: u64, pub fees_sat: u64, } @@ -104,12 +103,8 @@ pub struct PrepareSendRequest { #[derive(Debug, Serialize, Clone)] pub struct PrepareSendResponse { - pub id: String, - pub payer_amount_sat: u64, - pub receiver_amount_sat: u64, - pub total_fees: u64, - pub funding_address: String, pub invoice: String, + pub fees_sat: u64, } #[derive(Debug, Serialize)] @@ -137,9 +132,8 @@ pub struct RestoreRequest { pub(crate) enum OngoingSwap { Send { id: String, - funding_address: String, invoice: String, - receiver_amount_sat: u64, + payer_amount_sat: u64, txid: Option, }, Receive { @@ -149,8 +143,16 @@ pub(crate) enum OngoingSwap { blinding_key: String, invoice: String, receiver_amount_sat: u64, + claim_fees_sat: u64, }, } +impl OngoingSwap { + pub(crate) fn id(&self) -> &str { + match &self { + OngoingSwap::Send { id, .. } | OngoingSwap::Receive { id, .. } => id, + } + } +} #[derive(Debug, Clone, PartialEq, Serialize)] pub enum PaymentType { @@ -169,7 +171,6 @@ pub struct Payment { #[serde(rename(serialize = "type"))] pub payment_type: PaymentType, - /// Only for [PaymentType::PendingReceive] pub invoice: Option, } @@ -178,17 +179,17 @@ impl From for Payment { match swap { OngoingSwap::Send { invoice, - receiver_amount_sat, + payer_amount_sat, .. } => { - let payer_amount_sat = get_invoice_amount!(invoice); + let receiver_amount_sat = get_invoice_amount!(invoice); Payment { id: None, timestamp: None, payment_type: PaymentType::PendingSend, amount_sat: payer_amount_sat, invoice: Some(invoice), - fees_sat: Some(receiver_amount_sat - payer_amount_sat), + fees_sat: Some(payer_amount_sat - receiver_amount_sat), } } OngoingSwap::Receive { diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 2bad5b4..ea8d52b 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -7,16 +7,16 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { blinding_key TEXT NOT NULL, invoice TEXT NOT NULL, receiver_amount_sat INTEGER NOT NULL, - created_at TEXT DEFAULT CURRENT_TIMESTAMP - ) STRICT;", - "CREATE TABLE IF NOT EXISTS ongoing_send_swaps ( - id TEXT NOT NULL PRIMARY KEY, - funding_address TEXT NOT NULL, - invoice TEXT NOT NULL, - receiver_amount_sat INTEGER NOT NULL, - txid TEXT, - created_at TEXT DEFAULT CURRENT_TIMESTAMP + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + claim_fees_sat INTEGER NOT NULL ) STRICT;", + "CREATE TABLE IF NOT EXISTS ongoing_send_swaps( + id TEXT NOT NULL PRIMARY KEY, + invoice TEXT NOT NULL, + payer_amount_sat INTEGER NOT NULL, + txid TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP + ) STRICT;", "CREATE TABLE IF NOT EXISTS payment_data( id TEXT NOT NULL PRIMARY KEY, payer_amount_sat INTEGER NOT NULL diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index f66649b..7a223fd 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -54,24 +54,22 @@ impl Persister { match swap { OngoingSwap::Send { id, - funding_address, invoice, - receiver_amount_sat, + payer_amount_sat, txid, } => { let mut stmt = con.prepare( " INSERT OR REPLACE INTO ongoing_send_swaps ( id, - funding_address, invoice, - receiver_amount_sat, + payer_amount_sat, txid ) - VALUES (?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?) ", )?; - _ = stmt.execute((id, funding_address, invoice, receiver_amount_sat, txid))? + _ = stmt.execute((id, invoice, payer_amount_sat, txid))? } OngoingSwap::Receive { id, @@ -80,6 +78,7 @@ impl Persister { blinding_key, invoice, receiver_amount_sat, + claim_fees_sat, } => { let mut stmt = con.prepare( " @@ -89,9 +88,10 @@ impl Persister { redeem_script, blinding_key, invoice, - receiver_amount_sat + receiver_amount_sat, + claim_fees_sat ) - VALUES (?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) ", )?; @@ -102,6 +102,7 @@ impl Persister { blinding_key, invoice, receiver_amount_sat, + claim_fees_sat, ))? } } @@ -147,9 +148,8 @@ impl Persister { " SELECT id, - funding_address, invoice, - receiver_amount_sat, + payer_amount_sat, txid, created_at FROM ongoing_send_swaps @@ -161,10 +161,9 @@ impl Persister { .query_map(params![], |row| { Ok(OngoingSwap::Send { id: row.get(0)?, - funding_address: row.get(1)?, - invoice: row.get(2)?, - receiver_amount_sat: row.get(3)?, - txid: row.get(4)?, + invoice: row.get(1)?, + payer_amount_sat: row.get(2)?, + txid: row.get(3)?, }) })? .map(|i| i.unwrap()) @@ -183,6 +182,7 @@ impl Persister { blinding_key, invoice, receiver_amount_sat, + claim_fees_sat, created_at FROM ongoing_receive_swaps ORDER BY created_at @@ -198,6 +198,7 @@ impl Persister { blinding_key: row.get(3)?, invoice: row.get(4)?, receiver_amount_sat: row.get(5)?, + claim_fees_sat: row.get(6)?, }) })? .map(|i| i.unwrap()) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 5a673c0..b0fd071 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -15,12 +15,17 @@ use boltz_client::{ BoltzApiClient, CreateSwapRequest, RevSwapStates, SubSwapStates, SwapStatusRequest, BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL, }, + boltzv2::{ + BoltzApiClientV2, ReversePair, SubmarinePair, BOLTZ_MAINNET_URL_V2, + BOLTZ_TESTNET_URL_V2, + }, liquid::{LBtcSwapScript, LBtcSwapTx}, }, util::secrets::{LBtcReverseRecovery, LiquidSwapKey, Preimage, SwapKey}, Bolt11Invoice, Keypair, }; -use log::{debug, error, warn}; +use elements::hashes::hex::DisplayHex; +use log::{debug, error, info, warn}; use lwk_common::{singlesig_desc, Signer, Singlesig}; use lwk_signer::{AnySigner, SwSigner}; use lwk_wollet::{ @@ -31,10 +36,13 @@ use lwk_wollet::{ use crate::{ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister}; -/// Claim tx feerate for Receive, in sats per vbyte. +/// Claim tx feerate, in sats per vbyte. /// Since the Liquid blocks are consistently empty for now, we hardcode the minimum feerate. pub const LIQUID_CLAIM_TX_FEERATE: f32 = 0.1; +// TODO: Remove in favor of V2 API (necessary as V1 claim_estimate is not working) +pub const LIQUID_MIN_CLAIM_ABSOLUTE_FEES: u64 = 134; + pub const DEFAULT_DATA_DIR: &str = ".data"; pub struct LiquidSdk { @@ -121,6 +129,7 @@ impl LiquidSdk { redeem_script, blinding_key, invoice, + claim_fees_sat, .. } => { let status = client @@ -130,7 +139,7 @@ impl LiquidSdk { let swap_state = status .parse::() - .map_err(|_| anyhow!("Invalid swap state received for swap {id}: {status}",))?; + .map_err(|_| anyhow!("Invalid reverse swap state received for swap {id}: {status}",))?; match swap_state { RevSwapStates::SwapExpired @@ -142,13 +151,13 @@ impl LiquidSdk { .resolve_ongoing_swap(id, None) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; } - RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed => {} + RevSwapStates::TransactionConfirmed => {} _ => { - return Err(anyhow!("New swap state for swap {id}: {status}")); + return Err(anyhow!("New swap state for reverse swap {id}: {status}")); } } - match sdk.try_claim(preimage, redeem_script, blinding_key) { + match sdk.try_claim(preimage, redeem_script, blinding_key, *claim_fees_sat) { Ok(txid) => { let payer_amount_sat = get_invoice_amount!(invoice); sdk.persister @@ -183,7 +192,7 @@ impl LiquidSdk { let state: SubSwapStates = status .parse() - .map_err(|_| anyhow!("Invalid swap state received for swap {id}: {status}"))?; + .map_err(|_| anyhow!("Invalid submarine swap state received for swap {id}: {status}"))?; match state { SubSwapStates::TransactionClaimed @@ -200,7 +209,7 @@ impl LiquidSdk { .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; } _ => { - return Err(anyhow!("New swap state for swap {id}: {status}")); + return Err(anyhow!("New swap state for submarine swap {id}: {status}")); } } } @@ -215,18 +224,22 @@ impl LiquidSdk { thread::spawn(move || loop { thread::sleep(Duration::from_secs(5)); - let Ok(ongoing_swaps) = cloned.persister.list_ongoing_swaps() else { - error!("Could not read ongoing swaps from database"); - continue; - }; - - for swap in ongoing_swaps { - LiquidSdk::try_resolve_pending_swap(&cloned, &client, &swap).unwrap_or_else(|err| { - match swap { - OngoingSwap::Send { .. } => error!("[Ongoing Send] {err}"), - OngoingSwap::Receive { .. } => error!("[Ongoing Receive] {err}"), + match cloned.persister.list_ongoing_swaps() { + Ok(ongoing_swaps) => { + for swap in ongoing_swaps { + match LiquidSdk::try_resolve_pending_swap(&cloned, &client, &swap) { + Ok(_) => info!("Resolved pending swap {}", swap.id()), + Err(err) => match swap { + OngoingSwap::Send { .. } => error!("[Ongoing Send] {err}"), + OngoingSwap::Receive { .. } => error!("[Ongoing Receive] {err}"), + }, + } } - }) + } + Err(e) => { + error!("Could not read ongoing swaps from database: {e}"); + continue; + } } }); @@ -274,12 +287,21 @@ impl LiquidSdk { BoltzApiClient::new(base_url) } - fn get_network_config(&self) -> ElectrumConfig { + fn boltz_client_v2(&self) -> BoltzApiClientV2 { + let base_url = match self.network { + Network::LiquidTestnet => BOLTZ_TESTNET_URL_V2, + Network::Liquid => BOLTZ_MAINNET_URL_V2, + }; + + BoltzApiClientV2::new(base_url) + } + + fn network_config(&self) -> ElectrumConfig { ElectrumConfig::new( self.network.into(), &self.electrum_url.to_string(), true, - false, + true, 100, ) } @@ -297,83 +319,132 @@ impl LiquidSdk { Ok(lwk_wollet.finalize(&mut pset)?) } - pub fn prepare_send_payment( - &self, - req: PrepareSendRequest, - ) -> Result { - let client = self.boltz_client(); - let invoice = req - .invoice + fn validate_invoice(&self, invoice: &str) -> Result { + let invoice = invoice .trim() .parse::() .map_err(|_| PaymentError::InvalidInvoice)?; - // TODO Separate error type? Or make WalletError more generic? + match (invoice.network().to_string().as_str(), self.network) { + ("bitcoin", Network::Liquid) => {} + ("testnet", Network::LiquidTestnet) => {} + _ => return Err(PaymentError::InvalidInvoice), + } + + ensure_sdk!(!invoice.is_expired(), PaymentError::InvalidInvoice); + + Ok(invoice) + } + + #[allow(dead_code)] + fn validate_submarine_pairs( + client: &BoltzApiClientV2, + receiver_amount_sat: u64, + ) -> Result { + let lbtc_pair = client + .get_submarine_pairs()? + .get_lbtc_to_btc_pair() + .ok_or(PaymentError::PairsNotFound)?; + + lbtc_pair.limits.within(receiver_amount_sat)?; + + let fees_sat = lbtc_pair.fees.total(receiver_amount_sat); + + ensure_sdk!( + receiver_amount_sat > fees_sat, + PaymentError::AmountOutOfRange + ); + + Ok(lbtc_pair) + } + + pub fn prepare_send_payment( + &self, + req: &PrepareSendRequest, + ) -> Result { + let invoice = self.validate_invoice(&req.invoice)?; + let receiver_amount_sat = invoice + .amount_milli_satoshis() + .ok_or(PaymentError::AmountOutOfRange)? + / 1000; + + // let client = self.boltz_client_v2(); + // let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + + let client = self.boltz_client(); let lbtc_pair = client .get_pairs()? .get_lbtc_pair() .ok_or(PaymentError::PairsNotFound)?; - let payer_amount_sat = invoice - .amount_milli_satoshis() - .ok_or(PaymentError::AmountOutOfRange)? - / 1000; - - lbtc_pair - .limits - .within(payer_amount_sat) - .map_err(|_| PaymentError::AmountOutOfRange)?; - - let swap_response = client.create_swap(CreateSwapRequest::new_lbtc_submarine( - &lbtc_pair.hash, - &invoice.to_string(), - "", - ))?; - - let id = swap_response.get_id(); - let funding_address = swap_response.get_funding_address()?; - let receiver_amount_sat = swap_response.get_funding_amount()?; - let network_fees: u64 = self - .build_tx(None, &funding_address.to_string(), receiver_amount_sat)? - .all_fees() - .values() - .sum(); - - self.persister - .insert_or_update_ongoing_swap(&[OngoingSwap::Send { - id: id.clone(), - funding_address: funding_address.clone(), - invoice: invoice.to_string(), - receiver_amount_sat: receiver_amount_sat + network_fees, - txid: None, - }]) - .map_err(|_| PaymentError::PersistError)?; - Ok(PrepareSendResponse { - id, - funding_address, - invoice: invoice.to_string(), - payer_amount_sat, - receiver_amount_sat, - total_fees: receiver_amount_sat + network_fees - payer_amount_sat, + invoice: req.invoice.clone(), + fees_sat: lbtc_pair.fees.submarine_boltz(receiver_amount_sat) + + lbtc_pair.fees.submarine_lockup_estimate() + + LIQUID_MIN_CLAIM_ABSOLUTE_FEES, }) } pub fn send_payment( &self, - res: &PrepareSendResponse, + req: &PrepareSendResponse, ) -> Result { - let tx = self.build_tx(None, &res.funding_address, res.receiver_amount_sat)?; + let invoice = self.validate_invoice(&req.invoice)?; + let receiver_amount_sat = invoice + .amount_milli_satoshis() + .ok_or(PaymentError::AmountOutOfRange)? + / 1000; + + // let client = self.boltz_client_v2(); + // let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + + let client = self.boltz_client(); + let lbtc_pair = client + .get_pairs()? + .get_lbtc_pair() + .ok_or(PaymentError::PairsNotFound)?; + + // let new_fees = lbtc_pair.fees.total(receiver_amount_sat); + let new_fees = lbtc_pair.fees.submarine_boltz(receiver_amount_sat) + + lbtc_pair.fees.submarine_lockup_estimate() + + LIQUID_MIN_CLAIM_ABSOLUTE_FEES; + + ensure_sdk!(req.fees_sat == new_fees, PaymentError::InvalidOrExpiredFees); + + ensure_sdk!( + receiver_amount_sat + req.fees_sat <= self.total_balance_sat(true)?, + PaymentError::InsufficientFunds + ); + + let swap_response = client.create_swap(CreateSwapRequest::new_lbtc_submarine( + &lbtc_pair.hash, + &req.invoice.to_string(), + // TODO Add refund + "", + ))?; + + let id = swap_response.get_id(); + let funding_address = swap_response.get_funding_address()?; + let funding_amount_sat = swap_response.get_funding_amount()?; + + // let absolute_fees = self + // .build_tx(None, &funding_address, funding_amount_sat)? + // .all_fees() + // .values() + // .sum::(); + // let fee_rate = req.fees_sat as f32 * LIQUID_CLAIM_TX_FEERATE / absolute_fees as f32; + // + // let tx = self.build_tx(Some(fee_rate), &funding_address, funding_amount_sat)?; + let tx = self.build_tx(None, &funding_address, funding_amount_sat)?; let electrum_client = ElectrumClient::new(&self.electrum_url)?; let txid = electrum_client.broadcast(&tx)?.to_string(); self.persister .insert_or_update_ongoing_swap(&[OngoingSwap::Send { - id: res.id.clone(), - funding_address: res.funding_address.clone(), - invoice: res.invoice.clone(), - receiver_amount_sat: res.receiver_amount_sat + res.total_fees, + id, + invoice: req.invoice.clone(), + payer_amount_sat: receiver_amount_sat + req.fees_sat, txid: Some(txid.clone()), }]) .map_err(|_| PaymentError::PersistError)?; @@ -386,8 +457,9 @@ impl LiquidSdk { preimage: &str, redeem_script: &str, blinding_key: &str, + claim_fees_sat: u64, ) -> Result { - let network_config = &self.get_network_config(); + let network_config = &self.network_config(); let rev_swap_tx = LBtcSwapTx::new_claim( LBtcSwapScript::reverse_from_str(redeem_script, blinding_key)?, self.address()?.to_string(), @@ -406,64 +478,93 @@ impl LiquidSdk { let lsk = LiquidSwapKey::try_from(swap_key)?; let preimage = Preimage::from_str(preimage)?; - // Create a mock tx to calculate the size, then multiply by fee rate - let absolute_fees = (rev_swap_tx - .sign_claim(&lsk.keypair, &preimage, 100)? - .vsize() as f32 - * LIQUID_CLAIM_TX_FEERATE) - .ceil() as u64; + let signed_tx = rev_swap_tx.sign_claim(&lsk.keypair, &preimage, claim_fees_sat)?; + let tx_hex = elements::encode::serialize(&signed_tx).to_lower_hex_string(); - let signed_tx = rev_swap_tx.sign_claim(&lsk.keypair, &preimage, absolute_fees)?; - let txid = rev_swap_tx.broadcast(signed_tx, network_config)?; + let client = self.boltz_client_v2(); + let response = client.broadcast_tx(self.network.into(), &tx_hex)?; + let txid = response + .as_object() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .get("id") + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .as_str() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .to_string(); Ok(txid) } + #[allow(dead_code)] + fn validate_reverse_pairs( + client: &BoltzApiClientV2, + payer_amount_sat: u64, + ) -> Result { + let lbtc_pair = client + .get_reverse_pairs()? + .get_btc_to_lbtc_pair() + .ok_or(PaymentError::PairsNotFound)?; + + lbtc_pair.limits.within(payer_amount_sat)?; + + let fees_sat = lbtc_pair.fees.total(payer_amount_sat); + + ensure_sdk!(payer_amount_sat > fees_sat, PaymentError::AmountOutOfRange); + + Ok(lbtc_pair) + } + pub fn prepare_receive_payment( &self, req: &PrepareReceiveRequest, ) -> Result { + // let client = self.boltz_client_v2(); + // let lbtc_pair = Self::validate_reverse_pairs(&client, req.payer_amount_sat)?; + let client = self.boltz_client(); let lbtc_pair = client .get_pairs()? .get_lbtc_pair() .ok_or(PaymentError::PairsNotFound)?; - let payer_amount_sat = req.payer_amount_sat; - let fees_boltz = lbtc_pair.fees.reverse_boltz(payer_amount_sat); - let fees_lockup = lbtc_pair.fees.reverse_lockup(); - let fees_claim = lbtc_pair.fees.reverse_claim_estimate(); - let fees_total = fees_boltz + fees_lockup + fees_claim; - - ensure_sdk!( - payer_amount_sat > fees_total, - PaymentError::AmountOutOfRange - ); - - lbtc_pair - .limits - .within(payer_amount_sat) - .map_err(|_| PaymentError::AmountOutOfRange)?; - - debug!("Creating reverse swap with: payer_amount_sat {payer_amount_sat} sat, fees_total {fees_total} sat"); + let fees_sat = lbtc_pair.fees.reverse_total(req.payer_amount_sat); Ok(PrepareReceiveResponse { - pair_hash: lbtc_pair.hash, - payer_amount_sat, - fees_sat: fees_total, + payer_amount_sat: req.payer_amount_sat, + fees_sat, }) } pub fn receive_payment( &self, - res: &PrepareReceiveResponse, + req: &PrepareReceiveResponse, ) -> Result { + // let client = self.boltz_client_v2(); + // let lbtc_pair = Self::validate_reverse_pairs(&client, req.payer_amount_sat)?; + + let client = self.boltz_client(); + let lbtc_pair = client + .get_pairs()? + .get_lbtc_pair() + .ok_or(PaymentError::PairsNotFound)?; + + ensure_sdk!( + req.fees_sat == lbtc_pair.fees.reverse_total(req.payer_amount_sat), + PaymentError::InvalidOrExpiredFees + ); + let client = self.boltz_client(); let mnemonic = self .lwk_signer .mnemonic() .ok_or(PaymentError::SignerError { - err: "Could not claim: Mnemonic not found".to_string(), + err: "Could not start receive: Mnemonic not found".to_string(), })?; let swap_key = SwapKey::from_reverse_account(&mnemonic.to_string(), "", self.network.into(), 0)?; @@ -473,11 +574,15 @@ impl LiquidSdk { let preimage_str = preimage.to_string().ok_or(PaymentError::InvalidPreimage)?; let preimage_hash = preimage.sha256.to_string(); + debug!( + "Creating reverse swap with: payer_amount_sat {} sat, fees_total {} sat", + req.payer_amount_sat, req.fees_sat + ); let swap_response = client.create_swap(CreateSwapRequest::new_lbtc_reverse_invoice_amt( - res.pair_hash.clone(), + lbtc_pair.hash.clone(), preimage_hash.clone(), lsk.keypair.public_key().to_string(), - res.payer_amount_sat, + req.payer_amount_sat, ))?; let swap_id = swap_response.get_id(); @@ -496,14 +601,15 @@ impl LiquidSdk { }; self.persister - .insert_or_update_ongoing_swap(dbg!(&[OngoingSwap::Receive { + .insert_or_update_ongoing_swap(&[OngoingSwap::Receive { id: swap_id.clone(), preimage: preimage_str, blinding_key: blinding_str, redeem_script, invoice: invoice.to_string(), - receiver_amount_sat: payer_amount_sat - res.fees_sat, - }])) + receiver_amount_sat: payer_amount_sat - req.fees_sat, + claim_fees_sat: lbtc_pair.fees.reverse_claim_estimate(), + }]) .map_err(|_| PaymentError::PersistError)?; Ok(ReceivePaymentResponse { @@ -553,7 +659,7 @@ impl LiquidSdk { pub fn recover_funds(&self, recovery: &LBtcReverseRecovery) -> Result { let script: LBtcSwapScript = recovery.try_into().unwrap(); - let network_config = self.get_network_config(); + let network_config = self.network_config(); debug!("{:?}", script.fetch_utxo(&network_config)); let tx = @@ -638,7 +744,7 @@ mod tests { })?; let invoice = "lntb10u1pnqwkjrpp5j8ucv9mgww0ajk95yfpvuq0gg5825s207clrzl5thvtuzfn68h0sdqqcqzzsxqr23srzjqv8clnrfs9keq3zlg589jvzpw87cqh6rjks0f9g2t9tvuvcqgcl45f6pqqqqqfcqqyqqqqlgqqqqqqgq2qsp5jnuprlxrargr6hgnnahl28nvutj3gkmxmmssu8ztfhmmey3gq2ss9qyyssq9ejvcp6frwklf73xvskzdcuhnnw8dmxag6v44pffwqrxznsly4nqedem3p3zhn6u4ln7k79vk6zv55jjljhnac4gnvr677fyhfgn07qp4x6wrq".to_string(); - sdk.prepare_send_payment(PrepareSendRequest { invoice })?; + sdk.prepare_send_payment(&PrepareSendRequest { invoice })?; assert!(!list_pending(&sdk)?.is_empty()); Ok(()) diff --git a/packages/dart/lib/src/error.dart b/packages/dart/lib/src/error.dart index 4db5363..ba7cf14 100644 --- a/packages/dart/lib/src/error.dart +++ b/packages/dart/lib/src/error.dart @@ -13,6 +13,10 @@ sealed class PaymentError with _$PaymentError implements FrbException { const PaymentError._(); const factory PaymentError.amountOutOfRange() = PaymentError_AmountOutOfRange; + const factory PaymentError.invalidOrExpiredFees() = + PaymentError_InvalidOrExpiredFees; + const factory PaymentError.insufficientFunds() = + PaymentError_InsufficientFunds; const factory PaymentError.alreadyClaimed() = PaymentError_AlreadyClaimed; const factory PaymentError.generic({ required String err, diff --git a/packages/dart/lib/src/error.freezed.dart b/packages/dart/lib/src/error.freezed.dart index a11ce68..4aae1e4 100644 --- a/packages/dart/lib/src/error.freezed.dart +++ b/packages/dart/lib/src/error.freezed.dart @@ -82,6 +82,100 @@ abstract class PaymentError_AmountOutOfRange extends PaymentError { const PaymentError_AmountOutOfRange._() : super._(); } +/// @nodoc +abstract class _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { + factory _$$PaymentError_InvalidOrExpiredFeesImplCopyWith( + _$PaymentError_InvalidOrExpiredFeesImpl value, + $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) then) = + __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, + _$PaymentError_InvalidOrExpiredFeesImpl> + implements _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { + __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl( + _$PaymentError_InvalidOrExpiredFeesImpl _value, + $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$PaymentError_InvalidOrExpiredFeesImpl + extends PaymentError_InvalidOrExpiredFees { + const _$PaymentError_InvalidOrExpiredFeesImpl() : super._(); + + @override + String toString() { + return 'PaymentError.invalidOrExpiredFees()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PaymentError_InvalidOrExpiredFeesImpl); + } + + @override + int get hashCode => runtimeType.hashCode; +} + +abstract class PaymentError_InvalidOrExpiredFees extends PaymentError { + const factory PaymentError_InvalidOrExpiredFees() = + _$PaymentError_InvalidOrExpiredFeesImpl; + const PaymentError_InvalidOrExpiredFees._() : super._(); +} + +/// @nodoc +abstract class _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { + factory _$$PaymentError_InsufficientFundsImplCopyWith( + _$PaymentError_InsufficientFundsImpl value, + $Res Function(_$PaymentError_InsufficientFundsImpl) then) = + __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, + _$PaymentError_InsufficientFundsImpl> + implements _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { + __$$PaymentError_InsufficientFundsImplCopyWithImpl( + _$PaymentError_InsufficientFundsImpl _value, + $Res Function(_$PaymentError_InsufficientFundsImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$PaymentError_InsufficientFundsImpl + extends PaymentError_InsufficientFunds { + const _$PaymentError_InsufficientFundsImpl() : super._(); + + @override + String toString() { + return 'PaymentError.insufficientFunds()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PaymentError_InsufficientFundsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; +} + +abstract class PaymentError_InsufficientFunds extends PaymentError { + const factory PaymentError_InsufficientFunds() = + _$PaymentError_InsufficientFundsImpl; + const PaymentError_InsufficientFunds._() : super._(); +} + /// @nodoc abstract class _$$PaymentError_AlreadyClaimedImplCopyWith<$Res> { factory _$$PaymentError_AlreadyClaimedImplCopyWith( diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 3edd9da..01f0cdf 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -573,28 +573,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 0: return PaymentError_AmountOutOfRange(); case 1: - return PaymentError_AlreadyClaimed(); + return PaymentError_InvalidOrExpiredFees(); case 2: + return PaymentError_InsufficientFunds(); + case 3: + return PaymentError_AlreadyClaimed(); + case 4: return PaymentError_Generic( err: dco_decode_String(raw[1]), ); - case 3: - return PaymentError_InvalidInvoice(); - case 4: - return PaymentError_InvalidPreimage(); case 5: + return PaymentError_InvalidInvoice(); + case 6: + return PaymentError_InvalidPreimage(); + case 7: return PaymentError_LwkError( err: dco_decode_String(raw[1]), ); - case 6: - return PaymentError_PairsNotFound(); - case 7: - return PaymentError_PersistError(); case 8: + return PaymentError_PairsNotFound(); + case 9: + return PaymentError_PersistError(); + case 10: return PaymentError_SendError( err: dco_decode_String(raw[1]), ); - case 9: + case 11: return PaymentError_SignerError( err: dco_decode_String(raw[1]), ); @@ -624,12 +628,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareReceiveResponse dco_decode_prepare_receive_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 3) - throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return PrepareReceiveResponse( - pairHash: dco_decode_String(arr[0]), - payerAmountSat: dco_decode_u_64(arr[1]), - feesSat: dco_decode_u_64(arr[2]), + payerAmountSat: dco_decode_u_64(arr[0]), + feesSat: dco_decode_u_64(arr[1]), ); } @@ -648,15 +651,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendResponse dco_decode_prepare_send_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 6) - throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return PrepareSendResponse( - id: dco_decode_String(arr[0]), - payerAmountSat: dco_decode_u_64(arr[1]), - receiverAmountSat: dco_decode_u_64(arr[2]), - totalFees: dco_decode_u_64(arr[3]), - fundingAddress: dco_decode_String(arr[4]), - invoice: dco_decode_String(arr[5]), + invoice: dco_decode_String(arr[0]), + feesSat: dco_decode_u_64(arr[1]), ); } @@ -940,25 +939,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 0: return PaymentError_AmountOutOfRange(); case 1: - return PaymentError_AlreadyClaimed(); + return PaymentError_InvalidOrExpiredFees(); case 2: + return PaymentError_InsufficientFunds(); + case 3: + return PaymentError_AlreadyClaimed(); + case 4: var var_err = sse_decode_String(deserializer); return PaymentError_Generic(err: var_err); - case 3: - return PaymentError_InvalidInvoice(); - case 4: - return PaymentError_InvalidPreimage(); case 5: + return PaymentError_InvalidInvoice(); + case 6: + return PaymentError_InvalidPreimage(); + case 7: var var_err = sse_decode_String(deserializer); return PaymentError_LwkError(err: var_err); - case 6: - return PaymentError_PairsNotFound(); - case 7: - return PaymentError_PersistError(); case 8: + return PaymentError_PairsNotFound(); + case 9: + return PaymentError_PersistError(); + case 10: var var_err = sse_decode_String(deserializer); return PaymentError_SendError(err: var_err); - case 9: + case 11: var var_err = sse_decode_String(deserializer); return PaymentError_SignerError(err: var_err); default: @@ -985,13 +988,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareReceiveResponse sse_decode_prepare_receive_response( SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_pairHash = sse_decode_String(deserializer); var var_payerAmountSat = sse_decode_u_64(deserializer); var var_feesSat = sse_decode_u_64(deserializer); return PrepareReceiveResponse( - pairHash: var_pairHash, - payerAmountSat: var_payerAmountSat, - feesSat: var_feesSat); + payerAmountSat: var_payerAmountSat, feesSat: var_feesSat); } @protected @@ -1006,19 +1006,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendResponse sse_decode_prepare_send_response( SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_id = sse_decode_String(deserializer); - var var_payerAmountSat = sse_decode_u_64(deserializer); - var var_receiverAmountSat = sse_decode_u_64(deserializer); - var var_totalFees = sse_decode_u_64(deserializer); - var var_fundingAddress = sse_decode_String(deserializer); var var_invoice = sse_decode_String(deserializer); - return PrepareSendResponse( - id: var_id, - payerAmountSat: var_payerAmountSat, - receiverAmountSat: var_receiverAmountSat, - totalFees: var_totalFees, - fundingAddress: var_fundingAddress, - invoice: var_invoice); + var var_feesSat = sse_decode_u_64(deserializer); + return PrepareSendResponse(invoice: var_invoice, feesSat: var_feesSat); } @protected @@ -1334,27 +1324,31 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { switch (self) { case PaymentError_AmountOutOfRange(): sse_encode_i_32(0, serializer); - case PaymentError_AlreadyClaimed(): + case PaymentError_InvalidOrExpiredFees(): sse_encode_i_32(1, serializer); - case PaymentError_Generic(err: final err): + case PaymentError_InsufficientFunds(): sse_encode_i_32(2, serializer); + case PaymentError_AlreadyClaimed(): + sse_encode_i_32(3, serializer); + case PaymentError_Generic(err: final err): + sse_encode_i_32(4, serializer); sse_encode_String(err, serializer); case PaymentError_InvalidInvoice(): - sse_encode_i_32(3, serializer); - case PaymentError_InvalidPreimage(): - sse_encode_i_32(4, serializer); - case PaymentError_LwkError(err: final err): sse_encode_i_32(5, serializer); + case PaymentError_InvalidPreimage(): + sse_encode_i_32(6, serializer); + case PaymentError_LwkError(err: final err): + sse_encode_i_32(7, serializer); sse_encode_String(err, serializer); case PaymentError_PairsNotFound(): - sse_encode_i_32(6, serializer); - case PaymentError_PersistError(): - sse_encode_i_32(7, serializer); - case PaymentError_SendError(err: final err): sse_encode_i_32(8, serializer); + case PaymentError_PersistError(): + sse_encode_i_32(9, serializer); + case PaymentError_SendError(err: final err): + sse_encode_i_32(10, serializer); sse_encode_String(err, serializer); case PaymentError_SignerError(err: final err): - sse_encode_i_32(9, serializer); + sse_encode_i_32(11, serializer); sse_encode_String(err, serializer); } } @@ -1376,7 +1370,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_prepare_receive_response( PrepareReceiveResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String(self.pairHash, serializer); sse_encode_u_64(self.payerAmountSat, serializer); sse_encode_u_64(self.feesSat, serializer); } @@ -1392,12 +1385,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_prepare_send_response( PrepareSendResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String(self.id, serializer); - sse_encode_u_64(self.payerAmountSat, serializer); - sse_encode_u_64(self.receiverAmountSat, serializer); - sse_encode_u_64(self.totalFees, serializer); - sse_encode_String(self.fundingAddress, serializer); sse_encode_String(self.invoice, serializer); + sse_encode_u_64(self.feesSat, serializer); } @protected diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index c52987d..4281a16 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -500,47 +500,55 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.tag = 0; return; } - if (apiObj is PaymentError_AlreadyClaimed) { + if (apiObj is PaymentError_InvalidOrExpiredFees) { wireObj.tag = 1; return; } + if (apiObj is PaymentError_InsufficientFunds) { + wireObj.tag = 2; + return; + } + if (apiObj is PaymentError_AlreadyClaimed) { + wireObj.tag = 3; + return; + } if (apiObj is PaymentError_Generic) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 2; + wireObj.tag = 4; wireObj.kind.Generic.err = pre_err; return; } if (apiObj is PaymentError_InvalidInvoice) { - wireObj.tag = 3; + wireObj.tag = 5; return; } if (apiObj is PaymentError_InvalidPreimage) { - wireObj.tag = 4; + wireObj.tag = 6; return; } if (apiObj is PaymentError_LwkError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 5; + wireObj.tag = 7; wireObj.kind.LwkError.err = pre_err; return; } if (apiObj is PaymentError_PairsNotFound) { - wireObj.tag = 6; + wireObj.tag = 8; return; } if (apiObj is PaymentError_PersistError) { - wireObj.tag = 7; + wireObj.tag = 9; return; } if (apiObj is PaymentError_SendError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 8; + wireObj.tag = 10; wireObj.kind.SendError.err = pre_err; return; } if (apiObj is PaymentError_SignerError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 9; + wireObj.tag = 11; wireObj.kind.SignerError.err = pre_err; return; } @@ -556,7 +564,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void cst_api_fill_to_wire_prepare_receive_response( PrepareReceiveResponse apiObj, wire_cst_prepare_receive_response wireObj) { - wireObj.pair_hash = cst_encode_String(apiObj.pairHash); wireObj.payer_amount_sat = cst_encode_u_64(apiObj.payerAmountSat); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); } @@ -570,12 +577,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_prepare_send_response( PrepareSendResponse apiObj, wire_cst_prepare_send_response wireObj) { - wireObj.id = cst_encode_String(apiObj.id); - wireObj.payer_amount_sat = cst_encode_u_64(apiObj.payerAmountSat); - wireObj.receiver_amount_sat = cst_encode_u_64(apiObj.receiverAmountSat); - wireObj.total_fees = cst_encode_u_64(apiObj.totalFees); - wireObj.funding_address = cst_encode_String(apiObj.fundingAddress); wireObj.invoice = cst_encode_String(apiObj.invoice); + wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); } @protected @@ -799,7 +802,7 @@ class RustLibWire implements BaseWire { : _lookup = lookup; void store_dart_post_cobject( - DartPostCObjectFnType ptr, + int ptr, ) { return _store_dart_post_cobject( ptr, @@ -807,10 +810,10 @@ class RustLibWire implements BaseWire { } late final _store_dart_post_cobjectPtr = - _lookup>( + _lookup>( 'store_dart_post_cobject'); - late final _store_dart_post_cobject = _store_dart_post_cobjectPtr - .asFunction(); + late final _store_dart_post_cobject = + _store_dart_post_cobjectPtr.asFunction(); void wire_backup( int port_, @@ -877,8 +880,8 @@ class RustLibWire implements BaseWire { void wire_list_payments( int port_, - bool with_scan, - bool include_pending, + ffi.Pointer with_scan, + ffi.Pointer include_pending, ) { return _wire_list_payments( port_, @@ -888,10 +891,11 @@ class RustLibWire implements BaseWire { } late final _wire_list_paymentsPtr = _lookup< - ffi.NativeFunction>( - 'frbgen_breez_liquid_wire_list_payments'); - late final _wire_list_payments = - _wire_list_paymentsPtr.asFunction(); + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, ffi.Pointer, + ffi.Pointer)>>('frbgen_breez_liquid_wire_list_payments'); + late final _wire_list_payments = _wire_list_paymentsPtr + .asFunction, ffi.Pointer)>(); void wire_prepare_receive_payment( int port_, @@ -1189,15 +1193,6 @@ class RustLibWire implements BaseWire { _dummy_method_to_enforce_bundlingPtr.asFunction(); } -typedef DartPostCObjectFnType - = ffi.Pointer>; -typedef DartPostCObjectFnTypeFunction = ffi.Bool Function( - DartPort port_id, ffi.Pointer message); -typedef DartDartPostCObjectFnTypeFunction = bool Function( - DartDartPort port_id, ffi.Pointer message); -typedef DartPort = ffi.Int64; -typedef DartDartPort = int; - final class wire_cst_list_prim_u_8_strict extends ffi.Struct { external ffi.Pointer ptr; @@ -1215,10 +1210,11 @@ final class wire_cst_connect_request extends ffi.Struct { } final class wire_cst_get_info_request extends ffi.Struct { - @ffi.Bool() external bool with_scan; } +typedef bool = ffi.NativeFunction)>; + final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Uint64() external int payer_amount_sat; @@ -1229,8 +1225,6 @@ final class wire_cst_prepare_send_request extends ffi.Struct { } final class wire_cst_prepare_receive_response extends ffi.Struct { - external ffi.Pointer pair_hash; - @ffi.Uint64() external int payer_amount_sat; @@ -1243,20 +1237,10 @@ final class wire_cst_restore_request extends ffi.Struct { } final class wire_cst_prepare_send_response extends ffi.Struct { - external ffi.Pointer id; - - @ffi.Uint64() - external int payer_amount_sat; - - @ffi.Uint64() - external int receiver_amount_sat; - - @ffi.Uint64() - external int total_fees; - - external ffi.Pointer funding_address; - external ffi.Pointer invoice; + + @ffi.Uint64() + external int fees_sat; } final class wire_cst_payment extends ffi.Struct { @@ -1333,3 +1317,5 @@ final class wire_cst_send_payment_response extends ffi.Struct { } const double LIQUID_CLAIM_TX_FEERATE = 0.1; + +const int LIQUID_MIN_CLAIM_ABSOLUTE_FEES = 134; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 6d03b73..a9dd77a 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -81,8 +81,6 @@ class Payment { final int amountSat; final int? feesSat; final PaymentType paymentType; - - /// Only for [PaymentType::PendingReceive] final String? invoice; const Payment({ @@ -143,26 +141,22 @@ class PrepareReceiveRequest { } class PrepareReceiveResponse { - final String pairHash; final int payerAmountSat; final int feesSat; const PrepareReceiveResponse({ - required this.pairHash, required this.payerAmountSat, required this.feesSat, }); @override - int get hashCode => - pairHash.hashCode ^ payerAmountSat.hashCode ^ feesSat.hashCode; + int get hashCode => payerAmountSat.hashCode ^ feesSat.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareReceiveResponse && runtimeType == other.runtimeType && - pairHash == other.pairHash && payerAmountSat == other.payerAmountSat && feesSat == other.feesSat; } @@ -186,42 +180,24 @@ class PrepareSendRequest { } class PrepareSendResponse { - final String id; - final int payerAmountSat; - final int receiverAmountSat; - final int totalFees; - final String fundingAddress; final String invoice; + final int feesSat; const PrepareSendResponse({ - required this.id, - required this.payerAmountSat, - required this.receiverAmountSat, - required this.totalFees, - required this.fundingAddress, required this.invoice, + required this.feesSat, }); @override - int get hashCode => - id.hashCode ^ - payerAmountSat.hashCode ^ - receiverAmountSat.hashCode ^ - totalFees.hashCode ^ - fundingAddress.hashCode ^ - invoice.hashCode; + int get hashCode => invoice.hashCode ^ feesSat.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareSendResponse && runtimeType == other.runtimeType && - id == other.id && - payerAmountSat == other.payerAmountSat && - receiverAmountSat == other.receiverAmountSat && - totalFees == other.totalFees && - fundingAddress == other.fundingAddress && - invoice == other.invoice; + invoice == other.invoice && + feesSat == other.feesSat; } class ReceivePaymentResponse { diff --git a/packages/dart/pubspec.yaml b/packages/dart/pubspec.yaml index 0ed16d8..60ad6b0 100644 --- a/packages/dart/pubspec.yaml +++ b/packages/dart/pubspec.yaml @@ -23,6 +23,6 @@ dev_dependencies: test: ^1.25.4 dependencies: ffi: ^2.1.2 - flutter_rust_bridge: ">=2.0.0-dev.0 <=2.0.0-dev.33" + flutter_rust_bridge: 2.0.0-dev.33 freezed_annotation: ^2.4.1 meta: ^1.11.0 diff --git a/packages/flutter/example/lib/main.dart b/packages/flutter/example/lib/main.dart index 3ad0b6f..a165076 100644 --- a/packages/flutter/example/lib/main.dart +++ b/packages/flutter/example/lib/main.dart @@ -106,11 +106,7 @@ class _MyAppState extends State { return const Text('Loading...'); } - if (snapshot.requireData.pairHash.isEmpty) { - return const Text('No pair hash.'); - } final prepareReceiveResponse = snapshot.data!; - debugPrint(prepareReceiveResponse.pairHash); return Column( children: [ @@ -121,10 +117,6 @@ class _MyAppState extends State { style: Theme.of(context).textTheme.headlineSmall, ), ), - ListTile( - title: Text( - "Pair Hash: ${prepareReceiveResponse.pairHash}"), - ), ListTile( title: Text( "Payer Amount: ${prepareReceiveResponse.payerAmountSat} (in sats)"), diff --git a/packages/flutter/ffigen.yaml b/packages/flutter/ffigen.yaml index 2d2c6eb..5f49894 100644 --- a/packages/flutter/ffigen.yaml +++ b/packages/flutter/ffigen.yaml @@ -17,4 +17,4 @@ preamble: | comments: style: any length: full -ignore-source-errors: true \ No newline at end of file +ignore-source-errors: true diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index a15ddde..1b03db7 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -28,7 +28,7 @@ class FlutterBreezLiquidBindings { : _lookup = lookup; void store_dart_post_cobject( - DartPostCObjectFnType ptr, + int ptr, ) { return _store_dart_post_cobject( ptr, @@ -36,10 +36,10 @@ class FlutterBreezLiquidBindings { } late final _store_dart_post_cobjectPtr = - _lookup>( + _lookup>( 'store_dart_post_cobject'); - late final _store_dart_post_cobject = _store_dart_post_cobjectPtr - .asFunction(); + late final _store_dart_post_cobject = + _store_dart_post_cobjectPtr.asFunction(); void frbgen_breez_liquid_wire_backup( int port_, @@ -110,8 +110,8 @@ class FlutterBreezLiquidBindings { void frbgen_breez_liquid_wire_list_payments( int port_, - bool with_scan, - bool include_pending, + ffi.Pointer with_scan, + ffi.Pointer include_pending, ) { return _frbgen_breez_liquid_wire_list_payments( port_, @@ -121,11 +121,12 @@ class FlutterBreezLiquidBindings { } late final _frbgen_breez_liquid_wire_list_paymentsPtr = _lookup< - ffi.NativeFunction>( - 'frbgen_breez_liquid_wire_list_payments'); + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, ffi.Pointer, + ffi.Pointer)>>('frbgen_breez_liquid_wire_list_payments'); late final _frbgen_breez_liquid_wire_list_payments = - _frbgen_breez_liquid_wire_list_paymentsPtr - .asFunction(); + _frbgen_breez_liquid_wire_list_paymentsPtr.asFunction< + void Function(int, ffi.Pointer, ffi.Pointer)>(); void frbgen_breez_liquid_wire_prepare_receive_payment( int port_, @@ -454,15 +455,6 @@ final class WireSyncRust2DartSse extends ffi.Struct { external int len; } -typedef DartPostCObjectFnType - = ffi.Pointer>; -typedef DartPostCObjectFnTypeFunction = ffi.Bool Function( - DartPort port_id, ffi.Pointer message); -typedef DartDartPostCObjectFnTypeFunction = bool Function( - DartDartPort port_id, ffi.Pointer message); -typedef DartPort = ffi.Int64; -typedef DartDartPort = int; - final class _Dart_Handle extends ffi.Opaque {} final class wire_cst_list_prim_u_8_strict extends ffi.Struct { @@ -482,10 +474,11 @@ final class wire_cst_connect_request extends ffi.Struct { } final class wire_cst_get_info_request extends ffi.Struct { - @ffi.Bool() external bool with_scan; } +typedef bool = ffi.NativeFunction)>; + final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Uint64() external int payer_amount_sat; @@ -496,8 +489,6 @@ final class wire_cst_prepare_send_request extends ffi.Struct { } final class wire_cst_prepare_receive_response extends ffi.Struct { - external ffi.Pointer pair_hash; - @ffi.Uint64() external int payer_amount_sat; @@ -510,20 +501,10 @@ final class wire_cst_restore_request extends ffi.Struct { } final class wire_cst_prepare_send_response extends ffi.Struct { - external ffi.Pointer id; - - @ffi.Uint64() - external int payer_amount_sat; - - @ffi.Uint64() - external int receiver_amount_sat; - - @ffi.Uint64() - external int total_fees; - - external ffi.Pointer funding_address; - external ffi.Pointer invoice; + + @ffi.Uint64() + external int fees_sat; } final class wire_cst_payment extends ffi.Struct { @@ -600,3 +581,5 @@ final class wire_cst_send_payment_response extends ffi.Struct { } const double LIQUID_CLAIM_TX_FEERATE = 0.1; + +const int LIQUID_MIN_CLAIM_ABSOLUTE_FEES = 134; diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt index a450d71..5a0a752 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt @@ -220,45 +220,66 @@ fun asPrepareSendRequestList(arr: ReadableArray): List { return list } -fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse? { +fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { if (!validateMandatoryFields( - prepareSendResponse, + prepareSendRequest, arrayOf( - "id", - "payerAmountSat", - "receiverAmountSat", - "totalFees", - "fundingAddress", "invoice", ), ) ) { return null } - val id = prepareSendResponse.getString("id")!! - val payerAmountSat = prepareSendResponse.getDouble("payerAmountSat").toULong() - val receiverAmountSat = prepareSendResponse.getDouble("receiverAmountSat").toULong() - val totalFees = prepareSendResponse.getDouble("totalFees").toULong() - val fundingAddress = prepareSendResponse.getString("fundingAddress")!! - val invoice = prepareSendResponse.getString("invoice")!! - return PrepareSendResponse( - id, - payerAmountSat, - receiverAmountSat, - totalFees, - fundingAddress, + val invoice = prepareSendRequest.getString("invoice")!! + return PrepareSendRequest( invoice, ) } +fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap { + return readableMapOf( + "invoice" to prepareSendRequest.invoice, + ) +} + +fun asPrepareSendRequestList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toArrayList()) { + when (value) { + is ReadableMap -> list.add(asPrepareSendRequest(value)!!) + else -> throw LsSdkException.Generic(errUnexpectedType("${value::class.java.name}")) + } + } + return list +} + +fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse? { + if (!validateMandatoryFields( + prepareSendResponse, + arrayOf( + "invoice", + "pairHash", + "feesSat", + ), + ) + ) { + return null + } + val invoice = prepareSendResponse.getString("invoice")!! + val pairHash = prepareSendResponse.getString("pairHash")!! + val feesSat = prepareSendResponse.getDouble("feesSat").toULong() + return PrepareSendResponse( + invoice, + pairHash, + feesSat, + ) +} + fun readableMapOf(prepareSendResponse: PrepareSendResponse): ReadableMap { return readableMapOf( - "id" to prepareSendResponse.id, - "payerAmountSat" to prepareSendResponse.payerAmountSat, - "receiverAmountSat" to prepareSendResponse.receiverAmountSat, - "totalFees" to prepareSendResponse.totalFees, - "fundingAddress" to prepareSendResponse.fundingAddress, "invoice" to prepareSendResponse.invoice, + "pairHash" to prepareSendResponse.pairHash, + "feesSat" to prepareSendResponse.feesSat, ) } From 18257dbc9130a1578f2f23d3e5c5c82ed4331dc5 Mon Sep 17 00:00:00 2001 From: yse <70684173+hydra-yse@users.noreply.github.com> Date: Thu, 9 May 2024 15:47:23 +0300 Subject: [PATCH 02/29] feat: migrate `send_payment` to v2 (#171) --- cli/Cargo.lock | 2 + lib/Cargo.lock | 2 + lib/core/Cargo.toml | 2 + lib/core/src/lib.rs | 1 + lib/core/src/sdk.rs | 223 +++++++++++++++++++++++++++++------------- lib/core/src/utils.rs | 67 +++++++++++++ 6 files changed, 231 insertions(+), 66 deletions(-) create mode 100644 lib/core/src/utils.rs diff --git a/cli/Cargo.lock b/cli/Cargo.lock index daadbd2..f4f3cb5 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -405,7 +405,9 @@ dependencies = [ "rusqlite", "rusqlite_migration", "serde", + "serde_json", "thiserror", + "tungstenite", ] [[package]] diff --git a/lib/Cargo.lock b/lib/Cargo.lock index b8d1ab6..527c8d4 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -512,8 +512,10 @@ dependencies = [ "rusqlite", "rusqlite_migration", "serde", + "serde_json", "tempdir", "thiserror", + "tungstenite", "uuid", ] diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index f91e7ac..85f917c 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -23,6 +23,8 @@ thiserror = { workspace = true } openssl = { version = "0.10", features = ["vendored"] } # TODO Remove once fully migrated to v2 API elements = "0.24.1" +serde_json = "1.0.116" +tungstenite = { version = "0.21.0", features = ["native-tls-vendored"] } [dev-dependencies] tempdir = "0.3.7" diff --git a/lib/core/src/lib.rs b/lib/core/src/lib.rs index bf40ec2..cd70d01 100644 --- a/lib/core/src/lib.rs +++ b/lib/core/src/lib.rs @@ -6,3 +6,4 @@ pub mod frb; pub mod model; pub mod persist; pub mod sdk; +pub(crate) mod utils; diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index b0fd071..4ca8107 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -16,8 +16,8 @@ use boltz_client::{ BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL, }, boltzv2::{ - BoltzApiClientV2, ReversePair, SubmarinePair, BOLTZ_MAINNET_URL_V2, - BOLTZ_TESTNET_URL_V2, + BoltzApiClientV2, CreateSubmarineRequest, ReversePair, SubmarinePair, Subscription, + BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2, }, liquid::{LBtcSwapScript, LBtcSwapTx}, }, @@ -34,15 +34,15 @@ use lwk_wollet::{ ElementsNetwork, FsPersister, Wollet as LwkWollet, WolletDescriptor, }; -use crate::{ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister}; +use crate::{ + ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister, + utils::get_swap_status_v2, +}; /// Claim tx feerate, in sats per vbyte. /// Since the Liquid blocks are consistently empty for now, we hardcode the minimum feerate. pub const LIQUID_CLAIM_TX_FEERATE: f32 = 0.1; -// TODO: Remove in favor of V2 API (necessary as V1 claim_estimate is not working) -pub const LIQUID_MIN_CLAIM_ABSOLUTE_FEES: u64 = 134; - pub const DEFAULT_DATA_DIR: &str = ".data"; pub struct LiquidSdk { @@ -137,9 +137,9 @@ impl LiquidSdk { .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? .status; - let swap_state = status - .parse::() - .map_err(|_| anyhow!("Invalid reverse swap state received for swap {id}: {status}",))?; + let swap_state = status.parse::().map_err(|_| { + anyhow!("Invalid reverse swap state received for swap {id}: {status}",) + })?; match swap_state { RevSwapStates::SwapExpired @@ -190,9 +190,9 @@ impl LiquidSdk { .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? .status; - let state: SubSwapStates = status - .parse() - .map_err(|_| anyhow!("Invalid submarine swap state received for swap {id}: {status}"))?; + let state: SubSwapStates = status.parse().map_err(|_| { + anyhow!("Invalid submarine swap state received for swap {id}: {status}") + })?; match state { SubSwapStates::TransactionClaimed @@ -368,20 +368,12 @@ impl LiquidSdk { .ok_or(PaymentError::AmountOutOfRange)? / 1000; - // let client = self.boltz_client_v2(); - // let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; - - let client = self.boltz_client(); - let lbtc_pair = client - .get_pairs()? - .get_lbtc_pair() - .ok_or(PaymentError::PairsNotFound)?; + let client = self.boltz_client_v2(); + let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; Ok(PrepareSendResponse { invoice: req.invoice.clone(), - fees_sat: lbtc_pair.fees.submarine_boltz(receiver_amount_sat) - + lbtc_pair.fees.submarine_lockup_estimate() - + LIQUID_MIN_CLAIM_ABSOLUTE_FEES, + fees_sat: lbtc_pair.fees.total(receiver_amount_sat), }) } @@ -395,61 +387,160 @@ impl LiquidSdk { .ok_or(PaymentError::AmountOutOfRange)? / 1000; - // let client = self.boltz_client_v2(); - // let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; - - let client = self.boltz_client(); - let lbtc_pair = client - .get_pairs()? - .get_lbtc_pair() - .ok_or(PaymentError::PairsNotFound)?; - - // let new_fees = lbtc_pair.fees.total(receiver_amount_sat); - let new_fees = lbtc_pair.fees.submarine_boltz(receiver_amount_sat) - + lbtc_pair.fees.submarine_lockup_estimate() - + LIQUID_MIN_CLAIM_ABSOLUTE_FEES; - - ensure_sdk!(req.fees_sat == new_fees, PaymentError::InvalidOrExpiredFees); + let client = self.boltz_client_v2(); + let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; ensure_sdk!( - receiver_amount_sat + req.fees_sat <= self.total_balance_sat(true)?, - PaymentError::InsufficientFunds + req.fees_sat == lbtc_pair.fees.total(receiver_amount_sat), + PaymentError::InvalidOrExpiredFees ); - let swap_response = client.create_swap(CreateSwapRequest::new_lbtc_submarine( - &lbtc_pair.hash, - &req.invoice.to_string(), - // TODO Add refund - "", - ))?; + let lwk_wollet = self.lwk_wollet.lock().unwrap(); + // let our_pubkey = self + // .address()? + // .to_unconfidential() + // .blinding_pubkey + // .ok_or(PaymentError::Generic { + // err: "Could not retrieve wallet pubkey".to_string(), + // })? + // .into(); + let refund_public_key = lwk_wollet + .address(None)? + .address() + .blinding_pubkey + .ok_or(PaymentError::Generic { + err: "Could not generate refund pubkey".to_string(), + })? + .into(); - let id = swap_response.get_id(); - let funding_address = swap_response.get_funding_address()?; - let funding_amount_sat = swap_response.get_funding_amount()?; + // Unlock lwk wallet so it can be used to build the tx + std::mem::drop(lwk_wollet); - // let absolute_fees = self - // .build_tx(None, &funding_address, funding_amount_sat)? - // .all_fees() - // .values() - // .sum::(); - // let fee_rate = req.fees_sat as f32 * LIQUID_CLAIM_TX_FEERATE / absolute_fees as f32; - // - // let tx = self.build_tx(Some(fee_rate), &funding_address, funding_amount_sat)?; - let tx = self.build_tx(None, &funding_address, funding_amount_sat)?; + let create_response = client.post_swap_req(&CreateSubmarineRequest { + from: "L-BTC".to_string(), + to: "BTC".to_string(), + invoice: req.invoice.to_string(), + // TODO: Add refund flow + refund_public_key, + // TODO: Add referral id + referral_id: None, + })?; - let electrum_client = ElectrumClient::new(&self.electrum_url)?; - let txid = electrum_client.broadcast(&tx)?.to_string(); + // let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp(&create_response, our_pubkey)?; + debug!("Opening WS connection for swap {}", create_response.id); + + let mut socket = client.connect_ws().unwrap(); + let subscription = Subscription::new(&create_response.id); + let subscribe_json = serde_json::to_string(&subscription) + .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; + socket + .send(tungstenite::Message::Text(subscribe_json)) + .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; self.persister .insert_or_update_ongoing_swap(&[OngoingSwap::Send { - id, + id: create_response.id.clone(), invoice: req.invoice.clone(), - payer_amount_sat: receiver_amount_sat + req.fees_sat, - txid: Some(txid.clone()), - }]) - .map_err(|_| PaymentError::PersistError)?; + payer_amount_sat: req.fees_sat + receiver_amount_sat, + txid: None, + }])?; - Ok(SendPaymentResponse { txid }) + let result; + loop { + let data = match get_swap_status_v2(&mut socket, &create_response.id) { + Ok(data) => data, + Err(_) => continue, + }; + + let state = data + .parse::() + .map_err(|_| PaymentError::Generic { + err: "Invalid state received from swapper".to_string(), + })?; + + match state { + SubSwapStates::TransactionMempool | SubSwapStates::TransactionConfirmed => { + // Send detected by Boltz, waiting for invoice + // to be settled + } + SubSwapStates::InvoiceSet => { + debug!( + "Send {} sats to BTC address {}", + create_response.expected_amount, create_response.address + ); + // let absolute_fees = self + // .build_tx( + // None, + // &create_response.address, + // create_response.expected_amount, + // )? + // .all_fees() + // .values() + // .sum::(); + // let fee_rate = + // req.fees_sat as f32 / absolute_fees as f32 * LIQUID_CLAIM_TX_FEERATE; + let tx = self.build_tx( + None, + &create_response.address, + create_response.expected_amount, + )?; + + let txid = match self.network { + Network::Liquid => { + let tx_hex = elements::encode::serialize(&tx).to_lower_hex_string(); + let response = client.broadcast_tx(self.network.into(), &tx_hex)?; + response + .as_object() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .get("id") + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .as_str() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .to_string() + } + Network::LiquidTestnet => { + let electrum_client = ElectrumClient::new(&self.electrum_url)?; + electrum_client.broadcast(&tx)?.to_string() + } + }; + + self.persister + .insert_or_update_ongoing_swap(&[OngoingSwap::Send { + id: create_response.id.clone(), + invoice: req.invoice.clone(), + payer_amount_sat: req.fees_sat + receiver_amount_sat, + txid: Some(txid.clone()), + }])?; + + result = Ok(SendPaymentResponse { txid }); + break; + } + SubSwapStates::TransactionClaimed + | SubSwapStates::InvoiceFailedToPay + | SubSwapStates::SwapExpired => { + result = Err(PaymentError::Generic { + err: format!("Payment state is unrecoverable: {}", state.to_string()), + }); + break; + } + _ => info!( + "New state for swap {}: {}", + create_response.id, + state.to_string() + ), + }; + + thread::sleep(Duration::from_millis(500)); + } + + socket.close(None).unwrap(); + result } fn try_claim( diff --git a/lib/core/src/utils.rs b/lib/core/src/utils.rs new file mode 100644 index 0000000..dc57137 --- /dev/null +++ b/lib/core/src/utils.rs @@ -0,0 +1,67 @@ +use std::net::TcpStream; + +use anyhow::{anyhow, ensure, Result}; +use boltz_client::swaps::boltzv2::SwapUpdate; +use log::{error, info}; +use tungstenite::{stream::MaybeTlsStream, WebSocket}; + +/// Fetch the swap status using the websocket endpoint +pub(crate) fn get_swap_status_v2( + socket: &mut WebSocket>, + swap_id: &str, +) -> Result { + loop { + let response: SwapUpdate = serde_json::from_str(&socket.read()?.to_string()) + .map_err(|e| anyhow!("WS response is invalid SwapUpdate: {e:?}"))?; + + match response { + SwapUpdate::Subscription { + event, + channel, + args, + } => { + ensure!(event == "subscribe", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + let first_arg = args.first(); + let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == &swap_id); + ensure!(is_ok, "Wrong WS reply subscription ID {first_arg:?}"); + + info!("Subscription successful for swap : {swap_id}"); + } + + SwapUpdate::Update { + event, + channel, + args, + } => { + ensure!(event == "update", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + return match args.first() { + Some(update) if update.id == swap_id => { + info!("Got new reverse swap status: {}", update.status); + + Ok(update.status.clone()) + } + Some(update) => Err(anyhow!("WS reply has wrong swap ID {update:?}")), + None => Err(anyhow!("WS reply contains no update")), + }; + } + + SwapUpdate::Error { + event, + channel, + args, + } => { + ensure!(event == "update", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + for e in &args { + error!("Got error: {} for swap: {}", e.error, e.id); + } + return Err(anyhow!("Got SwapUpdate errors: {args:?}")); + } + } + } +} From d67756bf43a247bb71d973eaec9a2e0f769a9df6 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 17:13:15 +0200 Subject: [PATCH 03/29] Migrate receive-payment to V2 API, use WS to get status --- cli/Cargo.lock | 2 +- lib/Cargo.lock | 2 +- lib/core/Cargo.toml | 7 +- lib/core/src/model.rs | 51 ++--- lib/core/src/persist/mod.rs | 144 ++------------ lib/core/src/persist/swap_in.rs | 84 ++++++++ lib/core/src/persist/swap_out.rs | 96 +++++++++ lib/core/src/sdk.rs | 324 ++++++++++++++++--------------- lib/core/src/utils.rs | 81 +++++++- 9 files changed, 478 insertions(+), 313 deletions(-) create mode 100644 lib/core/src/persist/swap_in.rs create mode 100644 lib/core/src/persist/swap_out.rs diff --git a/cli/Cargo.lock b/cli/Cargo.lock index f4f3cb5..7c227a5 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=410d3a95e528fce36c02e8d414d5b647a31cc28f#410d3a95e528fce36c02e8d414d5b647a31cc28f" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=50b93fb7eba043e12a71fd7ddb1e9604a946c21b#50b93fb7eba043e12a71fd7ddb1e9604a946c21b" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 527c8d4..f30073c 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,7 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=410d3a95e528fce36c02e8d414d5b647a31cc28f#410d3a95e528fce36c02e8d414d5b647a31cc28f" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=50b93fb7eba043e12a71fd7ddb1e9604a946c21b#50b93fb7eba043e12a71fd7ddb1e9604a946c21b" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index 85f917c..e251f64 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -10,7 +10,8 @@ crate-type = ["lib", "cdylib", "staticlib"] [dependencies] anyhow = { workspace = true } bip39 = { version = "2.0.0", features = ["serde"] } -boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "410d3a95e528fce36c02e8d414d5b647a31cc28f" } +#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "6f45fff8b87c7530c847eb05f018906c48785a6c" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "50b93fb7eba043e12a71fd7ddb1e9604a946c21b" } flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" lwk_common = "0.3.0" @@ -19,12 +20,12 @@ lwk_wollet = "0.3.0" rusqlite = { version = "0.31", features = ["backup", "bundled"] } rusqlite_migration = "1.0" serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.116" thiserror = { workspace = true } +tungstenite = { version = "0.21.0", features = ["native-tls-vendored"] } openssl = { version = "0.10", features = ["vendored"] } # TODO Remove once fully migrated to v2 API elements = "0.24.1" -serde_json = "1.0.116" -tungstenite = { version = "0.21.0", features = ["native-tls-vendored"] } [dev-dependencies] tempdir = "0.3.7" diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 3b02843..ea92596 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -128,32 +128,39 @@ pub struct RestoreRequest { pub backup_path: Option, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) enum OngoingSwap { - Send { - id: String, - invoice: String, - payer_amount_sat: u64, - txid: Option, - }, - Receive { - id: String, - preimage: String, - redeem_script: String, - blinding_key: String, - invoice: String, - receiver_amount_sat: u64, - claim_fees_sat: u64, - }, + Send(OngoingSwapIn), + Receive(OngoingSwapOut), } impl OngoingSwap { - pub(crate) fn id(&self) -> &str { + pub(crate) fn id(&self) -> String { match &self { - OngoingSwap::Send { id, .. } | OngoingSwap::Receive { id, .. } => id, + OngoingSwap::Send(OngoingSwapIn { id, .. }) + | OngoingSwap::Receive(OngoingSwapOut { id, .. }) => id.clone(), } } } +#[derive(Clone, Debug)] +pub(crate) struct OngoingSwapIn { + pub(crate) id: String, + pub(crate) invoice: String, + pub(crate) payer_amount_sat: u64, + pub(crate) txid: Option, +} + +#[derive(Clone, Debug)] +pub(crate) struct OngoingSwapOut { + pub(crate) id: String, + pub(crate) preimage: String, + pub(crate) redeem_script: String, + pub(crate) blinding_key: String, + pub(crate) invoice: String, + pub(crate) receiver_amount_sat: u64, + pub(crate) claim_fees_sat: u64, +} + #[derive(Debug, Clone, PartialEq, Serialize)] pub enum PaymentType { Sent, @@ -177,11 +184,11 @@ pub struct Payment { impl From for Payment { fn from(swap: OngoingSwap) -> Self { match swap { - OngoingSwap::Send { + OngoingSwap::Send(OngoingSwapIn { invoice, payer_amount_sat, .. - } => { + }) => { let receiver_amount_sat = get_invoice_amount!(invoice); Payment { id: None, @@ -192,11 +199,11 @@ impl From for Payment { fees_sat: Some(payer_amount_sat - receiver_amount_sat), } } - OngoingSwap::Receive { + OngoingSwap::Receive(OngoingSwapOut { receiver_amount_sat, invoice, .. - } => { + }) => { let payer_amount_sat = get_invoice_amount!(invoice); Payment { id: None, diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 7a223fd..797e111 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -1,5 +1,7 @@ mod backup; mod migrations; +mod swap_in; +mod swap_out; use std::{collections::HashMap, fs::create_dir_all, path::PathBuf, str::FromStr}; @@ -8,7 +10,7 @@ use migrations::current_migrations; use rusqlite::{params, Connection}; use rusqlite_migration::{Migrations, M}; -use crate::model::{Network, Network::*, OngoingSwap, PaymentData}; +use crate::model::{Network::*, *}; pub(crate) struct Persister { main_db_dir: PathBuf, @@ -47,70 +49,6 @@ impl Persister { Ok(()) } - pub fn insert_or_update_ongoing_swap(&self, swaps: &[OngoingSwap]) -> Result<()> { - let con = self.get_connection()?; - - for swap in swaps { - match swap { - OngoingSwap::Send { - id, - invoice, - payer_amount_sat, - txid, - } => { - let mut stmt = con.prepare( - " - INSERT OR REPLACE INTO ongoing_send_swaps ( - id, - invoice, - payer_amount_sat, - txid - ) - VALUES (?, ?, ?, ?) - ", - )?; - _ = stmt.execute((id, invoice, payer_amount_sat, txid))? - } - OngoingSwap::Receive { - id, - preimage, - redeem_script, - blinding_key, - invoice, - receiver_amount_sat, - claim_fees_sat, - } => { - let mut stmt = con.prepare( - " - INSERT OR REPLACE INTO ongoing_receive_swaps ( - id, - preimage, - redeem_script, - blinding_key, - invoice, - receiver_amount_sat, - claim_fees_sat - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - ", - )?; - - _ = stmt.execute(( - id, - preimage, - redeem_script, - blinding_key, - invoice, - receiver_amount_sat, - claim_fees_sat, - ))? - } - } - } - - Ok(()) - } - pub fn resolve_ongoing_swap( &self, id: &str, @@ -136,75 +74,19 @@ impl Persister { Ok(()) } - pub fn list_ongoing_swaps(&self) -> Result> { + pub(crate) fn list_ongoing_swaps(&self) -> Result> { let con = self.get_connection()?; - let mut ongoing_swaps = self.list_ongoing_send(&con)?; - ongoing_swaps.append(&mut self.list_ongoing_receive(&con)?); - Ok(ongoing_swaps) - } - - fn list_ongoing_send(&self, con: &Connection) -> Result, rusqlite::Error> { - let mut stmt = con.prepare( - " - SELECT - id, - invoice, - payer_amount_sat, - txid, - created_at - FROM ongoing_send_swaps - ORDER BY created_at - ", - )?; - - let ongoing_send = stmt - .query_map(params![], |row| { - Ok(OngoingSwap::Send { - id: row.get(0)?, - invoice: row.get(1)?, - payer_amount_sat: row.get(2)?, - txid: row.get(3)?, - }) - })? - .map(|i| i.unwrap()) + let ongoing_swap_ins: Vec = self + .list_ongoing_send(&con, vec![])? + .into_iter() + .map(OngoingSwap::Send) .collect(); - - Ok(ongoing_send) - } - - fn list_ongoing_receive(&self, con: &Connection) -> Result, rusqlite::Error> { - let mut stmt = con.prepare( - " - SELECT - id, - preimage, - redeem_script, - blinding_key, - invoice, - receiver_amount_sat, - claim_fees_sat, - created_at - FROM ongoing_receive_swaps - ORDER BY created_at - ", - )?; - - let ongoing_receive = stmt - .query_map(params![], |row| { - Ok(OngoingSwap::Receive { - id: row.get(0)?, - preimage: row.get(1)?, - redeem_script: row.get(2)?, - blinding_key: row.get(3)?, - invoice: row.get(4)?, - receiver_amount_sat: row.get(5)?, - claim_fees_sat: row.get(6)?, - }) - })? - .map(|i| i.unwrap()) + let ongoing_swap_outs: Vec = self + .list_ongoing_receive(&con, vec![])? + .into_iter() + .map(OngoingSwap::Receive) .collect(); - - Ok(ongoing_receive) + Ok([ongoing_swap_ins, ongoing_swap_outs].concat()) } pub fn get_payment_data(&self) -> Result> { diff --git a/lib/core/src/persist/swap_in.rs b/lib/core/src/persist/swap_in.rs new file mode 100644 index 0000000..e4a5645 --- /dev/null +++ b/lib/core/src/persist/swap_in.rs @@ -0,0 +1,84 @@ +use crate::model::*; +use crate::persist::Persister; + +use anyhow::Result; +use rusqlite::{params, Connection, OptionalExtension, Row}; + +impl Persister { + pub(crate) fn insert_or_update_ongoing_swap_in(&self, swap_in: OngoingSwapIn) -> Result<()> { + let con = self.get_connection()?; + + let mut stmt = con.prepare( + " + INSERT OR REPLACE INTO ongoing_send_swaps ( + id, + invoice, + payer_amount_sat, + txid + ) + VALUES (?, ?, ?, ?)", + )?; + _ = stmt.execute(( + swap_in.id, + swap_in.invoice, + swap_in.payer_amount_sat, + swap_in.txid, + ))?; + + Ok(()) + } + + fn list_ongoing_swap_in_query(where_clauses: Vec<&str>) -> String { + let mut where_clause_str = String::new(); + if !where_clauses.is_empty() { + where_clause_str = String::from("WHERE "); + where_clause_str.push_str(where_clauses.join(" AND ").as_str()); + } + + format!( + " + SELECT + id, + invoice, + payer_amount_sat, + txid, + created_at + FROM ongoing_send_swaps + {where_clause_str} + ORDER BY created_at + " + ) + } + + pub(crate) fn fetch_ongoing_swap_in( + con: &Connection, + id: &str, + ) -> rusqlite::Result> { + let query = Self::list_ongoing_swap_in_query(vec!["id = ?1"]); + con.query_row(&query, [id], Self::sql_row_to_ongoing_swap_in) + .optional() + } + + fn sql_row_to_ongoing_swap_in(row: &Row) -> rusqlite::Result { + Ok(OngoingSwapIn { + id: row.get(0)?, + invoice: row.get(1)?, + payer_amount_sat: row.get(2)?, + txid: row.get(3)?, + }) + } + + pub(crate) fn list_ongoing_send( + &self, + con: &Connection, + where_clauses: Vec<&str>, + ) -> rusqlite::Result> { + let query = Self::list_ongoing_swap_in_query(where_clauses); + let ongoing_send = con + .prepare(&query)? + .query_map(params![], Self::sql_row_to_ongoing_swap_in)? + .map(|i| i.unwrap()) + .collect(); + Ok(ongoing_send) + } +} diff --git a/lib/core/src/persist/swap_out.rs b/lib/core/src/persist/swap_out.rs new file mode 100644 index 0000000..52df9b2 --- /dev/null +++ b/lib/core/src/persist/swap_out.rs @@ -0,0 +1,96 @@ +use crate::model::*; +use crate::persist::Persister; + +use anyhow::Result; +use rusqlite::{params, Connection, OptionalExtension, Row}; + +impl Persister { + pub(crate) fn insert_or_update_ongoing_swap_out(&self, swap_out: OngoingSwapOut) -> Result<()> { + let con = self.get_connection()?; + + let mut stmt = con.prepare( + " + INSERT OR REPLACE INTO ongoing_receive_swaps ( + id, + preimage, + redeem_script, + blinding_key, + invoice, + receiver_amount_sat, + claim_fees_sat + ) + VALUES (?, ?, ?, ?, ?, ?, ?)", + )?; + _ = stmt.execute(( + swap_out.id, + swap_out.preimage, + swap_out.redeem_script, + swap_out.blinding_key, + swap_out.invoice, + swap_out.receiver_amount_sat, + swap_out.claim_fees_sat, + ))?; + + Ok(()) + } + + fn list_ongoing_swap_out_query(where_clauses: Vec<&str>) -> String { + let mut where_clause_str = String::new(); + if !where_clauses.is_empty() { + where_clause_str = String::from("WHERE "); + where_clause_str.push_str(where_clauses.join(" AND ").as_str()); + } + + format!( + " + SELECT + id, + preimage, + redeem_script, + blinding_key, + invoice, + receiver_amount_sat, + claim_fees_sat, + created_at + FROM ongoing_receive_swaps + {where_clause_str} + ORDER BY created_at + " + ) + } + + pub(crate) fn fetch_ongoing_swap_out( + con: &Connection, + id: &str, + ) -> rusqlite::Result> { + let query = Self::list_ongoing_swap_out_query(vec!["id = ?1"]); + con.query_row(&query, [id], Self::sql_row_to_ongoing_swap_out) + .optional() + } + + fn sql_row_to_ongoing_swap_out(row: &Row) -> rusqlite::Result { + Ok(OngoingSwapOut { + id: row.get(0)?, + preimage: row.get(1)?, + redeem_script: row.get(2)?, + blinding_key: row.get(3)?, + invoice: row.get(4)?, + receiver_amount_sat: row.get(5)?, + claim_fees_sat: row.get(6)?, + }) + } + + pub(crate) fn list_ongoing_receive( + &self, + con: &Connection, + where_clauses: Vec<&str>, + ) -> rusqlite::Result> { + let query = Self::list_ongoing_swap_out_query(where_clauses); + let ongoing_receive = con + .prepare(&query)? + .query_map(params![], Self::sql_row_to_ongoing_swap_out)? + .map(|i| i.unwrap()) + .collect(); + Ok(ongoing_receive) + } +} diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 4ca8107..b57be01 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -15,14 +15,12 @@ use boltz_client::{ BoltzApiClient, CreateSwapRequest, RevSwapStates, SubSwapStates, SwapStatusRequest, BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL, }, - boltzv2::{ - BoltzApiClientV2, CreateSubmarineRequest, ReversePair, SubmarinePair, Subscription, - BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2, - }, + boltzv2::*, liquid::{LBtcSwapScript, LBtcSwapTx}, + liquidv2::LBtcSwapTxV2, }, util::secrets::{LBtcReverseRecovery, LiquidSwapKey, Preimage, SwapKey}, - Bolt11Invoice, Keypair, + Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, }; use elements::hashes::hex::DisplayHex; use log::{debug, error, info, warn}; @@ -35,8 +33,7 @@ use lwk_wollet::{ }; use crate::{ - ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister, - utils::get_swap_status_v2, + ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister, utils, }; /// Claim tx feerate, in sats per vbyte. @@ -117,60 +114,40 @@ impl LiquidSdk { Ok(descriptor_str.parse()?) } - fn try_resolve_pending_swap( - sdk: &Arc, - client: &BoltzApiClient, - swap: &OngoingSwap, + pub(crate) fn try_handle_reverse_swap_status( + &self, + swap_state: RevSwapStates, + id: &str, ) -> Result<()> { - match swap { - OngoingSwap::Receive { - id, - preimage, - redeem_script, - blinding_key, - invoice, - claim_fees_sat, - .. - } => { - let status = client - .swap_status(SwapStatusRequest { id: id.clone() }) - .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? - .status; + let con = self.persister.get_connection()?; + let ongoing_swap_out = Persister::fetch_ongoing_swap_out(&con, id)? + .ok_or(anyhow!("No ongoing swap out found for ID {id}"))?; - let swap_state = status.parse::().map_err(|_| { - anyhow!("Invalid reverse swap state received for swap {id}: {status}",) - })?; - - match swap_state { - RevSwapStates::SwapExpired - | RevSwapStates::InvoiceExpired - | RevSwapStates::TransactionFailed - | RevSwapStates::TransactionRefunded => { - warn!("Cannot claim swap {id}, unrecoverable state: {status}"); - sdk.persister - .resolve_ongoing_swap(id, None) - .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; - } - RevSwapStates::TransactionConfirmed => {} - _ => { - return Err(anyhow!("New swap state for reverse swap {id}: {status}")); - } - } - - match sdk.try_claim(preimage, redeem_script, blinding_key, *claim_fees_sat) { + match swap_state { + RevSwapStates::SwapExpired + | RevSwapStates::InvoiceExpired + | RevSwapStates::TransactionFailed + | RevSwapStates::TransactionRefunded => { + warn!("Cannot claim swap {id}, unrecoverable state: {swap_state:?}"); + self.persister + .resolve_ongoing_swap(id, None) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + } + RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed => { + match self.try_claim_v2(&ongoing_swap_out) { Ok(txid) => { - let payer_amount_sat = get_invoice_amount!(invoice); - sdk.persister + let payer_amount_sat = get_invoice_amount!(ongoing_swap_out.invoice); + self.persister .resolve_ongoing_swap( id, Some((txid, PaymentData { payer_amount_sat })), ) - .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + .map_err(|e| anyhow!("Could not resolve swap {id}: {e}"))?; } Err(err) => { if let PaymentError::AlreadyClaimed = err { warn!("Funds already claimed"); - sdk.persister + self.persister .resolve_ongoing_swap(id, None) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; } @@ -178,40 +155,66 @@ impl LiquidSdk { } } } - OngoingSwap::Send { - id, invoice, txid, .. - } => { - let Some(txid) = txid.clone() else { - return Err(anyhow!("Transaction not broadcast yet for swap {id}")); - }; + RevSwapStates::Created | RevSwapStates::MinerFeePaid => { + // Too soon to try to claim + } + RevSwapStates::InvoiceSettled => { + // Reverse swap already completed at this point, from our perspective + } + } + Ok(()) + } + + pub(crate) fn try_handle_submarine_swap_status( + &self, + swap_state: SubSwapStates, + id: &str, + ) -> Result<()> { + let con = self.persister.get_connection()?; + let ongoing_swap_in = Persister::fetch_ongoing_swap_in(&con, id)? + .ok_or(anyhow!("No ongoing swap in found for ID {id}"))?; + + let Some(txid) = ongoing_swap_in.txid.clone() else { + return Err(anyhow!("Transaction not broadcast yet for swap {id}")); + }; + + match swap_state { + SubSwapStates::TransactionClaimed + | SubSwapStates::InvoiceFailedToPay + | SubSwapStates::SwapExpired => { + warn!("Cannot positively resolve swap {id}, unrecoverable state: {swap_state:?}"); + + let payer_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); + self.persister + .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .map_err(|_| anyhow!("Could not resolve swap {id} in database")) + } + _ => Err(anyhow!("New state for submarine swap {id}: {swap_state:?}")), + } + } + + fn try_resolve_pending_swap(&self, swap: &OngoingSwap) -> Result<()> { + let client = self.boltz_client(); + let client_v2 = self.boltz_client_v2(); + + match swap { + OngoingSwap::Receive(ongoing_swap_out) => { + let swap_state = utils::get_rev_swap_status_v2(client_v2, &ongoing_swap_out.id)?; + self.try_handle_reverse_swap_status(swap_state, &ongoing_swap_out.id)?; + } + OngoingSwap::Send(ongoing_swap_in) => { + let id = &ongoing_swap_in.id; let status = client .swap_status(SwapStatusRequest { id: id.clone() }) .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? .status; - let state: SubSwapStates = status.parse().map_err(|_| { + let swap_state: SubSwapStates = status.parse().map_err(|_| { anyhow!("Invalid submarine swap state received for swap {id}: {status}") })?; - match state { - SubSwapStates::TransactionClaimed - | SubSwapStates::InvoiceFailedToPay - | SubSwapStates::SwapExpired => { - warn!("Cannot positively resolve swap {id}, unrecoverable state: {status}"); - - let payer_amount_sat = get_invoice_amount!(invoice); - sdk.persister - .resolve_ongoing_swap( - id, - Some((txid, PaymentData { payer_amount_sat })), - ) - .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; - } - _ => { - return Err(anyhow!("New swap state for submarine swap {id}: {status}")); - } - } + self.try_handle_submarine_swap_status(swap_state, &ongoing_swap_in.id)?; } }; @@ -220,14 +223,12 @@ impl LiquidSdk { fn track_pending_swaps(self: &Arc) -> Result<()> { let cloned = self.clone(); - let client = self.boltz_client(); - thread::spawn(move || loop { thread::sleep(Duration::from_secs(5)); match cloned.persister.list_ongoing_swaps() { Ok(ongoing_swaps) => { for swap in ongoing_swaps { - match LiquidSdk::try_resolve_pending_swap(&cloned, &client, &swap) { + match cloned.try_resolve_pending_swap(&swap) { Ok(_) => info!("Resolved pending swap {}", swap.id()), Err(err) => match swap { OngoingSwap::Send { .. } => error!("[Ongoing Send] {err}"), @@ -438,16 +439,16 @@ impl LiquidSdk { .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; self.persister - .insert_or_update_ongoing_swap(&[OngoingSwap::Send { + .insert_or_update_ongoing_swap_in(OngoingSwapIn { id: create_response.id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, txid: None, - }])?; + })?; let result; loop { - let data = match get_swap_status_v2(&mut socket, &create_response.id) { + let data = match utils::get_swap_status_v2(&mut socket, &create_response.id) { Ok(data) => data, Err(_) => continue, }; @@ -511,12 +512,12 @@ impl LiquidSdk { }; self.persister - .insert_or_update_ongoing_swap(&[OngoingSwap::Send { + .insert_or_update_ongoing_swap_in(OngoingSwapIn { id: create_response.id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, txid: Some(txid.clone()), - }])?; + })?; result = Ok(SendPaymentResponse { txid }); break; @@ -543,19 +544,8 @@ impl LiquidSdk { result } - fn try_claim( - &self, - preimage: &str, - redeem_script: &str, - blinding_key: &str, - claim_fees_sat: u64, - ) -> Result { - let network_config = &self.network_config(); - let rev_swap_tx = LBtcSwapTx::new_claim( - LBtcSwapScript::reverse_from_str(redeem_script, blinding_key)?, - self.address()?.to_string(), - network_config, - )?; + fn try_claim_v2(&self, ongoing_swap_out: &OngoingSwapOut) -> Result { + debug!("Trying to claim reverse swap {}", ongoing_swap_out.id); let mnemonic = self .lwk_signer @@ -565,31 +555,40 @@ impl LiquidSdk { })?; let swap_key = SwapKey::from_reverse_account(&mnemonic.to_string(), "", self.network.into(), 0)?; - let lsk = LiquidSwapKey::try_from(swap_key)?; - let preimage = Preimage::from_str(preimage)?; + let our_keys = lsk.keypair; - let signed_tx = rev_swap_tx.sign_claim(&lsk.keypair, &preimage, claim_fees_sat)?; - let tx_hex = elements::encode::serialize(&signed_tx).to_lower_hex_string(); + let swap_response_v2: CreateReverseResponse = + serde_json::from_str(&ongoing_swap_out.redeem_script).unwrap(); + let swap_script = LBtcSwapScriptV2::reverse_from_swap_resp( + &swap_response_v2, + our_keys.public_key().into(), + )?; - let client = self.boltz_client_v2(); - let response = client.broadcast_tx(self.network.into(), &tx_hex)?; - let txid = response - .as_object() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .get("id") - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .as_str() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .to_string(); + let claim_address = self.address()?.to_string(); + let claim_tx = LBtcSwapTxV2::new_claim( + swap_script.clone(), + claim_address, + &ElectrumConfig::default_liquid(), + )?; - Ok(txid) + let tx = claim_tx.sign_claim( + &our_keys, + &Preimage::from_str(&ongoing_swap_out.preimage)?, + Amount::from_sat(ongoing_swap_out.claim_fees_sat), + Some((&self.boltz_client_v2(), ongoing_swap_out.id.clone())), + )?; + + claim_tx.broadcast( + &tx, + &ElectrumConfig::default(self.network.into(), None)?, + None, + )?; + + info!("Succesfully broadcasted claim tx {}", tx.txid()); + debug!("Claim Tx {:?}", tx); + + Ok(tx.txid().to_string()) } #[allow(dead_code)] @@ -615,19 +614,26 @@ impl LiquidSdk { &self, req: &PrepareReceiveRequest, ) -> Result { - // let client = self.boltz_client_v2(); - // let lbtc_pair = Self::validate_reverse_pairs(&client, req.payer_amount_sat)?; - - let client = self.boltz_client(); - let lbtc_pair = client - .get_pairs()? - .get_lbtc_pair() + let reverse_pair = self + .boltz_client_v2() + .get_reverse_pairs()? + .get_btc_to_lbtc_pair() .ok_or(PaymentError::PairsNotFound)?; - let fees_sat = lbtc_pair.fees.reverse_total(req.payer_amount_sat); + let payer_amount_sat = req.payer_amount_sat; + let fees_sat = reverse_pair.fees.total(req.payer_amount_sat); + + ensure_sdk!(payer_amount_sat > fees_sat, PaymentError::AmountOutOfRange); + + reverse_pair + .limits + .within(payer_amount_sat) + .map_err(|_| PaymentError::AmountOutOfRange)?; + + debug!("Preparing reverse swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"); Ok(PrepareReceiveResponse { - payer_amount_sat: req.payer_amount_sat, + payer_amount_sat, fees_sat, }) } @@ -636,21 +642,19 @@ impl LiquidSdk { &self, req: &PrepareReceiveResponse, ) -> Result { - // let client = self.boltz_client_v2(); - // let lbtc_pair = Self::validate_reverse_pairs(&client, req.payer_amount_sat)?; + let payer_amount_sat = req.payer_amount_sat; + let fees_sat = req.fees_sat; - let client = self.boltz_client(); - let lbtc_pair = client - .get_pairs()? - .get_lbtc_pair() + let reverse_pair = self + .boltz_client_v2() + .get_reverse_pairs()? + .get_btc_to_lbtc_pair() .ok_or(PaymentError::PairsNotFound)?; + let new_fees_sat = reverse_pair.fees.total(req.payer_amount_sat); + ensure_sdk!(fees_sat == new_fees_sat, PaymentError::InvalidOrExpiredFees); - ensure_sdk!( - req.fees_sat == lbtc_pair.fees.reverse_total(req.payer_amount_sat), - PaymentError::InvalidOrExpiredFees - ); + debug!("Creating reverse swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"); - let client = self.boltz_client(); let mnemonic = self .lwk_signer .mnemonic() @@ -665,21 +669,33 @@ impl LiquidSdk { let preimage_str = preimage.to_string().ok_or(PaymentError::InvalidPreimage)?; let preimage_hash = preimage.sha256.to_string(); - debug!( - "Creating reverse swap with: payer_amount_sat {} sat, fees_total {} sat", - req.payer_amount_sat, req.fees_sat - ); - let swap_response = client.create_swap(CreateSwapRequest::new_lbtc_reverse_invoice_amt( - lbtc_pair.hash.clone(), - preimage_hash.clone(), - lsk.keypair.public_key().to_string(), - req.payer_amount_sat, - ))?; + let v2_req = CreateReverseRequest { + invoice_amount: req.payer_amount_sat as u32, // TODO update our model + from: "BTC".to_string(), + to: "L-BTC".to_string(), + preimage_hash: preimage.sha256, + claim_public_key: lsk.keypair.public_key().into(), + address: None, + address_signature: None, + referral_id: None, + }; + let swap_response_v2 = self.boltz_client_v2().post_reverse_req(v2_req)?; - let swap_id = swap_response.get_id(); - let invoice = swap_response.get_invoice()?; - let blinding_str = swap_response.get_blinding_key()?; - let redeem_script = swap_response.get_redeem_script()?; + // TODO Persisting this in the DB (reusing "redeem_script" field), as we need it later when claiming + let redeem_script = serde_json::to_string(&swap_response_v2).unwrap(); + + let swap_id = swap_response_v2.id; + let invoice = Bolt11Invoice::from_str(&swap_response_v2.invoice).map_err(|_| { + boltz_client::error::Error::Protocol( + "Boltz response does not contain an invoice.".to_string(), + ) + })?; + let blinding_str = + swap_response_v2 + .blinding_key + .ok_or(boltz_client::error::Error::Protocol( + "Boltz response does not contain a blinding key.".to_string(), + ))?; let payer_amount_sat = invoice .amount_milli_satoshis() .ok_or(PaymentError::InvalidInvoice)? @@ -692,15 +708,15 @@ impl LiquidSdk { }; self.persister - .insert_or_update_ongoing_swap(&[OngoingSwap::Receive { + .insert_or_update_ongoing_swap_out(OngoingSwapOut { id: swap_id.clone(), preimage: preimage_str, blinding_key: blinding_str, redeem_script, invoice: invoice.to_string(), receiver_amount_sat: payer_amount_sat - req.fees_sat, - claim_fees_sat: lbtc_pair.fees.reverse_claim_estimate(), - }]) + claim_fees_sat: reverse_pair.fees.claim_estimate(), + }) .map_err(|_| PaymentError::PersistError)?; Ok(ReceivePaymentResponse { diff --git a/lib/core/src/utils.rs b/lib/core/src/utils.rs index dc57137..851e380 100644 --- a/lib/core/src/utils.rs +++ b/lib/core/src/utils.rs @@ -1,7 +1,11 @@ use std::net::TcpStream; +use std::str::FromStr; use anyhow::{anyhow, ensure, Result}; -use boltz_client::swaps::boltzv2::SwapUpdate; +use boltz_client::swaps::{ + boltz::RevSwapStates, + boltzv2::{BoltzApiClientV2, Subscription, SwapUpdate}, +}; use log::{error, info}; use tungstenite::{stream::MaybeTlsStream, WebSocket}; @@ -65,3 +69,78 @@ pub(crate) fn get_swap_status_v2( } } } + +/// Fetch the reverse swap status using the websocket endpoint +pub(crate) fn get_rev_swap_status_v2( + client_v2: BoltzApiClientV2, + swap_id: &str, +) -> Result { + let mut socket = client_v2 + .connect_ws() + .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; + + let sub_id = swap_id.to_string(); + let subscription = Subscription::new(&sub_id); + let subscribe_json = serde_json::to_string(&subscription) + .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; + socket + .send(tungstenite::Message::Text(subscribe_json)) + .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; + + loop { + let response: SwapUpdate = serde_json::from_str(&socket.read()?.to_string()) + .map_err(|e| anyhow!("WS response is invalid SwapUpdate: {e:?}"))?; + + match response { + SwapUpdate::Subscription { + event, + channel, + args, + } => { + ensure!(event == "subscribe", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + let first_arg = args.first(); + let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == &sub_id); + ensure!(is_ok, "Wrong WS reply subscription ID {first_arg:?}"); + + info!("Subscription successful for swap : {sub_id}"); + } + + SwapUpdate::Update { + event, + channel, + args, + } => { + ensure!(event == "update", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + return match args.first() { + Some(update) if update.id == sub_id => { + info!("Got new reverse swap status: {}", update.status); + + RevSwapStates::from_str(&update.status).map_err(|_| { + anyhow!("Invalid state for rev swap {swap_id}: {}", update.status) + }) + } + Some(update) => Err(anyhow!("WS reply has wrong swap ID {update:?}")), + None => Err(anyhow!("WS reply contains no update")), + }; + } + + SwapUpdate::Error { + event, + channel, + args, + } => { + ensure!(event == "update", "Wrong WS reply event {event}"); + ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); + + for e in &args { + error!("Got error: {} for swap: {}", e.error, e.id); + } + return Err(anyhow!("Got SwapUpdate errors: {args:?}")); + } + } + } +} From 24861cb3728839ff6072848e5b4efb65ee644b71 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 8 May 2024 14:35:50 +0200 Subject: [PATCH 04/29] Track swap status updates via websocket event stream --- lib/core/src/boltz_status_stream.rs | 169 ++++++++++++++++++++++++++++ lib/core/src/lib.rs | 1 + lib/core/src/sdk.rs | 16 ++- 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 lib/core/src/boltz_status_stream.rs diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs new file mode 100644 index 0000000..89bfdc7 --- /dev/null +++ b/lib/core/src/boltz_status_stream.rs @@ -0,0 +1,169 @@ +use std::collections::HashMap; +use std::net::TcpStream; +use std::str::FromStr; +use std::sync::Arc; +use std::thread; + +use anyhow::{anyhow, ensure, Result}; +use boltz_client::swaps::{ + boltz::{RevSwapStates, SubSwapStates}, + boltzv2::{Subscription, SwapUpdate}, +}; +use log::{error, info, warn}; +use tungstenite::stream::MaybeTlsStream; +use tungstenite::{Message, WebSocket}; + +use crate::model::*; +use crate::sdk::LiquidSdk; + +pub(super) struct BoltzStatusStream { + // socket: WebSocket>, +} +impl BoltzStatusStream { + pub(super) fn track_pending_swaps(sdk: Arc) -> Result<()> { + let mut socket = sdk + .boltz_client_v2() + .connect_ws() + .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; + + thread::spawn(move || loop { + // Map of (subscribed swap ID, is_swap_out) + let mut subscribed_ids: HashMap = HashMap::new(); + + // Initially subscribe to all ongoing swaps + match sdk.list_ongoing_swaps() { + Ok(initial_ongoing_swaps) => { + info!("Got {} initial ongoing swaps", initial_ongoing_swaps.len()); + + for ongoing_swap in &initial_ongoing_swaps { + let id = &ongoing_swap.id(); + info!("Subscribing to status for initial ongoing swap ID {id}"); + + let subscription = Subscription::new(id); + let subscribe_json = serde_json::to_string(&subscription) + .map_err(|e| anyhow!("Invalid subscription msg: {e:?}")) + .unwrap(); + socket + .send(tungstenite::Message::Text(subscribe_json)) + .map_err(|e| anyhow!("Failed to subscribe to {id}: {e:?}")) + .unwrap(); + + subscribed_ids + .insert(id.clone(), matches!(ongoing_swap, OngoingSwap::Receive(_))); + } + } + Err(e) => error!("Failed to list initial ongoing swaps: {e:?}"), + } + + loop { + match &socket.read() { + Ok(Message::Close(_)) => { + warn!("Received close msg, exiting socket loop"); + break; + } + Ok(msg) => { + info!("Received msg : {msg:?}"); + + // Each time socket.read() returns, we have the opportunity to socket.send(). + // We use this window to subscribe to any new ongoing swaps. + // This happens on any non-close socket messages, in particular: + // Ping (periodic keep-alive), Text (status update) + match sdk.list_ongoing_swaps() { + Ok(ongoing_swaps) => { + let new_ongoing_swaps: Vec = ongoing_swaps + .into_iter() + .filter(|os| !subscribed_ids.contains_key(&os.id())) + .collect(); + for ongoing_swap in &new_ongoing_swaps { + let id = ongoing_swap.id(); + info!("Subscribing to statuses for ongoing swap ID: {id}"); + + let subscription = Subscription::new(&id); + let subscribe_json = serde_json::to_string(&subscription) + .map_err(|e| anyhow!("Invalid subscription msg: {e:?}")) + .unwrap(); + socket + .send(tungstenite::Message::Text(subscribe_json)) + .map_err(|e| anyhow!("Failed to subscribe to {id}: {e:?}")) + .unwrap(); + + subscribed_ids.insert( + id.clone(), + matches!(ongoing_swap, OngoingSwap::Receive(_)), + ); + } + } + Err(e) => error!("Failed to list new ongoing swaps: {e:?}"), + } + + // We parse and handle any Text websocket messages, which are likely status updates + if msg.is_text() { + let response: SwapUpdate = serde_json::from_str(&msg.to_string()) + .map_err(|e| anyhow!("WS response is invalid SwapUpdate: {e:?}")) + .unwrap(); + info!("Received update : {response:?}"); + + match response { + // Subscription confirmation + boltz_client::swaps::boltzv2::SwapUpdate::Subscription { + .. + } => {} + + // Status update + boltz_client::swaps::boltzv2::SwapUpdate::Update { + event, + channel, + args, + } => { + let update = args.first().unwrap().clone(); // TODO + let update_swap_id = update.id.clone(); + let update_state_str = update.status.clone(); + + match subscribed_ids.get(&update_swap_id) { + Some(true) => { + // Known OngoingSwapOut / receive swap + + let new_state = RevSwapStates::from_str(&update_state_str).map_err(|_| { + anyhow!("Invalid state for reverse swap {update_swap_id}: {update_state_str}") + }).unwrap(); + let res = sdk.try_handle_reverse_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapOut / receive try_handle_reverse_swap_status res: {res:?}"); + } + Some(false) => { + // Known OngoingSwapIn / Send swap + + let new_state = SubSwapStates::from_str(&update_state_str).map_err(|_| { + anyhow!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") + }).unwrap(); + let res = sdk.try_handle_submarine_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); + } + None => { + // We got an update for a swap we did not track as ongoing + todo!() + } + } + } + + // Error related to subscription, like "Unknown swap ID" + boltz_client::swaps::boltzv2::SwapUpdate::Error { .. } => todo!(), + } + } + } + Err(e) => { + error!("Received stream error : {e:?}"); + break; + } + } + } + }); + + Ok(()) + } +} diff --git a/lib/core/src/lib.rs b/lib/core/src/lib.rs index cd70d01..5e202cb 100644 --- a/lib/core/src/lib.rs +++ b/lib/core/src/lib.rs @@ -1,5 +1,6 @@ #[cfg(feature = "frb")] pub mod bindings; +pub(crate) mod boltz_status_stream; pub mod error; #[cfg(feature = "frb")] pub mod frb; diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index b57be01..12428d4 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -33,7 +33,8 @@ use lwk_wollet::{ }; use crate::{ - ensure_sdk, error::PaymentError, get_invoice_amount, model::*, persist::Persister, utils, + boltz_status_stream::BoltzStatusStream, ensure_sdk, error::PaymentError, get_invoice_amount, + model::*, persist::Persister, utils, }; /// Claim tx feerate, in sats per vbyte. @@ -97,7 +98,8 @@ impl LiquidSdk { data_dir_path, }); - LiquidSdk::track_pending_swaps(&sdk)?; + // LiquidSdk::track_pending_swaps(&sdk)?; + BoltzStatusStream::track_pending_swaps(sdk.clone())?; Ok(sdk) } @@ -194,6 +196,7 @@ impl LiquidSdk { } } + // TODO Not needed anymore with the event stream fn try_resolve_pending_swap(&self, swap: &OngoingSwap) -> Result<()> { let client = self.boltz_client(); let client_v2 = self.boltz_client_v2(); @@ -221,6 +224,7 @@ impl LiquidSdk { Ok(()) } + // TODO Not needed anymore with the event stream fn track_pending_swaps(self: &Arc) -> Result<()> { let cloned = self.clone(); thread::spawn(move || loop { @@ -247,6 +251,10 @@ impl LiquidSdk { Ok(()) } + pub(crate) fn list_ongoing_swaps(&self) -> Result> { + self.persister.list_ongoing_swaps() + } + fn scan(&self) -> Result<(), lwk_wollet::Error> { let mut electrum_client = ElectrumClient::new(&self.electrum_url)?; let mut lwk_wollet = self.lwk_wollet.lock().unwrap(); @@ -288,7 +296,7 @@ impl LiquidSdk { BoltzApiClient::new(base_url) } - fn boltz_client_v2(&self) -> BoltzApiClientV2 { + pub(crate) fn boltz_client_v2(&self) -> BoltzApiClientV2 { let base_url = match self.network { Network::LiquidTestnet => BOLTZ_TESTNET_URL_V2, Network::Liquid => BOLTZ_MAINNET_URL_V2, @@ -576,7 +584,9 @@ impl LiquidSdk { &our_keys, &Preimage::from_str(&ongoing_swap_out.preimage)?, Amount::from_sat(ongoing_swap_out.claim_fees_sat), + // Enable cooperative claim (Some) or not (None) Some((&self.boltz_client_v2(), ongoing_swap_out.id.clone())), + // None )?; claim_tx.broadcast( From dc569f0721744a49c58122e1c5707612c9185b84 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 13:49:42 +0200 Subject: [PATCH 05/29] Simplify swap subscription --- lib/core/src/boltz_status_stream.rs | 118 +++++++++++++--------------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs index 89bfdc7..9baa2be 100644 --- a/lib/core/src/boltz_status_stream.rs +++ b/lib/core/src/boltz_status_stream.rs @@ -1,7 +1,8 @@ -use std::collections::HashMap; +use std::collections::HashSet; +use std::mem::swap; use std::net::TcpStream; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::thread; use anyhow::{anyhow, ensure, Result}; @@ -21,25 +22,29 @@ pub(super) struct BoltzStatusStream { } impl BoltzStatusStream { pub(super) fn track_pending_swaps(sdk: Arc) -> Result<()> { + // Track subscribed swap IDs + let swap_in_ids = Arc::new(Mutex::new(HashSet::new())); + let swap_out_ids = Arc::new(Mutex::new(HashSet::new())); + let mut socket = sdk .boltz_client_v2() .connect_ws() .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; thread::spawn(move || loop { - // Map of (subscribed swap ID, is_swap_out) - let mut subscribed_ids: HashMap = HashMap::new(); + let maybe_subscribe_fn = + |ongoing_swap: &OngoingSwap, socket: &mut WebSocket>| { + let id = ongoing_swap.id(); - // Initially subscribe to all ongoing swaps - match sdk.list_ongoing_swaps() { - Ok(initial_ongoing_swaps) => { - info!("Got {} initial ongoing swaps", initial_ongoing_swaps.len()); + let is_ongoing_swap_already_tracked = match ongoing_swap { + OngoingSwap::Send(_) => swap_in_ids.lock().unwrap().contains(&id), + OngoingSwap::Receive(_) => swap_out_ids.lock().unwrap().contains(&id), + }; - for ongoing_swap in &initial_ongoing_swaps { - let id = &ongoing_swap.id(); - info!("Subscribing to status for initial ongoing swap ID {id}"); + if !is_ongoing_swap_already_tracked { + info!("Subscribing to status for ongoing swap ID {id}"); - let subscription = Subscription::new(id); + let subscription = Subscription::new(&id); let subscribe_json = serde_json::to_string(&subscription) .map_err(|e| anyhow!("Invalid subscription msg: {e:?}")) .unwrap(); @@ -48,8 +53,19 @@ impl BoltzStatusStream { .map_err(|e| anyhow!("Failed to subscribe to {id}: {e:?}")) .unwrap(); - subscribed_ids - .insert(id.clone(), matches!(ongoing_swap, OngoingSwap::Receive(_))); + match ongoing_swap { + OngoingSwap::Send(_) => swap_in_ids.lock().unwrap().insert(id), + OngoingSwap::Receive(_) => swap_out_ids.lock().unwrap().insert(id), + }; + } + }; + + // Initially subscribe to all ongoing swaps + match sdk.list_ongoing_swaps() { + Ok(initial_ongoing_swaps) => { + info!("Got {} initial ongoing swaps", initial_ongoing_swaps.len()); + for ongoing_swap in &initial_ongoing_swaps { + maybe_subscribe_fn(ongoing_swap, &mut socket); } } Err(e) => error!("Failed to list initial ongoing swaps: {e:?}"), @@ -70,27 +86,8 @@ impl BoltzStatusStream { // Ping (periodic keep-alive), Text (status update) match sdk.list_ongoing_swaps() { Ok(ongoing_swaps) => { - let new_ongoing_swaps: Vec = ongoing_swaps - .into_iter() - .filter(|os| !subscribed_ids.contains_key(&os.id())) - .collect(); - for ongoing_swap in &new_ongoing_swaps { - let id = ongoing_swap.id(); - info!("Subscribing to statuses for ongoing swap ID: {id}"); - - let subscription = Subscription::new(&id); - let subscribe_json = serde_json::to_string(&subscription) - .map_err(|e| anyhow!("Invalid subscription msg: {e:?}")) - .unwrap(); - socket - .send(tungstenite::Message::Text(subscribe_json)) - .map_err(|e| anyhow!("Failed to subscribe to {id}: {e:?}")) - .unwrap(); - - subscribed_ids.insert( - id.clone(), - matches!(ongoing_swap, OngoingSwap::Receive(_)), - ); + for ongoing_swap in &ongoing_swaps { + maybe_subscribe_fn(ongoing_swap, &mut socket); } } Err(e) => error!("Failed to list new ongoing swaps: {e:?}"), @@ -119,35 +116,32 @@ impl BoltzStatusStream { let update_swap_id = update.id.clone(); let update_state_str = update.status.clone(); - match subscribed_ids.get(&update_swap_id) { - Some(true) => { - // Known OngoingSwapOut / receive swap + if swap_in_ids.lock().unwrap().contains(&update_swap_id) { + // Known OngoingSwapIn / Send swap - let new_state = RevSwapStates::from_str(&update_state_str).map_err(|_| { - anyhow!("Invalid state for reverse swap {update_swap_id}: {update_state_str}") - }).unwrap(); - let res = sdk.try_handle_reverse_swap_status( - new_state, - &update_swap_id, - ); - info!("OngoingSwapOut / receive try_handle_reverse_swap_status res: {res:?}"); - } - Some(false) => { - // Known OngoingSwapIn / Send swap + let new_state = SubSwapStates::from_str(&update_state_str).map_err(|_| { + anyhow!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") + }).unwrap(); + let res = sdk.try_handle_submarine_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); + } else if swap_out_ids.lock().unwrap().contains(&update_swap_id) + { + // Known OngoingSwapOut / receive swap - let new_state = SubSwapStates::from_str(&update_state_str).map_err(|_| { - anyhow!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") - }).unwrap(); - let res = sdk.try_handle_submarine_swap_status( - new_state, - &update_swap_id, - ); - info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); - } - None => { - // We got an update for a swap we did not track as ongoing - todo!() - } + let new_state = RevSwapStates::from_str(&update_state_str).map_err(|_| { + anyhow!("Invalid state for reverse swap {update_swap_id}: {update_state_str}") + }).unwrap(); + let res = sdk.try_handle_reverse_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapOut / receive try_handle_reverse_swap_status res: {res:?}"); + } else { + // We got an update for a swap we did not track as ongoing + todo!() } } From e4353b75be415556b7d32307bdd213521ce94f61 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 19:35:51 +0200 Subject: [PATCH 06/29] Use correct ElectrumConfig when claiming --- lib/core/src/sdk.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 12428d4..8b1bfed 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -555,6 +555,7 @@ impl LiquidSdk { fn try_claim_v2(&self, ongoing_swap_out: &OngoingSwapOut) -> Result { debug!("Trying to claim reverse swap {}", ongoing_swap_out.id); + let electrum_config = ElectrumConfig::default(self.network.into(), None)?; let mnemonic = self .lwk_signer .mnemonic() @@ -574,11 +575,8 @@ impl LiquidSdk { )?; let claim_address = self.address()?.to_string(); - let claim_tx = LBtcSwapTxV2::new_claim( - swap_script.clone(), - claim_address, - &ElectrumConfig::default_liquid(), - )?; + let claim_tx = + LBtcSwapTxV2::new_claim(swap_script.clone(), claim_address, &electrum_config)?; let tx = claim_tx.sign_claim( &our_keys, @@ -589,11 +587,7 @@ impl LiquidSdk { // None )?; - claim_tx.broadcast( - &tx, - &ElectrumConfig::default(self.network.into(), None)?, - None, - )?; + claim_tx.broadcast(&tx, &electrum_config, None)?; info!("Succesfully broadcasted claim tx {}", tx.txid()); debug!("Claim Tx {:?}", tx); From 135fa9da6258bd7ba54c34c57c73a3e38e8a9bc2 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 20:54:16 +0200 Subject: [PATCH 07/29] Rev swap claims: broadcast claim tx with lowball fees --- lib/core/src/sdk.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 8b1bfed..1c2ca36 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -587,7 +587,19 @@ impl LiquidSdk { // None )?; - claim_tx.broadcast(&tx, &electrum_config, None)?; + // Electrum only broadcasts txs with lowball fees on testnet + // For mainnet, we use Boltz to broadcast + match self.network { + Network::Liquid => { + let tx_hex = elements::encode::serialize(&tx).to_lower_hex_string(); + let response = self.boltz_client_v2().broadcast_tx(self.network.into(), &tx_hex)?; + info!("Claim broadcast response: {response:?}"); + } + Network::LiquidTestnet => { + let electrum_client = ElectrumClient::new(&self.electrum_url)?; + electrum_client.broadcast(&tx)?; + } + }; info!("Succesfully broadcasted claim tx {}", tx.txid()); debug!("Claim Tx {:?}", tx); From 91b6598cf829815f20d38d37256ae11956ec9e99 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 20:59:48 +0200 Subject: [PATCH 08/29] Update claim conditions for pending rev swaps --- lib/core/src/sdk.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 1c2ca36..eea5be2 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -135,7 +135,9 @@ impl LiquidSdk { .resolve_ongoing_swap(id, None) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; } - RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed => { + // We may be offline, or claiming failued due to other reasons until the swap reached these states + // If an ongoing reverse swap is in any of these states, we should be able to claim + RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed | RevSwapStates::InvoiceSettled => { match self.try_claim_v2(&ongoing_swap_out) { Ok(txid) => { let payer_amount_sat = get_invoice_amount!(ongoing_swap_out.invoice); @@ -160,9 +162,6 @@ impl LiquidSdk { RevSwapStates::Created | RevSwapStates::MinerFeePaid => { // Too soon to try to claim } - RevSwapStates::InvoiceSettled => { - // Reverse swap already completed at this point, from our perspective - } } Ok(()) From fb7190032ad7929e9c484bbf2b9409eef9910ae4 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 21:22:57 +0200 Subject: [PATCH 09/29] Bump boltz-rust client to latest patch level --- cli/Cargo.lock | 2 +- lib/Cargo.lock | 2 +- lib/core/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 7c227a5..e446912 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=50b93fb7eba043e12a71fd7ddb1e9604a946c21b#50b93fb7eba043e12a71fd7ddb1e9604a946c21b" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=88a2f30a431952ba1ed94a4a054a5ebbd321320a#88a2f30a431952ba1ed94a4a054a5ebbd321320a" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index f30073c..4375c22 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,7 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=50b93fb7eba043e12a71fd7ddb1e9604a946c21b#50b93fb7eba043e12a71fd7ddb1e9604a946c21b" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=88a2f30a431952ba1ed94a4a054a5ebbd321320a#88a2f30a431952ba1ed94a4a054a5ebbd321320a" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index e251f64..ef4c761 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["lib", "cdylib", "staticlib"] anyhow = { workspace = true } bip39 = { version = "2.0.0", features = ["serde"] } #boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "6f45fff8b87c7530c847eb05f018906c48785a6c" } -boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "50b93fb7eba043e12a71fd7ddb1e9604a946c21b" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "88a2f30a431952ba1ed94a4a054a5ebbd321320a" } flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" lwk_common = "0.3.0" From d8cbcce5ae521f74f1101ad1c7d1570e505f6409 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 21:45:04 +0200 Subject: [PATCH 10/29] Correctly handle swap status parsing errors --- lib/core/src/boltz_status_stream.rs | 36 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs index 9baa2be..58d4234 100644 --- a/lib/core/src/boltz_status_stream.rs +++ b/lib/core/src/boltz_status_stream.rs @@ -119,26 +119,30 @@ impl BoltzStatusStream { if swap_in_ids.lock().unwrap().contains(&update_swap_id) { // Known OngoingSwapIn / Send swap - let new_state = SubSwapStates::from_str(&update_state_str).map_err(|_| { - anyhow!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") - }).unwrap(); - let res = sdk.try_handle_submarine_swap_status( - new_state, - &update_swap_id, - ); - info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); + match SubSwapStates::from_str(&update_state_str) { + Ok(new_state) => { + let res = sdk.try_handle_submarine_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); + } + Err(_) => error!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") + } } else if swap_out_ids.lock().unwrap().contains(&update_swap_id) { // Known OngoingSwapOut / receive swap - let new_state = RevSwapStates::from_str(&update_state_str).map_err(|_| { - anyhow!("Invalid state for reverse swap {update_swap_id}: {update_state_str}") - }).unwrap(); - let res = sdk.try_handle_reverse_swap_status( - new_state, - &update_swap_id, - ); - info!("OngoingSwapOut / receive try_handle_reverse_swap_status res: {res:?}"); + match RevSwapStates::from_str(&update_state_str) { + Ok(new_state) => { + let res = sdk.try_handle_reverse_swap_status( + new_state, + &update_swap_id, + ); + info!("OngoingSwapOut / receive try_handle_reverse_swap_status res: {res:?}"); + } + Err(_) => error!("Invalid state for reverse swap {update_swap_id}: {update_state_str}") + } } else { // We got an update for a swap we did not track as ongoing todo!() From 5476ddad5421fff1bed268b82db245b4d9249357 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Thu, 9 May 2024 19:27:18 +0200 Subject: [PATCH 11/29] Add non-blocking socket (fix #173) --- lib/core/src/boltz_status_stream.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs index 58d4234..a21478f 100644 --- a/lib/core/src/boltz_status_stream.rs +++ b/lib/core/src/boltz_status_stream.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; -use std::mem::swap; +use std::io::ErrorKind; use std::net::TcpStream; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; +use std::time::Duration; use anyhow::{anyhow, ensure, Result}; use boltz_client::swaps::{ @@ -31,6 +32,15 @@ impl BoltzStatusStream { .connect_ws() .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; + // Set underlying TCP stream to nonblocking mode + match socket.get_mut() { + tungstenite::stream::MaybeTlsStream::Plain(s) => s.set_nonblocking(true)?, + tungstenite::stream::MaybeTlsStream::NativeTls(s) => { + s.get_mut().set_nonblocking(true)? + } + _ => Err(anyhow!("Unsupported stream type"))? + }; + thread::spawn(move || loop { let maybe_subscribe_fn = |ongoing_swap: &OngoingSwap, socket: &mut WebSocket>| { @@ -154,6 +164,18 @@ impl BoltzStatusStream { } } } + Err(tungstenite::Error::Io(io_err)) => { + match io_err.kind() { + // Calling socket.read() on a non-blocking stream when there is nothing + // to read results in an WouldBlock error. In this case, we do nothing + // and continue the loop. + ErrorKind::WouldBlock => {}, + _ => { + error!("Received stream IO error : {io_err:?}"); + break; + } + } + } Err(e) => { error!("Received stream error : {e:?}"); break; From 2e8bbb3e4aaa21981021a1aebf01386433f207fc Mon Sep 17 00:00:00 2001 From: yse Date: Fri, 10 May 2024 18:19:49 +0200 Subject: [PATCH 12/29] fix: change send_payment resolve --- lib/core/src/boltz_status_stream.rs | 11 +- lib/core/src/sdk.rs | 236 ++++++++++++++-------------- lib/core/src/utils.rs | 156 +++++++++--------- 3 files changed, 200 insertions(+), 203 deletions(-) diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs index a21478f..7446826 100644 --- a/lib/core/src/boltz_status_stream.rs +++ b/lib/core/src/boltz_status_stream.rs @@ -4,9 +4,8 @@ use std::net::TcpStream; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; -use std::time::Duration; -use anyhow::{anyhow, ensure, Result}; +use anyhow::{anyhow, Result}; use boltz_client::swaps::{ boltz::{RevSwapStates, SubSwapStates}, boltzv2::{Subscription, SwapUpdate}, @@ -38,7 +37,7 @@ impl BoltzStatusStream { tungstenite::stream::MaybeTlsStream::NativeTls(s) => { s.get_mut().set_nonblocking(true)? } - _ => Err(anyhow!("Unsupported stream type"))? + _ => Err(anyhow!("Unsupported stream type"))?, }; thread::spawn(move || loop { @@ -118,8 +117,8 @@ impl BoltzStatusStream { // Status update boltz_client::swaps::boltzv2::SwapUpdate::Update { - event, - channel, + event: _, + channel: _, args, } => { let update = args.first().unwrap().clone(); // TODO @@ -169,7 +168,7 @@ impl BoltzStatusStream { // Calling socket.read() on a non-blocking stream when there is nothing // to read results in an WouldBlock error. In this case, we do nothing // and continue the loop. - ErrorKind::WouldBlock => {}, + ErrorKind::WouldBlock => {} _ => { error!("Received stream IO error : {io_err:?}"); break; diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index eea5be2..0215b82 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -11,10 +11,7 @@ use anyhow::{anyhow, Result}; use boltz_client::{ network::electrum::ElectrumConfig, swaps::{ - boltz::{ - BoltzApiClient, CreateSwapRequest, RevSwapStates, SubSwapStates, SwapStatusRequest, - BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL, - }, + boltz::{RevSwapStates, SubSwapStates}, boltzv2::*, liquid::{LBtcSwapScript, LBtcSwapTx}, liquidv2::LBtcSwapTxV2, @@ -23,7 +20,7 @@ use boltz_client::{ Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, }; use elements::hashes::hex::DisplayHex; -use log::{debug, error, info, warn}; +use log::{debug, info, warn}; use lwk_common::{singlesig_desc, Signer, Singlesig}; use lwk_signer::{AnySigner, SwSigner}; use lwk_wollet::{ @@ -137,28 +134,25 @@ impl LiquidSdk { } // We may be offline, or claiming failued due to other reasons until the swap reached these states // If an ongoing reverse swap is in any of these states, we should be able to claim - RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed | RevSwapStates::InvoiceSettled => { - match self.try_claim_v2(&ongoing_swap_out) { - Ok(txid) => { - let payer_amount_sat = get_invoice_amount!(ongoing_swap_out.invoice); - self.persister - .resolve_ongoing_swap( - id, - Some((txid, PaymentData { payer_amount_sat })), - ) - .map_err(|e| anyhow!("Could not resolve swap {id}: {e}"))?; - } - Err(err) => { - if let PaymentError::AlreadyClaimed = err { - warn!("Funds already claimed"); - self.persister - .resolve_ongoing_swap(id, None) - .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; - } - warn!("Could not claim swap {id} yet. Err: {err}"); - } + RevSwapStates::TransactionMempool + | RevSwapStates::TransactionConfirmed + | RevSwapStates::InvoiceSettled => match self.try_claim_v2(&ongoing_swap_out) { + Ok(txid) => { + let payer_amount_sat = get_invoice_amount!(ongoing_swap_out.invoice); + self.persister + .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .map_err(|e| anyhow!("Could not resolve swap {id}: {e}"))?; } - } + Err(err) => { + if let PaymentError::AlreadyClaimed = err { + warn!("Funds already claimed"); + self.persister + .resolve_ongoing_swap(id, None) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + } + warn!("Could not claim swap {id} yet. Err: {err}"); + } + }, RevSwapStates::Created | RevSwapStates::MinerFeePaid => { // Too soon to try to claim } @@ -196,59 +190,59 @@ impl LiquidSdk { } // TODO Not needed anymore with the event stream - fn try_resolve_pending_swap(&self, swap: &OngoingSwap) -> Result<()> { - let client = self.boltz_client(); - let client_v2 = self.boltz_client_v2(); - - match swap { - OngoingSwap::Receive(ongoing_swap_out) => { - let swap_state = utils::get_rev_swap_status_v2(client_v2, &ongoing_swap_out.id)?; - self.try_handle_reverse_swap_status(swap_state, &ongoing_swap_out.id)?; - } - OngoingSwap::Send(ongoing_swap_in) => { - let id = &ongoing_swap_in.id; - let status = client - .swap_status(SwapStatusRequest { id: id.clone() }) - .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? - .status; - - let swap_state: SubSwapStates = status.parse().map_err(|_| { - anyhow!("Invalid submarine swap state received for swap {id}: {status}") - })?; - - self.try_handle_submarine_swap_status(swap_state, &ongoing_swap_in.id)?; - } - }; - - Ok(()) - } + // fn try_resolve_pending_swap(&self, swap: &OngoingSwap) -> Result<()> { + // let client = self.boltz_client(); + // let client_v2 = self.boltz_client_v2(); + // + // match swap { + // OngoingSwap::Receive(ongoing_swap_out) => { + // let swap_state = utils::get_rev_swap_status_v2(client_v2, &ongoing_swap_out.id)?; + // self.try_handle_reverse_swap_status(swap_state, &ongoing_swap_out.id)?; + // } + // OngoingSwap::Send(ongoing_swap_in) => { + // let id = &ongoing_swap_in.id; + // let status = client + // .swap_status(SwapStatusRequest { id: id.clone() }) + // .map_err(|e| anyhow!("Failed to fetch swap status for ID {id}: {e:?}"))? + // .status; + // + // let swap_state: SubSwapStates = status.parse().map_err(|_| { + // anyhow!("Invalid submarine swap state received for swap {id}: {status}") + // })?; + // + // self.try_handle_submarine_swap_status(swap_state, &ongoing_swap_in.id)?; + // } + // }; + // + // Ok(()) + // } // TODO Not needed anymore with the event stream - fn track_pending_swaps(self: &Arc) -> Result<()> { - let cloned = self.clone(); - thread::spawn(move || loop { - thread::sleep(Duration::from_secs(5)); - match cloned.persister.list_ongoing_swaps() { - Ok(ongoing_swaps) => { - for swap in ongoing_swaps { - match cloned.try_resolve_pending_swap(&swap) { - Ok(_) => info!("Resolved pending swap {}", swap.id()), - Err(err) => match swap { - OngoingSwap::Send { .. } => error!("[Ongoing Send] {err}"), - OngoingSwap::Receive { .. } => error!("[Ongoing Receive] {err}"), - }, - } - } - } - Err(e) => { - error!("Could not read ongoing swaps from database: {e}"); - continue; - } - } - }); - - Ok(()) - } + // fn track_pending_swaps(self: &Arc) -> Result<()> { + // let cloned = self.clone(); + // thread::spawn(move || loop { + // thread::sleep(Duration::from_secs(5)); + // match cloned.persister.list_ongoing_swaps() { + // Ok(ongoing_swaps) => { + // for swap in ongoing_swaps { + // match cloned.try_resolve_pending_swap(&swap) { + // Ok(_) => info!("Resolved pending swap {}", swap.id()), + // Err(err) => match swap { + // OngoingSwap::Send { .. } => error!("[Ongoing Send] {err}"), + // OngoingSwap::Receive { .. } => error!("[Ongoing Receive] {err}"), + // }, + // } + // } + // } + // Err(e) => { + // error!("Could not read ongoing swaps from database: {e}"); + // continue; + // } + // } + // }); + // + // Ok(()) + // } pub(crate) fn list_ongoing_swaps(&self) -> Result> { self.persister.list_ongoing_swaps() @@ -286,14 +280,14 @@ impl LiquidSdk { self.lwk_signer.clone() } - fn boltz_client(&self) -> BoltzApiClient { - let base_url = match self.network { - Network::LiquidTestnet => BOLTZ_TESTNET_URL, - Network::Liquid => BOLTZ_MAINNET_URL, - }; - - BoltzApiClient::new(base_url) - } + // fn boltz_client(&self) -> BoltzApiClient { + // let base_url = match self.network { + // Network::LiquidTestnet => BOLTZ_TESTNET_URL, + // Network::Liquid => BOLTZ_MAINNET_URL, + // }; + // + // BoltzApiClient::new(base_url) + // } pub(crate) fn boltz_client_v2(&self) -> BoltzApiClientV2 { let base_url = match self.network { @@ -454,10 +448,10 @@ impl LiquidSdk { })?; let result; + let mut txid = String::new(); loop { - let data = match utils::get_swap_status_v2(&mut socket, &create_response.id) { - Ok(data) => data, - Err(_) => continue, + let Ok(data) = utils::get_swap_status_v2(&mut socket, &create_response.id) else { + continue; }; let state = data @@ -466,34 +460,21 @@ impl LiquidSdk { err: "Invalid state received from swapper".to_string(), })?; + // See https://docs.boltz.exchange/v/api/lifecycle#normal-submarine-swaps match state { - SubSwapStates::TransactionMempool | SubSwapStates::TransactionConfirmed => { - // Send detected by Boltz, waiting for invoice - // to be settled - } + // Boltz has locked the HTLC, we proceed with locking up the funds SubSwapStates::InvoiceSet => { debug!( - "Send {} sats to BTC address {}", + "Initiated swap-in: send {} sats to liquid address {}", create_response.expected_amount, create_response.address ); - // let absolute_fees = self - // .build_tx( - // None, - // &create_response.address, - // create_response.expected_amount, - // )? - // .all_fees() - // .values() - // .sum::(); - // let fee_rate = - // req.fees_sat as f32 / absolute_fees as f32 * LIQUID_CLAIM_TX_FEERATE; let tx = self.build_tx( None, &create_response.address, create_response.expected_amount, )?; - let txid = match self.network { + txid = match self.network { Network::Liquid => { let tx_hex = elements::encode::serialize(&tx).to_lower_hex_string(); let response = client.broadcast_tx(self.network.into(), &tx_hex)?; @@ -518,26 +499,45 @@ impl LiquidSdk { } }; - self.persister - .insert_or_update_ongoing_swap_in(OngoingSwapIn { - id: create_response.id.clone(), - invoice: req.invoice.clone(), - payer_amount_sat: req.fees_sat + receiver_amount_sat, - txid: Some(txid.clone()), - })?; + debug!( + "Successfully broadcast lockup transaction for swap {}. Txid: {}", + &create_response.id, &txid + ); + self.persister.resolve_ongoing_swap( + &create_response.id, + Some(( + txid.clone(), + PaymentData { + payer_amount_sat: receiver_amount_sat + req.fees_sat, + }, + )), + )?; + + debug!( + "Successfully resolved ongoing swap-in {}", + &create_response.id + ); + } + // Boltz has detected our lockup + SubSwapStates::TransactionMempool | SubSwapStates::TransactionConfirmed => {} + // Boltz has broadcast the claim to the mempool, resolve with success + SubSwapStates::TransactionClaimed => { result = Ok(SendPaymentResponse { txid }); break; } - SubSwapStates::TransactionClaimed - | SubSwapStates::InvoiceFailedToPay - | SubSwapStates::SwapExpired => { + // Either: + // 1. Boltz failed to pay + // 2. The swap has expired (>24h) + // 3. TODO: Lockup failed (we sent too little funds) + // We initiate a cooperative refund, and then fallback to a regular one + SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired => { result = Err(PaymentError::Generic { err: format!("Payment state is unrecoverable: {}", state.to_string()), }); break; } - _ => info!( + _ => debug!( "New state for swap {}: {}", create_response.id, state.to_string() @@ -591,7 +591,9 @@ impl LiquidSdk { match self.network { Network::Liquid => { let tx_hex = elements::encode::serialize(&tx).to_lower_hex_string(); - let response = self.boltz_client_v2().broadcast_tx(self.network.into(), &tx_hex)?; + let response = self + .boltz_client_v2() + .broadcast_tx(self.network.into(), &tx_hex)?; info!("Claim broadcast response: {response:?}"); } Network::LiquidTestnet => { diff --git a/lib/core/src/utils.rs b/lib/core/src/utils.rs index 851e380..41a923f 100644 --- a/lib/core/src/utils.rs +++ b/lib/core/src/utils.rs @@ -1,11 +1,7 @@ use std::net::TcpStream; -use std::str::FromStr; use anyhow::{anyhow, ensure, Result}; -use boltz_client::swaps::{ - boltz::RevSwapStates, - boltzv2::{BoltzApiClientV2, Subscription, SwapUpdate}, -}; +use boltz_client::swaps::boltzv2::SwapUpdate; use log::{error, info}; use tungstenite::{stream::MaybeTlsStream, WebSocket}; @@ -28,7 +24,7 @@ pub(crate) fn get_swap_status_v2( ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); let first_arg = args.first(); - let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == &swap_id); + let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == swap_id); ensure!(is_ok, "Wrong WS reply subscription ID {first_arg:?}"); info!("Subscription successful for swap : {swap_id}"); @@ -70,77 +66,77 @@ pub(crate) fn get_swap_status_v2( } } -/// Fetch the reverse swap status using the websocket endpoint -pub(crate) fn get_rev_swap_status_v2( - client_v2: BoltzApiClientV2, - swap_id: &str, -) -> Result { - let mut socket = client_v2 - .connect_ws() - .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; - - let sub_id = swap_id.to_string(); - let subscription = Subscription::new(&sub_id); - let subscribe_json = serde_json::to_string(&subscription) - .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; - socket - .send(tungstenite::Message::Text(subscribe_json)) - .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; - - loop { - let response: SwapUpdate = serde_json::from_str(&socket.read()?.to_string()) - .map_err(|e| anyhow!("WS response is invalid SwapUpdate: {e:?}"))?; - - match response { - SwapUpdate::Subscription { - event, - channel, - args, - } => { - ensure!(event == "subscribe", "Wrong WS reply event {event}"); - ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); - - let first_arg = args.first(); - let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == &sub_id); - ensure!(is_ok, "Wrong WS reply subscription ID {first_arg:?}"); - - info!("Subscription successful for swap : {sub_id}"); - } - - SwapUpdate::Update { - event, - channel, - args, - } => { - ensure!(event == "update", "Wrong WS reply event {event}"); - ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); - - return match args.first() { - Some(update) if update.id == sub_id => { - info!("Got new reverse swap status: {}", update.status); - - RevSwapStates::from_str(&update.status).map_err(|_| { - anyhow!("Invalid state for rev swap {swap_id}: {}", update.status) - }) - } - Some(update) => Err(anyhow!("WS reply has wrong swap ID {update:?}")), - None => Err(anyhow!("WS reply contains no update")), - }; - } - - SwapUpdate::Error { - event, - channel, - args, - } => { - ensure!(event == "update", "Wrong WS reply event {event}"); - ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); - - for e in &args { - error!("Got error: {} for swap: {}", e.error, e.id); - } - return Err(anyhow!("Got SwapUpdate errors: {args:?}")); - } - } - } -} +// Fetch the reverse swap status using the websocket endpoint +// pub(crate) fn get_rev_swap_status_v2( +// client_v2: BoltzApiClientV2, +// swap_id: &str, +// ) -> Result { +// let mut socket = client_v2 +// .connect_ws() +// .map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?; +// +// let sub_id = swap_id.to_string(); +// let subscription = Subscription::new(&sub_id); +// let subscribe_json = serde_json::to_string(&subscription) +// .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; +// socket +// .send(tungstenite::Message::Text(subscribe_json)) +// .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; +// +// loop { +// let response: SwapUpdate = serde_json::from_str(&socket.read()?.to_string()) +// .map_err(|e| anyhow!("WS response is invalid SwapUpdate: {e:?}"))?; +// +// match response { +// SwapUpdate::Subscription { +// event, +// channel, +// args, +// } => { +// ensure!(event == "subscribe", "Wrong WS reply event {event}"); +// ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); +// +// let first_arg = args.first(); +// let is_ok = matches!(first_arg.as_ref(), Some(&x) if x == &sub_id); +// ensure!(is_ok, "Wrong WS reply subscription ID {first_arg:?}"); +// +// info!("Subscription successful for swap : {sub_id}"); +// } +// +// SwapUpdate::Update { +// event, +// channel, +// args, +// } => { +// ensure!(event == "update", "Wrong WS reply event {event}"); +// ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); +// +// return match args.first() { +// Some(update) if update.id == sub_id => { +// info!("Got new reverse swap status: {}", update.status); +// +// RevSwapStates::from_str(&update.status).map_err(|_| { +// anyhow!("Invalid state for rev swap {swap_id}: {}", update.status) +// }) +// } +// Some(update) => Err(anyhow!("WS reply has wrong swap ID {update:?}")), +// None => Err(anyhow!("WS reply contains no update")), +// }; +// } +// +// SwapUpdate::Error { +// event, +// channel, +// args, +// } => { +// ensure!(event == "update", "Wrong WS reply event {event}"); +// ensure!(channel == "swap.update", "Wrong WS reply channel {channel}"); +// +// for e in &args { +// error!("Got error: {} for swap: {}", e.error, e.id); +// } +// return Err(anyhow!("Got SwapUpdate errors: {args:?}")); +// } +// } +// } +// } From 8f93587129234706c134c5a2ce3e0d1fefd43c09 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Sun, 12 May 2024 18:05:36 +0200 Subject: [PATCH 13/29] Extract get_liquid_swap_key() --- lib/core/src/sdk.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 0215b82..2d068b2 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -555,15 +555,7 @@ impl LiquidSdk { debug!("Trying to claim reverse swap {}", ongoing_swap_out.id); let electrum_config = ElectrumConfig::default(self.network.into(), None)?; - let mnemonic = self - .lwk_signer - .mnemonic() - .ok_or(PaymentError::SignerError { - err: "Could not claim: Mnemonic not found".to_string(), - })?; - let swap_key = - SwapKey::from_reverse_account(&mnemonic.to_string(), "", self.network.into(), 0)?; - let lsk = LiquidSwapKey::try_from(swap_key)?; + let lsk = self.get_liquid_swap_key()?; let our_keys = lsk.keypair; let swap_response_v2: CreateReverseResponse = @@ -672,15 +664,7 @@ impl LiquidSdk { debug!("Creating reverse swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"); - let mnemonic = self - .lwk_signer - .mnemonic() - .ok_or(PaymentError::SignerError { - err: "Could not start receive: Mnemonic not found".to_string(), - })?; - let swap_key = - SwapKey::from_reverse_account(&mnemonic.to_string(), "", self.network.into(), 0)?; - let lsk = LiquidSwapKey::try_from(swap_key)?; + let lsk = self.get_liquid_swap_key()?; let preimage = Preimage::new(); let preimage_str = preimage.to_string().ok_or(PaymentError::InvalidPreimage)?; @@ -823,6 +807,20 @@ impl LiquidSdk { pub fn backup(&self) -> Result<()> { self.persister.backup() } + + fn get_liquid_swap_key(&self) -> Result { + let mnemonic = self + .lwk_signer + .mnemonic() + .ok_or(PaymentError::SignerError { + err: "Mnemonic not found".to_string(), + })?; + let swap_key = + SwapKey::from_reverse_account(&mnemonic.to_string(), "", self.network.into(), 0)?; + LiquidSwapKey::try_from(swap_key).map_err(|e| PaymentError::SignerError { + err: format!("Could not create LiquidSwapKey: {e:?}"), + }) + } } #[cfg(test)] From 6b58682a91b3143f9f4c5836f31da8fc7c45e96d Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Sun, 12 May 2024 18:34:44 +0200 Subject: [PATCH 14/29] Receive: broadcast claim tx when lockup tx in mempool --- cli/Cargo.lock | 2 +- lib/Cargo.lock | 2 +- lib/core/Cargo.toml | 12 ++++++++++-- lib/core/src/sdk.rs | 22 ++++++++++++++-------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index e446912..a94d49e 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=88a2f30a431952ba1ed94a4a054a5ebbd321320a#88a2f30a431952ba1ed94a4a054a5ebbd321320a" +source = "git+https://github.com/ok300/boltz-rust?rev=0ec36d8af79db0b4e28aaeed6a66c18db9205c49#0ec36d8af79db0b4e28aaeed6a66c18db9205c49" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 4375c22..90a84e2 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,7 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=88a2f30a431952ba1ed94a4a054a5ebbd321320a#88a2f30a431952ba1ed94a4a054a5ebbd321320a" +source = "git+https://github.com/ok300/boltz-rust?rev=0ec36d8af79db0b4e28aaeed6a66c18db9205c49#0ec36d8af79db0b4e28aaeed6a66c18db9205c49" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index ef4c761..7e12e28 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -10,8 +10,16 @@ crate-type = ["lib", "cdylib", "staticlib"] [dependencies] anyhow = { workspace = true } bip39 = { version = "2.0.0", features = ["serde"] } -#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "6f45fff8b87c7530c847eb05f018906c48785a6c" } -boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "88a2f30a431952ba1ed94a4a054a5ebbd321320a" } + +#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" } +#boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "b632238935ad5f5b60435a7fc494ded2f232cf12" } + +# Combination of +# - latest fixes from Boltz (fallback for get_utxos) needed for claiming as soon as lockup tx is seen in mempool (receive case) +# - latest fixes from Antonio (get reverse swap pairs, necessary structs, etc) +# https://github.com/ok300/boltz-rust/commits/ok300-combo-boltz-fallback-get-utxos-yse-get-submarine-pairs/ +boltz-client = { git = "https://github.com/ok300/boltz-rust", rev = "0ec36d8af79db0b4e28aaeed6a66c18db9205c49" } + flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" lwk_common = "0.3.0" diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 2d068b2..12063cf 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -290,12 +290,14 @@ impl LiquidSdk { // } pub(crate) fn boltz_client_v2(&self) -> BoltzApiClientV2 { - let base_url = match self.network { + BoltzApiClientV2::new(self.boltz_url_v2()) + } + + pub(crate) fn boltz_url_v2(&self) -> &str { + match self.network { Network::LiquidTestnet => BOLTZ_TESTNET_URL_V2, Network::Liquid => BOLTZ_MAINNET_URL_V2, - }; - - BoltzApiClientV2::new(base_url) + } } fn network_config(&self) -> ElectrumConfig { @@ -552,9 +554,8 @@ impl LiquidSdk { } fn try_claim_v2(&self, ongoing_swap_out: &OngoingSwapOut) -> Result { - debug!("Trying to claim reverse swap {}", ongoing_swap_out.id); + debug!("Trying to claim reverse swap {}", &ongoing_swap_out.id); - let electrum_config = ElectrumConfig::default(self.network.into(), None)?; let lsk = self.get_liquid_swap_key()?; let our_keys = lsk.keypair; @@ -566,8 +567,13 @@ impl LiquidSdk { )?; let claim_address = self.address()?.to_string(); - let claim_tx = - LBtcSwapTxV2::new_claim(swap_script.clone(), claim_address, &electrum_config)?; + let claim_tx = LBtcSwapTxV2::new_claim( + swap_script, + claim_address, + &self.network_config(), + self.boltz_url_v2().into(), + ongoing_swap_out.id.clone(), + )?; let tx = claim_tx.sign_claim( &our_keys, From a1e437a04e4bb620fc2640a6e17c69c068ea69bc Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Mon, 13 May 2024 17:05:50 +0200 Subject: [PATCH 15/29] Fix error handling in receive_payment --- lib/core/src/sdk.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 12063cf..afde29a 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -692,11 +692,8 @@ impl LiquidSdk { let redeem_script = serde_json::to_string(&swap_response_v2).unwrap(); let swap_id = swap_response_v2.id; - let invoice = Bolt11Invoice::from_str(&swap_response_v2.invoice).map_err(|_| { - boltz_client::error::Error::Protocol( - "Boltz response does not contain an invoice.".to_string(), - ) - })?; + let invoice = Bolt11Invoice::from_str(&swap_response_v2.invoice) + .map_err(|_| PaymentError::InvalidInvoice)?; let blinding_str = swap_response_v2 .blinding_key From 28d4f264ebf14489927a5abdaaacc439d09d3217 Mon Sep 17 00:00:00 2001 From: yse Date: Fri, 10 May 2024 20:53:56 +0200 Subject: [PATCH 16/29] feat: add refund and improve send flow --- cli/Cargo.lock | 1 - lib/Cargo.lock | 1 - .../include/breez_liquid_sdk.h | 17 +- lib/core/src/bindings.rs | 9 - lib/core/src/error.rs | 3 + lib/core/src/frb/bridge.io.rs | 76 ++--- lib/core/src/frb/bridge.rs | 169 +++-------- lib/core/src/sdk.rs | 273 ++++++++++++------ packages/dart/lib/src/bindings.dart | 23 -- packages/dart/lib/src/error.dart | 4 + packages/dart/lib/src/error.freezed.dart | 86 ++++++ packages/dart/lib/src/frb_generated.dart | 195 +++---------- packages/dart/lib/src/frb_generated.io.dart | 165 +++-------- ...utter_breez_liquid_bindings_generated.dart | 92 ++---- 14 files changed, 456 insertions(+), 658 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index a94d49e..7fe6ab3 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,7 +355,6 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/ok300/boltz-rust?rev=0ec36d8af79db0b4e28aaeed6a66c18db9205c49#0ec36d8af79db0b4e28aaeed6a66c18db9205c49" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 90a84e2..a60a6a0 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,7 +478,6 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/ok300/boltz-rust?rev=0ec36d8af79db0b4e28aaeed6a66c18db9205c49#0ec36d8af79db0b4e28aaeed6a66c18db9205c49" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h index 6c9997e..6e04c2f 100644 --- a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h +++ b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h @@ -20,8 +20,6 @@ typedef struct _Dart_Handle* Dart_Handle; */ #define LIQUID_CLAIM_TX_FEERATE 0.1 -#define LIQUID_MIN_CLAIM_ABSOLUTE_FEES 134 - typedef struct wire_cst_list_prim_u_8_strict { uint8_t *ptr; int32_t len; @@ -78,6 +76,11 @@ typedef struct wire_cst_get_info_response { struct wire_cst_list_prim_u_8_strict *pubkey; } wire_cst_get_info_response; +typedef struct wire_cst_PaymentError_Refunded { + struct wire_cst_list_prim_u_8_strict *err; + struct wire_cst_list_prim_u_8_strict *txid; +} wire_cst_PaymentError_Refunded; + typedef struct wire_cst_PaymentError_Generic { struct wire_cst_list_prim_u_8_strict *err; } wire_cst_PaymentError_Generic; @@ -95,6 +98,7 @@ typedef struct wire_cst_PaymentError_SignerError { } wire_cst_PaymentError_SignerError; typedef union PaymentErrorKind { + struct wire_cst_PaymentError_Refunded Refunded; struct wire_cst_PaymentError_Generic Generic; struct wire_cst_PaymentError_LwkError LwkError; struct wire_cst_PaymentError_SendError SendError; @@ -134,17 +138,11 @@ void frbgen_breez_liquid_wire_prepare_send_payment(int64_t port_, void frbgen_breez_liquid_wire_receive_payment(int64_t port_, struct wire_cst_prepare_receive_response *req); -void frbgen_breez_liquid_wire_recover_funds(int64_t port_, uintptr_t recovery); - void frbgen_breez_liquid_wire_restore(int64_t port_, struct wire_cst_restore_request *req); void frbgen_breez_liquid_wire_send_payment(int64_t port_, struct wire_cst_prepare_send_response *req); -void frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery(const void *ptr); - -void frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery(const void *ptr); - struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect_request(void); struct wire_cst_get_info_request *frbgen_breez_liquid_cst_new_box_autoadd_get_info_request(void); @@ -179,8 +177,6 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_u_64); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_prim_u_8_strict); - dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery); - dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_backup); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_connect); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_empty_wallet_cache); @@ -189,7 +185,6 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_prepare_receive_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_prepare_send_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_receive_payment); - dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_recover_funds); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_restore); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire_send_payment); dummy_var ^= ((int64_t) (void*) store_dart_post_cobject); diff --git a/lib/core/src/bindings.rs b/lib/core/src/bindings.rs index 6506909..efde068 100644 --- a/lib/core/src/bindings.rs +++ b/lib/core/src/bindings.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -pub(crate) use boltz_client::util::secrets::LBtcReverseRecovery; use std::sync::{Arc, OnceLock}; use crate::{error::*, model::*, sdk::LiquidSdk}; @@ -65,14 +64,6 @@ pub fn list_payments(with_scan: bool, include_pending: bool) -> Result Result { - LIQUID_SDK_INSTANCE - .get() - .ok_or(anyhow!("Not initialized")) - .map_err(|e| LiquidSdkError::Generic { err: e.to_string() })? - .recover_funds(&recovery) -} - pub fn empty_wallet_cache() -> Result<()> { LIQUID_SDK_INSTANCE .get() diff --git a/lib/core/src/error.rs b/lib/core/src/error.rs index e18b732..e5abe78 100644 --- a/lib/core/src/error.rs +++ b/lib/core/src/error.rs @@ -36,6 +36,9 @@ pub enum PaymentError { #[error("The specified funds have already been claimed")] AlreadyClaimed, + #[error("The payment has been refunded. Reason for failure: {err}")] + Refunded { err: String, txid: String }, + #[error("Generic error: {err}")] Generic { err: String }, diff --git a/lib/core/src/frb/bridge.io.rs b/lib/core/src/frb/bridge.io.rs index 7438471..caf4a19 100644 --- a/lib/core/src/frb/bridge.io.rs +++ b/lib/core/src/frb/bridge.io.rs @@ -4,7 +4,6 @@ // Section: imports use super::*; -use crate::bindings::*; use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use flutter_rust_bridge::for_generated::transform_result_dco; use flutter_rust_bridge::{Handler, IntoIntoDart}; @@ -23,30 +22,6 @@ impl CstDecode unimplemented!() } } -impl CstDecode for usize { - // Codec=Cst (C-struct based), see doc to use other codecs - fn cst_decode(self) -> LBtcReverseRecovery { - CstDecode::< - RustOpaqueNom< - flutter_rust_bridge::for_generated::RustAutoOpaqueInner, - >, - >::cst_decode(self) - .rust_auto_opaque_decode_owned() - } -} -impl - CstDecode< - RustOpaqueNom>, - > for usize -{ - // Codec=Cst (C-struct based), see doc to use other codecs - fn cst_decode( - self, - ) -> RustOpaqueNom> - { - unsafe { decode_rust_opaque_nom(self as _) } - } -} impl CstDecode for *mut wire_cst_list_prim_u_8_strict { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> String { @@ -183,28 +158,35 @@ impl CstDecode for wire_cst_payment_error { 2 => crate::error::PaymentError::InsufficientFunds, 3 => crate::error::PaymentError::AlreadyClaimed, 4 => { + let ans = unsafe { self.kind.Refunded }; + crate::error::PaymentError::Refunded { + err: ans.err.cst_decode(), + txid: ans.txid.cst_decode(), + } + } + 5 => { let ans = unsafe { self.kind.Generic }; crate::error::PaymentError::Generic { err: ans.err.cst_decode(), } } - 5 => crate::error::PaymentError::InvalidInvoice, - 6 => crate::error::PaymentError::InvalidPreimage, - 7 => { + 6 => crate::error::PaymentError::InvalidInvoice, + 7 => crate::error::PaymentError::InvalidPreimage, + 8 => { let ans = unsafe { self.kind.LwkError }; crate::error::PaymentError::LwkError { err: ans.err.cst_decode(), } } - 8 => crate::error::PaymentError::PairsNotFound, - 9 => crate::error::PaymentError::PersistError, - 10 => { + 9 => crate::error::PaymentError::PairsNotFound, + 10 => crate::error::PaymentError::PersistError, + 11 => { let ans = unsafe { self.kind.SendError }; crate::error::PaymentError::SendError { err: ans.err.cst_decode(), } } - 11 => { + 12 => { let ans = unsafe { self.kind.SignerError }; crate::error::PaymentError::SignerError { err: ans.err.cst_decode(), @@ -486,11 +468,6 @@ pub extern "C" fn frbgen_breez_liquid_wire_receive_payment( wire_receive_payment_impl(port_, req) } -#[no_mangle] -pub extern "C" fn frbgen_breez_liquid_wire_recover_funds(port_: i64, recovery: usize) { - wire_recover_funds_impl(port_, recovery) -} - #[no_mangle] pub extern "C" fn frbgen_breez_liquid_wire_restore(port_: i64, req: *mut wire_cst_restore_request) { wire_restore_impl(port_, req) @@ -504,24 +481,6 @@ pub extern "C" fn frbgen_breez_liquid_wire_send_payment( wire_send_payment_impl(port_, req) } -#[no_mangle] -pub extern "C" fn frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr: *const std::ffi::c_void, -) { - unsafe { - StdArc::>::increment_strong_count(ptr as _); - } -} - -#[no_mangle] -pub extern "C" fn frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr: *const std::ffi::c_void, -) { - unsafe { - StdArc::>::decrement_strong_count(ptr as _); - } -} - #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_connect_request( ) -> *mut wire_cst_connect_request { @@ -660,6 +619,7 @@ pub struct wire_cst_payment_error { #[repr(C)] #[derive(Clone, Copy)] pub union PaymentErrorKind { + Refunded: wire_cst_PaymentError_Refunded, Generic: wire_cst_PaymentError_Generic, LwkError: wire_cst_PaymentError_LwkError, SendError: wire_cst_PaymentError_SendError, @@ -668,6 +628,12 @@ pub union PaymentErrorKind { } #[repr(C)] #[derive(Clone, Copy)] +pub struct wire_cst_PaymentError_Refunded { + err: *mut wire_cst_list_prim_u_8_strict, + txid: *mut wire_cst_list_prim_u_8_strict, +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct wire_cst_PaymentError_Generic { err: *mut wire_cst_list_prim_u_8_strict, } diff --git a/lib/core/src/frb/bridge.rs b/lib/core/src/frb/bridge.rs index aeb0441..af537e7 100644 --- a/lib/core/src/frb/bridge.rs +++ b/lib/core/src/frb/bridge.rs @@ -19,7 +19,6 @@ // Section: imports -use crate::bindings::*; use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use flutter_rust_bridge::for_generated::transform_result_dco; use flutter_rust_bridge::{Handler, IntoIntoDart}; @@ -32,7 +31,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.0.0-dev.33"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1225779344; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -451265040; // Section: executor @@ -170,24 +169,6 @@ fn wire_receive_payment_impl( }, ) } -fn wire_recover_funds_impl( - port_: flutter_rust_bridge::for_generated::MessagePort, - recovery: impl CstDecode, -) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( - flutter_rust_bridge::for_generated::TaskInfo { - debug_name: "recover_funds", - port: Some(port_), - mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, - }, - move || { - let api_recovery = recovery.cst_decode(); - move |context| { - transform_result_dco((move || crate::bindings::recover_funds(api_recovery))()) - } - }, - ) -} fn wire_restore_impl( port_: flutter_rust_bridge::for_generated::MessagePort, req: impl CstDecode, @@ -275,12 +256,6 @@ impl CstDecode for u8 { self } } -impl CstDecode for usize { - // Codec=Cst (C-struct based), see doc to use other codecs - fn cst_decode(self) -> usize { - self - } -} impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -288,26 +263,6 @@ impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { } } -impl SseDecode for LBtcReverseRecovery { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut inner = , - >>::sse_decode(deserializer); - return inner.rust_auto_opaque_decode_owned(); - } -} - -impl SseDecode - for RustOpaqueNom> -{ - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut inner = ::sse_decode(deserializer); - return unsafe { decode_rust_opaque_nom(inner) }; - } -} - impl SseDecode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -474,29 +429,37 @@ impl SseDecode for crate::error::PaymentError { } 4 => { let mut var_err = ::sse_decode(deserializer); - return crate::error::PaymentError::Generic { err: var_err }; + let mut var_txid = ::sse_decode(deserializer); + return crate::error::PaymentError::Refunded { + err: var_err, + txid: var_txid, + }; } 5 => { - return crate::error::PaymentError::InvalidInvoice; + let mut var_err = ::sse_decode(deserializer); + return crate::error::PaymentError::Generic { err: var_err }; } 6 => { - return crate::error::PaymentError::InvalidPreimage; + return crate::error::PaymentError::InvalidInvoice; } 7 => { + return crate::error::PaymentError::InvalidPreimage; + } + 8 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::LwkError { err: var_err }; } - 8 => { + 9 => { return crate::error::PaymentError::PairsNotFound; } - 9 => { + 10 => { return crate::error::PaymentError::PersistError; } - 10 => { + 11 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::SendError { err: var_err }; } - 11 => { + 12 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::SignerError { err: var_err }; } @@ -621,13 +584,6 @@ impl SseDecode for () { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} } -impl SseDecode for usize { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_u64::().unwrap() as _ - } -} - fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, @@ -655,24 +611,6 @@ fn pde_ffi_dispatcher_sync_impl( // Section: rust2dart -// Codec=Dco (DartCObject based), see doc to use other codecs -impl flutter_rust_bridge::IntoDart for FrbWrapper { - fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { - flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, StdArc<_>>(self.0) - .into_dart() - } -} -impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive - for FrbWrapper -{ -} - -impl flutter_rust_bridge::IntoIntoDart> for LBtcReverseRecovery { - fn into_into_dart(self) -> FrbWrapper { - self.into() - } -} - // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::model::ConnectRequest { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -767,21 +705,27 @@ impl flutter_rust_bridge::IntoDart for crate::error::PaymentError { crate::error::PaymentError::InvalidOrExpiredFees => [1.into_dart()].into_dart(), crate::error::PaymentError::InsufficientFunds => [2.into_dart()].into_dart(), crate::error::PaymentError::AlreadyClaimed => [3.into_dart()].into_dart(), + crate::error::PaymentError::Refunded { err, txid } => [ + 4.into_dart(), + err.into_into_dart().into_dart(), + txid.into_into_dart().into_dart(), + ] + .into_dart(), crate::error::PaymentError::Generic { err } => { - [4.into_dart(), err.into_into_dart().into_dart()].into_dart() + [5.into_dart(), err.into_into_dart().into_dart()].into_dart() } - crate::error::PaymentError::InvalidInvoice => [5.into_dart()].into_dart(), - crate::error::PaymentError::InvalidPreimage => [6.into_dart()].into_dart(), + crate::error::PaymentError::InvalidInvoice => [6.into_dart()].into_dart(), + crate::error::PaymentError::InvalidPreimage => [7.into_dart()].into_dart(), crate::error::PaymentError::LwkError { err } => { - [7.into_dart(), err.into_into_dart().into_dart()].into_dart() + [8.into_dart(), err.into_into_dart().into_dart()].into_dart() } - crate::error::PaymentError::PairsNotFound => [8.into_dart()].into_dart(), - crate::error::PaymentError::PersistError => [9.into_dart()].into_dart(), + crate::error::PaymentError::PairsNotFound => [9.into_dart()].into_dart(), + crate::error::PaymentError::PersistError => [10.into_dart()].into_dart(), crate::error::PaymentError::SendError { err } => { - [10.into_dart(), err.into_into_dart().into_dart()].into_dart() + [11.into_dart(), err.into_into_dart().into_dart()].into_dart() } crate::error::PaymentError::SignerError { err } => { - [11.into_dart(), err.into_into_dart().into_dart()].into_dart() + [12.into_dart(), err.into_into_dart().into_dart()].into_dart() } } } @@ -945,24 +889,6 @@ impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { } } -impl SseEncode for LBtcReverseRecovery { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, StdArc<_>>(self), serializer); - } -} - -impl SseEncode - for RustOpaqueNom> -{ - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - let (ptr, size) = self.sse_encode_raw(); - ::sse_encode(ptr, serializer); - ::sse_encode(size, serializer); - } -} - impl SseEncode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1102,32 +1028,37 @@ impl SseEncode for crate::error::PaymentError { crate::error::PaymentError::AlreadyClaimed => { ::sse_encode(3, serializer); } - crate::error::PaymentError::Generic { err } => { + crate::error::PaymentError::Refunded { err, txid } => { ::sse_encode(4, serializer); ::sse_encode(err, serializer); + ::sse_encode(txid, serializer); + } + crate::error::PaymentError::Generic { err } => { + ::sse_encode(5, serializer); + ::sse_encode(err, serializer); } crate::error::PaymentError::InvalidInvoice => { - ::sse_encode(5, serializer); - } - crate::error::PaymentError::InvalidPreimage => { ::sse_encode(6, serializer); } - crate::error::PaymentError::LwkError { err } => { + crate::error::PaymentError::InvalidPreimage => { ::sse_encode(7, serializer); + } + crate::error::PaymentError::LwkError { err } => { + ::sse_encode(8, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::PairsNotFound => { - ::sse_encode(8, serializer); - } - crate::error::PaymentError::PersistError => { ::sse_encode(9, serializer); } - crate::error::PaymentError::SendError { err } => { + crate::error::PaymentError::PersistError => { ::sse_encode(10, serializer); + } + crate::error::PaymentError::SendError { err } => { + ::sse_encode(11, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::SignerError { err } => { - ::sse_encode(11, serializer); + ::sse_encode(12, serializer); ::sse_encode(err, serializer); } } @@ -1230,16 +1161,6 @@ impl SseEncode for () { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} } -impl SseEncode for usize { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer - .cursor - .write_u64::(self as _) - .unwrap(); - } -} - #[cfg(not(target_family = "wasm"))] #[path = "bridge.io.rs"] mod io; diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index afde29a..d9fbf79 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -8,15 +8,15 @@ use std::{ }; use anyhow::{anyhow, Result}; +use bip39::rand::Rng; use boltz_client::{ network::electrum::ElectrumConfig, swaps::{ boltz::{RevSwapStates, SubSwapStates}, boltzv2::*, - liquid::{LBtcSwapScript, LBtcSwapTx}, liquidv2::LBtcSwapTxV2, }, - util::secrets::{LBtcReverseRecovery, LiquidSwapKey, Preimage, SwapKey}, + util::secrets::{LiquidSwapKey, Preimage, SwapKey}, Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, }; use elements::hashes::hex::DisplayHex; @@ -381,6 +381,147 @@ impl LiquidSdk { }) } + fn broadcast_lowball( + &self, + client: &BoltzApiClientV2, + tx: &Transaction, + ) -> Result { + let tx_hex = elements::encode::serialize(tx).to_lower_hex_string(); + let response = client.broadcast_tx(self.network.into(), &tx_hex)?; + Ok(response + .as_object() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .get("id") + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .as_str() + .ok_or(PaymentError::Generic { + err: "Invalid data received from swapper".to_string(), + })? + .to_string()) + } + + fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> { + let preimage = Preimage::from_str(preimage)?; + let preimage_hash = preimage.sha256.to_string(); + let invoice = + Bolt11Invoice::from_str(&invoice).map_err(|_| PaymentError::InvalidInvoice)?; + let invoice_payment_hash = invoice.payment_hash(); + + (invoice_payment_hash.to_string() == preimage_hash.to_string()) + .then(|| ()) + .ok_or(PaymentError::InvalidPreimage) + } + + fn new_swap_tx(&self, swap_script: &LBtcSwapScriptV2) -> Result { + let output_address = self.address()?.to_string(); + let network_config = self.network_config(); + Ok(LBtcSwapTxV2::new_refund( + swap_script.clone(), + &output_address, + &network_config, + )?) + } + + fn try_refund( + &self, + swap_id: &str, + swap_script: &LBtcSwapScriptV2, + keypair: &Keypair, + fees_sat: u64, + ) -> Result { + let swap_tx = self.new_swap_tx(swap_script)?; + + let client = self.boltz_client_v2(); + let is_lowball = Some((&client, boltz_client::network::Chain::from(self.network))); + match swap_tx.sign_refund( + keypair, + Amount::from_sat(fees_sat), + Some((&client, &swap_id.to_string())), + ) { + // Try with cooperative refund + Ok(tx) => { + let txid = swap_tx.broadcast(&tx, &self.network_config(), is_lowball)?; + debug!("Successfully broadcast cooperative refund for swap-in {swap_id}"); + Ok(txid) + } + // Try with non-cooperative refund + Err(e) => { + debug!("Cooperative refund failed: {:?}", e); + + let tx = swap_tx.sign_refund(keypair, Amount::from_sat(fees_sat), None)?; + let txid = swap_tx.broadcast(&tx, &self.network_config(), is_lowball)?; + debug!("Successfully broadcast non-cooperative refund for swap-in {swap_id}"); + Ok(txid) + } + } + } + + fn get_keys(&self, derivation_index: i16) -> Result { + let mnemonic = self + .lwk_signer + .mnemonic() + .ok_or(PaymentError::SignerError { + err: "Could not claim: Mnemonic not found".to_string(), + })?; + let swap_key = SwapKey::from_submarine_account( + &mnemonic.to_string(), + "", + self.network.into(), + derivation_index as u64, + )?; + let lsk = LiquidSwapKey::try_from(swap_key)?; + Ok(lsk.keypair) + } + + fn try_lockup( + &self, + invoice: &str, + create_response: &CreateSubmarineResponse, + swap_script: &LBtcSwapScriptV2, + keypair: &Keypair, + ) -> Result { + let client = self.boltz_client_v2(); + let swap_id = &create_response.id; + + // First try using cooperative submarine + { + let swap_tx = self.new_swap_tx(swap_script)?; + + let claim_tx_response = client.get_claim_tx_details(dbg!(&swap_id.to_string()))?; + + debug!("Received claim tx details: {:?}", &claim_tx_response); + + Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; + + let (partial_sig, pub_nonce) = + swap_tx.submarine_partial_sig(keypair, &claim_tx_response)?; + + client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; + Ok(String::new()) + } + // Then try non-cooperatively + .or_else(|_: PaymentError| { + let tx = self.build_tx( + None, + &create_response.address, + create_response.expected_amount, + )?; + + let txid = match self.network { + Network::Liquid => self.broadcast_lowball(&client, &tx)?, + Network::LiquidTestnet => { + let electrum_client = ElectrumClient::new(&self.electrum_url)?; + electrum_client.broadcast(&tx)?.to_string() + } + }; + Ok(txid) + }) + } + pub fn send_payment( &self, req: &PrepareSendResponse, @@ -399,42 +540,32 @@ impl LiquidSdk { PaymentError::InvalidOrExpiredFees ); - let lwk_wollet = self.lwk_wollet.lock().unwrap(); - // let our_pubkey = self - // .address()? - // .to_unconfidential() - // .blinding_pubkey - // .ok_or(PaymentError::Generic { - // err: "Could not retrieve wallet pubkey".to_string(), - // })? - // .into(); - let refund_public_key = lwk_wollet - .address(None)? - .address() - .blinding_pubkey - .ok_or(PaymentError::Generic { - err: "Could not generate refund pubkey".to_string(), - })? - .into(); - - // Unlock lwk wallet so it can be used to build the tx - std::mem::drop(lwk_wollet); + let derivation_index: i16 = bip39::rand::thread_rng().gen(); + let keypair = self.get_keys(derivation_index)?; + let refund_public_key = boltz_client::PublicKey { + compressed: true, + inner: keypair.public_key(), + }; let create_response = client.post_swap_req(&CreateSubmarineRequest { from: "L-BTC".to_string(), to: "BTC".to_string(), invoice: req.invoice.to_string(), - // TODO: Add refund flow refund_public_key, // TODO: Add referral id referral_id: None, })?; - // let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp(&create_response, our_pubkey)?; - debug!("Opening WS connection for swap {}", create_response.id); + let swap_id = &create_response.id; + let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( + &create_response, + keypair.public_key().into(), + )?; + + debug!("Opening WS connection for swap {}", &swap_id); let mut socket = client.connect_ws().unwrap(); - let subscription = Subscription::new(&create_response.id); + let subscription = Subscription::new(&swap_id); let subscribe_json = serde_json::to_string(&subscription) .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; socket @@ -443,7 +574,7 @@ impl LiquidSdk { self.persister .insert_or_update_ongoing_swap_in(OngoingSwapIn { - id: create_response.id.clone(), + id: swap_id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, txid: None, @@ -452,8 +583,12 @@ impl LiquidSdk { let result; let mut txid = String::new(); loop { - let Ok(data) = utils::get_swap_status_v2(&mut socket, &create_response.id) else { - continue; + let data = match utils::get_swap_status_v2(&mut socket, &swap_id) { + Ok(data) => data, + Err(_) => { + // TODO: close socket if dead, skip EOF errors + continue; + } }; let state = data @@ -470,44 +605,21 @@ impl LiquidSdk { "Initiated swap-in: send {} sats to liquid address {}", create_response.expected_amount, create_response.address ); - let tx = self.build_tx( - None, - &create_response.address, - create_response.expected_amount, + + txid = self.try_lockup( + &req.invoice, + &create_response, + &swap_script, + &keypair )?; - txid = match self.network { - Network::Liquid => { - let tx_hex = elements::encode::serialize(&tx).to_lower_hex_string(); - let response = client.broadcast_tx(self.network.into(), &tx_hex)?; - response - .as_object() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .get("id") - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .as_str() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .to_string() - } - Network::LiquidTestnet => { - let electrum_client = ElectrumClient::new(&self.electrum_url)?; - electrum_client.broadcast(&tx)?.to_string() - } - }; - debug!( - "Successfully broadcast lockup transaction for swap {}. Txid: {}", - &create_response.id, &txid + "Successfully broadcast lockup transaction for swap-in {}. Txid: {}", + &swap_id, &txid ); self.persister.resolve_ongoing_swap( - &create_response.id, + &swap_id, Some(( txid.clone(), PaymentData { @@ -518,7 +630,7 @@ impl LiquidSdk { debug!( "Successfully resolved ongoing swap-in {}", - &create_response.id + &swap_id ); } // Boltz has detected our lockup @@ -533,15 +645,23 @@ impl LiquidSdk { // 2. The swap has expired (>24h) // 3. TODO: Lockup failed (we sent too little funds) // We initiate a cooperative refund, and then fallback to a regular one - SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired => { - result = Err(PaymentError::Generic { - err: format!("Payment state is unrecoverable: {}", state.to_string()), - }); + SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired + // | SubSwapStates::LockupFailed (needs addition to Boltz crate) + => { + let txid = self.try_refund( + &swap_id, + &swap_script, + &keypair, + // TODO Don't hardcode min broadcast fee + 133 + )?; + + result = Err(PaymentError::Refunded { err: format!("Unrecoverable state for swap-in {}: {}", &swap_id, state.to_string()), txid: txid.clone() }); break; } _ => debug!( "New state for swap {}: {}", - create_response.id, + swap_id, state.to_string() ), }; @@ -768,25 +888,6 @@ impl LiquidSdk { Ok(payments) } - pub fn recover_funds(&self, recovery: &LBtcReverseRecovery) -> Result { - let script: LBtcSwapScript = recovery.try_into().unwrap(); - let network_config = self.network_config(); - debug!("{:?}", script.fetch_utxo(&network_config)); - - let tx = - LBtcSwapTx::new_claim(script.clone(), self.address()?.to_string(), &network_config) - .expect("Expecting valid tx"); - let keypair: Keypair = recovery.try_into().unwrap(); - let preimage: Preimage = recovery.try_into().unwrap(); - - let signed_tx = tx.sign_claim(&keypair, &preimage, 1_000).unwrap(); - let txid = tx.broadcast(signed_tx, &network_config).unwrap(); - - debug!("Funds recovered successfully! Txid: {txid}"); - - Ok(txid) - } - /// Empties all Liquid Wallet caches for this network type. pub fn empty_wallet_cache(&self) -> Result<()> { let mut path = PathBuf::from(self.data_dir_path.clone()); diff --git a/packages/dart/lib/src/bindings.dart b/packages/dart/lib/src/bindings.dart index a8fc542..3bff09a 100644 --- a/packages/dart/lib/src/bindings.dart +++ b/packages/dart/lib/src/bindings.dart @@ -35,10 +35,6 @@ Future> listPayments( RustLib.instance.api.listPayments( withScan: withScan, includePending: includePending, hint: hint); -Future recoverFunds( - {required LBtcReverseRecovery recovery, dynamic hint}) => - RustLib.instance.api.recoverFunds(recovery: recovery, hint: hint); - Future emptyWalletCache({dynamic hint}) => RustLib.instance.api.emptyWalletCache(hint: hint); @@ -46,22 +42,3 @@ Future backup({dynamic hint}) => RustLib.instance.api.backup(hint: hint); Future restore({required RestoreRequest req, dynamic hint}) => RustLib.instance.api.restore(req: req, hint: hint); - -// Rust type: RustOpaqueNom> -@sealed -class LBtcReverseRecovery extends RustOpaque { - LBtcReverseRecovery.dcoDecode(List wire) - : super.dcoDecode(wire, _kStaticData); - - LBtcReverseRecovery.sseDecode(int ptr, int externalSizeOnNative) - : super.sseDecode(ptr, externalSizeOnNative, _kStaticData); - - static final _kStaticData = RustArcStaticData( - rustArcIncrementStrongCount: RustLib - .instance.api.rust_arc_increment_strong_count_LBtcReverseRecovery, - rustArcDecrementStrongCount: RustLib - .instance.api.rust_arc_decrement_strong_count_LBtcReverseRecovery, - rustArcDecrementStrongCountPtr: RustLib - .instance.api.rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr, - ); -} diff --git a/packages/dart/lib/src/error.dart b/packages/dart/lib/src/error.dart index ba7cf14..31537c4 100644 --- a/packages/dart/lib/src/error.dart +++ b/packages/dart/lib/src/error.dart @@ -18,6 +18,10 @@ sealed class PaymentError with _$PaymentError implements FrbException { const factory PaymentError.insufficientFunds() = PaymentError_InsufficientFunds; const factory PaymentError.alreadyClaimed() = PaymentError_AlreadyClaimed; + const factory PaymentError.refunded({ + required String err, + required String txid, + }) = PaymentError_Refunded; const factory PaymentError.generic({ required String err, }) = PaymentError_Generic; diff --git a/packages/dart/lib/src/error.freezed.dart b/packages/dart/lib/src/error.freezed.dart index 4aae1e4..0319ec9 100644 --- a/packages/dart/lib/src/error.freezed.dart +++ b/packages/dart/lib/src/error.freezed.dart @@ -221,6 +221,92 @@ abstract class PaymentError_AlreadyClaimed extends PaymentError { const PaymentError_AlreadyClaimed._() : super._(); } +/// @nodoc +abstract class _$$PaymentError_RefundedImplCopyWith<$Res> { + factory _$$PaymentError_RefundedImplCopyWith( + _$PaymentError_RefundedImpl value, + $Res Function(_$PaymentError_RefundedImpl) then) = + __$$PaymentError_RefundedImplCopyWithImpl<$Res>; + @useResult + $Res call({String err, String txid}); +} + +/// @nodoc +class __$$PaymentError_RefundedImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_RefundedImpl> + implements _$$PaymentError_RefundedImplCopyWith<$Res> { + __$$PaymentError_RefundedImplCopyWithImpl(_$PaymentError_RefundedImpl _value, + $Res Function(_$PaymentError_RefundedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? err = null, + Object? txid = null, + }) { + return _then(_$PaymentError_RefundedImpl( + err: null == err + ? _value.err + : err // ignore: cast_nullable_to_non_nullable + as String, + txid: null == txid + ? _value.txid + : txid // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$PaymentError_RefundedImpl extends PaymentError_Refunded { + const _$PaymentError_RefundedImpl({required this.err, required this.txid}) + : super._(); + + @override + final String err; + @override + final String txid; + + @override + String toString() { + return 'PaymentError.refunded(err: $err, txid: $txid)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PaymentError_RefundedImpl && + (identical(other.err, err) || other.err == err) && + (identical(other.txid, txid) || other.txid == txid)); + } + + @override + int get hashCode => Object.hash(runtimeType, err, txid); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> + get copyWith => __$$PaymentError_RefundedImplCopyWithImpl< + _$PaymentError_RefundedImpl>(this, _$identity); +} + +abstract class PaymentError_Refunded extends PaymentError { + const factory PaymentError_Refunded( + {required final String err, + required final String txid}) = _$PaymentError_RefundedImpl; + const PaymentError_Refunded._() : super._(); + + String get err; + String get txid; + @JsonKey(ignore: true) + _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> + get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$PaymentError_GenericImplCopyWith<$Res> { factory _$$PaymentError_GenericImplCopyWith(_$PaymentError_GenericImpl value, diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 01f0cdf..cf0ad3e 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -56,7 +56,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.0.0-dev.33'; @override - int get rustContentHash => -1225779344; + int get rustContentHash => -451265040; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -87,22 +87,10 @@ abstract class RustLibApi extends BaseApi { Future receivePayment( {required PrepareReceiveResponse req, dynamic hint}); - Future recoverFunds( - {required LBtcReverseRecovery recovery, dynamic hint}); - Future restore({required RestoreRequest req, dynamic hint}); Future sendPayment( {required PrepareSendResponse req, dynamic hint}); - - RustArcIncrementStrongCountFnType - get rust_arc_increment_strong_count_LBtcReverseRecovery; - - RustArcDecrementStrongCountFnType - get rust_arc_decrement_strong_count_LBtcReverseRecovery; - - CrossPlatformFinalizerArg - get rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr; } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -300,32 +288,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["req"], ); - @override - Future recoverFunds( - {required LBtcReverseRecovery recovery, dynamic hint}) { - return handler.executeNormal(NormalTask( - callFfi: (port_) { - var arg0 = - cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - recovery); - return wire.wire_recover_funds(port_, arg0); - }, - codec: DcoCodec( - decodeSuccessData: dco_decode_String, - decodeErrorData: dco_decode_AnyhowException, - ), - constMeta: kRecoverFundsConstMeta, - argValues: [recovery], - apiImpl: this, - hint: hint, - )); - } - - TaskConstMeta get kRecoverFundsConstMeta => const TaskConstMeta( - debugName: "recover_funds", - argNames: ["recovery"], - ); - @override Future restore({required RestoreRequest req, dynamic hint}) { return handler.executeNormal(NormalTask( @@ -373,36 +335,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["req"], ); - RustArcIncrementStrongCountFnType - get rust_arc_increment_strong_count_LBtcReverseRecovery => wire - .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery; - - RustArcDecrementStrongCountFnType - get rust_arc_decrement_strong_count_LBtcReverseRecovery => wire - .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return AnyhowException(raw as String); } - @protected - LBtcReverseRecovery - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return LBtcReverseRecovery.dcoDecode(raw as List); - } - - @protected - LBtcReverseRecovery - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return LBtcReverseRecovery.dcoDecode(raw as List); - } - @protected String dco_decode_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -579,26 +517,31 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 3: return PaymentError_AlreadyClaimed(); case 4: + return PaymentError_Refunded( + err: dco_decode_String(raw[1]), + txid: dco_decode_String(raw[2]), + ); + case 5: return PaymentError_Generic( err: dco_decode_String(raw[1]), ); - case 5: - return PaymentError_InvalidInvoice(); case 6: - return PaymentError_InvalidPreimage(); + return PaymentError_InvalidInvoice(); case 7: + return PaymentError_InvalidPreimage(); + case 8: return PaymentError_LwkError( err: dco_decode_String(raw[1]), ); - case 8: - return PaymentError_PairsNotFound(); case 9: - return PaymentError_PersistError(); + return PaymentError_PairsNotFound(); case 10: + return PaymentError_PersistError(); + case 11: return PaymentError_SendError( err: dco_decode_String(raw[1]), ); - case 11: + case 12: return PaymentError_SignerError( err: dco_decode_String(raw[1]), ); @@ -717,12 +660,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return; } - @protected - int dco_decode_usize(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return dcoDecodeI64OrU64(raw); - } - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -730,24 +667,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return AnyhowException(inner); } - @protected - LBtcReverseRecovery - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return LBtcReverseRecovery.sseDecode( - sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - - @protected - LBtcReverseRecovery - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return LBtcReverseRecovery.sseDecode( - sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - @protected String sse_decode_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -946,22 +865,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return PaymentError_AlreadyClaimed(); case 4: var var_err = sse_decode_String(deserializer); - return PaymentError_Generic(err: var_err); + var var_txid = sse_decode_String(deserializer); + return PaymentError_Refunded(err: var_err, txid: var_txid); case 5: - return PaymentError_InvalidInvoice(); + var var_err = sse_decode_String(deserializer); + return PaymentError_Generic(err: var_err); case 6: - return PaymentError_InvalidPreimage(); + return PaymentError_InvalidInvoice(); case 7: + return PaymentError_InvalidPreimage(); + case 8: var var_err = sse_decode_String(deserializer); return PaymentError_LwkError(err: var_err); - case 8: - return PaymentError_PairsNotFound(); case 9: - return PaymentError_PersistError(); + return PaymentError_PairsNotFound(); case 10: + return PaymentError_PersistError(); + case 11: var var_err = sse_decode_String(deserializer); return PaymentError_SendError(err: var_err); - case 11: + case 12: var var_err = sse_decode_String(deserializer); return PaymentError_SignerError(err: var_err); default: @@ -1058,28 +981,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs } - @protected - int sse_decode_usize(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getUint64(); - } - - @protected - int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw) { - // Codec=Cst (C-struct based), see doc to use other codecs -// ignore: invalid_use_of_internal_member - return raw.cstEncode(move: true); - } - - @protected - int cst_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw) { - // Codec=Cst (C-struct based), see doc to use other codecs -// ignore: invalid_use_of_internal_member - return raw.cstEncode(); - } - @protected bool cst_encode_bool(bool raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1122,12 +1023,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw; } - @protected - int cst_encode_usize(int raw) { - // Codec=Cst (C-struct based), see doc to use other codecs - return raw; - } - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer) { @@ -1135,22 +1030,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError('Unreachable ()'); } - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize(self.sseEncode(move: true), serializer); - } - - @protected - void - sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize(self.sseEncode(move: null), serializer); - } - @protected void sse_encode_String(String self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1330,25 +1209,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_i_32(2, serializer); case PaymentError_AlreadyClaimed(): sse_encode_i_32(3, serializer); - case PaymentError_Generic(err: final err): + case PaymentError_Refunded(err: final err, txid: final txid): sse_encode_i_32(4, serializer); sse_encode_String(err, serializer); - case PaymentError_InvalidInvoice(): + sse_encode_String(txid, serializer); + case PaymentError_Generic(err: final err): sse_encode_i_32(5, serializer); - case PaymentError_InvalidPreimage(): + sse_encode_String(err, serializer); + case PaymentError_InvalidInvoice(): sse_encode_i_32(6, serializer); - case PaymentError_LwkError(err: final err): + case PaymentError_InvalidPreimage(): sse_encode_i_32(7, serializer); + case PaymentError_LwkError(err: final err): + sse_encode_i_32(8, serializer); sse_encode_String(err, serializer); case PaymentError_PairsNotFound(): - sse_encode_i_32(8, serializer); - case PaymentError_PersistError(): sse_encode_i_32(9, serializer); - case PaymentError_SendError(err: final err): + case PaymentError_PersistError(): sse_encode_i_32(10, serializer); + case PaymentError_SendError(err: final err): + sse_encode_i_32(11, serializer); sse_encode_String(err, serializer); case PaymentError_SignerError(err: final err): - sse_encode_i_32(11, serializer); + sse_encode_i_32(12, serializer); sse_encode_String(err, serializer); } } @@ -1433,10 +1316,4 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs } - - @protected - void sse_encode_usize(int self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putUint64(self); - } } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 4281a16..15f2e78 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -20,23 +20,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); - CrossPlatformFinalizerArg - get rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr => wire - ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw); - @protected - LBtcReverseRecovery - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw); - - @protected - LBtcReverseRecovery - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw); - @protected String dco_decode_String(dynamic raw); @@ -144,22 +130,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void dco_decode_unit(dynamic raw); - @protected - int dco_decode_usize(dynamic raw); - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); - @protected - LBtcReverseRecovery - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer); - - @protected - LBtcReverseRecovery - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer); - @protected String sse_decode_String(SseDeserializer deserializer); @@ -278,9 +251,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_decode_unit(SseDeserializer deserializer); - @protected - int sse_decode_usize(SseDeserializer deserializer); - @protected ffi.Pointer cst_encode_AnyhowException( AnyhowException raw) { @@ -512,43 +482,51 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.tag = 3; return; } + if (apiObj is PaymentError_Refunded) { + var pre_err = cst_encode_String(apiObj.err); + var pre_txid = cst_encode_String(apiObj.txid); + wireObj.tag = 4; + wireObj.kind.Refunded.err = pre_err; + wireObj.kind.Refunded.txid = pre_txid; + return; + } if (apiObj is PaymentError_Generic) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 4; + wireObj.tag = 5; wireObj.kind.Generic.err = pre_err; return; } if (apiObj is PaymentError_InvalidInvoice) { - wireObj.tag = 5; + wireObj.tag = 6; return; } if (apiObj is PaymentError_InvalidPreimage) { - wireObj.tag = 6; + wireObj.tag = 7; return; } if (apiObj is PaymentError_LwkError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 7; + wireObj.tag = 8; wireObj.kind.LwkError.err = pre_err; return; } if (apiObj is PaymentError_PairsNotFound) { - wireObj.tag = 8; + wireObj.tag = 9; return; } if (apiObj is PaymentError_PersistError) { - wireObj.tag = 9; + wireObj.tag = 10; return; } if (apiObj is PaymentError_SendError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 10; + wireObj.tag = 11; wireObj.kind.SendError.err = pre_err; return; } if (apiObj is PaymentError_SignerError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 11; + wireObj.tag = 12; wireObj.kind.SignerError.err = pre_err; return; } @@ -601,14 +579,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.txid = cst_encode_String(apiObj.txid); } - @protected - int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw); - - @protected - int cst_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw); - @protected bool cst_encode_bool(bool raw); @@ -630,23 +600,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_encode_unit(void raw); - @protected - int cst_encode_usize(int raw); - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer); - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer); - - @protected - void - sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer); - @protected void sse_encode_String(String self, SseSerializer serializer); @@ -769,9 +726,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_unit(void self, SseSerializer serializer); - - @protected - void sse_encode_usize(int self, SseSerializer serializer); } // Section: wire_class @@ -802,7 +756,7 @@ class RustLibWire implements BaseWire { : _lookup = lookup; void store_dart_post_cobject( - int ptr, + DartPostCObjectFnType ptr, ) { return _store_dart_post_cobject( ptr, @@ -810,10 +764,10 @@ class RustLibWire implements BaseWire { } late final _store_dart_post_cobjectPtr = - _lookup>( + _lookup>( 'store_dart_post_cobject'); - late final _store_dart_post_cobject = - _store_dart_post_cobjectPtr.asFunction(); + late final _store_dart_post_cobject = _store_dart_post_cobjectPtr + .asFunction(); void wire_backup( int port_, @@ -880,8 +834,8 @@ class RustLibWire implements BaseWire { void wire_list_payments( int port_, - ffi.Pointer with_scan, - ffi.Pointer include_pending, + bool with_scan, + bool include_pending, ) { return _wire_list_payments( port_, @@ -891,11 +845,10 @@ class RustLibWire implements BaseWire { } late final _wire_list_paymentsPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Int64, ffi.Pointer, - ffi.Pointer)>>('frbgen_breez_liquid_wire_list_payments'); - late final _wire_list_payments = _wire_list_paymentsPtr - .asFunction, ffi.Pointer)>(); + ffi.NativeFunction>( + 'frbgen_breez_liquid_wire_list_payments'); + late final _wire_list_payments = + _wire_list_paymentsPtr.asFunction(); void wire_prepare_receive_payment( int port_, @@ -953,22 +906,6 @@ class RustLibWire implements BaseWire { late final _wire_receive_payment = _wire_receive_paymentPtr.asFunction< void Function(int, ffi.Pointer)>(); - void wire_recover_funds( - int port_, - int recovery, - ) { - return _wire_recover_funds( - port_, - recovery, - ); - } - - late final _wire_recover_fundsPtr = - _lookup>( - 'frbgen_breez_liquid_wire_recover_funds'); - late final _wire_recover_funds = - _wire_recover_fundsPtr.asFunction(); - void wire_restore( int port_, ffi.Pointer req, @@ -1005,38 +942,6 @@ class RustLibWire implements BaseWire { late final _wire_send_payment = _wire_send_paymentPtr.asFunction< void Function(int, ffi.Pointer)>(); - void - rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - - void - rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - ffi.Pointer cst_new_box_autoadd_connect_request() { return _cst_new_box_autoadd_connect_request(); } @@ -1193,6 +1098,11 @@ class RustLibWire implements BaseWire { _dummy_method_to_enforce_bundlingPtr.asFunction(); } +typedef DartPostCObjectFnType = ffi.Pointer< + ffi.NativeFunction< + ffi.Bool Function(DartPort port_id, ffi.Pointer message)>>; +typedef DartPort = ffi.Int64; + final class wire_cst_list_prim_u_8_strict extends ffi.Struct { external ffi.Pointer ptr; @@ -1210,11 +1120,10 @@ final class wire_cst_connect_request extends ffi.Struct { } final class wire_cst_get_info_request extends ffi.Struct { + @ffi.Bool() external bool with_scan; } -typedef bool = ffi.NativeFunction)>; - final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Uint64() external int payer_amount_sat; @@ -1273,6 +1182,12 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer pubkey; } +final class wire_cst_PaymentError_Refunded extends ffi.Struct { + external ffi.Pointer err; + + external ffi.Pointer txid; +} + final class wire_cst_PaymentError_Generic extends ffi.Struct { external ffi.Pointer err; } @@ -1290,6 +1205,8 @@ final class wire_cst_PaymentError_SignerError extends ffi.Struct { } final class PaymentErrorKind extends ffi.Union { + external wire_cst_PaymentError_Refunded Refunded; + external wire_cst_PaymentError_Generic Generic; external wire_cst_PaymentError_LwkError LwkError; @@ -1317,5 +1234,3 @@ final class wire_cst_send_payment_response extends ffi.Struct { } const double LIQUID_CLAIM_TX_FEERATE = 0.1; - -const int LIQUID_MIN_CLAIM_ABSOLUTE_FEES = 134; diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index 1b03db7..1b8c695 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -28,7 +28,7 @@ class FlutterBreezLiquidBindings { : _lookup = lookup; void store_dart_post_cobject( - int ptr, + DartPostCObjectFnType ptr, ) { return _store_dart_post_cobject( ptr, @@ -36,10 +36,10 @@ class FlutterBreezLiquidBindings { } late final _store_dart_post_cobjectPtr = - _lookup>( + _lookup>( 'store_dart_post_cobject'); - late final _store_dart_post_cobject = - _store_dart_post_cobjectPtr.asFunction(); + late final _store_dart_post_cobject = _store_dart_post_cobjectPtr + .asFunction(); void frbgen_breez_liquid_wire_backup( int port_, @@ -110,8 +110,8 @@ class FlutterBreezLiquidBindings { void frbgen_breez_liquid_wire_list_payments( int port_, - ffi.Pointer with_scan, - ffi.Pointer include_pending, + bool with_scan, + bool include_pending, ) { return _frbgen_breez_liquid_wire_list_payments( port_, @@ -121,12 +121,11 @@ class FlutterBreezLiquidBindings { } late final _frbgen_breez_liquid_wire_list_paymentsPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Int64, ffi.Pointer, - ffi.Pointer)>>('frbgen_breez_liquid_wire_list_payments'); + ffi.NativeFunction>( + 'frbgen_breez_liquid_wire_list_payments'); late final _frbgen_breez_liquid_wire_list_payments = - _frbgen_breez_liquid_wire_list_paymentsPtr.asFunction< - void Function(int, ffi.Pointer, ffi.Pointer)>(); + _frbgen_breez_liquid_wire_list_paymentsPtr + .asFunction(); void frbgen_breez_liquid_wire_prepare_receive_payment( int port_, @@ -185,23 +184,6 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_wire_receive_paymentPtr.asFunction< void Function(int, ffi.Pointer)>(); - void frbgen_breez_liquid_wire_recover_funds( - int port_, - int recovery, - ) { - return _frbgen_breez_liquid_wire_recover_funds( - port_, - recovery, - ); - } - - late final _frbgen_breez_liquid_wire_recover_fundsPtr = - _lookup>( - 'frbgen_breez_liquid_wire_recover_funds'); - late final _frbgen_breez_liquid_wire_recover_funds = - _frbgen_breez_liquid_wire_recover_fundsPtr - .asFunction(); - void frbgen_breez_liquid_wire_restore( int port_, ffi.Pointer req, @@ -240,38 +222,6 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_wire_send_paymentPtr.asFunction< void Function(int, ffi.Pointer)>(); - void - frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - - void - frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_connect_request() { return _frbgen_breez_liquid_cst_new_box_autoadd_connect_request(); @@ -455,6 +405,15 @@ final class WireSyncRust2DartSse extends ffi.Struct { external int len; } +typedef DartPostCObjectFnType + = ffi.Pointer>; +typedef DartPostCObjectFnTypeFunction = ffi.Bool Function( + DartPort port_id, ffi.Pointer message); +typedef DartDartPostCObjectFnTypeFunction = bool Function( + DartDartPort port_id, ffi.Pointer message); +typedef DartPort = ffi.Int64; +typedef DartDartPort = int; + final class _Dart_Handle extends ffi.Opaque {} final class wire_cst_list_prim_u_8_strict extends ffi.Struct { @@ -474,11 +433,10 @@ final class wire_cst_connect_request extends ffi.Struct { } final class wire_cst_get_info_request extends ffi.Struct { + @ffi.Bool() external bool with_scan; } -typedef bool = ffi.NativeFunction)>; - final class wire_cst_prepare_receive_request extends ffi.Struct { @ffi.Uint64() external int payer_amount_sat; @@ -537,6 +495,12 @@ final class wire_cst_get_info_response extends ffi.Struct { external ffi.Pointer pubkey; } +final class wire_cst_PaymentError_Refunded extends ffi.Struct { + external ffi.Pointer err; + + external ffi.Pointer txid; +} + final class wire_cst_PaymentError_Generic extends ffi.Struct { external ffi.Pointer err; } @@ -554,6 +518,8 @@ final class wire_cst_PaymentError_SignerError extends ffi.Struct { } final class PaymentErrorKind extends ffi.Union { + external wire_cst_PaymentError_Refunded Refunded; + external wire_cst_PaymentError_Generic Generic; external wire_cst_PaymentError_LwkError LwkError; @@ -581,5 +547,3 @@ final class wire_cst_send_payment_response extends ffi.Struct { } const double LIQUID_CLAIM_TX_FEERATE = 0.1; - -const int LIQUID_MIN_CLAIM_ABSOLUTE_FEES = 134; From a92e83a446401b06418f62078ac9209d33ab38ab Mon Sep 17 00:00:00 2001 From: yse Date: Mon, 13 May 2024 14:26:16 +0200 Subject: [PATCH 17/29] feat: improve send and refund --- cli/Cargo.lock | 1 + lib/Cargo.lock | 1 + lib/core/Cargo.toml | 2 +- lib/core/src/boltz_status_stream.rs | 34 ++- lib/core/src/model.rs | 1 + lib/core/src/persist/migrations.rs | 1 + lib/core/src/persist/swap_in.rs | 8 +- lib/core/src/sdk.rs | 318 +++++++++++++++++----------- lib/core/src/utils.rs | 2 +- 9 files changed, 235 insertions(+), 133 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 7fe6ab3..f028ebc 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=fac7573028cd46de5b1097916d43973d7e835460#fac7573028cd46de5b1097916d43973d7e835460" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index a60a6a0..ae23212 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,6 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=fac7573028cd46de5b1097916d43973d7e835460#fac7573028cd46de5b1097916d43973d7e835460" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index 7e12e28..76b363c 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -18,7 +18,7 @@ bip39 = { version = "2.0.0", features = ["serde"] } # - latest fixes from Boltz (fallback for get_utxos) needed for claiming as soon as lockup tx is seen in mempool (receive case) # - latest fixes from Antonio (get reverse swap pairs, necessary structs, etc) # https://github.com/ok300/boltz-rust/commits/ok300-combo-boltz-fallback-get-utxos-yse-get-submarine-pairs/ -boltz-client = { git = "https://github.com/ok300/boltz-rust", rev = "0ec36d8af79db0b4e28aaeed6a66c18db9205c49" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "fac7573028cd46de5b1097916d43973d7e835460" } flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" diff --git a/lib/core/src/boltz_status_stream.rs b/lib/core/src/boltz_status_stream.rs index 7446826..9e97a81 100644 --- a/lib/core/src/boltz_status_stream.rs +++ b/lib/core/src/boltz_status_stream.rs @@ -10,6 +10,7 @@ use boltz_client::swaps::{ boltz::{RevSwapStates, SubSwapStates}, boltzv2::{Subscription, SwapUpdate}, }; +use boltz_client::SwapType; use log::{error, info, warn}; use tungstenite::stream::MaybeTlsStream; use tungstenite::{Message, WebSocket}; @@ -19,13 +20,33 @@ use crate::sdk::LiquidSdk; pub(super) struct BoltzStatusStream { // socket: WebSocket>, + swap_in_ids: Arc>>, + swap_out_ids: Arc>>, } impl BoltzStatusStream { - pub(super) fn track_pending_swaps(sdk: Arc) -> Result<()> { - // Track subscribed swap IDs - let swap_in_ids = Arc::new(Mutex::new(HashSet::new())); - let swap_out_ids = Arc::new(Mutex::new(HashSet::new())); + pub(super) fn new() -> Self { + BoltzStatusStream { + swap_in_ids: Default::default(), + swap_out_ids: Default::default(), + } + } + pub(super) fn insert_tracked_swap(&self, id: &str, swap_type: SwapType) { + match swap_type { + SwapType::Submarine => self.swap_in_ids.lock().unwrap().insert(id.to_string()), + SwapType::ReverseSubmarine => self.swap_out_ids.lock().unwrap().insert(id.to_string()), + }; + } + + pub(super) fn resolve_tracked_swap(&self, id: &str, swap_type: SwapType) { + match swap_type { + SwapType::Submarine => self.swap_in_ids.lock().unwrap().remove(id), + SwapType::ReverseSubmarine => self.swap_out_ids.lock().unwrap().remove(id), + }; + } + + pub(super) fn track_pending_swaps(&self, sdk: Arc) -> Result<()> { + // Track subscribed swap IDs let mut socket = sdk .boltz_client_v2() .connect_ws() @@ -40,6 +61,9 @@ impl BoltzStatusStream { _ => Err(anyhow!("Unsupported stream type"))?, }; + let swap_in_ids = self.swap_in_ids.clone(); + let swap_out_ids = self.swap_out_ids.clone(); + thread::spawn(move || loop { let maybe_subscribe_fn = |ongoing_swap: &OngoingSwap, socket: &mut WebSocket>| { @@ -134,7 +158,7 @@ impl BoltzStatusStream { new_state, &update_swap_id, ); - info!("OngoingSwapIn / Send try_handle_submarine_swap_status res: {res:?}"); + info!("ongoingswapin / send try_handle_submarine_swap_status res: {res:?}"); } Err(_) => error!("Invalid state for submarine swap {update_swap_id}: {update_state_str}") } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index ea92596..3f46231 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -147,6 +147,7 @@ pub(crate) struct OngoingSwapIn { pub(crate) id: String, pub(crate) invoice: String, pub(crate) payer_amount_sat: u64, + pub(crate) swap_response: String, pub(crate) txid: Option, } diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index ea8d52b..541054f 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -14,6 +14,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { id TEXT NOT NULL PRIMARY KEY, invoice TEXT NOT NULL, payer_amount_sat INTEGER NOT NULL, + swap_response TEXT NOT NULL, txid TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) STRICT;", diff --git a/lib/core/src/persist/swap_in.rs b/lib/core/src/persist/swap_in.rs index e4a5645..2a8fceb 100644 --- a/lib/core/src/persist/swap_in.rs +++ b/lib/core/src/persist/swap_in.rs @@ -14,14 +14,16 @@ impl Persister { id, invoice, payer_amount_sat, + swap_response, txid ) - VALUES (?, ?, ?, ?)", + VALUES (?, ?, ?, ?, ?)", )?; _ = stmt.execute(( swap_in.id, swap_in.invoice, swap_in.payer_amount_sat, + swap_in.swap_response, swap_in.txid, ))?; @@ -41,6 +43,7 @@ impl Persister { id, invoice, payer_amount_sat, + swap_response, txid, created_at FROM ongoing_send_swaps @@ -64,7 +67,8 @@ impl Persister { id: row.get(0)?, invoice: row.get(1)?, payer_amount_sat: row.get(2)?, - txid: row.get(3)?, + swap_response: row.get(3)?, + txid: row.get(4)?, }) } diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index d9fbf79..e0dca74 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -3,12 +3,11 @@ use std::{ path::PathBuf, str::FromStr, sync::{Arc, Mutex}, - thread, + thread::sleep, time::Duration, }; use anyhow::{anyhow, Result}; -use bip39::rand::Rng; use boltz_client::{ network::electrum::ElectrumConfig, swaps::{ @@ -17,7 +16,7 @@ use boltz_client::{ liquidv2::LBtcSwapTxV2, }, util::secrets::{LiquidSwapKey, Preimage, SwapKey}, - Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, + Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, SwapType, }; use elements::hashes::hex::DisplayHex; use log::{debug, info, warn}; @@ -49,6 +48,7 @@ pub struct LiquidSdk { lwk_signer: SwSigner, active_address: Option, persister: Persister, + status_stream: BoltzStatusStream, data_dir_path: String, } @@ -85,6 +85,8 @@ impl LiquidSdk { let persister = Persister::new(&data_dir_path, network)?; persister.init()?; + let status_stream = BoltzStatusStream::new(); + let sdk = Arc::new(LiquidSdk { lwk_wollet, network, @@ -93,10 +95,10 @@ impl LiquidSdk { active_address: None, persister, data_dir_path, + status_stream, }); - // LiquidSdk::track_pending_swaps(&sdk)?; - BoltzStatusStream::track_pending_swaps(sdk.clone())?; + sdk.status_stream.track_pending_swaps(sdk.clone())?; Ok(sdk) } @@ -113,6 +115,23 @@ impl LiquidSdk { Ok(descriptor_str.parse()?) } + fn get_keys(&self, derivation_index: i8) -> Result { + let mnemonic = self + .lwk_signer + .mnemonic() + .ok_or(PaymentError::SignerError { + err: "Could not claim: Mnemonic not found".to_string(), + })?; + let swap_key = SwapKey::from_submarine_account( + &mnemonic.to_string(), + "", + self.network.into(), + derivation_index as u64, + )?; + let lsk = LiquidSwapKey::try_from(swap_key)?; + Ok(lsk.keypair) + } + pub(crate) fn try_handle_reverse_swap_status( &self, swap_state: RevSwapStates, @@ -170,19 +189,80 @@ impl LiquidSdk { let ongoing_swap_in = Persister::fetch_ongoing_swap_in(&con, id)? .ok_or(anyhow!("No ongoing swap in found for ID {id}"))?; - let Some(txid) = ongoing_swap_in.txid.clone() else { - return Err(anyhow!("Transaction not broadcast yet for swap {id}")); - }; + let payer_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); + let keypair = self.get_keys(0)?; + let swap_response: CreateSubmarineResponse = + serde_json::from_str(&ongoing_swap_in.swap_response)?; match swap_state { - SubSwapStates::TransactionClaimed - | SubSwapStates::InvoiceFailedToPay - | SubSwapStates::SwapExpired => { - warn!("Cannot positively resolve swap {id}, unrecoverable state: {swap_state:?}"); + // SubSwapStates::InvoiceSet => { + // self.lockup_funds( + // id, + // &swap_response + // )?; + // Ok(()) + // } + SubSwapStates::TransactionClaimPending => { + let Ok(swap_script) = LBtcSwapScriptV2::submarine_from_swap_resp( + &swap_response, + keypair.public_key().into(), + ) else { + self.persister + .resolve_ongoing_swap(id, None) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + + return Err(anyhow!("Could not rebuild refund details for swap-in {id}")); + }; + + self.post_subarmine_claim_details( + id, + &swap_script, + &ongoing_swap_in.invoice, + &keypair, + ) + .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; + + Ok(()) + } + SubSwapStates::TransactionClaimed => { + warn!("Swap-in {id} has already been claimed. Resolving..."); + + let Some(txid) = ongoing_swap_in.txid.clone() else { + return Err(anyhow!( + "Swap-in {id} has been claimed but no txid is present" + )); + }; - let payer_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); self.persister .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + + warn!("Swap-in {id} resolved successfully"); + Ok(()) + } + SubSwapStates::TransactionLockupFailed + | SubSwapStates::InvoiceFailedToPay + | SubSwapStates::SwapExpired => { + warn!("Swap-in {id} is in an unrecoverable state: {swap_state:?}"); + + // If swap state is unrecoverable, try refunding + let Ok(swap_script) = LBtcSwapScriptV2::submarine_from_swap_resp( + &swap_response, + keypair.public_key().into(), + ) else { + self.persister + .resolve_ongoing_swap(id, None) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + + return Err(anyhow!("Could not rebuild refund details for swap-in {id}")); + }; + + let refund_txid = self.try_refund(id, &swap_script, &keypair, 133)?; + + warn!("Swap-in {id} refunded successfully. Txid: {refund_txid}"); + + self.persister + .resolve_ongoing_swap(id, None) .map_err(|_| anyhow!("Could not resolve swap {id} in database")) } _ => Err(anyhow!("New state for submarine swap {id}: {swap_state:?}")), @@ -381,13 +461,11 @@ impl LiquidSdk { }) } - fn broadcast_lowball( - &self, - client: &BoltzApiClientV2, - tx: &Transaction, - ) -> Result { + fn broadcast_boltz(&self, tx: &Transaction) -> Result { let tx_hex = elements::encode::serialize(tx).to_lower_hex_string(); - let response = client.broadcast_tx(self.network.into(), &tx_hex)?; + let response = self + .boltz_client_v2() + .broadcast_tx(self.network.into(), &tx_hex)?; Ok(response .as_object() .ok_or(PaymentError::Generic { @@ -407,17 +485,17 @@ impl LiquidSdk { fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> { let preimage = Preimage::from_str(preimage)?; let preimage_hash = preimage.sha256.to_string(); - let invoice = - Bolt11Invoice::from_str(&invoice).map_err(|_| PaymentError::InvalidInvoice)?; + let invoice = Bolt11Invoice::from_str(invoice).map_err(|_| PaymentError::InvalidInvoice)?; let invoice_payment_hash = invoice.payment_hash(); - (invoice_payment_hash.to_string() == preimage_hash.to_string()) - .then(|| ()) + (invoice_payment_hash.to_string() == preimage_hash) + .then_some(()) .ok_or(PaymentError::InvalidPreimage) } - fn new_swap_tx(&self, swap_script: &LBtcSwapScriptV2) -> Result { - let output_address = self.address()?.to_string(); + fn new_refund_tx(&self, swap_script: &LBtcSwapScriptV2) -> Result { + let wallet = self.lwk_wollet.lock().unwrap(); + let output_address = wallet.address(Some(0))?.address().to_string(); let network_config = self.network_config(); Ok(LBtcSwapTxV2::new_refund( swap_script.clone(), @@ -433,7 +511,7 @@ impl LiquidSdk { keypair: &Keypair, fees_sat: u64, ) -> Result { - let swap_tx = self.new_swap_tx(swap_script)?; + let swap_tx = self.new_refund_tx(swap_script)?; let client = self.boltz_client_v2(); let is_lowball = Some((&client, boltz_client::network::Chain::from(self.network))); @@ -451,7 +529,6 @@ impl LiquidSdk { // Try with non-cooperative refund Err(e) => { debug!("Cooperative refund failed: {:?}", e); - let tx = swap_tx.sign_refund(keypair, Amount::from_sat(fees_sat), None)?; let txid = swap_tx.broadcast(&tx, &self.network_config(), is_lowball)?; debug!("Successfully broadcast non-cooperative refund for swap-in {swap_id}"); @@ -460,66 +537,51 @@ impl LiquidSdk { } } - fn get_keys(&self, derivation_index: i16) -> Result { - let mnemonic = self - .lwk_signer - .mnemonic() - .ok_or(PaymentError::SignerError { - err: "Could not claim: Mnemonic not found".to_string(), - })?; - let swap_key = SwapKey::from_submarine_account( - &mnemonic.to_string(), - "", - self.network.into(), - derivation_index as u64, - )?; - let lsk = LiquidSwapKey::try_from(swap_key)?; - Ok(lsk.keypair) + fn post_subarmine_claim_details( + &self, + swap_id: &str, + swap_script: &LBtcSwapScriptV2, + invoice: &str, + keypair: &Keypair, + ) -> Result<(), PaymentError> { + debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); + let client = self.boltz_client_v2(); + let swap_tx = self.new_refund_tx(&swap_script)?; + + let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?; + + debug!("Received claim tx details: {:?}", &claim_tx_response); + + Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; + + let (partial_sig, pub_nonce) = + swap_tx.submarine_partial_sig(&keypair, &claim_tx_response)?; + + client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; + debug!("Successfully sent claim details for swap-in {swap_id}"); + Ok(()) } - fn try_lockup( + fn lockup_funds( &self, - invoice: &str, + swap_id: &str, create_response: &CreateSubmarineResponse, - swap_script: &LBtcSwapScriptV2, - keypair: &Keypair, ) -> Result { - let client = self.boltz_client_v2(); - let swap_id = &create_response.id; + debug!( + "Initiated swap-in: send {} sats to liquid address {}", + create_response.expected_amount, create_response.address + ); - // First try using cooperative submarine - { - let swap_tx = self.new_swap_tx(swap_script)?; + let tx = self.build_tx( + None, + &create_response.address, + // TODO change testing + create_response.expected_amount, + )?; - let claim_tx_response = client.get_claim_tx_details(dbg!(&swap_id.to_string()))?; - - debug!("Received claim tx details: {:?}", &claim_tx_response); - - Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; - - let (partial_sig, pub_nonce) = - swap_tx.submarine_partial_sig(keypair, &claim_tx_response)?; - - client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; - Ok(String::new()) - } - // Then try non-cooperatively - .or_else(|_: PaymentError| { - let tx = self.build_tx( - None, - &create_response.address, - create_response.expected_amount, - )?; - - let txid = match self.network { - Network::Liquid => self.broadcast_lowball(&client, &tx)?, - Network::LiquidTestnet => { - let electrum_client = ElectrumClient::new(&self.electrum_url)?; - electrum_client.broadcast(&tx)?.to_string() - } - }; - Ok(txid) - }) + let txid = self.broadcast_boltz(&tx)?; + debug!("Successfully broadcast lockup transaction for swap-in {swap_id}. Txid: {txid}"); + Ok(txid) } pub fn send_payment( @@ -540,8 +602,7 @@ impl LiquidSdk { PaymentError::InvalidOrExpiredFees ); - let derivation_index: i16 = bip39::rand::thread_rng().gen(); - let keypair = self.get_keys(derivation_index)?; + let keypair = self.get_keys(0)?; let refund_public_key = boltz_client::PublicKey { compressed: true, inner: keypair.public_key(), @@ -555,6 +616,10 @@ impl LiquidSdk { // TODO: Add referral id referral_id: None, })?; + let create_response_str = + serde_json::to_string(&create_response).map_err(|_| PaymentError::Generic { + err: "Could not store swap response locally".to_string(), + })?; let swap_id = &create_response.id; let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( @@ -564,26 +629,31 @@ impl LiquidSdk { debug!("Opening WS connection for swap {}", &swap_id); - let mut socket = client.connect_ws().unwrap(); - let subscription = Subscription::new(&swap_id); + let mut socket = client.connect_ws()?; + let subscription = Subscription::new(swap_id); let subscribe_json = serde_json::to_string(&subscription) .map_err(|e| anyhow!("Failed to serialize subscription msg: {e:?}"))?; socket .send(tungstenite::Message::Text(subscribe_json)) .map_err(|e| anyhow!("Failed to subscribe to websocket updates: {e:?}"))?; + // We insert the pending send to avoid it being handled by the status stream + self.status_stream + .insert_tracked_swap(swap_id, SwapType::Submarine); + self.persister .insert_or_update_ongoing_swap_in(OngoingSwapIn { id: swap_id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, + swap_response: create_response_str, txid: None, })?; let result; let mut txid = String::new(); loop { - let data = match utils::get_swap_status_v2(&mut socket, &swap_id) { + let data = match utils::get_swap_status_v2(&mut socket, swap_id) { Ok(data) => data, Err(_) => { // TODO: close socket if dead, skip EOF errors @@ -601,25 +671,14 @@ impl LiquidSdk { match state { // Boltz has locked the HTLC, we proceed with locking up the funds SubSwapStates::InvoiceSet => { - debug!( - "Initiated swap-in: send {} sats to liquid address {}", - create_response.expected_amount, create_response.address - ); - - txid = self.try_lockup( - &req.invoice, - &create_response, - &swap_script, - &keypair - )?; - - debug!( - "Successfully broadcast lockup transaction for swap-in {}. Txid: {}", - &swap_id, &txid - ); + txid = self.lockup_funds(swap_id, &create_response)?; + } + // Boltz has broadcast the claim to the mempool, resolve with success + SubSwapStates::TransactionClaimed => { + debug!("Boltz successfully claimed the funds. Resolving swap-in {swap_id}"); self.persister.resolve_ongoing_swap( - &swap_id, + swap_id, Some(( txid.clone(), PaymentData { @@ -628,45 +687,56 @@ impl LiquidSdk { )), )?; - debug!( - "Successfully resolved ongoing swap-in {}", - &swap_id - ); - } - // Boltz has detected our lockup - SubSwapStates::TransactionMempool | SubSwapStates::TransactionConfirmed => {} - // Boltz has broadcast the claim to the mempool, resolve with success - SubSwapStates::TransactionClaimed => { + self.status_stream + .resolve_tracked_swap(swap_id, SwapType::ReverseSubmarine); + result = Ok(SendPaymentResponse { txid }); + + debug!("Successfully resolved swap-in {swap_id}"); break; } + + // Boltz has detected the lockup in the mempool, we can speed up + // the claim by doing so cooperatively + SubSwapStates::TransactionClaimPending => self.post_subarmine_claim_details( + swap_id, + &swap_script, + &req.invoice, + &keypair, + )?, + // Either: // 1. Boltz failed to pay // 2. The swap has expired (>24h) - // 3. TODO: Lockup failed (we sent too little funds) + // 3. Lockup failed (we sent too little funds) // We initiate a cooperative refund, and then fallback to a regular one - SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired - // | SubSwapStates::LockupFailed (needs addition to Boltz crate) - => { + SubSwapStates::InvoiceFailedToPay + | SubSwapStates::SwapExpired + | SubSwapStates::TransactionLockupFailed => { + // Wait for the lockup to be confirmed + sleep(Duration::from_secs(30)); + let txid = self.try_refund( - &swap_id, + swap_id, &swap_script, &keypair, // TODO Don't hardcode min broadcast fee - 133 + 133, )?; - result = Err(PaymentError::Refunded { err: format!("Unrecoverable state for swap-in {}: {}", &swap_id, state.to_string()), txid: txid.clone() }); + result = Err(PaymentError::Refunded { + err: format!( + "Unrecoverable state for swap-in {swap_id}: {}", + state.to_string() + ), + txid: txid.clone(), + }); break; } - _ => debug!( - "New state for swap {}: {}", - swap_id, - state.to_string() - ), + _ => {} }; - thread::sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(500)); } socket.close(None).unwrap(); diff --git a/lib/core/src/utils.rs b/lib/core/src/utils.rs index 41a923f..3536879 100644 --- a/lib/core/src/utils.rs +++ b/lib/core/src/utils.rs @@ -40,7 +40,7 @@ pub(crate) fn get_swap_status_v2( return match args.first() { Some(update) if update.id == swap_id => { - info!("Got new reverse swap status: {}", update.status); + info!("Got new swap status: {}", update.status); Ok(update.status.clone()) } From 55804e88c5c1dc1a8696088c89c2ea917ec579a6 Mon Sep 17 00:00:00 2001 From: yse Date: Mon, 13 May 2024 19:10:33 +0200 Subject: [PATCH 18/29] feat: add double lockup checks and resolve on claim details --- cli/Cargo.lock | 1 - lib/Cargo.lock | 2 +- lib/core/Cargo.toml | 2 +- lib/core/src/sdk.rs | 89 ++++++++++++++++++++++++--------------------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index f028ebc..7fe6ab3 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,7 +355,6 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=fac7573028cd46de5b1097916d43973d7e835460#fac7573028cd46de5b1097916d43973d7e835460" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index ae23212..c4ff871 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -478,7 +478,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/hydra-yse/boltz-rust?rev=fac7573028cd46de5b1097916d43973d7e835460#fac7573028cd46de5b1097916d43973d7e835460" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=be8395900495e415699a54e15f4bd0bc6d774237#be8395900495e415699a54e15f4bd0bc6d774237" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index 76b363c..bc5da05 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -18,7 +18,7 @@ bip39 = { version = "2.0.0", features = ["serde"] } # - latest fixes from Boltz (fallback for get_utxos) needed for claiming as soon as lockup tx is seen in mempool (receive case) # - latest fixes from Antonio (get reverse swap pairs, necessary structs, etc) # https://github.com/ok300/boltz-rust/commits/ok300-combo-boltz-fallback-get-utxos-yse-get-submarine-pairs/ -boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "fac7573028cd46de5b1097916d43973d7e835460" } +boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "be8395900495e415699a54e15f4bd0bc6d774237" } flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index e0dca74..23f4d47 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -115,7 +115,7 @@ impl LiquidSdk { Ok(descriptor_str.parse()?) } - fn get_keys(&self, derivation_index: i8) -> Result { + fn get_submarine_keys(&self, derivation_index: i32) -> Result { let mnemonic = self .lwk_signer .mnemonic() @@ -189,19 +189,17 @@ impl LiquidSdk { let ongoing_swap_in = Persister::fetch_ongoing_swap_in(&con, id)? .ok_or(anyhow!("No ongoing swap in found for ID {id}"))?; + let Some(txid) = ongoing_swap_in.txid.clone() else { + return Err(anyhow!( + "Swap-in {id} has been claimed but no txid is present" + )); + }; let payer_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); - let keypair = self.get_keys(0)?; + let keypair = self.get_submarine_keys(0)?; let swap_response: CreateSubmarineResponse = serde_json::from_str(&ongoing_swap_in.swap_response)?; match swap_state { - // SubSwapStates::InvoiceSet => { - // self.lockup_funds( - // id, - // &swap_response - // )?; - // Ok(()) - // } SubSwapStates::TransactionClaimPending => { let Ok(swap_script) = LBtcSwapScriptV2::submarine_from_swap_resp( &swap_response, @@ -222,17 +220,15 @@ impl LiquidSdk { ) .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; + self.persister + .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; + Ok(()) } SubSwapStates::TransactionClaimed => { warn!("Swap-in {id} has already been claimed. Resolving..."); - let Some(txid) = ongoing_swap_in.txid.clone() else { - return Err(anyhow!( - "Swap-in {id} has been claimed but no txid is present" - )); - }; - self.persister .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; @@ -356,10 +352,6 @@ impl LiquidSdk { }) } - fn get_signer(&self) -> SwSigner { - self.lwk_signer.clone() - } - // fn boltz_client(&self) -> BoltzApiClient { // let base_url = match self.network { // Network::LiquidTestnet => BOLTZ_TESTNET_URL, @@ -396,9 +388,10 @@ impl LiquidSdk { recipient_address: &str, amount_sat: u64, ) -> Result { + self.scan()?; let lwk_wollet = self.lwk_wollet.lock().unwrap(); let mut pset = lwk_wollet.send_lbtc(amount_sat, recipient_address, fee_rate)?; - let signer = AnySigner::Software(self.get_signer()); + let signer = AnySigner::Software(self.lwk_signer.clone()); signer.sign(&mut pset)?; Ok(lwk_wollet.finalize(&mut pset)?) } @@ -546,7 +539,7 @@ impl LiquidSdk { ) -> Result<(), PaymentError> { debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); let client = self.boltz_client_v2(); - let swap_tx = self.new_refund_tx(&swap_script)?; + let swap_tx = self.new_refund_tx(swap_script)?; let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?; @@ -555,7 +548,7 @@ impl LiquidSdk { Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; let (partial_sig, pub_nonce) = - swap_tx.submarine_partial_sig(&keypair, &claim_tx_response)?; + swap_tx.submarine_partial_sig(keypair, &claim_tx_response)?; client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; debug!("Successfully sent claim details for swap-in {swap_id}"); @@ -573,12 +566,10 @@ impl LiquidSdk { ); let tx = self.build_tx( - None, + Some(0.02), &create_response.address, - // TODO change testing create_response.expected_amount, )?; - let txid = self.broadcast_boltz(&tx)?; debug!("Successfully broadcast lockup transaction for swap-in {swap_id}. Txid: {txid}"); Ok(txid) @@ -602,7 +593,7 @@ impl LiquidSdk { PaymentError::InvalidOrExpiredFees ); - let keypair = self.get_keys(0)?; + let keypair = self.get_submarine_keys(0)?; let refund_public_key = boltz_client::PublicKey { compressed: true, inner: keypair.public_key(), @@ -613,6 +604,7 @@ impl LiquidSdk { to: "BTC".to_string(), invoice: req.invoice.to_string(), refund_public_key, + pair_hash: Some(lbtc_pair.hash), // TODO: Add referral id referral_id: None, })?; @@ -646,7 +638,7 @@ impl LiquidSdk { id: swap_id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, - swap_response: create_response_str, + swap_response: create_response_str.clone(), txid: None, })?; @@ -671,11 +663,38 @@ impl LiquidSdk { match state { // Boltz has locked the HTLC, we proceed with locking up the funds SubSwapStates::InvoiceSet => { + // Check that we have not persisted the swap already + let con = self.persister.get_connection()?; + + if let Some(ongoing_swap) = Persister::fetch_ongoing_swap_in(&con, swap_id) + .map_err(|_| PaymentError::PersistError)? + { + if ongoing_swap.txid.is_some() { + continue; + } + }; + txid = self.lockup_funds(swap_id, &create_response)?; + self.persister + .insert_or_update_ongoing_swap_in(OngoingSwapIn { + id: swap_id.clone(), + invoice: req.invoice.clone(), + payer_amount_sat: req.fees_sat + receiver_amount_sat, + swap_response: create_response_str.clone(), + txid: Some(txid.clone()), + })?; } - // Boltz has broadcast the claim to the mempool, resolve with success - SubSwapStates::TransactionClaimed => { + // Boltz has detected the lockup in the mempool, we can speed up + // the claim by doing so cooperatively + SubSwapStates::TransactionClaimPending => { + self.post_subarmine_claim_details( + swap_id, + &swap_script, + &req.invoice, + &keypair, + )?; + debug!("Boltz successfully claimed the funds. Resolving swap-in {swap_id}"); self.persister.resolve_ongoing_swap( swap_id, @@ -696,15 +715,6 @@ impl LiquidSdk { break; } - // Boltz has detected the lockup in the mempool, we can speed up - // the claim by doing so cooperatively - SubSwapStates::TransactionClaimPending => self.post_subarmine_claim_details( - swap_id, - &swap_script, - &req.invoice, - &keypair, - )?, - // Either: // 1. Boltz failed to pay // 2. The swap has expired (>24h) @@ -713,9 +723,6 @@ impl LiquidSdk { SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired | SubSwapStates::TransactionLockupFailed => { - // Wait for the lockup to be confirmed - sleep(Duration::from_secs(30)); - let txid = self.try_refund( swap_id, &swap_script, From 5a9dc1d5cef1bcb9b5a4a24d50c24d86bc783326 Mon Sep 17 00:00:00 2001 From: yse Date: Tue, 14 May 2024 13:04:35 +0200 Subject: [PATCH 19/29] feat: fix fee calculation issues --- cli/Cargo.lock | 1 + lib/core/src/error.rs | 7 ++++++ lib/core/src/sdk.rs | 53 ++++++++++++++++++++----------------------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 7fe6ab3..ba0b938 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" +source = "git+https://github.com/hydra-yse/boltz-rust?rev=be8395900495e415699a54e15f4bd0bc6d774237#be8395900495e415699a54e15f4bd0bc6d774237" dependencies = [ "bip39", "bitcoin 0.31.2", diff --git a/lib/core/src/error.rs b/lib/core/src/error.rs index e5abe78..94db714 100644 --- a/lib/core/src/error.rs +++ b/lib/core/src/error.rs @@ -74,6 +74,13 @@ impl From for PaymentError { PaymentError::Generic { err: msg } } + boltz_client::error::Error::HTTP(ureq) => { + dbg!(ureq.into_response().unwrap().into_string().unwrap()); + + PaymentError::Generic { + err: "Could not contact servers".to_string(), + } + } _ => PaymentError::Generic { err: format!("{err:?}"), }, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 23f4d47..3798839 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -413,7 +413,6 @@ impl LiquidSdk { Ok(invoice) } - #[allow(dead_code)] fn validate_submarine_pairs( client: &BoltzApiClientV2, receiver_amount_sat: u64, @@ -435,6 +434,14 @@ impl LiquidSdk { Ok(lbtc_pair) } + fn broadcast_fee_estimation(&self, amount_sat: u64) -> Result { + Ok(self + .build_tx(None, &self.address()?.to_string(), amount_sat)? + .all_fees() + .values() + .sum()) + } + pub fn prepare_send_payment( &self, req: &PrepareSendRequest, @@ -448,33 +455,14 @@ impl LiquidSdk { let client = self.boltz_client_v2(); let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + let broadcast_fees_sat = self.broadcast_fee_estimation(receiver_amount_sat)?; + Ok(PrepareSendResponse { invoice: req.invoice.clone(), - fees_sat: lbtc_pair.fees.total(receiver_amount_sat), + fees_sat: lbtc_pair.fees.total(receiver_amount_sat) + broadcast_fees_sat, }) } - fn broadcast_boltz(&self, tx: &Transaction) -> Result { - let tx_hex = elements::encode::serialize(tx).to_lower_hex_string(); - let response = self - .boltz_client_v2() - .broadcast_tx(self.network.into(), &tx_hex)?; - Ok(response - .as_object() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .get("id") - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .as_str() - .ok_or(PaymentError::Generic { - err: "Invalid data received from swapper".to_string(), - })? - .to_string()) - } - fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> { let preimage = Preimage::from_str(preimage)?; let preimage_hash = preimage.sha256.to_string(); @@ -565,14 +553,19 @@ impl LiquidSdk { create_response.expected_amount, create_response.address ); - let tx = self.build_tx( - Some(0.02), + let lockup_tx = self.build_tx( + None, &create_response.address, create_response.expected_amount, )?; - let txid = self.broadcast_boltz(&tx)?; - debug!("Successfully broadcast lockup transaction for swap-in {swap_id}. Txid: {txid}"); - Ok(txid) + + let electrum_client = ElectrumClient::new(&self.electrum_url)?; + let lockup_txid = electrum_client.broadcast(&lockup_tx)?.to_string(); + + debug!( + "Successfully broadcast lockup transaction for swap-in {swap_id}. Txid: {lockup_txid}" + ); + Ok(lockup_txid) } pub fn send_payment( @@ -588,8 +581,10 @@ impl LiquidSdk { let client = self.boltz_client_v2(); let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + let broadcast_fees_sat = self.broadcast_fee_estimation(receiver_amount_sat)?; + ensure_sdk!( - req.fees_sat == lbtc_pair.fees.total(receiver_amount_sat), + req.fees_sat == lbtc_pair.fees.total(receiver_amount_sat) + broadcast_fees_sat, PaymentError::InvalidOrExpiredFees ); From 70e2db708aa42add2d86c703b98ad7506d9ff0c3 Mon Sep 17 00:00:00 2001 From: yse Date: Tue, 14 May 2024 15:33:26 +0200 Subject: [PATCH 20/29] feat: add better fee estimation --- lib/core/src/model.rs | 1 + lib/core/src/persist/migrations.rs | 3 +- lib/core/src/persist/mod.rs | 13 +++++--- lib/core/src/sdk.rs | 48 +++++++++++++++++++++++------- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 3f46231..68316af 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -221,6 +221,7 @@ impl From for Payment { pub(crate) struct PaymentData { pub payer_amount_sat: u64, + pub receiver_amount_sat: u64, } #[macro_export] diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 541054f..a6914e2 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -20,7 +20,8 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { ) STRICT;", "CREATE TABLE IF NOT EXISTS payment_data( id TEXT NOT NULL PRIMARY KEY, - payer_amount_sat INTEGER NOT NULL + payer_amount_sat INTEGER NOT NULL, + receiver_amount_sat INTEGER NOT NULL ) STRICT;", ] } diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 797e111..7cd8c05 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -64,9 +64,13 @@ impl Persister { )?; if let Some((txid, payment_data)) = payment_data { tx.execute( - "INSERT INTO payment_data(id, payer_amount_sat) - VALUES(?, ?)", - (txid, payment_data.payer_amount_sat), + "INSERT INTO payment_data(id, payer_amount_sat, receiver_amount_sat) + VALUES(?, ?, ?)", + ( + txid, + payment_data.payer_amount_sat, + payment_data.receiver_amount_sat, + ), )?; } tx.commit()?; @@ -94,7 +98,7 @@ impl Persister { let mut stmt = con.prepare( " - SELECT id, payer_amount_sat + SELECT id, payer_amount_sat, receiver_amount_sat FROM payment_data ", )?; @@ -105,6 +109,7 @@ impl Persister { row.get(0)?, PaymentData { payer_amount_sat: row.get(1)?, + receiver_amount_sat: row.get(2)?, }, )) })? diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 3798839..b29096d 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -35,7 +35,7 @@ use crate::{ /// Claim tx feerate, in sats per vbyte. /// Since the Liquid blocks are consistently empty for now, we hardcode the minimum feerate. -pub const LIQUID_CLAIM_TX_FEERATE: f32 = 0.1; +pub const LIQUID_CLAIM_TX_FEERATE_MSAT: f32 = 100.0; pub const DEFAULT_DATA_DIR: &str = ".data"; @@ -159,7 +159,16 @@ impl LiquidSdk { Ok(txid) => { let payer_amount_sat = get_invoice_amount!(ongoing_swap_out.invoice); self.persister - .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .resolve_ongoing_swap( + id, + Some(( + txid, + PaymentData { + payer_amount_sat, + receiver_amount_sat: ongoing_swap_out.receiver_amount_sat, + }, + )), + ) .map_err(|e| anyhow!("Could not resolve swap {id}: {e}"))?; } Err(err) => { @@ -194,7 +203,7 @@ impl LiquidSdk { "Swap-in {id} has been claimed but no txid is present" )); }; - let payer_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); + let receiver_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); let keypair = self.get_submarine_keys(0)?; let swap_response: CreateSubmarineResponse = serde_json::from_str(&ongoing_swap_in.swap_response)?; @@ -221,7 +230,16 @@ impl LiquidSdk { .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; self.persister - .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .resolve_ongoing_swap( + id, + Some(( + txid, + PaymentData { + payer_amount_sat: ongoing_swap_in.payer_amount_sat, + receiver_amount_sat, + }, + )), + ) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; Ok(()) @@ -230,7 +248,16 @@ impl LiquidSdk { warn!("Swap-in {id} has already been claimed. Resolving..."); self.persister - .resolve_ongoing_swap(id, Some((txid, PaymentData { payer_amount_sat }))) + .resolve_ongoing_swap( + id, + Some(( + txid, + PaymentData { + payer_amount_sat: ongoing_swap_in.payer_amount_sat, + receiver_amount_sat, + }, + )), + ) .map_err(|_| anyhow!("Could not resolve swap {id} in database"))?; warn!("Swap-in {id} resolved successfully"); @@ -434,7 +461,7 @@ impl LiquidSdk { Ok(lbtc_pair) } - fn broadcast_fee_estimation(&self, amount_sat: u64) -> Result { + fn get_broadcast_fee_estimation(&self, amount_sat: u64) -> Result { Ok(self .build_tx(None, &self.address()?.to_string(), amount_sat)? .all_fees() @@ -455,7 +482,7 @@ impl LiquidSdk { let client = self.boltz_client_v2(); let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; - let broadcast_fees_sat = self.broadcast_fee_estimation(receiver_amount_sat)?; + let broadcast_fees_sat = self.get_broadcast_fee_estimation(receiver_amount_sat)?; Ok(PrepareSendResponse { invoice: req.invoice.clone(), @@ -581,7 +608,7 @@ impl LiquidSdk { let client = self.boltz_client_v2(); let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; - let broadcast_fees_sat = self.broadcast_fee_estimation(receiver_amount_sat)?; + let broadcast_fees_sat = self.get_broadcast_fee_estimation(receiver_amount_sat)?; ensure_sdk!( req.fees_sat == lbtc_pair.fees.total(receiver_amount_sat) + broadcast_fees_sat, @@ -697,6 +724,7 @@ impl LiquidSdk { txid.clone(), PaymentData { payer_amount_sat: receiver_amount_sat + req.fees_sat, + receiver_amount_sat, }, )), )?; @@ -935,6 +963,7 @@ impl LiquidSdk { let id = tx.txid.to_string(); let data = payment_data.get(&id); let amount_sat = tx.balance.values().sum::(); + let fees_sat = data.map(|d| d.payer_amount_sat - d.receiver_amount_sat); Payment { id: Some(id.clone()), @@ -945,8 +974,7 @@ impl LiquidSdk { false => PaymentType::Sent, }, invoice: None, - fees_sat: data - .map(|d| (amount_sat.abs() - d.payer_amount_sat as i64).unsigned_abs()), + fees_sat, } }) .collect(); From 4496318089d2fd33527857429306fd8bdaf6adff Mon Sep 17 00:00:00 2001 From: yse Date: Tue, 14 May 2024 15:44:30 +0200 Subject: [PATCH 21/29] fix: renaming --- lib/core/src/model.rs | 4 +- lib/core/src/persist/migrations.rs | 4 +- lib/core/src/persist/swap_in.rs | 16 +++--- lib/core/src/sdk.rs | 85 +++++++++++++++--------------- 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 68316af..109a678 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -147,8 +147,8 @@ pub(crate) struct OngoingSwapIn { pub(crate) id: String, pub(crate) invoice: String, pub(crate) payer_amount_sat: u64, - pub(crate) swap_response: String, - pub(crate) txid: Option, + pub(crate) create_response_json: String, + pub(crate) lockup_txid: Option, } #[derive(Clone, Debug)] diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index a6914e2..fe7e25a 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -14,8 +14,8 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { id TEXT NOT NULL PRIMARY KEY, invoice TEXT NOT NULL, payer_amount_sat INTEGER NOT NULL, - swap_response TEXT NOT NULL, - txid TEXT, + create_response_json TEXT NOT NULL, + lockup_txid TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) STRICT;", "CREATE TABLE IF NOT EXISTS payment_data( diff --git a/lib/core/src/persist/swap_in.rs b/lib/core/src/persist/swap_in.rs index 2a8fceb..304202c 100644 --- a/lib/core/src/persist/swap_in.rs +++ b/lib/core/src/persist/swap_in.rs @@ -14,8 +14,8 @@ impl Persister { id, invoice, payer_amount_sat, - swap_response, - txid + create_response_json, + lockup_txid ) VALUES (?, ?, ?, ?, ?)", )?; @@ -23,8 +23,8 @@ impl Persister { swap_in.id, swap_in.invoice, swap_in.payer_amount_sat, - swap_in.swap_response, - swap_in.txid, + swap_in.create_response_json, + swap_in.lockup_txid, ))?; Ok(()) @@ -43,8 +43,8 @@ impl Persister { id, invoice, payer_amount_sat, - swap_response, - txid, + create_response_json, + lockup_txid, created_at FROM ongoing_send_swaps {where_clause_str} @@ -67,8 +67,8 @@ impl Persister { id: row.get(0)?, invoice: row.get(1)?, payer_amount_sat: row.get(2)?, - swap_response: row.get(3)?, - txid: row.get(4)?, + create_response_json: row.get(3)?, + lockup_txid: row.get(4)?, }) } diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index b29096d..703f91e 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -198,20 +198,20 @@ impl LiquidSdk { let ongoing_swap_in = Persister::fetch_ongoing_swap_in(&con, id)? .ok_or(anyhow!("No ongoing swap in found for ID {id}"))?; - let Some(txid) = ongoing_swap_in.txid.clone() else { + let Some(txid) = ongoing_swap_in.lockup_txid.clone() else { return Err(anyhow!( "Swap-in {id} has been claimed but no txid is present" )); }; let receiver_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); let keypair = self.get_submarine_keys(0)?; - let swap_response: CreateSubmarineResponse = - serde_json::from_str(&ongoing_swap_in.swap_response)?; + let create_response: CreateSubmarineResponse = + serde_json::from_str(&ongoing_swap_in.create_response_json)?; match swap_state { SubSwapStates::TransactionClaimPending => { let Ok(swap_script) = LBtcSwapScriptV2::submarine_from_swap_resp( - &swap_response, + &create_response, keypair.public_key().into(), ) else { self.persister @@ -221,7 +221,7 @@ impl LiquidSdk { return Err(anyhow!("Could not rebuild refund details for swap-in {id}")); }; - self.post_subarmine_claim_details( + self.post_submarine_claim_details( id, &swap_script, &ongoing_swap_in.invoice, @@ -270,7 +270,7 @@ impl LiquidSdk { // If swap state is unrecoverable, try refunding let Ok(swap_script) = LBtcSwapScriptV2::submarine_from_swap_resp( - &swap_response, + &create_response, keypair.public_key().into(), ) else { self.persister @@ -280,7 +280,8 @@ impl LiquidSdk { return Err(anyhow!("Could not rebuild refund details for swap-in {id}")); }; - let refund_txid = self.try_refund(id, &swap_script, &keypair, 133)?; + let refund_txid = + self.try_refund(id, &swap_script, &keypair, receiver_amount_sat)?; warn!("Swap-in {id} refunded successfully. Txid: {refund_txid}"); @@ -517,35 +518,37 @@ impl LiquidSdk { swap_id: &str, swap_script: &LBtcSwapScriptV2, keypair: &Keypair, - fees_sat: u64, + amount_sat: u64, ) -> Result { - let swap_tx = self.new_refund_tx(swap_script)?; + let refund_tx = self.new_refund_tx(swap_script)?; + let broadcast_fees_sat = Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat)?); let client = self.boltz_client_v2(); let is_lowball = Some((&client, boltz_client::network::Chain::from(self.network))); - match swap_tx.sign_refund( + + match refund_tx.sign_refund( keypair, - Amount::from_sat(fees_sat), + broadcast_fees_sat, Some((&client, &swap_id.to_string())), ) { // Try with cooperative refund Ok(tx) => { - let txid = swap_tx.broadcast(&tx, &self.network_config(), is_lowball)?; + let txid = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?; debug!("Successfully broadcast cooperative refund for swap-in {swap_id}"); Ok(txid) } // Try with non-cooperative refund Err(e) => { debug!("Cooperative refund failed: {:?}", e); - let tx = swap_tx.sign_refund(keypair, Amount::from_sat(fees_sat), None)?; - let txid = swap_tx.broadcast(&tx, &self.network_config(), is_lowball)?; + let tx = refund_tx.sign_refund(keypair, broadcast_fees_sat, None)?; + let txid = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?; debug!("Successfully broadcast non-cooperative refund for swap-in {swap_id}"); Ok(txid) } } } - fn post_subarmine_claim_details( + fn post_submarine_claim_details( &self, swap_id: &str, swap_script: &LBtcSwapScriptV2, @@ -554,7 +557,7 @@ impl LiquidSdk { ) -> Result<(), PaymentError> { debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); let client = self.boltz_client_v2(); - let swap_tx = self.new_refund_tx(swap_script)?; + let refund_tx = self.new_refund_tx(swap_script)?; let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?; @@ -563,7 +566,7 @@ impl LiquidSdk { Self::verify_payment_hash(&claim_tx_response.preimage, invoice)?; let (partial_sig, pub_nonce) = - swap_tx.submarine_partial_sig(keypair, &claim_tx_response)?; + refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; debug!("Successfully sent claim details for swap-in {swap_id}"); @@ -630,7 +633,7 @@ impl LiquidSdk { // TODO: Add referral id referral_id: None, })?; - let create_response_str = + let create_response_json = serde_json::to_string(&create_response).map_err(|_| PaymentError::Generic { err: "Could not store swap response locally".to_string(), })?; @@ -660,12 +663,12 @@ impl LiquidSdk { id: swap_id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, - swap_response: create_response_str.clone(), - txid: None, + create_response_json: create_response_json.clone(), + lockup_txid: None, })?; let result; - let mut txid = String::new(); + let mut lockup_txid = String::new(); loop { let data = match utils::get_swap_status_v2(&mut socket, swap_id) { Ok(data) => data, @@ -691,26 +694,26 @@ impl LiquidSdk { if let Some(ongoing_swap) = Persister::fetch_ongoing_swap_in(&con, swap_id) .map_err(|_| PaymentError::PersistError)? { - if ongoing_swap.txid.is_some() { + if ongoing_swap.lockup_txid.is_some() { continue; } }; - txid = self.lockup_funds(swap_id, &create_response)?; + lockup_txid = self.lockup_funds(swap_id, &create_response)?; self.persister .insert_or_update_ongoing_swap_in(OngoingSwapIn { id: swap_id.clone(), invoice: req.invoice.clone(), payer_amount_sat: req.fees_sat + receiver_amount_sat, - swap_response: create_response_str.clone(), - txid: Some(txid.clone()), + create_response_json: create_response_json.clone(), + lockup_txid: Some(lockup_txid.clone()), })?; } // Boltz has detected the lockup in the mempool, we can speed up // the claim by doing so cooperatively SubSwapStates::TransactionClaimPending => { - self.post_subarmine_claim_details( + self.post_submarine_claim_details( swap_id, &swap_script, &req.invoice, @@ -721,7 +724,7 @@ impl LiquidSdk { self.persister.resolve_ongoing_swap( swap_id, Some(( - txid.clone(), + lockup_txid.clone(), PaymentData { payer_amount_sat: receiver_amount_sat + req.fees_sat, receiver_amount_sat, @@ -732,7 +735,8 @@ impl LiquidSdk { self.status_stream .resolve_tracked_swap(swap_id, SwapType::ReverseSubmarine); - result = Ok(SendPaymentResponse { txid }); + // TODO: Change lockup txid to claim txid + result = Ok(SendPaymentResponse { txid: lockup_txid }); debug!("Successfully resolved swap-in {swap_id}"); break; @@ -746,20 +750,15 @@ impl LiquidSdk { SubSwapStates::InvoiceFailedToPay | SubSwapStates::SwapExpired | SubSwapStates::TransactionLockupFailed => { - let txid = self.try_refund( - swap_id, - &swap_script, - &keypair, - // TODO Don't hardcode min broadcast fee - 133, - )?; + let refund_txid = + self.try_refund(swap_id, &swap_script, &keypair, receiver_amount_sat)?; result = Err(PaymentError::Refunded { err: format!( "Unrecoverable state for swap-in {swap_id}: {}", state.to_string() ), - txid: txid.clone(), + txid: refund_txid.clone(), }); break; } @@ -779,10 +778,10 @@ impl LiquidSdk { let lsk = self.get_liquid_swap_key()?; let our_keys = lsk.keypair; - let swap_response_v2: CreateReverseResponse = + let create_response: CreateReverseResponse = serde_json::from_str(&ongoing_swap_out.redeem_script).unwrap(); let swap_script = LBtcSwapScriptV2::reverse_from_swap_resp( - &swap_response_v2, + &create_response, our_keys.public_key().into(), )?; @@ -906,16 +905,16 @@ impl LiquidSdk { address_signature: None, referral_id: None, }; - let swap_response_v2 = self.boltz_client_v2().post_reverse_req(v2_req)?; + let create_response = self.boltz_client_v2().post_reverse_req(v2_req)?; // TODO Persisting this in the DB (reusing "redeem_script" field), as we need it later when claiming - let redeem_script = serde_json::to_string(&swap_response_v2).unwrap(); + let redeem_script = serde_json::to_string(&create_response).unwrap(); - let swap_id = swap_response_v2.id; - let invoice = Bolt11Invoice::from_str(&swap_response_v2.invoice) + let swap_id = create_response.id; + let invoice = Bolt11Invoice::from_str(&create_response.invoice) .map_err(|_| PaymentError::InvalidInvoice)?; let blinding_str = - swap_response_v2 + create_response .blinding_key .ok_or(boltz_client::error::Error::Protocol( "Boltz response does not contain a blinding key.".to_string(), From 67aa6a38f877cf312215d228d6ebda64060f9180 Mon Sep 17 00:00:00 2001 From: yse Date: Tue, 14 May 2024 15:45:39 +0200 Subject: [PATCH 22/29] fix: reword lockup tx error --- lib/core/src/sdk.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 703f91e..3f66169 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -199,9 +199,7 @@ impl LiquidSdk { .ok_or(anyhow!("No ongoing swap in found for ID {id}"))?; let Some(txid) = ongoing_swap_in.lockup_txid.clone() else { - return Err(anyhow!( - "Swap-in {id} has been claimed but no txid is present" - )); + return Err(anyhow!("Swap-in {id} is pending but no txid is present")); }; let receiver_amount_sat = get_invoice_amount!(ongoing_swap_in.invoice); let keypair = self.get_submarine_keys(0)?; From 6b884613410785bb3407046a13ca96e9fdb89527 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:22:47 +0200 Subject: [PATCH 23/29] Add PaymentError::Refunded to UDL --- lib/bindings/src/breez_liquid_sdk.udl | 5 +++-- lib/core/src/error.rs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/bindings/src/breez_liquid_sdk.udl b/lib/bindings/src/breez_liquid_sdk.udl index 79fb619..45e5f8b 100644 --- a/lib/bindings/src/breez_liquid_sdk.udl +++ b/lib/bindings/src/breez_liquid_sdk.udl @@ -5,16 +5,17 @@ enum LiquidSdkError { [Error] enum PaymentError { + "AlreadyClaimed", "AmountOutOfRange", + "Generic", "InvalidOrExpiredFees", "InsufficientFunds", - "AlreadyClaimed", - "Generic", "InvalidInvoice", "InvalidPreimage", "LwkError", "PairsNotFound", "PersistError", + "Refunded", "SendError", "SignerError", }; diff --git a/lib/core/src/error.rs b/lib/core/src/error.rs index 94db714..27bed16 100644 --- a/lib/core/src/error.rs +++ b/lib/core/src/error.rs @@ -24,24 +24,21 @@ impl From for LiquidSdkError { #[derive(thiserror::Error, Debug)] pub enum PaymentError { + #[error("The specified funds have already been claimed")] + AlreadyClaimed, + #[error("Invoice amount is out of range")] AmountOutOfRange, + #[error("Generic error: {err}")] + Generic { err: String }, + #[error("The provided fees have expired")] InvalidOrExpiredFees, #[error("Cannot pay: not enough funds")] InsufficientFunds, - #[error("The specified funds have already been claimed")] - AlreadyClaimed, - - #[error("The payment has been refunded. Reason for failure: {err}")] - Refunded { err: String, txid: String }, - - #[error("Generic error: {err}")] - Generic { err: String }, - #[error("The specified invoice is not valid")] InvalidInvoice, @@ -57,6 +54,9 @@ pub enum PaymentError { #[error("Could not store the swap details locally")] PersistError, + #[error("The payment has been refunded. Reason for failure: {err}")] + Refunded { err: String, txid: String }, + #[error("Could not sign/send the transaction: {err}")] SendError { err: String }, From 8cfba8a247ac356f37e4a92e0f977b87deb10a37 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:26:01 +0200 Subject: [PATCH 24/29] Update RN bindings --- .../breezliquidsdk/BreezLiquidSDKMapper.kt | 289 ++++++------------ .../breezliquidsdk/BreezLiquidSDKModule.kt | 95 ++---- .../ios/BreezLiquidSDKMapper.swift | 37 +-- packages/react-native/src/index.ts | 27 +- 4 files changed, 134 insertions(+), 314 deletions(-) diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt index 5a0a752..ab0b438 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt @@ -1,34 +1,33 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter +import java.io.File import java.util.* - +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + fun asConnectRequest(connectRequest: ReadableMap): ConnectRequest? { - if (!validateMandatoryFields( - connectRequest, - arrayOf( - "mnemonic", - "network", - ), - ) - ) { + if (!validateMandatoryFields(connectRequest, arrayOf( + "mnemonic", + "network", + ))) { return null } val mnemonic = connectRequest.getString("mnemonic")!! - val network = connectRequest.getString("network")?.let { asNetwork(it) }!! + val network = connectRequest.getString("network")?.let { asNetwork(it)}!! val dataDir = if (hasNonNullKey(connectRequest, "dataDir")) connectRequest.getString("dataDir") else null return ConnectRequest( mnemonic, network, - dataDir, - ) + dataDir,) } fun readableMapOf(connectRequest: ConnectRequest): ReadableMap { return readableMapOf( - "mnemonic" to connectRequest.mnemonic, - "network" to connectRequest.network.name.lowercase(), - "dataDir" to connectRequest.dataDir, + "mnemonic" to connectRequest.mnemonic, + "network" to connectRequest.network.name.lowercase(), + "dataDir" to connectRequest.dataDir, ) } @@ -36,32 +35,26 @@ fun asConnectRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asConnectRequest(value)!!) + is ReadableMap -> list.add(asConnectRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asGetInfoRequest(getInfoRequest: ReadableMap): GetInfoRequest? { - if (!validateMandatoryFields( - getInfoRequest, - arrayOf( - "withScan", - ), - ) - ) { + if (!validateMandatoryFields(getInfoRequest, arrayOf( + "withScan", + ))) { return null } val withScan = getInfoRequest.getBoolean("withScan") return GetInfoRequest( - withScan, - ) + withScan,) } fun readableMapOf(getInfoRequest: GetInfoRequest): ReadableMap { return readableMapOf( - "withScan" to getInfoRequest.withScan, + "withScan" to getInfoRequest.withScan, ) } @@ -69,36 +62,30 @@ fun asGetInfoRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoRequest(value)!!) + is ReadableMap -> list.add(asGetInfoRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { - if (!validateMandatoryFields( - getInfoResponse, - arrayOf( - "balanceSat", - "pubkey", - ), - ) - ) { + if (!validateMandatoryFields(getInfoResponse, arrayOf( + "balanceSat", + "pubkey", + ))) { return null } val balanceSat = getInfoResponse.getDouble("balanceSat").toULong() val pubkey = getInfoResponse.getString("pubkey")!! return GetInfoResponse( balanceSat, - pubkey, - ) + pubkey,) } fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap { return readableMapOf( - "balanceSat" to getInfoResponse.balanceSat, - "pubkey" to getInfoResponse.pubkey, + "balanceSat" to getInfoResponse.balanceSat, + "pubkey" to getInfoResponse.pubkey, ) } @@ -106,32 +93,26 @@ fun asGetInfoResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoResponse(value)!!) + is ReadableMap -> list.add(asGetInfoResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveRequest? { - if (!validateMandatoryFields( - prepareReceiveRequest, - arrayOf( - "payerAmountSat", - ), - ) - ) { + if (!validateMandatoryFields(prepareReceiveRequest, arrayOf( + "payerAmountSat", + ))) { return null } val payerAmountSat = prepareReceiveRequest.getDouble("payerAmountSat").toULong() return PrepareReceiveRequest( - payerAmountSat, - ) + payerAmountSat,) } fun readableMapOf(prepareReceiveRequest: PrepareReceiveRequest): ReadableMap { return readableMapOf( - "payerAmountSat" to prepareReceiveRequest.payerAmountSat, + "payerAmountSat" to prepareReceiveRequest.payerAmountSat, ) } @@ -139,40 +120,30 @@ fun asPrepareReceiveRequestList(arr: ReadableArray): List val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) + is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiveResponse? { - if (!validateMandatoryFields( - prepareReceiveResponse, - arrayOf( - "pairHash", - "payerAmountSat", - "feesSat", - ), - ) - ) { + if (!validateMandatoryFields(prepareReceiveResponse, arrayOf( + "payerAmountSat", + "feesSat", + ))) { return null } - val pairHash = prepareReceiveResponse.getString("pairHash")!! val payerAmountSat = prepareReceiveResponse.getDouble("payerAmountSat").toULong() val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong() return PrepareReceiveResponse( - pairHash, payerAmountSat, - feesSat, - ) + feesSat,) } fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap { return readableMapOf( - "pairHash" to prepareReceiveResponse.pairHash, - "payerAmountSat" to prepareReceiveResponse.payerAmountSat, - "feesSat" to prepareReceiveResponse.feesSat, + "payerAmountSat" to prepareReceiveResponse.payerAmountSat, + "feesSat" to prepareReceiveResponse.feesSat, ) } @@ -180,32 +151,26 @@ fun asPrepareReceiveResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) + is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { - if (!validateMandatoryFields( - prepareSendRequest, - arrayOf( - "invoice", - ), - ) - ) { + if (!validateMandatoryFields(prepareSendRequest, arrayOf( + "invoice", + ))) { return null } val invoice = prepareSendRequest.getString("invoice")!! return PrepareSendRequest( - invoice, - ) + invoice,) } fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap { return readableMapOf( - "invoice" to prepareSendRequest.invoice, + "invoice" to prepareSendRequest.invoice, ) } @@ -213,73 +178,30 @@ fun asPrepareSendRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendRequest(value)!!) + is ReadableMap -> list.add(asPrepareSendRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - -fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { - if (!validateMandatoryFields( - prepareSendRequest, - arrayOf( - "invoice", - ), - ) - ) { - return null - } - val invoice = prepareSendRequest.getString("invoice")!! - return PrepareSendRequest( - invoice, - ) -} - -fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap { - return readableMapOf( - "invoice" to prepareSendRequest.invoice, - ) -} - -fun asPrepareSendRequestList(arr: ReadableArray): List { - val list = ArrayList() - for (value in arr.toArrayList()) { - when (value) { - is ReadableMap -> list.add(asPrepareSendRequest(value)!!) - else -> throw LsSdkException.Generic(errUnexpectedType("${value::class.java.name}")) - } - } - return list -} - fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse? { - if (!validateMandatoryFields( - prepareSendResponse, - arrayOf( - "invoice", - "pairHash", - "feesSat", - ), - ) - ) { + if (!validateMandatoryFields(prepareSendResponse, arrayOf( + "invoice", + "feesSat", + ))) { return null } val invoice = prepareSendResponse.getString("invoice")!! - val pairHash = prepareSendResponse.getString("pairHash")!! val feesSat = prepareSendResponse.getDouble("feesSat").toULong() return PrepareSendResponse( invoice, - pairHash, - feesSat, - ) + feesSat,) } fun readableMapOf(prepareSendResponse: PrepareSendResponse): ReadableMap { return readableMapOf( - "invoice" to prepareSendResponse.invoice, - "pairHash" to prepareSendResponse.pairHash, - "feesSat" to prepareSendResponse.feesSat, + "invoice" to prepareSendResponse.invoice, + "feesSat" to prepareSendResponse.feesSat, ) } @@ -287,36 +209,30 @@ fun asPrepareSendResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendResponse(value)!!) + is ReadableMap -> list.add(asPrepareSendResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asReceivePaymentResponse(receivePaymentResponse: ReadableMap): ReceivePaymentResponse? { - if (!validateMandatoryFields( - receivePaymentResponse, - arrayOf( - "id", - "invoice", - ), - ) - ) { + if (!validateMandatoryFields(receivePaymentResponse, arrayOf( + "id", + "invoice", + ))) { return null } val id = receivePaymentResponse.getString("id")!! val invoice = receivePaymentResponse.getString("invoice")!! return ReceivePaymentResponse( id, - invoice, - ) + invoice,) } fun readableMapOf(receivePaymentResponse: ReceivePaymentResponse): ReadableMap { return readableMapOf( - "id" to receivePaymentResponse.id, - "invoice" to receivePaymentResponse.invoice, + "id" to receivePaymentResponse.id, + "invoice" to receivePaymentResponse.invoice, ) } @@ -324,30 +240,25 @@ fun asReceivePaymentResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) + is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asRestoreRequest(restoreRequest: ReadableMap): RestoreRequest? { - if (!validateMandatoryFields( - restoreRequest, - arrayOf(), - ) - ) { + if (!validateMandatoryFields(restoreRequest, arrayOf( + ))) { return null } val backupPath = if (hasNonNullKey(restoreRequest, "backupPath")) restoreRequest.getString("backupPath") else null return RestoreRequest( - backupPath, - ) + backupPath,) } fun readableMapOf(restoreRequest: RestoreRequest): ReadableMap { return readableMapOf( - "backupPath" to restoreRequest.backupPath, + "backupPath" to restoreRequest.backupPath, ) } @@ -355,32 +266,26 @@ fun asRestoreRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asRestoreRequest(value)!!) + is ReadableMap -> list.add(asRestoreRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } - fun asSendPaymentResponse(sendPaymentResponse: ReadableMap): SendPaymentResponse? { - if (!validateMandatoryFields( - sendPaymentResponse, - arrayOf( - "txid", - ), - ) - ) { + if (!validateMandatoryFields(sendPaymentResponse, arrayOf( + "txid", + ))) { return null } val txid = sendPaymentResponse.getString("txid")!! return SendPaymentResponse( - txid, - ) + txid,) } fun readableMapOf(sendPaymentResponse: SendPaymentResponse): ReadableMap { return readableMapOf( - "txid" to sendPaymentResponse.txid, + "txid" to sendPaymentResponse.txid, ) } @@ -388,7 +293,7 @@ fun asSendPaymentResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asSendPaymentResponse(value)!!) + is ReadableMap -> list.add(asSendPaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } @@ -408,9 +313,7 @@ fun asNetworkList(arr: ReadableArray): List { } } return list -} - -fun readableMapOf(vararg values: Pair): ReadableMap { +}fun readableMapOf(vararg values: Pair): ReadableMap { val map = Arguments.createMap() for ((key, value) in values) { pushToMap(map, key, value) @@ -418,17 +321,11 @@ fun readableMapOf(vararg values: Pair): ReadableMap { return map } -fun hasNonNullKey( - map: ReadableMap, - key: String, -): Boolean { +fun hasNonNullKey(map: ReadableMap, key: String): Boolean { return map.hasKey(key) && !map.isNull(key) } -fun validateMandatoryFields( - map: ReadableMap, - keys: Array, -): Boolean { +fun validateMandatoryFields(map: ReadableMap, keys: Array): Boolean { for (k in keys) { if (!hasNonNullKey(map, k)) return false } @@ -436,10 +333,7 @@ fun validateMandatoryFields( return true } -fun pushToArray( - array: WritableArray, - value: Any?, -) { +fun pushToArray(array: WritableArray, value: Any?) { when (value) { null -> array.pushNull() is Array<*> -> array.pushArray(readableArrayOf(value.asIterable())) @@ -448,11 +342,7 @@ fun pushToArray( } } -fun pushToMap( - map: WritableMap, - key: String, - value: Any?, -) { +fun pushToMap(map: WritableMap, key: String, value: Any?) { when (value) { null -> map.putNull(key) is Boolean -> map.putBoolean(key, value) @@ -505,22 +395,19 @@ fun asStringList(arr: ReadableArray): List { return list } -fun errMissingMandatoryField( - fieldName: String, - typeName: String, -): String { - return "Missing mandatory field $fieldName for type $typeName" -} +fun errMissingMandatoryField(fieldName: String, typeName: String): String { + return "Missing mandatory field ${fieldName} for type ${typeName}" + } fun errUnexpectedType(typeName: String): String { - return "Unexpected type $typeName" -} + return "Unexpected type ${typeName}" + } fun errUnexpectedValue(fieldName: String): String { - return "Unexpected value for optional field $fieldName" + return "Unexpected value for optional field ${fieldName}" } fun camelToUpperSnakeCase(str: String): String { val pattern = "(?<=.)[A-Z]".toRegex() return str.replace(pattern, "_$0").uppercase() -} +} \ No newline at end of file diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt index e9def90..630eb81 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt @@ -2,10 +2,13 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter +import java.io.File import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors + class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private lateinit var executor: ExecutorService private var bindingLiquidSdk: BindingLiquidSdk? = null @@ -39,11 +42,10 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext @ReactMethod fun removeListeners(count: Int) {} + + @ReactMethod - fun connect( - req: ReadableMap, - promise: Promise, - ) { + fun connect(req: ReadableMap, promise: Promise) { if (bindingLiquidSdk != null) { promise.reject("Generic", "Already initialized") return @@ -51,13 +53,8 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext executor.execute { try { - var connectRequest = - asConnectRequest( - req, - ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } - connectRequest.dataDir = connectRequest.dataDir?.takeUnless { - it.isEmpty() - } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } + var connectRequest = asConnectRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } + connectRequest.dataDir = connectRequest.dataDir?.takeUnless { it.isEmpty() } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } bindingLiquidSdk = connect(connectRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -66,17 +63,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } + @ReactMethod - fun getInfo( - req: ReadableMap, - promise: Promise, - ) { + fun getInfo(req: ReadableMap, promise: Promise) { executor.execute { try { - val getInfoRequest = - asGetInfoRequest( - req, - ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } + val getInfoRequest = asGetInfoRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } val res = getBindingLiquidSdk().getInfo(getInfoRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -84,18 +76,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareSendPayment( - req: ReadableMap, - promise: Promise, - ) { + fun prepareSendPayment(req: ReadableMap, promise: Promise) { executor.execute { try { - val prepareSendRequest = - asPrepareSendRequest(req) ?: run { - throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) - } + val prepareSendRequest = asPrepareSendRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) } val res = getBindingLiquidSdk().prepareSendPayment(prepareSendRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -103,18 +89,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun sendPayment( - req: ReadableMap, - promise: Promise, - ) { + fun sendPayment(req: ReadableMap, promise: Promise) { executor.execute { try { - val prepareSendResponse = - asPrepareSendResponse(req) ?: run { - throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) - } + val prepareSendResponse = asPrepareSendResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) } val res = getBindingLiquidSdk().sendPayment(prepareSendResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -122,18 +102,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareReceivePayment( - req: ReadableMap, - promise: Promise, - ) { + fun prepareReceivePayment(req: ReadableMap, promise: Promise) { executor.execute { try { - val prepareReceiveRequest = - asPrepareReceiveRequest(req) ?: run { - throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) - } + val prepareReceiveRequest = asPrepareReceiveRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) } val res = getBindingLiquidSdk().prepareReceivePayment(prepareReceiveRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -141,18 +115,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun receivePayment( - req: ReadableMap, - promise: Promise, - ) { + fun receivePayment(req: ReadableMap, promise: Promise) { executor.execute { try { - val prepareReceiveResponse = - asPrepareReceiveResponse(req) ?: run { - throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) - } + val prepareReceiveResponse = asPrepareReceiveResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) } val res = getBindingLiquidSdk().receivePayment(prepareReceiveResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -160,7 +128,7 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod fun backup(promise: Promise) { executor.execute { @@ -172,18 +140,12 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun restore( - req: ReadableMap, - promise: Promise, - ) { + fun restore(req: ReadableMap, promise: Promise) { executor.execute { try { - val restoreRequest = - asRestoreRequest( - req, - ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } + val restoreRequest = asRestoreRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } getBindingLiquidSdk().restore(restoreRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -191,4 +153,5 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } -} + +} \ No newline at end of file diff --git a/packages/react-native/ios/BreezLiquidSDKMapper.swift b/packages/react-native/ios/BreezLiquidSDKMapper.swift index fbfa04c..8409800 100644 --- a/packages/react-native/ios/BreezLiquidSDKMapper.swift +++ b/packages/react-native/ios/BreezLiquidSDKMapper.swift @@ -154,9 +154,6 @@ enum BreezLiquidSDKMapper { } static func asPrepareReceiveResponse(prepareReceiveResponse: [String: Any?]) throws -> PrepareReceiveResponse { - guard let pairHash = prepareReceiveResponse["pairHash"] as? String else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "pairHash", typeName: "PrepareReceiveResponse")) - } guard let payerAmountSat = prepareReceiveResponse["payerAmountSat"] as? UInt64 else { throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "PrepareReceiveResponse")) } @@ -165,7 +162,6 @@ enum BreezLiquidSDKMapper { } return PrepareReceiveResponse( - pairHash: pairHash, payerAmountSat: payerAmountSat, feesSat: feesSat ) @@ -173,7 +169,6 @@ enum BreezLiquidSDKMapper { static func dictionaryOf(prepareReceiveResponse: PrepareReceiveResponse) -> [String: Any?] { return [ - "pairHash": prepareReceiveResponse.pairHash, "payerAmountSat": prepareReceiveResponse.payerAmountSat, "feesSat": prepareReceiveResponse.feesSat, ] @@ -229,43 +224,23 @@ enum BreezLiquidSDKMapper { } static func asPrepareSendResponse(prepareSendResponse: [String: Any?]) throws -> PrepareSendResponse { - guard let id = prepareSendResponse["id"] as? String else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "id", typeName: "PrepareSendResponse")) - } - guard let payerAmountSat = prepareSendResponse["payerAmountSat"] as? UInt64 else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "PrepareSendResponse")) - } - guard let receiverAmountSat = prepareSendResponse["receiverAmountSat"] as? UInt64 else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "receiverAmountSat", typeName: "PrepareSendResponse")) - } - guard let totalFees = prepareSendResponse["totalFees"] as? UInt64 else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "totalFees", typeName: "PrepareSendResponse")) - } - guard let fundingAddress = prepareSendResponse["fundingAddress"] as? String else { - throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "fundingAddress", typeName: "PrepareSendResponse")) - } guard let invoice = prepareSendResponse["invoice"] as? String else { throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "invoice", typeName: "PrepareSendResponse")) } + guard let feesSat = prepareSendResponse["feesSat"] as? UInt64 else { + throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareSendResponse")) + } return PrepareSendResponse( - id: id, - payerAmountSat: payerAmountSat, - receiverAmountSat: receiverAmountSat, - totalFees: totalFees, - fundingAddress: fundingAddress, - invoice: invoice + invoice: invoice, + feesSat: feesSat ) } static func dictionaryOf(prepareSendResponse: PrepareSendResponse) -> [String: Any?] { return [ - "id": prepareSendResponse.id, - "payerAmountSat": prepareSendResponse.payerAmountSat, - "receiverAmountSat": prepareSendResponse.receiverAmountSat, - "totalFees": prepareSendResponse.totalFees, - "fundingAddress": prepareSendResponse.fundingAddress, "invoice": prepareSendResponse.invoice, + "feesSat": prepareSendResponse.feesSat, ] } diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index e9e81fb..4b7161d 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -17,54 +17,49 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK } ) -export interface ConnectRequest { +export type ConnectRequest = { mnemonic: string network: Network dataDir?: string } -export interface GetInfoRequest { +export type GetInfoRequest = { withScan: boolean } -export interface GetInfoResponse { +export type GetInfoResponse = { balanceSat: number pubkey: string } -export interface PrepareReceiveRequest { +export type PrepareReceiveRequest = { payerAmountSat: number } -export interface PrepareReceiveResponse { - pairHash: string +export type PrepareReceiveResponse = { payerAmountSat: number feesSat: number } -export interface PrepareSendRequest { +export type PrepareSendRequest = { invoice: string } -export interface PrepareSendResponse { - id: string - payerAmountSat: number - receiverAmountSat: number - totalFees: number - fundingAddress: string +export type PrepareSendResponse = { invoice: string + feesSat: number } -export interface ReceivePaymentResponse { +export type ReceivePaymentResponse = { id: string invoice: string } -export interface RestoreRequest { +export type RestoreRequest = { backupPath?: string } -export interface SendPaymentResponse { +export type SendPaymentResponse = { txid: string } From 177127d3764fb2c35d9e6f07e825d491a7aa2c44 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:41:06 +0200 Subject: [PATCH 25/29] Update flutter bindings --- .../include/breez_liquid_sdk.h | 14 +- lib/core/src/frb/bridge.io.rs | 56 +-- lib/core/src/frb/bridge.rs | 118 +++--- packages/dart/lib/src/bindings.dart | 19 - packages/dart/lib/src/error.dart | 16 +- packages/dart/lib/src/error.freezed.dart | 378 +++++++++--------- packages/dart/lib/src/frb_generated.dart | 251 +++--------- packages/dart/lib/src/frb_generated.io.dart | 167 ++------ packages/dart/lib/src/model.dart | 35 +- 9 files changed, 390 insertions(+), 664 deletions(-) diff --git a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h index 6e04c2f..b7f3b17 100644 --- a/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h +++ b/lib/bindings/bindings-flutter/breez_liquid_sdk/include/breez_liquid_sdk.h @@ -18,7 +18,7 @@ typedef struct _Dart_Handle* Dart_Handle; * Claim tx feerate, in sats per vbyte. * Since the Liquid blocks are consistently empty for now, we hardcode the minimum feerate. */ -#define LIQUID_CLAIM_TX_FEERATE 0.1 +#define LIQUID_CLAIM_TX_FEERATE_MSAT 100.0 typedef struct wire_cst_list_prim_u_8_strict { uint8_t *ptr; @@ -76,11 +76,6 @@ typedef struct wire_cst_get_info_response { struct wire_cst_list_prim_u_8_strict *pubkey; } wire_cst_get_info_response; -typedef struct wire_cst_PaymentError_Refunded { - struct wire_cst_list_prim_u_8_strict *err; - struct wire_cst_list_prim_u_8_strict *txid; -} wire_cst_PaymentError_Refunded; - typedef struct wire_cst_PaymentError_Generic { struct wire_cst_list_prim_u_8_strict *err; } wire_cst_PaymentError_Generic; @@ -89,6 +84,11 @@ typedef struct wire_cst_PaymentError_LwkError { struct wire_cst_list_prim_u_8_strict *err; } wire_cst_PaymentError_LwkError; +typedef struct wire_cst_PaymentError_Refunded { + struct wire_cst_list_prim_u_8_strict *err; + struct wire_cst_list_prim_u_8_strict *txid; +} wire_cst_PaymentError_Refunded; + typedef struct wire_cst_PaymentError_SendError { struct wire_cst_list_prim_u_8_strict *err; } wire_cst_PaymentError_SendError; @@ -98,9 +98,9 @@ typedef struct wire_cst_PaymentError_SignerError { } wire_cst_PaymentError_SignerError; typedef union PaymentErrorKind { - struct wire_cst_PaymentError_Refunded Refunded; struct wire_cst_PaymentError_Generic Generic; struct wire_cst_PaymentError_LwkError LwkError; + struct wire_cst_PaymentError_Refunded Refunded; struct wire_cst_PaymentError_SendError SendError; struct wire_cst_PaymentError_SignerError SignerError; } PaymentErrorKind; diff --git a/lib/core/src/frb/bridge.io.rs b/lib/core/src/frb/bridge.io.rs index caf4a19..ce276ad 100644 --- a/lib/core/src/frb/bridge.io.rs +++ b/lib/core/src/frb/bridge.io.rs @@ -153,33 +153,33 @@ impl CstDecode for wire_cst_payment_error { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::error::PaymentError { match self.tag { - 0 => crate::error::PaymentError::AmountOutOfRange, - 1 => crate::error::PaymentError::InvalidOrExpiredFees, - 2 => crate::error::PaymentError::InsufficientFunds, - 3 => crate::error::PaymentError::AlreadyClaimed, - 4 => { + 0 => crate::error::PaymentError::AlreadyClaimed, + 1 => crate::error::PaymentError::AmountOutOfRange, + 2 => { + let ans = unsafe { self.kind.Generic }; + crate::error::PaymentError::Generic { + err: ans.err.cst_decode(), + } + } + 3 => crate::error::PaymentError::InvalidOrExpiredFees, + 4 => crate::error::PaymentError::InsufficientFunds, + 5 => crate::error::PaymentError::InvalidInvoice, + 6 => crate::error::PaymentError::InvalidPreimage, + 7 => { + let ans = unsafe { self.kind.LwkError }; + crate::error::PaymentError::LwkError { + err: ans.err.cst_decode(), + } + } + 8 => crate::error::PaymentError::PairsNotFound, + 9 => crate::error::PaymentError::PersistError, + 10 => { let ans = unsafe { self.kind.Refunded }; crate::error::PaymentError::Refunded { err: ans.err.cst_decode(), txid: ans.txid.cst_decode(), } } - 5 => { - let ans = unsafe { self.kind.Generic }; - crate::error::PaymentError::Generic { - err: ans.err.cst_decode(), - } - } - 6 => crate::error::PaymentError::InvalidInvoice, - 7 => crate::error::PaymentError::InvalidPreimage, - 8 => { - let ans = unsafe { self.kind.LwkError }; - crate::error::PaymentError::LwkError { - err: ans.err.cst_decode(), - } - } - 9 => crate::error::PaymentError::PairsNotFound, - 10 => crate::error::PaymentError::PersistError, 11 => { let ans = unsafe { self.kind.SendError }; crate::error::PaymentError::SendError { @@ -619,21 +619,15 @@ pub struct wire_cst_payment_error { #[repr(C)] #[derive(Clone, Copy)] pub union PaymentErrorKind { - Refunded: wire_cst_PaymentError_Refunded, Generic: wire_cst_PaymentError_Generic, LwkError: wire_cst_PaymentError_LwkError, + Refunded: wire_cst_PaymentError_Refunded, SendError: wire_cst_PaymentError_SendError, SignerError: wire_cst_PaymentError_SignerError, nil__: (), } #[repr(C)] #[derive(Clone, Copy)] -pub struct wire_cst_PaymentError_Refunded { - err: *mut wire_cst_list_prim_u_8_strict, - txid: *mut wire_cst_list_prim_u_8_strict, -} -#[repr(C)] -#[derive(Clone, Copy)] pub struct wire_cst_PaymentError_Generic { err: *mut wire_cst_list_prim_u_8_strict, } @@ -644,6 +638,12 @@ pub struct wire_cst_PaymentError_LwkError { } #[repr(C)] #[derive(Clone, Copy)] +pub struct wire_cst_PaymentError_Refunded { + err: *mut wire_cst_list_prim_u_8_strict, + txid: *mut wire_cst_list_prim_u_8_strict, +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct wire_cst_PaymentError_SendError { err: *mut wire_cst_list_prim_u_8_strict, } diff --git a/lib/core/src/frb/bridge.rs b/lib/core/src/frb/bridge.rs index af537e7..25a1212 100644 --- a/lib/core/src/frb/bridge.rs +++ b/lib/core/src/frb/bridge.rs @@ -416,18 +416,38 @@ impl SseDecode for crate::error::PaymentError { let mut tag_ = ::sse_decode(deserializer); match tag_ { 0 => { - return crate::error::PaymentError::AmountOutOfRange; - } - 1 => { - return crate::error::PaymentError::InvalidOrExpiredFees; - } - 2 => { - return crate::error::PaymentError::InsufficientFunds; - } - 3 => { return crate::error::PaymentError::AlreadyClaimed; } + 1 => { + return crate::error::PaymentError::AmountOutOfRange; + } + 2 => { + let mut var_err = ::sse_decode(deserializer); + return crate::error::PaymentError::Generic { err: var_err }; + } + 3 => { + return crate::error::PaymentError::InvalidOrExpiredFees; + } 4 => { + return crate::error::PaymentError::InsufficientFunds; + } + 5 => { + return crate::error::PaymentError::InvalidInvoice; + } + 6 => { + return crate::error::PaymentError::InvalidPreimage; + } + 7 => { + let mut var_err = ::sse_decode(deserializer); + return crate::error::PaymentError::LwkError { err: var_err }; + } + 8 => { + return crate::error::PaymentError::PairsNotFound; + } + 9 => { + return crate::error::PaymentError::PersistError; + } + 10 => { let mut var_err = ::sse_decode(deserializer); let mut var_txid = ::sse_decode(deserializer); return crate::error::PaymentError::Refunded { @@ -435,26 +455,6 @@ impl SseDecode for crate::error::PaymentError { txid: var_txid, }; } - 5 => { - let mut var_err = ::sse_decode(deserializer); - return crate::error::PaymentError::Generic { err: var_err }; - } - 6 => { - return crate::error::PaymentError::InvalidInvoice; - } - 7 => { - return crate::error::PaymentError::InvalidPreimage; - } - 8 => { - let mut var_err = ::sse_decode(deserializer); - return crate::error::PaymentError::LwkError { err: var_err }; - } - 9 => { - return crate::error::PaymentError::PairsNotFound; - } - 10 => { - return crate::error::PaymentError::PersistError; - } 11 => { let mut var_err = ::sse_decode(deserializer); return crate::error::PaymentError::SendError { err: var_err }; @@ -701,26 +701,26 @@ impl flutter_rust_bridge::IntoIntoDart for crate::model:: impl flutter_rust_bridge::IntoDart for crate::error::PaymentError { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { - crate::error::PaymentError::AmountOutOfRange => [0.into_dart()].into_dart(), - crate::error::PaymentError::InvalidOrExpiredFees => [1.into_dart()].into_dart(), - crate::error::PaymentError::InsufficientFunds => [2.into_dart()].into_dart(), - crate::error::PaymentError::AlreadyClaimed => [3.into_dart()].into_dart(), + crate::error::PaymentError::AlreadyClaimed => [0.into_dart()].into_dart(), + crate::error::PaymentError::AmountOutOfRange => [1.into_dart()].into_dart(), + crate::error::PaymentError::Generic { err } => { + [2.into_dart(), err.into_into_dart().into_dart()].into_dart() + } + crate::error::PaymentError::InvalidOrExpiredFees => [3.into_dart()].into_dart(), + crate::error::PaymentError::InsufficientFunds => [4.into_dart()].into_dart(), + crate::error::PaymentError::InvalidInvoice => [5.into_dart()].into_dart(), + crate::error::PaymentError::InvalidPreimage => [6.into_dart()].into_dart(), + crate::error::PaymentError::LwkError { err } => { + [7.into_dart(), err.into_into_dart().into_dart()].into_dart() + } + crate::error::PaymentError::PairsNotFound => [8.into_dart()].into_dart(), + crate::error::PaymentError::PersistError => [9.into_dart()].into_dart(), crate::error::PaymentError::Refunded { err, txid } => [ - 4.into_dart(), + 10.into_dart(), err.into_into_dart().into_dart(), txid.into_into_dart().into_dart(), ] .into_dart(), - crate::error::PaymentError::Generic { err } => { - [5.into_dart(), err.into_into_dart().into_dart()].into_dart() - } - crate::error::PaymentError::InvalidInvoice => [6.into_dart()].into_dart(), - crate::error::PaymentError::InvalidPreimage => [7.into_dart()].into_dart(), - crate::error::PaymentError::LwkError { err } => { - [8.into_dart(), err.into_into_dart().into_dart()].into_dart() - } - crate::error::PaymentError::PairsNotFound => [9.into_dart()].into_dart(), - crate::error::PaymentError::PersistError => [10.into_dart()].into_dart(), crate::error::PaymentError::SendError { err } => { [11.into_dart(), err.into_into_dart().into_dart()].into_dart() } @@ -1016,42 +1016,42 @@ impl SseEncode for crate::error::PaymentError { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { match self { - crate::error::PaymentError::AmountOutOfRange => { + crate::error::PaymentError::AlreadyClaimed => { ::sse_encode(0, serializer); } - crate::error::PaymentError::InvalidOrExpiredFees => { + crate::error::PaymentError::AmountOutOfRange => { ::sse_encode(1, serializer); } - crate::error::PaymentError::InsufficientFunds => { + crate::error::PaymentError::Generic { err } => { ::sse_encode(2, serializer); + ::sse_encode(err, serializer); } - crate::error::PaymentError::AlreadyClaimed => { + crate::error::PaymentError::InvalidOrExpiredFees => { ::sse_encode(3, serializer); } - crate::error::PaymentError::Refunded { err, txid } => { + crate::error::PaymentError::InsufficientFunds => { ::sse_encode(4, serializer); - ::sse_encode(err, serializer); - ::sse_encode(txid, serializer); - } - crate::error::PaymentError::Generic { err } => { - ::sse_encode(5, serializer); - ::sse_encode(err, serializer); } crate::error::PaymentError::InvalidInvoice => { - ::sse_encode(6, serializer); + ::sse_encode(5, serializer); } crate::error::PaymentError::InvalidPreimage => { - ::sse_encode(7, serializer); + ::sse_encode(6, serializer); } crate::error::PaymentError::LwkError { err } => { - ::sse_encode(8, serializer); + ::sse_encode(7, serializer); ::sse_encode(err, serializer); } crate::error::PaymentError::PairsNotFound => { - ::sse_encode(9, serializer); + ::sse_encode(8, serializer); } crate::error::PaymentError::PersistError => { + ::sse_encode(9, serializer); + } + crate::error::PaymentError::Refunded { err, txid } => { ::sse_encode(10, serializer); + ::sse_encode(err, serializer); + ::sse_encode(txid, serializer); } crate::error::PaymentError::SendError { err } => { ::sse_encode(11, serializer); diff --git a/packages/dart/lib/src/bindings.dart b/packages/dart/lib/src/bindings.dart index 1d9e33b..0648bb1 100644 --- a/packages/dart/lib/src/bindings.dart +++ b/packages/dart/lib/src/bindings.dart @@ -29,28 +29,9 @@ Future receivePayment({required PrepareReceiveResponse r Future> listPayments({required bool withScan, required bool includePending, dynamic hint}) => RustLib.instance.api.listPayments(withScan: withScan, includePending: includePending, hint: hint); -Future recoverFunds({required LBtcReverseRecovery recovery, dynamic hint}) => - RustLib.instance.api.recoverFunds(recovery: recovery, hint: hint); - Future emptyWalletCache({dynamic hint}) => RustLib.instance.api.emptyWalletCache(hint: hint); Future backup({dynamic hint}) => RustLib.instance.api.backup(hint: hint); Future restore({required RestoreRequest req, dynamic hint}) => RustLib.instance.api.restore(req: req, hint: hint); - -// Rust type: RustOpaqueNom> -@sealed -class LBtcReverseRecovery extends RustOpaque { - LBtcReverseRecovery.dcoDecode(List wire) : super.dcoDecode(wire, _kStaticData); - - LBtcReverseRecovery.sseDecode(int ptr, int externalSizeOnNative) - : super.sseDecode(ptr, externalSizeOnNative, _kStaticData); - - static final _kStaticData = RustArcStaticData( - rustArcIncrementStrongCount: RustLib.instance.api.rust_arc_increment_strong_count_LBtcReverseRecovery, - rustArcDecrementStrongCount: RustLib.instance.api.rust_arc_decrement_strong_count_LBtcReverseRecovery, - rustArcDecrementStrongCountPtr: - RustLib.instance.api.rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr, - ); -} diff --git a/packages/dart/lib/src/error.dart b/packages/dart/lib/src/error.dart index 31537c4..7649c98 100644 --- a/packages/dart/lib/src/error.dart +++ b/packages/dart/lib/src/error.dart @@ -12,19 +12,13 @@ part 'error.freezed.dart'; sealed class PaymentError with _$PaymentError implements FrbException { const PaymentError._(); - const factory PaymentError.amountOutOfRange() = PaymentError_AmountOutOfRange; - const factory PaymentError.invalidOrExpiredFees() = - PaymentError_InvalidOrExpiredFees; - const factory PaymentError.insufficientFunds() = - PaymentError_InsufficientFunds; const factory PaymentError.alreadyClaimed() = PaymentError_AlreadyClaimed; - const factory PaymentError.refunded({ - required String err, - required String txid, - }) = PaymentError_Refunded; + const factory PaymentError.amountOutOfRange() = PaymentError_AmountOutOfRange; const factory PaymentError.generic({ required String err, }) = PaymentError_Generic; + const factory PaymentError.invalidOrExpiredFees() = PaymentError_InvalidOrExpiredFees; + const factory PaymentError.insufficientFunds() = PaymentError_InsufficientFunds; const factory PaymentError.invalidInvoice() = PaymentError_InvalidInvoice; const factory PaymentError.invalidPreimage() = PaymentError_InvalidPreimage; const factory PaymentError.lwkError({ @@ -32,6 +26,10 @@ sealed class PaymentError with _$PaymentError implements FrbException { }) = PaymentError_LwkError; const factory PaymentError.pairsNotFound() = PaymentError_PairsNotFound; const factory PaymentError.persistError() = PaymentError_PersistError; + const factory PaymentError.refunded({ + required String err, + required String txid, + }) = PaymentError_Refunded; const factory PaymentError.sendError({ required String err, }) = PaymentError_SendError; diff --git a/packages/dart/lib/src/error.freezed.dart b/packages/dart/lib/src/error.freezed.dart index ff5179d..95f81a5 100644 --- a/packages/dart/lib/src/error.freezed.dart +++ b/packages/dart/lib/src/error.freezed.dart @@ -33,141 +33,6 @@ class _$PaymentErrorCopyWithImpl<$Res, $Val extends PaymentError> implements $Pa final $Res Function($Val) _then; } -/// @nodoc -abstract class _$$PaymentError_AmountOutOfRangeImplCopyWith<$Res> { - factory _$$PaymentError_AmountOutOfRangeImplCopyWith(_$PaymentError_AmountOutOfRangeImpl value, - $Res Function(_$PaymentError_AmountOutOfRangeImpl) then) = - __$$PaymentError_AmountOutOfRangeImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$PaymentError_AmountOutOfRangeImplCopyWithImpl<$Res> - extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_AmountOutOfRangeImpl> - implements _$$PaymentError_AmountOutOfRangeImplCopyWith<$Res> { - __$$PaymentError_AmountOutOfRangeImplCopyWithImpl( - _$PaymentError_AmountOutOfRangeImpl _value, $Res Function(_$PaymentError_AmountOutOfRangeImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$PaymentError_AmountOutOfRangeImpl extends PaymentError_AmountOutOfRange { - const _$PaymentError_AmountOutOfRangeImpl() : super._(); - - @override - String toString() { - return 'PaymentError.amountOutOfRange()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$PaymentError_AmountOutOfRangeImpl); - } - - @override - int get hashCode => runtimeType.hashCode; -} - -abstract class PaymentError_AmountOutOfRange extends PaymentError { - const factory PaymentError_AmountOutOfRange() = _$PaymentError_AmountOutOfRangeImpl; - const PaymentError_AmountOutOfRange._() : super._(); -} - -/// @nodoc -abstract class _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { - factory _$$PaymentError_InvalidOrExpiredFeesImplCopyWith( - _$PaymentError_InvalidOrExpiredFeesImpl value, - $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) then) = - __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res> - extends _$PaymentErrorCopyWithImpl<$Res, - _$PaymentError_InvalidOrExpiredFeesImpl> - implements _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { - __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl( - _$PaymentError_InvalidOrExpiredFeesImpl _value, - $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$PaymentError_InvalidOrExpiredFeesImpl - extends PaymentError_InvalidOrExpiredFees { - const _$PaymentError_InvalidOrExpiredFeesImpl() : super._(); - - @override - String toString() { - return 'PaymentError.invalidOrExpiredFees()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PaymentError_InvalidOrExpiredFeesImpl); - } - - @override - int get hashCode => runtimeType.hashCode; -} - -abstract class PaymentError_InvalidOrExpiredFees extends PaymentError { - const factory PaymentError_InvalidOrExpiredFees() = - _$PaymentError_InvalidOrExpiredFeesImpl; - const PaymentError_InvalidOrExpiredFees._() : super._(); -} - -/// @nodoc -abstract class _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { - factory _$$PaymentError_InsufficientFundsImplCopyWith( - _$PaymentError_InsufficientFundsImpl value, - $Res Function(_$PaymentError_InsufficientFundsImpl) then) = - __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res> - extends _$PaymentErrorCopyWithImpl<$Res, - _$PaymentError_InsufficientFundsImpl> - implements _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { - __$$PaymentError_InsufficientFundsImplCopyWithImpl( - _$PaymentError_InsufficientFundsImpl _value, - $Res Function(_$PaymentError_InsufficientFundsImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$PaymentError_InsufficientFundsImpl - extends PaymentError_InsufficientFunds { - const _$PaymentError_InsufficientFundsImpl() : super._(); - - @override - String toString() { - return 'PaymentError.insufficientFunds()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PaymentError_InsufficientFundsImpl); - } - - @override - int get hashCode => runtimeType.hashCode; -} - -abstract class PaymentError_InsufficientFunds extends PaymentError { - const factory PaymentError_InsufficientFunds() = - _$PaymentError_InsufficientFundsImpl; - const PaymentError_InsufficientFunds._() : super._(); -} - /// @nodoc abstract class _$$PaymentError_AlreadyClaimedImplCopyWith<$Res> { factory _$$PaymentError_AlreadyClaimedImplCopyWith( @@ -210,89 +75,44 @@ abstract class PaymentError_AlreadyClaimed extends PaymentError { } /// @nodoc -abstract class _$$PaymentError_RefundedImplCopyWith<$Res> { - factory _$$PaymentError_RefundedImplCopyWith( - _$PaymentError_RefundedImpl value, - $Res Function(_$PaymentError_RefundedImpl) then) = - __$$PaymentError_RefundedImplCopyWithImpl<$Res>; - @useResult - $Res call({String err, String txid}); +abstract class _$$PaymentError_AmountOutOfRangeImplCopyWith<$Res> { + factory _$$PaymentError_AmountOutOfRangeImplCopyWith(_$PaymentError_AmountOutOfRangeImpl value, + $Res Function(_$PaymentError_AmountOutOfRangeImpl) then) = + __$$PaymentError_AmountOutOfRangeImplCopyWithImpl<$Res>; } /// @nodoc -class __$$PaymentError_RefundedImplCopyWithImpl<$Res> - extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_RefundedImpl> - implements _$$PaymentError_RefundedImplCopyWith<$Res> { - __$$PaymentError_RefundedImplCopyWithImpl(_$PaymentError_RefundedImpl _value, - $Res Function(_$PaymentError_RefundedImpl) _then) +class __$$PaymentError_AmountOutOfRangeImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_AmountOutOfRangeImpl> + implements _$$PaymentError_AmountOutOfRangeImplCopyWith<$Res> { + __$$PaymentError_AmountOutOfRangeImplCopyWithImpl( + _$PaymentError_AmountOutOfRangeImpl _value, $Res Function(_$PaymentError_AmountOutOfRangeImpl) _then) : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? err = null, - Object? txid = null, - }) { - return _then(_$PaymentError_RefundedImpl( - err: null == err - ? _value.err - : err // ignore: cast_nullable_to_non_nullable - as String, - txid: null == txid - ? _value.txid - : txid // ignore: cast_nullable_to_non_nullable - as String, - )); - } } /// @nodoc -class _$PaymentError_RefundedImpl extends PaymentError_Refunded { - const _$PaymentError_RefundedImpl({required this.err, required this.txid}) - : super._(); - - @override - final String err; - @override - final String txid; +class _$PaymentError_AmountOutOfRangeImpl extends PaymentError_AmountOutOfRange { + const _$PaymentError_AmountOutOfRangeImpl() : super._(); @override String toString() { - return 'PaymentError.refunded(err: $err, txid: $txid)'; + return 'PaymentError.amountOutOfRange()'; } @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PaymentError_RefundedImpl && - (identical(other.err, err) || other.err == err) && - (identical(other.txid, txid) || other.txid == txid)); + (other.runtimeType == runtimeType && other is _$PaymentError_AmountOutOfRangeImpl); } @override - int get hashCode => Object.hash(runtimeType, err, txid); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> - get copyWith => __$$PaymentError_RefundedImplCopyWithImpl< - _$PaymentError_RefundedImpl>(this, _$identity); + int get hashCode => runtimeType.hashCode; } -abstract class PaymentError_Refunded extends PaymentError { - const factory PaymentError_Refunded( - {required final String err, - required final String txid}) = _$PaymentError_RefundedImpl; - const PaymentError_Refunded._() : super._(); - - String get err; - String get txid; - @JsonKey(ignore: true) - _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> - get copyWith => throw _privateConstructorUsedError; +abstract class PaymentError_AmountOutOfRange extends PaymentError { + const factory PaymentError_AmountOutOfRange() = _$PaymentError_AmountOutOfRangeImpl; + const PaymentError_AmountOutOfRange._() : super._(); } /// @nodoc @@ -367,6 +187,88 @@ abstract class PaymentError_Generic extends PaymentError { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { + factory _$$PaymentError_InvalidOrExpiredFeesImplCopyWith(_$PaymentError_InvalidOrExpiredFeesImpl value, + $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) then) = + __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_InvalidOrExpiredFeesImpl> + implements _$$PaymentError_InvalidOrExpiredFeesImplCopyWith<$Res> { + __$$PaymentError_InvalidOrExpiredFeesImplCopyWithImpl(_$PaymentError_InvalidOrExpiredFeesImpl _value, + $Res Function(_$PaymentError_InvalidOrExpiredFeesImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$PaymentError_InvalidOrExpiredFeesImpl extends PaymentError_InvalidOrExpiredFees { + const _$PaymentError_InvalidOrExpiredFeesImpl() : super._(); + + @override + String toString() { + return 'PaymentError.invalidOrExpiredFees()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$PaymentError_InvalidOrExpiredFeesImpl); + } + + @override + int get hashCode => runtimeType.hashCode; +} + +abstract class PaymentError_InvalidOrExpiredFees extends PaymentError { + const factory PaymentError_InvalidOrExpiredFees() = _$PaymentError_InvalidOrExpiredFeesImpl; + const PaymentError_InvalidOrExpiredFees._() : super._(); +} + +/// @nodoc +abstract class _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { + factory _$$PaymentError_InsufficientFundsImplCopyWith(_$PaymentError_InsufficientFundsImpl value, + $Res Function(_$PaymentError_InsufficientFundsImpl) then) = + __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$PaymentError_InsufficientFundsImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_InsufficientFundsImpl> + implements _$$PaymentError_InsufficientFundsImplCopyWith<$Res> { + __$$PaymentError_InsufficientFundsImplCopyWithImpl( + _$PaymentError_InsufficientFundsImpl _value, $Res Function(_$PaymentError_InsufficientFundsImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$PaymentError_InsufficientFundsImpl extends PaymentError_InsufficientFunds { + const _$PaymentError_InsufficientFundsImpl() : super._(); + + @override + String toString() { + return 'PaymentError.insufficientFunds()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$PaymentError_InsufficientFundsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; +} + +abstract class PaymentError_InsufficientFunds extends PaymentError { + const factory PaymentError_InsufficientFunds() = _$PaymentError_InsufficientFundsImpl; + const PaymentError_InsufficientFunds._() : super._(); +} + /// @nodoc abstract class _$$PaymentError_InvalidInvoiceImplCopyWith<$Res> { factory _$$PaymentError_InvalidInvoiceImplCopyWith( @@ -603,6 +505,88 @@ abstract class PaymentError_PersistError extends PaymentError { const PaymentError_PersistError._() : super._(); } +/// @nodoc +abstract class _$$PaymentError_RefundedImplCopyWith<$Res> { + factory _$$PaymentError_RefundedImplCopyWith( + _$PaymentError_RefundedImpl value, $Res Function(_$PaymentError_RefundedImpl) then) = + __$$PaymentError_RefundedImplCopyWithImpl<$Res>; + @useResult + $Res call({String err, String txid}); +} + +/// @nodoc +class __$$PaymentError_RefundedImplCopyWithImpl<$Res> + extends _$PaymentErrorCopyWithImpl<$Res, _$PaymentError_RefundedImpl> + implements _$$PaymentError_RefundedImplCopyWith<$Res> { + __$$PaymentError_RefundedImplCopyWithImpl( + _$PaymentError_RefundedImpl _value, $Res Function(_$PaymentError_RefundedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? err = null, + Object? txid = null, + }) { + return _then(_$PaymentError_RefundedImpl( + err: null == err + ? _value.err + : err // ignore: cast_nullable_to_non_nullable + as String, + txid: null == txid + ? _value.txid + : txid // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$PaymentError_RefundedImpl extends PaymentError_Refunded { + const _$PaymentError_RefundedImpl({required this.err, required this.txid}) : super._(); + + @override + final String err; + @override + final String txid; + + @override + String toString() { + return 'PaymentError.refunded(err: $err, txid: $txid)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PaymentError_RefundedImpl && + (identical(other.err, err) || other.err == err) && + (identical(other.txid, txid) || other.txid == txid)); + } + + @override + int get hashCode => Object.hash(runtimeType, err, txid); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> get copyWith => + __$$PaymentError_RefundedImplCopyWithImpl<_$PaymentError_RefundedImpl>(this, _$identity); +} + +abstract class PaymentError_Refunded extends PaymentError { + const factory PaymentError_Refunded({required final String err, required final String txid}) = + _$PaymentError_RefundedImpl; + const PaymentError_Refunded._() : super._(); + + String get err; + String get txid; + @JsonKey(ignore: true) + _$$PaymentError_RefundedImplCopyWith<_$PaymentError_RefundedImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$PaymentError_SendErrorImplCopyWith<$Res> { factory _$$PaymentError_SendErrorImplCopyWith( diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index a1773a6..08158dd 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -53,7 +53,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.0.0-dev.33'; @override - int get rustContentHash => -1225779344; + int get rustContentHash => -451265040; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( stem: 'breez_liquid_sdk', @@ -79,17 +79,9 @@ abstract class RustLibApi extends BaseApi { Future receivePayment({required PrepareReceiveResponse req, dynamic hint}); - Future recoverFunds({required LBtcReverseRecovery recovery, dynamic hint}); - Future restore({required RestoreRequest req, dynamic hint}); Future sendPayment({required PrepareSendResponse req, dynamic hint}); - - RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_LBtcReverseRecovery; - - RustArcDecrementStrongCountFnType get rust_arc_decrement_strong_count_LBtcReverseRecovery; - - CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr; } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -283,31 +275,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["req"], ); - @override - Future recoverFunds({required LBtcReverseRecovery recovery, dynamic hint}) { - return handler.executeNormal(NormalTask( - callFfi: (port_) { - var arg0 = - cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - recovery); - return wire.wire_recover_funds(port_, arg0); - }, - codec: DcoCodec( - decodeSuccessData: dco_decode_String, - decodeErrorData: dco_decode_AnyhowException, - ), - constMeta: kRecoverFundsConstMeta, - argValues: [recovery], - apiImpl: this, - hint: hint, - )); - } - - TaskConstMeta get kRecoverFundsConstMeta => const TaskConstMeta( - debugName: "recover_funds", - argNames: ["recovery"], - ); - @override Future restore({required RestoreRequest req, dynamic hint}) { return handler.executeNormal(NormalTask( @@ -354,34 +321,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["req"], ); - RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_LBtcReverseRecovery => wire - .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery; - - RustArcDecrementStrongCountFnType get rust_arc_decrement_strong_count_LBtcReverseRecovery => wire - .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return AnyhowException(raw as String); } - @protected - LBtcReverseRecovery - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return LBtcReverseRecovery.dcoDecode(raw as List); - } - - @protected - LBtcReverseRecovery - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return LBtcReverseRecovery.dcoDecode(raw as List); - } - @protected String dco_decode_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -543,30 +488,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Dco (DartCObject based), see doc to use other codecs switch (raw[0]) { case 0: - return PaymentError_AmountOutOfRange(); - case 1: return PaymentError_AlreadyClaimed(); + case 1: + return PaymentError_AmountOutOfRange(); case 2: return PaymentError_Generic( err: dco_decode_String(raw[1]), ); case 3: - return PaymentError_InvalidInvoice(); + return PaymentError_InvalidOrExpiredFees(); case 4: - return PaymentError_InvalidPreimage(); + return PaymentError_InsufficientFunds(); case 5: + return PaymentError_InvalidInvoice(); + case 6: + return PaymentError_InvalidPreimage(); + case 7: return PaymentError_LwkError( err: dco_decode_String(raw[1]), ); - case 6: - return PaymentError_PairsNotFound(); - case 7: - return PaymentError_PersistError(); case 8: + return PaymentError_PairsNotFound(); + case 9: + return PaymentError_PersistError(); + case 10: + return PaymentError_Refunded( + err: dco_decode_String(raw[1]), + txid: dco_decode_String(raw[2]), + ); + case 11: return PaymentError_SendError( err: dco_decode_String(raw[1]), ); - case 9: + case 12: return PaymentError_SignerError( err: dco_decode_String(raw[1]), ); @@ -595,11 +549,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareReceiveResponse dco_decode_prepare_receive_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return PrepareReceiveResponse( - pairHash: dco_decode_String(arr[0]), - payerAmountSat: dco_decode_u_64(arr[1]), - feesSat: dco_decode_u_64(arr[2]), + payerAmountSat: dco_decode_u_64(arr[0]), + feesSat: dco_decode_u_64(arr[1]), ); } @@ -617,14 +570,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendResponse dco_decode_prepare_send_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return PrepareSendResponse( - id: dco_decode_String(arr[0]), - payerAmountSat: dco_decode_u_64(arr[1]), - receiverAmountSat: dco_decode_u_64(arr[2]), - totalFees: dco_decode_u_64(arr[3]), - fundingAddress: dco_decode_String(arr[4]), - invoice: dco_decode_String(arr[5]), + invoice: dco_decode_String(arr[0]), + feesSat: dco_decode_u_64(arr[1]), ); } @@ -683,12 +632,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return; } - @protected - int dco_decode_usize(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return dcoDecodeI64OrU64(raw); - } - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -696,22 +639,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return AnyhowException(inner); } - @protected - LBtcReverseRecovery - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return LBtcReverseRecovery.sseDecode(sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - - @protected - LBtcReverseRecovery - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return LBtcReverseRecovery.sseDecode(sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - @protected String sse_decode_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -893,27 +820,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var tag_ = sse_decode_i_32(deserializer); switch (tag_) { case 0: - return PaymentError_AmountOutOfRange(); - case 1: return PaymentError_AlreadyClaimed(); + case 1: + return PaymentError_AmountOutOfRange(); case 2: var var_err = sse_decode_String(deserializer); return PaymentError_Generic(err: var_err); case 3: - return PaymentError_InvalidInvoice(); + return PaymentError_InvalidOrExpiredFees(); case 4: - return PaymentError_InvalidPreimage(); + return PaymentError_InsufficientFunds(); case 5: + return PaymentError_InvalidInvoice(); + case 6: + return PaymentError_InvalidPreimage(); + case 7: var var_err = sse_decode_String(deserializer); return PaymentError_LwkError(err: var_err); - case 6: - return PaymentError_PairsNotFound(); - case 7: - return PaymentError_PersistError(); case 8: + return PaymentError_PairsNotFound(); + case 9: + return PaymentError_PersistError(); + case 10: + var var_err = sse_decode_String(deserializer); + var var_txid = sse_decode_String(deserializer); + return PaymentError_Refunded(err: var_err, txid: var_txid); + case 11: var var_err = sse_decode_String(deserializer); return PaymentError_SendError(err: var_err); - case 9: + case 12: var var_err = sse_decode_String(deserializer); return PaymentError_SignerError(err: var_err); default: @@ -938,11 +873,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected PrepareReceiveResponse sse_decode_prepare_receive_response(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_pairHash = sse_decode_String(deserializer); var var_payerAmountSat = sse_decode_u_64(deserializer); var var_feesSat = sse_decode_u_64(deserializer); - return PrepareReceiveResponse( - pairHash: var_pairHash, payerAmountSat: var_payerAmountSat, feesSat: var_feesSat); + return PrepareReceiveResponse(payerAmountSat: var_payerAmountSat, feesSat: var_feesSat); } @protected @@ -955,19 +888,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected PrepareSendResponse sse_decode_prepare_send_response(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - var var_id = sse_decode_String(deserializer); - var var_payerAmountSat = sse_decode_u_64(deserializer); - var var_receiverAmountSat = sse_decode_u_64(deserializer); - var var_totalFees = sse_decode_u_64(deserializer); - var var_fundingAddress = sse_decode_String(deserializer); var var_invoice = sse_decode_String(deserializer); - return PrepareSendResponse( - id: var_id, - payerAmountSat: var_payerAmountSat, - receiverAmountSat: var_receiverAmountSat, - totalFees: var_totalFees, - fundingAddress: var_fundingAddress, - invoice: var_invoice); + var var_feesSat = sse_decode_u_64(deserializer); + return PrepareSendResponse(invoice: var_invoice, feesSat: var_feesSat); } @protected @@ -1015,28 +938,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs } - @protected - int sse_decode_usize(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getUint64(); - } - - @protected - int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw) { - // Codec=Cst (C-struct based), see doc to use other codecs -// ignore: invalid_use_of_internal_member - return raw.cstEncode(move: true); - } - - @protected - int cst_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw) { - // Codec=Cst (C-struct based), see doc to use other codecs -// ignore: invalid_use_of_internal_member - return raw.cstEncode(); - } - @protected bool cst_encode_bool(bool raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1079,33 +980,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw; } - @protected - int cst_encode_usize(int raw) { - // Codec=Cst (C-struct based), see doc to use other codecs - return raw; - } - @protected void sse_encode_AnyhowException(AnyhowException self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs throw UnimplementedError('Unreachable ()'); } - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize(self.sseEncode(move: true), serializer); - } - - @protected - void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize(self.sseEncode(move: null), serializer); - } - @protected void sse_encode_String(String self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1267,29 +1147,37 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_payment_error(PaymentError self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs switch (self) { - case PaymentError_AmountOutOfRange(): - sse_encode_i_32(0, serializer); case PaymentError_AlreadyClaimed(): + sse_encode_i_32(0, serializer); + case PaymentError_AmountOutOfRange(): sse_encode_i_32(1, serializer); case PaymentError_Generic(err: final err): sse_encode_i_32(2, serializer); sse_encode_String(err, serializer); - case PaymentError_InvalidInvoice(): + case PaymentError_InvalidOrExpiredFees(): sse_encode_i_32(3, serializer); - case PaymentError_InvalidPreimage(): + case PaymentError_InsufficientFunds(): sse_encode_i_32(4, serializer); - case PaymentError_LwkError(err: final err): + case PaymentError_InvalidInvoice(): sse_encode_i_32(5, serializer); + case PaymentError_InvalidPreimage(): + sse_encode_i_32(6, serializer); + case PaymentError_LwkError(err: final err): + sse_encode_i_32(7, serializer); sse_encode_String(err, serializer); case PaymentError_PairsNotFound(): - sse_encode_i_32(6, serializer); - case PaymentError_PersistError(): - sse_encode_i_32(7, serializer); - case PaymentError_SendError(err: final err): sse_encode_i_32(8, serializer); + case PaymentError_PersistError(): + sse_encode_i_32(9, serializer); + case PaymentError_Refunded(err: final err, txid: final txid): + sse_encode_i_32(10, serializer); + sse_encode_String(err, serializer); + sse_encode_String(txid, serializer); + case PaymentError_SendError(err: final err): + sse_encode_i_32(11, serializer); sse_encode_String(err, serializer); case PaymentError_SignerError(err: final err): - sse_encode_i_32(9, serializer); + sse_encode_i_32(12, serializer); sse_encode_String(err, serializer); } } @@ -1309,7 +1197,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected void sse_encode_prepare_receive_response(PrepareReceiveResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String(self.pairHash, serializer); sse_encode_u_64(self.payerAmountSat, serializer); sse_encode_u_64(self.feesSat, serializer); } @@ -1323,12 +1210,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @protected void sse_encode_prepare_send_response(PrepareSendResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String(self.id, serializer); - sse_encode_u_64(self.payerAmountSat, serializer); - sse_encode_u_64(self.receiverAmountSat, serializer); - sse_encode_u_64(self.totalFees, serializer); - sse_encode_String(self.fundingAddress, serializer); sse_encode_String(self.invoice, serializer); + sse_encode_u_64(self.feesSat, serializer); } @protected @@ -1372,10 +1255,4 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs } - - @protected - void sse_encode_usize(int self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putUint64(self); - } } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 4e7eef8..4dfa98e 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -20,22 +20,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); - CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_LBtcReverseRecoveryPtr => wire - ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw); - @protected - LBtcReverseRecovery - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw); - - @protected - LBtcReverseRecovery - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - dynamic raw); - @protected String dco_decode_String(dynamic raw); @@ -141,22 +128,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void dco_decode_unit(dynamic raw); - @protected - int dco_decode_usize(dynamic raw); - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); - @protected - LBtcReverseRecovery - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer); - - @protected - LBtcReverseRecovery - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - SseDeserializer deserializer); - @protected String sse_decode_String(SseDeserializer deserializer); @@ -262,9 +236,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_decode_unit(SseDeserializer deserializer); - @protected - int sse_decode_usize(SseDeserializer deserializer); - @protected ffi.Pointer cst_encode_AnyhowException(AnyhowException raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -463,11 +434,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_payment_error(PaymentError apiObj, wire_cst_payment_error wireObj) { - if (apiObj is PaymentError_AmountOutOfRange) { + if (apiObj is PaymentError_AlreadyClaimed) { wireObj.tag = 0; return; } - if (apiObj is PaymentError_AlreadyClaimed) { + if (apiObj is PaymentError_AmountOutOfRange) { wireObj.tag = 1; return; } @@ -477,37 +448,53 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.kind.Generic.err = pre_err; return; } - if (apiObj is PaymentError_InvalidInvoice) { + if (apiObj is PaymentError_InvalidOrExpiredFees) { wireObj.tag = 3; return; } - if (apiObj is PaymentError_InvalidPreimage) { + if (apiObj is PaymentError_InsufficientFunds) { wireObj.tag = 4; return; } + if (apiObj is PaymentError_InvalidInvoice) { + wireObj.tag = 5; + return; + } + if (apiObj is PaymentError_InvalidPreimage) { + wireObj.tag = 6; + return; + } if (apiObj is PaymentError_LwkError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 5; + wireObj.tag = 7; wireObj.kind.LwkError.err = pre_err; return; } if (apiObj is PaymentError_PairsNotFound) { - wireObj.tag = 6; + wireObj.tag = 8; return; } if (apiObj is PaymentError_PersistError) { - wireObj.tag = 7; + wireObj.tag = 9; + return; + } + if (apiObj is PaymentError_Refunded) { + var pre_err = cst_encode_String(apiObj.err); + var pre_txid = cst_encode_String(apiObj.txid); + wireObj.tag = 10; + wireObj.kind.Refunded.err = pre_err; + wireObj.kind.Refunded.txid = pre_txid; return; } if (apiObj is PaymentError_SendError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 8; + wireObj.tag = 11; wireObj.kind.SendError.err = pre_err; return; } if (apiObj is PaymentError_SignerError) { var pre_err = cst_encode_String(apiObj.err); - wireObj.tag = 9; + wireObj.tag = 12; wireObj.kind.SignerError.err = pre_err; return; } @@ -522,7 +509,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_prepare_receive_response( PrepareReceiveResponse apiObj, wire_cst_prepare_receive_response wireObj) { - wireObj.pair_hash = cst_encode_String(apiObj.pairHash); wireObj.payer_amount_sat = cst_encode_u_64(apiObj.payerAmountSat); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); } @@ -536,12 +522,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_api_fill_to_wire_prepare_send_response( PrepareSendResponse apiObj, wire_cst_prepare_send_response wireObj) { - wireObj.id = cst_encode_String(apiObj.id); - wireObj.payer_amount_sat = cst_encode_u_64(apiObj.payerAmountSat); - wireObj.receiver_amount_sat = cst_encode_u_64(apiObj.receiverAmountSat); - wireObj.total_fees = cst_encode_u_64(apiObj.totalFees); - wireObj.funding_address = cst_encode_String(apiObj.fundingAddress); wireObj.invoice = cst_encode_String(apiObj.invoice); + wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); } @protected @@ -562,14 +544,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.txid = cst_encode_String(apiObj.txid); } - @protected - int cst_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw); - - @protected - int cst_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery raw); - @protected bool cst_encode_bool(bool raw); @@ -591,21 +565,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void cst_encode_unit(void raw); - @protected - int cst_encode_usize(int raw); - @protected void sse_encode_AnyhowException(AnyhowException self, SseSerializer serializer); - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer); - - @protected - void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - LBtcReverseRecovery self, SseSerializer serializer); - @protected void sse_encode_String(String self, SseSerializer serializer); @@ -710,9 +672,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_unit(void self, SseSerializer serializer); - - @protected - void sse_encode_usize(int self, SseSerializer serializer); } // Section: wire_class @@ -871,20 +830,6 @@ class RustLibWire implements BaseWire { late final _wire_receive_payment = _wire_receive_paymentPtr .asFunction)>(); - void wire_recover_funds( - int port_, - int recovery, - ) { - return _wire_recover_funds( - port_, - recovery, - ); - } - - late final _wire_recover_fundsPtr = _lookup>( - 'frbgen_breez_liquid_wire_recover_funds'); - late final _wire_recover_funds = _wire_recover_fundsPtr.asFunction(); - void wire_restore( int port_, ffi.Pointer req, @@ -917,38 +862,6 @@ class RustLibWire implements BaseWire { late final _wire_send_payment = _wire_send_paymentPtr.asFunction)>(); - void - rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - - void - rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - ffi.Pointer cst_new_box_autoadd_connect_request() { return _cst_new_box_autoadd_connect_request(); } @@ -1123,8 +1036,6 @@ final class wire_cst_prepare_send_request extends ffi.Struct { } final class wire_cst_prepare_receive_response extends ffi.Struct { - external ffi.Pointer pair_hash; - @ffi.Uint64() external int payer_amount_sat; @@ -1137,20 +1048,10 @@ final class wire_cst_restore_request extends ffi.Struct { } final class wire_cst_prepare_send_response extends ffi.Struct { - external ffi.Pointer id; - - @ffi.Uint64() - external int payer_amount_sat; - - @ffi.Uint64() - external int receiver_amount_sat; - - @ffi.Uint64() - external int total_fees; - - external ffi.Pointer funding_address; - external ffi.Pointer invoice; + + @ffi.Uint64() + external int fees_sat; } final class wire_cst_payment extends ffi.Struct { @@ -1191,6 +1092,12 @@ final class wire_cst_PaymentError_LwkError extends ffi.Struct { external ffi.Pointer err; } +final class wire_cst_PaymentError_Refunded extends ffi.Struct { + external ffi.Pointer err; + + external ffi.Pointer txid; +} + final class wire_cst_PaymentError_SendError extends ffi.Struct { external ffi.Pointer err; } @@ -1204,6 +1111,8 @@ final class PaymentErrorKind extends ffi.Union { external wire_cst_PaymentError_LwkError LwkError; + external wire_cst_PaymentError_Refunded Refunded; + external wire_cst_PaymentError_SendError SendError; external wire_cst_PaymentError_SignerError SignerError; @@ -1226,4 +1135,4 @@ final class wire_cst_send_payment_response extends ffi.Struct { external ffi.Pointer txid; } -const double LIQUID_CLAIM_TX_FEERATE = 0.1; +const double LIQUID_CLAIM_TX_FEERATE_MSAT = 100.0; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 1dc65fa..169301d 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -79,8 +79,6 @@ class Payment { final int amountSat; final int? feesSat; final PaymentType paymentType; - - /// Only for [PaymentType::PendingReceive] final String? invoice; const Payment({ @@ -141,25 +139,22 @@ class PrepareReceiveRequest { } class PrepareReceiveResponse { - final String pairHash; final int payerAmountSat; final int feesSat; const PrepareReceiveResponse({ - required this.pairHash, required this.payerAmountSat, required this.feesSat, }); @override - int get hashCode => pairHash.hashCode ^ payerAmountSat.hashCode ^ feesSat.hashCode; + int get hashCode => payerAmountSat.hashCode ^ feesSat.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareReceiveResponse && runtimeType == other.runtimeType && - pairHash == other.pairHash && payerAmountSat == other.payerAmountSat && feesSat == other.feesSat; } @@ -181,42 +176,24 @@ class PrepareSendRequest { } class PrepareSendResponse { - final String id; - final int payerAmountSat; - final int receiverAmountSat; - final int totalFees; - final String fundingAddress; final String invoice; + final int feesSat; const PrepareSendResponse({ - required this.id, - required this.payerAmountSat, - required this.receiverAmountSat, - required this.totalFees, - required this.fundingAddress, required this.invoice, + required this.feesSat, }); @override - int get hashCode => - id.hashCode ^ - payerAmountSat.hashCode ^ - receiverAmountSat.hashCode ^ - totalFees.hashCode ^ - fundingAddress.hashCode ^ - invoice.hashCode; + int get hashCode => invoice.hashCode ^ feesSat.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is PrepareSendResponse && runtimeType == other.runtimeType && - id == other.id && - payerAmountSat == other.payerAmountSat && - receiverAmountSat == other.receiverAmountSat && - totalFees == other.totalFees && - fundingAddress == other.fundingAddress && - invoice == other.invoice; + invoice == other.invoice && + feesSat == other.feesSat; } class ReceivePaymentResponse { From ce0cd99b860a2d11aa046a559b930fbbaa91bfcd Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:45:50 +0200 Subject: [PATCH 26/29] Update RN bindings --- .../breezliquidsdk/BreezLiquidSDKMapper.kt | 248 +++++++++++------- .../breezliquidsdk/BreezLiquidSDKModule.kt | 95 +++++-- packages/react-native/src/index.ts | 20 +- 3 files changed, 236 insertions(+), 127 deletions(-) diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt index ab0b438..2f04e6f 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt @@ -1,33 +1,34 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import java.io.File import java.util.* -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - + fun asConnectRequest(connectRequest: ReadableMap): ConnectRequest? { - if (!validateMandatoryFields(connectRequest, arrayOf( - "mnemonic", - "network", - ))) { + if (!validateMandatoryFields( + connectRequest, + arrayOf( + "mnemonic", + "network", + ), + ) + ) { return null } val mnemonic = connectRequest.getString("mnemonic")!! - val network = connectRequest.getString("network")?.let { asNetwork(it)}!! + val network = connectRequest.getString("network")?.let { asNetwork(it) }!! val dataDir = if (hasNonNullKey(connectRequest, "dataDir")) connectRequest.getString("dataDir") else null return ConnectRequest( mnemonic, network, - dataDir,) + dataDir, + ) } fun readableMapOf(connectRequest: ConnectRequest): ReadableMap { return readableMapOf( - "mnemonic" to connectRequest.mnemonic, - "network" to connectRequest.network.name.lowercase(), - "dataDir" to connectRequest.dataDir, + "mnemonic" to connectRequest.mnemonic, + "network" to connectRequest.network.name.lowercase(), + "dataDir" to connectRequest.dataDir, ) } @@ -35,26 +36,32 @@ fun asConnectRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asConnectRequest(value)!!) + is ReadableMap -> list.add(asConnectRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asGetInfoRequest(getInfoRequest: ReadableMap): GetInfoRequest? { - if (!validateMandatoryFields(getInfoRequest, arrayOf( - "withScan", - ))) { + if (!validateMandatoryFields( + getInfoRequest, + arrayOf( + "withScan", + ), + ) + ) { return null } val withScan = getInfoRequest.getBoolean("withScan") return GetInfoRequest( - withScan,) + withScan, + ) } fun readableMapOf(getInfoRequest: GetInfoRequest): ReadableMap { return readableMapOf( - "withScan" to getInfoRequest.withScan, + "withScan" to getInfoRequest.withScan, ) } @@ -62,30 +69,36 @@ fun asGetInfoRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoRequest(value)!!) + is ReadableMap -> list.add(asGetInfoRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { - if (!validateMandatoryFields(getInfoResponse, arrayOf( - "balanceSat", - "pubkey", - ))) { + if (!validateMandatoryFields( + getInfoResponse, + arrayOf( + "balanceSat", + "pubkey", + ), + ) + ) { return null } val balanceSat = getInfoResponse.getDouble("balanceSat").toULong() val pubkey = getInfoResponse.getString("pubkey")!! return GetInfoResponse( balanceSat, - pubkey,) + pubkey, + ) } fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap { return readableMapOf( - "balanceSat" to getInfoResponse.balanceSat, - "pubkey" to getInfoResponse.pubkey, + "balanceSat" to getInfoResponse.balanceSat, + "pubkey" to getInfoResponse.pubkey, ) } @@ -93,26 +106,32 @@ fun asGetInfoResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoResponse(value)!!) + is ReadableMap -> list.add(asGetInfoResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveRequest? { - if (!validateMandatoryFields(prepareReceiveRequest, arrayOf( - "payerAmountSat", - ))) { + if (!validateMandatoryFields( + prepareReceiveRequest, + arrayOf( + "payerAmountSat", + ), + ) + ) { return null } val payerAmountSat = prepareReceiveRequest.getDouble("payerAmountSat").toULong() return PrepareReceiveRequest( - payerAmountSat,) + payerAmountSat, + ) } fun readableMapOf(prepareReceiveRequest: PrepareReceiveRequest): ReadableMap { return readableMapOf( - "payerAmountSat" to prepareReceiveRequest.payerAmountSat, + "payerAmountSat" to prepareReceiveRequest.payerAmountSat, ) } @@ -120,30 +139,36 @@ fun asPrepareReceiveRequestList(arr: ReadableArray): List val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) + is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiveResponse? { - if (!validateMandatoryFields(prepareReceiveResponse, arrayOf( - "payerAmountSat", - "feesSat", - ))) { + if (!validateMandatoryFields( + prepareReceiveResponse, + arrayOf( + "payerAmountSat", + "feesSat", + ), + ) + ) { return null } val payerAmountSat = prepareReceiveResponse.getDouble("payerAmountSat").toULong() val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong() return PrepareReceiveResponse( payerAmountSat, - feesSat,) + feesSat, + ) } fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap { return readableMapOf( - "payerAmountSat" to prepareReceiveResponse.payerAmountSat, - "feesSat" to prepareReceiveResponse.feesSat, + "payerAmountSat" to prepareReceiveResponse.payerAmountSat, + "feesSat" to prepareReceiveResponse.feesSat, ) } @@ -151,26 +176,32 @@ fun asPrepareReceiveResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) + is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { - if (!validateMandatoryFields(prepareSendRequest, arrayOf( - "invoice", - ))) { + if (!validateMandatoryFields( + prepareSendRequest, + arrayOf( + "invoice", + ), + ) + ) { return null } val invoice = prepareSendRequest.getString("invoice")!! return PrepareSendRequest( - invoice,) + invoice, + ) } fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap { return readableMapOf( - "invoice" to prepareSendRequest.invoice, + "invoice" to prepareSendRequest.invoice, ) } @@ -178,30 +209,36 @@ fun asPrepareSendRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendRequest(value)!!) + is ReadableMap -> list.add(asPrepareSendRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse? { - if (!validateMandatoryFields(prepareSendResponse, arrayOf( - "invoice", - "feesSat", - ))) { + if (!validateMandatoryFields( + prepareSendResponse, + arrayOf( + "invoice", + "feesSat", + ), + ) + ) { return null } val invoice = prepareSendResponse.getString("invoice")!! val feesSat = prepareSendResponse.getDouble("feesSat").toULong() return PrepareSendResponse( invoice, - feesSat,) + feesSat, + ) } fun readableMapOf(prepareSendResponse: PrepareSendResponse): ReadableMap { return readableMapOf( - "invoice" to prepareSendResponse.invoice, - "feesSat" to prepareSendResponse.feesSat, + "invoice" to prepareSendResponse.invoice, + "feesSat" to prepareSendResponse.feesSat, ) } @@ -209,30 +246,36 @@ fun asPrepareSendResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendResponse(value)!!) + is ReadableMap -> list.add(asPrepareSendResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asReceivePaymentResponse(receivePaymentResponse: ReadableMap): ReceivePaymentResponse? { - if (!validateMandatoryFields(receivePaymentResponse, arrayOf( - "id", - "invoice", - ))) { + if (!validateMandatoryFields( + receivePaymentResponse, + arrayOf( + "id", + "invoice", + ), + ) + ) { return null } val id = receivePaymentResponse.getString("id")!! val invoice = receivePaymentResponse.getString("invoice")!! return ReceivePaymentResponse( id, - invoice,) + invoice, + ) } fun readableMapOf(receivePaymentResponse: ReceivePaymentResponse): ReadableMap { return readableMapOf( - "id" to receivePaymentResponse.id, - "invoice" to receivePaymentResponse.invoice, + "id" to receivePaymentResponse.id, + "invoice" to receivePaymentResponse.invoice, ) } @@ -240,25 +283,30 @@ fun asReceivePaymentResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) + is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asRestoreRequest(restoreRequest: ReadableMap): RestoreRequest? { - if (!validateMandatoryFields(restoreRequest, arrayOf( - ))) { + if (!validateMandatoryFields( + restoreRequest, + arrayOf(), + ) + ) { return null } val backupPath = if (hasNonNullKey(restoreRequest, "backupPath")) restoreRequest.getString("backupPath") else null return RestoreRequest( - backupPath,) + backupPath, + ) } fun readableMapOf(restoreRequest: RestoreRequest): ReadableMap { return readableMapOf( - "backupPath" to restoreRequest.backupPath, + "backupPath" to restoreRequest.backupPath, ) } @@ -266,26 +314,32 @@ fun asRestoreRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asRestoreRequest(value)!!) + is ReadableMap -> list.add(asRestoreRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asSendPaymentResponse(sendPaymentResponse: ReadableMap): SendPaymentResponse? { - if (!validateMandatoryFields(sendPaymentResponse, arrayOf( - "txid", - ))) { + if (!validateMandatoryFields( + sendPaymentResponse, + arrayOf( + "txid", + ), + ) + ) { return null } val txid = sendPaymentResponse.getString("txid")!! return SendPaymentResponse( - txid,) + txid, + ) } fun readableMapOf(sendPaymentResponse: SendPaymentResponse): ReadableMap { return readableMapOf( - "txid" to sendPaymentResponse.txid, + "txid" to sendPaymentResponse.txid, ) } @@ -293,7 +347,7 @@ fun asSendPaymentResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asSendPaymentResponse(value)!!) + is ReadableMap -> list.add(asSendPaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } @@ -313,7 +367,9 @@ fun asNetworkList(arr: ReadableArray): List { } } return list -}fun readableMapOf(vararg values: Pair): ReadableMap { +} + +fun readableMapOf(vararg values: Pair): ReadableMap { val map = Arguments.createMap() for ((key, value) in values) { pushToMap(map, key, value) @@ -321,11 +377,17 @@ fun asNetworkList(arr: ReadableArray): List { return map } -fun hasNonNullKey(map: ReadableMap, key: String): Boolean { +fun hasNonNullKey( + map: ReadableMap, + key: String, +): Boolean { return map.hasKey(key) && !map.isNull(key) } -fun validateMandatoryFields(map: ReadableMap, keys: Array): Boolean { +fun validateMandatoryFields( + map: ReadableMap, + keys: Array, +): Boolean { for (k in keys) { if (!hasNonNullKey(map, k)) return false } @@ -333,7 +395,10 @@ fun validateMandatoryFields(map: ReadableMap, keys: Array): Boolean { return true } -fun pushToArray(array: WritableArray, value: Any?) { +fun pushToArray( + array: WritableArray, + value: Any?, +) { when (value) { null -> array.pushNull() is Array<*> -> array.pushArray(readableArrayOf(value.asIterable())) @@ -342,7 +407,11 @@ fun pushToArray(array: WritableArray, value: Any?) { } } -fun pushToMap(map: WritableMap, key: String, value: Any?) { +fun pushToMap( + map: WritableMap, + key: String, + value: Any?, +) { when (value) { null -> map.putNull(key) is Boolean -> map.putBoolean(key, value) @@ -395,19 +464,22 @@ fun asStringList(arr: ReadableArray): List { return list } -fun errMissingMandatoryField(fieldName: String, typeName: String): String { - return "Missing mandatory field ${fieldName} for type ${typeName}" - } +fun errMissingMandatoryField( + fieldName: String, + typeName: String, +): String { + return "Missing mandatory field $fieldName for type $typeName" +} fun errUnexpectedType(typeName: String): String { - return "Unexpected type ${typeName}" - } + return "Unexpected type $typeName" +} fun errUnexpectedValue(fieldName: String): String { - return "Unexpected value for optional field ${fieldName}" + return "Unexpected value for optional field $fieldName" } fun camelToUpperSnakeCase(str: String): String { val pattern = "(?<=.)[A-Z]".toRegex() return str.replace(pattern, "_$0").uppercase() -} \ No newline at end of file +} diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt index 630eb81..e9def90 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt @@ -2,13 +2,10 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import java.io.File import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors - class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private lateinit var executor: ExecutorService private var bindingLiquidSdk: BindingLiquidSdk? = null @@ -42,10 +39,11 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext @ReactMethod fun removeListeners(count: Int) {} - - @ReactMethod - fun connect(req: ReadableMap, promise: Promise) { + fun connect( + req: ReadableMap, + promise: Promise, + ) { if (bindingLiquidSdk != null) { promise.reject("Generic", "Already initialized") return @@ -53,8 +51,13 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext executor.execute { try { - var connectRequest = asConnectRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } - connectRequest.dataDir = connectRequest.dataDir?.takeUnless { it.isEmpty() } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } + var connectRequest = + asConnectRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } + connectRequest.dataDir = connectRequest.dataDir?.takeUnless { + it.isEmpty() + } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } bindingLiquidSdk = connect(connectRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -63,12 +66,17 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } - @ReactMethod - fun getInfo(req: ReadableMap, promise: Promise) { + fun getInfo( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val getInfoRequest = asGetInfoRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } + val getInfoRequest = + asGetInfoRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } val res = getBindingLiquidSdk().getInfo(getInfoRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -76,12 +84,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareSendPayment(req: ReadableMap, promise: Promise) { + fun prepareSendPayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareSendRequest = asPrepareSendRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) } + val prepareSendRequest = + asPrepareSendRequest(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) + } val res = getBindingLiquidSdk().prepareSendPayment(prepareSendRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -89,12 +103,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun sendPayment(req: ReadableMap, promise: Promise) { + fun sendPayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareSendResponse = asPrepareSendResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) } + val prepareSendResponse = + asPrepareSendResponse(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) + } val res = getBindingLiquidSdk().sendPayment(prepareSendResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -102,12 +122,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareReceivePayment(req: ReadableMap, promise: Promise) { + fun prepareReceivePayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareReceiveRequest = asPrepareReceiveRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) } + val prepareReceiveRequest = + asPrepareReceiveRequest(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) + } val res = getBindingLiquidSdk().prepareReceivePayment(prepareReceiveRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -115,12 +141,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun receivePayment(req: ReadableMap, promise: Promise) { + fun receivePayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareReceiveResponse = asPrepareReceiveResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) } + val prepareReceiveResponse = + asPrepareReceiveResponse(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) + } val res = getBindingLiquidSdk().receivePayment(prepareReceiveResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -128,7 +160,7 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod fun backup(promise: Promise) { executor.execute { @@ -140,12 +172,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun restore(req: ReadableMap, promise: Promise) { + fun restore( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val restoreRequest = asRestoreRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } + val restoreRequest = + asRestoreRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } getBindingLiquidSdk().restore(restoreRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -153,5 +191,4 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - -} \ No newline at end of file +} diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 4b7161d..8bf5d88 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -17,49 +17,49 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK } ) -export type ConnectRequest = { +export interface ConnectRequest { mnemonic: string network: Network dataDir?: string } -export type GetInfoRequest = { +export interface GetInfoRequest { withScan: boolean } -export type GetInfoResponse = { +export interface GetInfoResponse { balanceSat: number pubkey: string } -export type PrepareReceiveRequest = { +export interface PrepareReceiveRequest { payerAmountSat: number } -export type PrepareReceiveResponse = { +export interface PrepareReceiveResponse { payerAmountSat: number feesSat: number } -export type PrepareSendRequest = { +export interface PrepareSendRequest { invoice: string } -export type PrepareSendResponse = { +export interface PrepareSendResponse { invoice: string feesSat: number } -export type ReceivePaymentResponse = { +export interface ReceivePaymentResponse { id: string invoice: string } -export type RestoreRequest = { +export interface RestoreRequest { backupPath?: string } -export type SendPaymentResponse = { +export interface SendPaymentResponse { txid: string } From fd5a233e917d4d82acee2cb87c33acada8b27a43 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:58:43 +0200 Subject: [PATCH 27/29] Flutter example app: remove calls to inexistent field --- packages/flutter/example/lib/main.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/flutter/example/lib/main.dart b/packages/flutter/example/lib/main.dart index 98e3bf5..b805a19 100644 --- a/packages/flutter/example/lib/main.dart +++ b/packages/flutter/example/lib/main.dart @@ -106,11 +106,7 @@ class _MyAppState extends State { return const Text('Loading...'); } - if (snapshot.requireData.pairHash.isEmpty) { - return const Text('No pair hash.'); - } final prepareReceiveResponse = snapshot.data!; - debugPrint(prepareReceiveResponse.pairHash); return Column( children: [ @@ -121,9 +117,6 @@ class _MyAppState extends State { style: Theme.of(context).textTheme.headlineSmall, ), ), - ListTile( - title: Text("Pair Hash: ${prepareReceiveResponse.pairHash}"), - ), ListTile( title: Text("Payer Amount: ${prepareReceiveResponse.payerAmountSat} (in sats)"), ), From 783bedac343b79f60c863916af65e9a633b5c507 Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 10:09:12 +0200 Subject: [PATCH 28/29] Update flutter generated bindings file --- ...utter_breez_liquid_bindings_generated.dart | 76 +++---------------- 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index d4d3196..0db3640 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -164,22 +164,6 @@ class FlutterBreezLiquidBindings { late final _frbgen_breez_liquid_wire_receive_payment = _frbgen_breez_liquid_wire_receive_paymentPtr .asFunction)>(); - void frbgen_breez_liquid_wire_recover_funds( - int port_, - int recovery, - ) { - return _frbgen_breez_liquid_wire_recover_funds( - port_, - recovery, - ); - } - - late final _frbgen_breez_liquid_wire_recover_fundsPtr = - _lookup>( - 'frbgen_breez_liquid_wire_recover_funds'); - late final _frbgen_breez_liquid_wire_recover_funds = - _frbgen_breez_liquid_wire_recover_fundsPtr.asFunction(); - void frbgen_breez_liquid_wire_restore( int port_, ffi.Pointer req, @@ -212,38 +196,6 @@ class FlutterBreezLiquidBindings { late final _frbgen_breez_liquid_wire_send_payment = _frbgen_breez_liquid_wire_send_paymentPtr .asFunction)>(); - void - frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - - void - frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ffi.Pointer ptr, - ) { - return _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery( - ptr, - ); - } - - late final _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr = - _lookup)>>( - 'frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery'); - late final _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecovery = - _frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLBtcReverseRecoveryPtr - .asFunction)>(); - ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_connect_request() { return _frbgen_breez_liquid_cst_new_box_autoadd_connect_request(); } @@ -440,8 +392,6 @@ final class wire_cst_prepare_send_request extends ffi.Struct { } final class wire_cst_prepare_receive_response extends ffi.Struct { - external ffi.Pointer pair_hash; - @ffi.Uint64() external int payer_amount_sat; @@ -454,20 +404,10 @@ final class wire_cst_restore_request extends ffi.Struct { } final class wire_cst_prepare_send_response extends ffi.Struct { - external ffi.Pointer id; - - @ffi.Uint64() - external int payer_amount_sat; - - @ffi.Uint64() - external int receiver_amount_sat; - - @ffi.Uint64() - external int total_fees; - - external ffi.Pointer funding_address; - external ffi.Pointer invoice; + + @ffi.Uint64() + external int fees_sat; } final class wire_cst_payment extends ffi.Struct { @@ -508,6 +448,12 @@ final class wire_cst_PaymentError_LwkError extends ffi.Struct { external ffi.Pointer err; } +final class wire_cst_PaymentError_Refunded extends ffi.Struct { + external ffi.Pointer err; + + external ffi.Pointer txid; +} + final class wire_cst_PaymentError_SendError extends ffi.Struct { external ffi.Pointer err; } @@ -521,6 +467,8 @@ final class PaymentErrorKind extends ffi.Union { external wire_cst_PaymentError_LwkError LwkError; + external wire_cst_PaymentError_Refunded Refunded; + external wire_cst_PaymentError_SendError SendError; external wire_cst_PaymentError_SignerError SignerError; @@ -543,4 +491,4 @@ final class wire_cst_send_payment_response extends ffi.Struct { external ffi.Pointer txid; } -const double LIQUID_CLAIM_TX_FEERATE = 0.1; +const double LIQUID_CLAIM_TX_FEERATE_MSAT = 100.0; From 41fc748d44dae187ccdec4c6d05dfc9dd198c28f Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 10:13:44 +0200 Subject: [PATCH 29/29] Remove duplicated versions of boltz-client --- lib/core/Cargo.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index bc5da05..2916c2e 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -10,16 +10,8 @@ crate-type = ["lib", "cdylib", "staticlib"] [dependencies] anyhow = { workspace = true } bip39 = { version = "2.0.0", features = ["serde"] } - #boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" } -#boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "b632238935ad5f5b60435a7fc494ded2f232cf12" } - -# Combination of -# - latest fixes from Boltz (fallback for get_utxos) needed for claiming as soon as lockup tx is seen in mempool (receive case) -# - latest fixes from Antonio (get reverse swap pairs, necessary structs, etc) -# https://github.com/ok300/boltz-rust/commits/ok300-combo-boltz-fallback-get-utxos-yse-get-submarine-pairs/ boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", rev = "be8395900495e415699a54e15f4bd0bc6d774237" } - flutter_rust_bridge = { version = "=2.0.0-dev.33", features = ["chrono"], optional = true } log = "0.4.20" lwk_common = "0.3.0"