From d36151ae8a80ad8edfa5f17b8d017e9070fa1eb8 Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Wed, 13 Sep 2023 16:02:01 -0700 Subject: [PATCH] Project import generated by Copybara. GitOrigin-RevId: 5baa230fce6915a14efe6c6ce6c891bef0eb4c91 --- .fossa.yml | 6 + Cargo.toml | 42 +- README.md | 23 +- copy.bara.sky | 7 +- .../.gitignore | 10 + .../Cargo.toml | 11 + .../src/main.rs | 85 ++ examples/uma-demo/Cargo.toml | 10 + examples/uma-demo/src/config.rs | 2 + examples/uma-demo/src/main.rs | 63 + examples/uma-demo/src/vasp.rs | 58 + lightspark-remote-signing/.gitignore | 10 + lightspark-remote-signing/Cargo.toml | 16 + lightspark-remote-signing/src/handler.rs | 287 +++++ lightspark-remote-signing/src/lib.rs | 32 + lightspark-remote-signing/src/response.rs | 233 ++++ lightspark-remote-signing/src/signer.rs | 540 +++++++++ lightspark-remote-signing/src/validation.rs | 13 + lightspark/.gitignore | 10 + CHANGELOG.md => lightspark/CHANGELOG.md | 4 + lightspark/Cargo.toml | 33 + LICENSE => lightspark/LICENSE | 0 lightspark/README.md | 18 + {examples => lightspark/examples}/example.rs | 3 +- {src => lightspark/src}/client.rs | 78 +- {src => lightspark/src}/crypto.rs | 4 + {src => lightspark/src}/error.rs | 2 + lightspark/src/key.rs | 50 + {src => lightspark/src}/lib.rs | 7 +- {src => lightspark/src}/objects/account.rs | 33 + .../account_to_api_tokens_connection.rs | 0 .../objects/account_to_channels_connection.rs | 0 .../objects/account_to_nodes_connection.rs | 6 +- .../account_to_payment_requests_connection.rs | 1 - .../account_to_transactions_connection.rs | 5 +- .../objects/account_to_wallets_connection.rs | 0 {src => lightspark/src}/objects/api_token.rs | 0 {src => lightspark/src}/objects/balances.rs | 0 .../src}/objects/bitcoin_network.rs | 0 .../src}/objects/blockchain_balance.rs | 0 {src => lightspark/src}/objects/channel.rs | 0 .../objects/channel_closing_transaction.rs | 0 .../src}/objects/channel_fees.rs | 0 .../objects/channel_opening_transaction.rs | 0 .../src}/objects/channel_status.rs | 0 .../channel_to_transactions_connection.rs | 0 .../src/objects/compliance_provider.rs | 10 +- {src => lightspark/src}/objects/connection.rs | 18 + .../src}/objects/create_api_token_input.rs | 0 .../src}/objects/create_api_token_output.rs | 0 .../src}/objects/create_invoice_input.rs | 4 - .../src}/objects/create_invoice_output.rs | 0 .../objects/create_lnurl_invoice_input.rs | 4 - .../create_node_wallet_address_input.rs | 0 .../create_node_wallet_address_output.rs | 0 .../objects/create_test_mode_invoice_input.rs | 0 .../create_test_mode_invoice_output.rs | 0 .../objects/create_test_mode_payment_input.rs | 0 .../objects/create_test_mode_paymentoutput.rs | 7 + .../src/objects/create_uma_invoice_input.rs | 13 + .../src}/objects/currency_amount.rs | 0 .../src}/objects/currency_unit.rs | 0 .../objects/decline_to_sign_messages_input.rs | 9 + .../decline_to_sign_messages_output.rs | 19 + .../src}/objects/delete_api_token_input.rs | 0 .../src}/objects/delete_api_token_output.rs | 0 {src => lightspark/src}/objects/deposit.rs | 0 {src => lightspark/src}/objects/entity.rs | 0 .../src}/objects/fee_estimate.rs | 0 .../src}/objects/fund_node_input.rs | 0 .../src}/objects/fund_node_output.rs | 0 {src => lightspark/src}/objects/graph_node.rs | 0 {src => lightspark/src}/objects/hop.rs | 0 .../src}/objects/htlc_attempt_failure_code.rs | 0 .../src}/objects/id_and_signature.rs | 0 .../src}/objects/incoming_payment.rs | 17 + .../src}/objects/incoming_payment_attempt.rs | 0 .../incoming_payment_attempt_status.rs | 0 ...incoming_payment_to_attempts_connection.rs | 0 {src => lightspark/src}/objects/invoice.rs | 2 + .../src}/objects/invoice_data.rs | 10 +- .../src}/objects/invoice_type.rs | 0 ...ightning_fee_estimate_for_invoice_input.rs | 0 .../lightning_fee_estimate_for_node_input.rs | 0 .../objects/lightning_fee_estimate_output.rs | 0 .../src}/objects/lightning_transaction.rs | 0 .../src}/objects/lightspark_node.rs | 14 +- .../src}/objects/lightspark_node_owner.rs | 0 .../src}/objects/lightspark_node_status.rs | 0 .../lightspark_node_to_channels_connection.rs | 0 .../objects/lightspark_node_with_o_s_k.rs | 28 +- .../lightspark_node_with_remote_signing.rs | 28 +- {src => lightspark/src}/objects/mod.rs | 20 +- {src => lightspark/src}/objects/node.rs | 0 .../src}/objects/node_address.rs | 0 .../src}/objects/node_address_type.rs | 0 .../objects/node_to_addresses_connection.rs | 0 .../src}/objects/on_chain_transaction.rs | 0 .../src}/objects/outgoing_payment.rs | 30 +- .../src}/objects/outgoing_payment_attempt.rs | 0 .../outgoing_payment_attempt_status.rs | 0 ...oing_payment_attempt_to_hops_connection.rs | 0 ...outgoing_payment_to_attempts_connection.rs | 0 ...tgoing_payments_for_invoice_query_input.rs | 13 + ...going_payments_for_invoice_query_output.rs | 19 + {src => lightspark/src}/objects/page_info.rs | 0 .../src}/objects/pay_invoice_input.rs | 0 .../src}/objects/pay_invoice_output.rs | 0 .../src/objects/pay_uma_invoice_input.rs | 15 + lightspark/src/objects/payment_direction.rs | 29 + .../src}/objects/payment_failure_reason.rs | 0 .../src}/objects/payment_request.rs | 0 .../src}/objects/payment_request_data.rs | 0 .../src}/objects/payment_request_status.rs | 0 {src => lightspark/src}/objects/permission.rs | 0 .../src/objects/post_transaction_data.rs | 30 + .../src/objects/register_payment_input.rs | 15 + .../src/objects/register_payment_output.rs | 18 + ...ase_channel_per_commitment_secret_input.rs | 2 + ...se_channel_per_commitment_secret_output.rs | 0 .../objects/release_payment_preimage_input.rs | 0 .../release_payment_preimage_output.rs | 0 .../objects/remote_signing_sub_event_type.rs | 4 + .../src}/objects/request_withdrawal_input.rs | 0 .../src}/objects/request_withdrawal_output.rs | 0 {src => lightspark/src}/objects/rich_text.rs | 0 .../src}/objects/risk_rating.rs | 0 .../src}/objects/routing_transaction.rs | 0 .../routing_transaction_failure_reason.rs | 0 lightspark/src/objects/screen_node_input.rs | 10 + lightspark/src/objects/screen_node_output.rs | 16 + {src => lightspark/src}/objects/secret.rs | 0 .../src}/objects/send_payment_input.rs | 0 .../src}/objects/send_payment_output.rs | 0 .../objects/set_invoice_payment_hash_input.rs | 14 + .../set_invoice_payment_hash_output.rs | 18 + .../src}/objects/sign_invoice_input.rs | 0 .../src}/objects/sign_invoice_output.rs | 0 .../src}/objects/sign_messages_input.rs | 0 .../src}/objects/sign_messages_output.rs | 0 {src => lightspark/src}/objects/signable.rs | 0 .../src}/objects/signable_payload.rs | 0 .../src}/objects/signable_payload_status.rs | 4 + .../src}/objects/transaction.rs | 0 .../src}/objects/transaction_failures.rs | 0 .../src}/objects/transaction_status.rs | 0 .../src}/objects/transaction_type.rs | 0 ...date_channel_per_commitment_point_input.rs | 0 ...ate_channel_per_commitment_point_output.rs | 0 .../update_node_shared_secret_input.rs | 0 .../update_node_shared_secret_output.rs | 0 lightspark/src/objects/wallet.rs | 1074 +++++++++++++++++ .../src}/objects/wallet_status.rs | 0 .../wallet_to_payment_requests_connection.rs | 54 + .../wallet_to_transactions_connection.rs | 54 + .../src}/objects/webhook_event_type.rs | 0 {src => lightspark/src}/objects/withdrawal.rs | 0 .../src}/objects/withdrawal_mode.rs | 0 .../src}/objects/withdrawal_request.rs | 0 .../src}/objects/withdrawal_request_status.rs | 0 ...channel_closing_transactions_connection.rs | 0 ...channel_opening_transactions_connection.rs | 0 .../src}/request/auth_provider.rs | 0 {src => lightspark/src}/request/mod.rs | 0 {src => lightspark/src}/request/requester.rs | 34 +- .../src}/types/custom_date_formats.rs | 0 .../src}/types/entity_wrapper.rs | 0 {src => lightspark/src}/types/get_entity.rs | 0 {src => lightspark/src}/types/mod.rs | 0 {src => lightspark/src}/webhooks.rs | 0 src/objects/screen_bitcoin_addresses_input.rs | 11 - .../screen_bitcoin_addresses_output.rs | 17 - src/objects/wallet.rs | 215 ---- uma/Cargo.toml | 28 + uma/LICENSE | 201 +++ uma/README.md | 3 + {src/uma => uma/src}/currency.rs | 2 + uma/src/kyc_status.rs | 18 + uma/src/lib.rs | 14 + {src/uma => uma/src}/payer_data.rs | 22 +- {src/uma => uma/src}/protocol.rs | 80 +- {src/uma => uma/src}/public_key_cache.rs | 13 + src/uma/mod.rs => uma/src/uma.rs | 157 ++- {src/uma => uma/src}/uma_test.rs | 105 +- uma/src/version.rs | 102 ++ 185 files changed, 3862 insertions(+), 484 deletions(-) create mode 100644 .fossa.yml create mode 100644 examples/lightspark-remote-signing-server/.gitignore create mode 100644 examples/lightspark-remote-signing-server/Cargo.toml create mode 100644 examples/lightspark-remote-signing-server/src/main.rs create mode 100644 examples/uma-demo/Cargo.toml create mode 100644 examples/uma-demo/src/config.rs create mode 100644 examples/uma-demo/src/main.rs create mode 100644 examples/uma-demo/src/vasp.rs create mode 100644 lightspark-remote-signing/.gitignore create mode 100644 lightspark-remote-signing/Cargo.toml create mode 100644 lightspark-remote-signing/src/handler.rs create mode 100644 lightspark-remote-signing/src/lib.rs create mode 100644 lightspark-remote-signing/src/response.rs create mode 100644 lightspark-remote-signing/src/signer.rs create mode 100644 lightspark-remote-signing/src/validation.rs create mode 100644 lightspark/.gitignore rename CHANGELOG.md => lightspark/CHANGELOG.md (77%) create mode 100644 lightspark/Cargo.toml rename LICENSE => lightspark/LICENSE (100%) create mode 100644 lightspark/README.md rename {examples => lightspark/examples}/example.rs (99%) rename {src => lightspark/src}/client.rs (90%) rename {src => lightspark/src}/crypto.rs (96%) rename {src => lightspark/src}/error.rs (91%) create mode 100644 lightspark/src/key.rs rename {src => lightspark/src}/lib.rs (85%) rename {src => lightspark/src}/objects/account.rs (97%) rename {src => lightspark/src}/objects/account_to_api_tokens_connection.rs (100%) rename {src => lightspark/src}/objects/account_to_channels_connection.rs (100%) rename {src => lightspark/src}/objects/account_to_nodes_connection.rs (100%) rename {src => lightspark/src}/objects/account_to_payment_requests_connection.rs (99%) rename {src => lightspark/src}/objects/account_to_transactions_connection.rs (99%) rename {src => lightspark/src}/objects/account_to_wallets_connection.rs (100%) rename {src => lightspark/src}/objects/api_token.rs (100%) rename {src => lightspark/src}/objects/balances.rs (100%) rename {src => lightspark/src}/objects/bitcoin_network.rs (100%) rename {src => lightspark/src}/objects/blockchain_balance.rs (100%) rename {src => lightspark/src}/objects/channel.rs (100%) rename {src => lightspark/src}/objects/channel_closing_transaction.rs (100%) rename {src => lightspark/src}/objects/channel_fees.rs (100%) rename {src => lightspark/src}/objects/channel_opening_transaction.rs (100%) rename {src => lightspark/src}/objects/channel_status.rs (100%) rename {src => lightspark/src}/objects/channel_to_transactions_connection.rs (100%) rename src/objects/crypto_sanctions_screening_provider.rs => lightspark/src/objects/compliance_provider.rs (60%) rename {src => lightspark/src}/objects/connection.rs (84%) rename {src => lightspark/src}/objects/create_api_token_input.rs (100%) rename {src => lightspark/src}/objects/create_api_token_output.rs (100%) rename {src => lightspark/src}/objects/create_invoice_input.rs (88%) rename {src => lightspark/src}/objects/create_invoice_output.rs (100%) rename {src => lightspark/src}/objects/create_lnurl_invoice_input.rs (89%) rename {src => lightspark/src}/objects/create_node_wallet_address_input.rs (100%) rename {src => lightspark/src}/objects/create_node_wallet_address_output.rs (100%) rename {src => lightspark/src}/objects/create_test_mode_invoice_input.rs (100%) rename {src => lightspark/src}/objects/create_test_mode_invoice_output.rs (100%) rename {src => lightspark/src}/objects/create_test_mode_payment_input.rs (100%) rename {src => lightspark/src}/objects/create_test_mode_paymentoutput.rs (74%) create mode 100644 lightspark/src/objects/create_uma_invoice_input.rs rename {src => lightspark/src}/objects/currency_amount.rs (100%) rename {src => lightspark/src}/objects/currency_unit.rs (100%) create mode 100644 lightspark/src/objects/decline_to_sign_messages_input.rs create mode 100644 lightspark/src/objects/decline_to_sign_messages_output.rs rename {src => lightspark/src}/objects/delete_api_token_input.rs (100%) rename {src => lightspark/src}/objects/delete_api_token_output.rs (100%) rename {src => lightspark/src}/objects/deposit.rs (100%) rename {src => lightspark/src}/objects/entity.rs (100%) rename {src => lightspark/src}/objects/fee_estimate.rs (100%) rename {src => lightspark/src}/objects/fund_node_input.rs (100%) rename {src => lightspark/src}/objects/fund_node_output.rs (100%) rename {src => lightspark/src}/objects/graph_node.rs (100%) rename {src => lightspark/src}/objects/hop.rs (100%) rename {src => lightspark/src}/objects/htlc_attempt_failure_code.rs (100%) rename {src => lightspark/src}/objects/id_and_signature.rs (100%) rename {src => lightspark/src}/objects/incoming_payment.rs (90%) rename {src => lightspark/src}/objects/incoming_payment_attempt.rs (100%) rename {src => lightspark/src}/objects/incoming_payment_attempt_status.rs (100%) rename {src => lightspark/src}/objects/incoming_payment_to_attempts_connection.rs (100%) rename {src => lightspark/src}/objects/invoice.rs (98%) rename {src => lightspark/src}/objects/invoice_data.rs (98%) rename {src => lightspark/src}/objects/invoice_type.rs (100%) rename {src => lightspark/src}/objects/lightning_fee_estimate_for_invoice_input.rs (100%) rename {src => lightspark/src}/objects/lightning_fee_estimate_for_node_input.rs (100%) rename {src => lightspark/src}/objects/lightning_fee_estimate_output.rs (100%) rename {src => lightspark/src}/objects/lightning_transaction.rs (100%) rename {src => lightspark/src}/objects/lightspark_node.rs (94%) rename {src => lightspark/src}/objects/lightspark_node_owner.rs (100%) rename {src => lightspark/src}/objects/lightspark_node_status.rs (100%) rename {src => lightspark/src}/objects/lightspark_node_to_channels_connection.rs (100%) rename {src => lightspark/src}/objects/lightspark_node_with_o_s_k.rs (97%) rename {src => lightspark/src}/objects/lightspark_node_with_remote_signing.rs (97%) rename {src => lightspark/src}/objects/mod.rs (85%) rename {src => lightspark/src}/objects/node.rs (100%) rename {src => lightspark/src}/objects/node_address.rs (100%) rename {src => lightspark/src}/objects/node_address_type.rs (100%) rename {src => lightspark/src}/objects/node_to_addresses_connection.rs (100%) rename {src => lightspark/src}/objects/on_chain_transaction.rs (100%) rename {src => lightspark/src}/objects/outgoing_payment.rs (95%) rename {src => lightspark/src}/objects/outgoing_payment_attempt.rs (100%) rename {src => lightspark/src}/objects/outgoing_payment_attempt_status.rs (100%) rename {src => lightspark/src}/objects/outgoing_payment_attempt_to_hops_connection.rs (100%) rename {src => lightspark/src}/objects/outgoing_payment_to_attempts_connection.rs (100%) create mode 100644 lightspark/src/objects/outgoing_payments_for_invoice_query_input.rs create mode 100644 lightspark/src/objects/outgoing_payments_for_invoice_query_output.rs rename {src => lightspark/src}/objects/page_info.rs (100%) rename {src => lightspark/src}/objects/pay_invoice_input.rs (100%) rename {src => lightspark/src}/objects/pay_invoice_output.rs (100%) create mode 100644 lightspark/src/objects/pay_uma_invoice_input.rs create mode 100644 lightspark/src/objects/payment_direction.rs rename {src => lightspark/src}/objects/payment_failure_reason.rs (100%) rename {src => lightspark/src}/objects/payment_request.rs (100%) rename {src => lightspark/src}/objects/payment_request_data.rs (100%) rename {src => lightspark/src}/objects/payment_request_status.rs (100%) rename {src => lightspark/src}/objects/permission.rs (100%) create mode 100644 lightspark/src/objects/post_transaction_data.rs create mode 100644 lightspark/src/objects/register_payment_input.rs create mode 100644 lightspark/src/objects/register_payment_output.rs rename {src => lightspark/src}/objects/release_channel_per_commitment_secret_input.rs (88%) rename {src => lightspark/src}/objects/release_channel_per_commitment_secret_output.rs (100%) rename {src => lightspark/src}/objects/release_payment_preimage_input.rs (100%) rename {src => lightspark/src}/objects/release_payment_preimage_output.rs (100%) rename {src => lightspark/src}/objects/remote_signing_sub_event_type.rs (89%) rename {src => lightspark/src}/objects/request_withdrawal_input.rs (100%) rename {src => lightspark/src}/objects/request_withdrawal_output.rs (100%) rename {src => lightspark/src}/objects/rich_text.rs (100%) rename {src => lightspark/src}/objects/risk_rating.rs (100%) rename {src => lightspark/src}/objects/routing_transaction.rs (100%) rename {src => lightspark/src}/objects/routing_transaction_failure_reason.rs (100%) create mode 100644 lightspark/src/objects/screen_node_input.rs create mode 100644 lightspark/src/objects/screen_node_output.rs rename {src => lightspark/src}/objects/secret.rs (100%) rename {src => lightspark/src}/objects/send_payment_input.rs (100%) rename {src => lightspark/src}/objects/send_payment_output.rs (100%) create mode 100644 lightspark/src/objects/set_invoice_payment_hash_input.rs create mode 100644 lightspark/src/objects/set_invoice_payment_hash_output.rs rename {src => lightspark/src}/objects/sign_invoice_input.rs (100%) rename {src => lightspark/src}/objects/sign_invoice_output.rs (100%) rename {src => lightspark/src}/objects/sign_messages_input.rs (100%) rename {src => lightspark/src}/objects/sign_messages_output.rs (100%) rename {src => lightspark/src}/objects/signable.rs (100%) rename {src => lightspark/src}/objects/signable_payload.rs (100%) rename {src => lightspark/src}/objects/signable_payload_status.rs (83%) rename {src => lightspark/src}/objects/transaction.rs (100%) rename {src => lightspark/src}/objects/transaction_failures.rs (100%) rename {src => lightspark/src}/objects/transaction_status.rs (100%) rename {src => lightspark/src}/objects/transaction_type.rs (100%) rename {src => lightspark/src}/objects/update_channel_per_commitment_point_input.rs (100%) rename {src => lightspark/src}/objects/update_channel_per_commitment_point_output.rs (100%) rename {src => lightspark/src}/objects/update_node_shared_secret_input.rs (100%) rename {src => lightspark/src}/objects/update_node_shared_secret_output.rs (100%) create mode 100644 lightspark/src/objects/wallet.rs rename {src => lightspark/src}/objects/wallet_status.rs (100%) create mode 100644 lightspark/src/objects/wallet_to_payment_requests_connection.rs create mode 100644 lightspark/src/objects/wallet_to_transactions_connection.rs rename {src => lightspark/src}/objects/webhook_event_type.rs (100%) rename {src => lightspark/src}/objects/withdrawal.rs (100%) rename {src => lightspark/src}/objects/withdrawal_mode.rs (100%) rename {src => lightspark/src}/objects/withdrawal_request.rs (100%) rename {src => lightspark/src}/objects/withdrawal_request_status.rs (100%) rename {src => lightspark/src}/objects/withdrawal_request_to_channel_closing_transactions_connection.rs (100%) rename {src => lightspark/src}/objects/withdrawal_request_to_channel_opening_transactions_connection.rs (100%) rename {src => lightspark/src}/request/auth_provider.rs (100%) rename {src => lightspark/src}/request/mod.rs (100%) rename {src => lightspark/src}/request/requester.rs (86%) rename {src => lightspark/src}/types/custom_date_formats.rs (100%) rename {src => lightspark/src}/types/entity_wrapper.rs (100%) rename {src => lightspark/src}/types/get_entity.rs (100%) rename {src => lightspark/src}/types/mod.rs (100%) rename {src => lightspark/src}/webhooks.rs (100%) delete mode 100644 src/objects/screen_bitcoin_addresses_input.rs delete mode 100644 src/objects/screen_bitcoin_addresses_output.rs delete mode 100644 src/objects/wallet.rs create mode 100644 uma/Cargo.toml create mode 100644 uma/LICENSE create mode 100644 uma/README.md rename {src/uma => uma/src}/currency.rs (83%) create mode 100644 uma/src/kyc_status.rs create mode 100644 uma/src/lib.rs rename {src/uma => uma/src}/payer_data.rs (86%) rename {src/uma => uma/src}/protocol.rs (67%) rename {src/uma => uma/src}/public_key_cache.rs (69%) rename src/uma/mod.rs => uma/src/uma.rs (67%) rename {src/uma => uma/src}/uma_test.rs (69%) create mode 100644 uma/src/version.rs diff --git a/.fossa.yml b/.fossa.yml new file mode 100644 index 0000000..c159991 --- /dev/null +++ b/.fossa.yml @@ -0,0 +1,6 @@ +version: 3 + +project: + id: lightspark/rust-sdk + name: rust-sdk + url: https://github.com/lightsparkdev/webdev diff --git a/Cargo.toml b/Cargo.toml index 8eef735..67bab4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,10 @@ -[package] -name = "lightspark" -description = "Lightspark Rust SDK" -authors = ["Lightspark Group, Inc. "] -version = "0.5.0" -edition = "2021" -documentation = "https://app.lightspark.com/docs/sdk" -homepage = "https://www.lightspark.com/" -repository = "https://github.com/lightsparkdev/lightspark-rs" -license = "Apache-2.0" -readme = "README.md" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +resolver = "2" -[dependencies] -reqwest = { version = "0.11", features = ["blocking", "json"] } -futures = "0.3" -tokio = { version = "1.12.0", features = ["full"] } -base64 = "0.21.0" -serde_json = "1.0.94" -serde = { version = "1.0.155", features = ["derive"] } -regex = "1.7.1" -chrono = "0.4.24" -openssl = "0.10.48" -aes-gcm = "0.10.1" -rand = "0.8.5" -block-modes = "0.9.1" -os-version = "0.2.0" -version_check = "0.9.4" -hex = "0.4.3" -hmac = "0.12.1" -sha2 = "0.10.7" -rand_core = "0.6.4" -ecies = "0.2.6" -bitcoin = "0.30.1" -url = "2.4.1" +members = [ + "lightspark", + "lightspark-remote-signing", + "uma", + "examples/*", +] \ No newline at end of file diff --git a/README.md b/README.md index 3e21758..65bc76b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,11 @@ -# Lightspark Rust SDK - v0.5.0 - +# Lightspark Rust SDK The Lightspark Rust SDK provides a convenient way to interact with the Lightspark services from applications written in the Rust language. -***WARNING: This SDK is in version 0.4.0 (active development). It means that its APIs may not be fully stable. Please expect that changes to the APIs may happen until we move to v1.0.0.*** - -## Documentation - -The documentation for this SDK (installation, usage, etc.) is available at https://app.lightspark.com/docs/sdk - -## Sample code - -For your convenience, we included an example that shows you how to use the SDK. -Open the file `example/example.rs` and make sure to update the variables at the top of the page with your information, then run it using cargo: +## Project Structure +The rust-sdk consists of multiple crates that can be picked at your convenience: +- `lightspark`: The main crate that contains the SDK. +- `uma`: The UMA protocol implementation. +- `example`: Examples that shows you how to use the SDK. -``` -cargo run --example example -``` +## License +[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/copy.bara.sky b/copy.bara.sky index e5a6f57..1caf98f 100644 --- a/copy.bara.sky +++ b/copy.bara.sky @@ -16,7 +16,7 @@ core.workflow( mode = "SQUASH", origin_files = glob( - ["rust-sdk/lightspark/**", "copy.bara.sky"], + ["rust-sdk/**", "copy.bara.sky"], exclude = ["rust-sdk/lightspark/examples/internal_example.rs"], ), @@ -27,7 +27,7 @@ core.workflow( core.todo_replace( mode = 'SCRUB_NAMES' ), - core.move("rust-sdk/lightspark/", "") + core.move("rust-sdk/", "") ], ) @@ -68,8 +68,7 @@ core.workflow( ), destination = git.github_destination( url = "https://github.com/lightsparkdev/go-sdk.git", - # TODO: Move this back to main when we're ready to release. - push = "rc/remote-signing", + push = "main", ), # Switch to ITERATIVE mode to import each commit separately. mode = "SQUASH", diff --git a/examples/lightspark-remote-signing-server/.gitignore b/examples/lightspark-remote-signing-server/.gitignore new file mode 100644 index 0000000..fd274f4 --- /dev/null +++ b/examples/lightspark-remote-signing-server/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +!Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/examples/lightspark-remote-signing-server/Cargo.toml b/examples/lightspark-remote-signing-server/Cargo.toml new file mode 100644 index 0000000..c51fc20 --- /dev/null +++ b/examples/lightspark-remote-signing-server/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lightspark-remote-signing-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lightspark = { path = "../../lightspark" } +lightspark-remote-signing = { path = "../../lightspark-remote-signing" } +tokio = "1.32.0" diff --git a/examples/lightspark-remote-signing-server/src/main.rs b/examples/lightspark-remote-signing-server/src/main.rs new file mode 100644 index 0000000..7830c07 --- /dev/null +++ b/examples/lightspark-remote-signing-server/src/main.rs @@ -0,0 +1,85 @@ +use std::{ + io::{prelude::*, BufReader}, + net::{TcpListener, TcpStream}, +}; + +use lightspark::{ + client::LightsparkClient, + key::{OperationSigningKey, Secp256k1SigningKey}, + request::auth_provider::AccountAuthProvider, + webhooks::{self, WebhookEvent}, +}; +use lightspark_remote_signing::{ + handler::Handler, + signer::{self, LightsparkSigner, Seed}, + validation::{PositiveValidator, Validation}, +}; + +#[tokio::main] +async fn main() { + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + let seed = Seed::new("test".as_bytes().to_vec()); + let signer = LightsparkSigner::new(&seed, signer::Network::Bitcoin).unwrap(); + let validator = PositiveValidator; + let handler = Handler::new(signer, validator); + + let auth = AccountAuthProvider::new("test".to_owned(), "test".to_owned()); + let client = LightsparkClient::::new(auth).expect("Assume success"); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + handle_connection(stream, &handler, &client).await; + } +} + +async fn handle_connection( + stream: TcpStream, + handler: &Handler, + client: &LightsparkClient, +) { + let mut reader = BufReader::new(stream.try_clone().unwrap()); + let mut name = String::new(); + loop { + let r = reader.read_line(&mut name).unwrap(); + if r < 3 { + break; + } + } + let mut size = 0; + let linesplit = name.split('\n'); + let mut sig = ""; + for l in linesplit { + if l.starts_with("Content-Length") { + let sizeplit = l.split(':'); + for s in sizeplit { + if !(s.starts_with("Content-Length")) { + size = s.trim().parse::().unwrap(); + } + } + } else if l.starts_with(webhooks::SIGNATURE_HEADER) { + let sigsplit = l.split(':'); + for s in sigsplit { + if !(s.starts_with(webhooks::SIGNATURE_HEADER)) { + sig = s.trim(); + } + } + } + } + let mut buffer = vec![0; size]; + reader.read_exact(&mut buffer).unwrap(); + let body = String::from_utf8(buffer.clone()).unwrap(); + + println!("Signature: {}", sig); + println!("Request: {}", body); + + let event = WebhookEvent::verify_and_parse( + buffer.as_slice(), + sig.to_string(), + "webhook_secret".to_owned(), + ) + .expect("Assume verified"); + let response = handler.handle_remote_signing_webhook_msg(&event).expect(""); + let _ = client + .execute_graphql_request_variable(&response.query, response.variables.clone()) + .await; +} diff --git a/examples/uma-demo/Cargo.toml b/examples/uma-demo/Cargo.toml new file mode 100644 index 0000000..b5d87ff --- /dev/null +++ b/examples/uma-demo/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "uma-demo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4.4.0" +lightspark = { path = "../../lightspark" } diff --git a/examples/uma-demo/src/config.rs b/examples/uma-demo/src/config.rs new file mode 100644 index 0000000..e5e37b4 --- /dev/null +++ b/examples/uma-demo/src/config.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone)] +pub struct Config {} diff --git a/examples/uma-demo/src/main.rs b/examples/uma-demo/src/main.rs new file mode 100644 index 0000000..22b0531 --- /dev/null +++ b/examples/uma-demo/src/main.rs @@ -0,0 +1,63 @@ +pub mod config; +pub mod vasp; +use actix_web::{get, post, web, App, HttpServer, Responder}; + +use crate::vasp::{VASPReceiving, VASPSending, VASP}; + +#[get("/api/umalookup/{receiver}")] +async fn uma_lookup(vasp: web::Data, receiver: web::Path) -> impl Responder { + vasp.handle_client_uma_lookup(receiver.as_str()) +} + +#[get("/api/umapayreq/{callback_uuid}")] +async fn client_payreq(vasp: web::Data, callback_uuid: web::Path) -> impl Responder { + vasp.handle_client_pay_req(callback_uuid.as_str()) +} + +#[get("/api/sendpayment/{callback_uuid}")] +async fn send_payment(vasp: web::Data, callback_uuid: web::Path) -> impl Responder { + vasp.handle_client_payment_confirm(callback_uuid.as_str()) +} + +#[get("/.well-known/lnurlp/{username}")] +async fn well_known_lnurlp(vasp: web::Data, username: web::Path) -> impl Responder { + vasp.handle_well_known_lnurlp(username.as_str()) +} + +#[get("/api/uma/payreq/{uuid}")] +async fn lnurl_payreq(vasp: web::Data, uuid: web::Path) -> impl Responder { + vasp.handle_lnurl_payreq(uuid.as_str()) +} + +#[post("/api/uma/payreq/{uuid}")] +async fn uma_payreq(vasp: web::Data, uuid: web::Path) -> impl Responder { + vasp.handle_uma_payreq(uuid.as_str()) +} + +#[get("/.well-known/lnurlpubkey")] +async fn pubkey_request(vasp: web::Data) -> impl Responder { + vasp.handle_pubkey_request() +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let port = std::env::var("VASP_SERVER_PORT") + .unwrap() + .parse::() + .unwrap(); + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(VASP { + config: config::Config {}, + })) + .service(uma_lookup) + .service(client_payreq) + .service(send_payment) + .service(well_known_lnurlp) + .service(lnurl_payreq) + .service(uma_payreq) + }) + .bind(("127.0.0.1", port))? + .run() + .await +} diff --git a/examples/uma-demo/src/vasp.rs b/examples/uma-demo/src/vasp.rs new file mode 100644 index 0000000..3ac5596 --- /dev/null +++ b/examples/uma-demo/src/vasp.rs @@ -0,0 +1,58 @@ +use std::fmt::format; + +use crate::config::Config; + +pub trait VASPSending { + fn handle_client_uma_lookup(&self, receiver: &str) -> String; + fn handle_client_pay_req(&self, callback_uuid: &str) -> String; + fn handle_client_payment_confirm(&self, callback_uuid: &str) -> String; +} + +pub trait VASPReceiving { + fn handle_well_known_lnurlp(&self, username: &str) -> String; + fn handle_lnurl_payreq(&self, uuid: &str) -> String; + fn handle_uma_payreq(&self, uuid: &str) -> String; +} + +#[derive(Debug)] +pub struct VASP { + pub config: Config, +} + +impl VASP { + pub fn new(config: Config) -> VASP { + VASP { config } + } + + pub fn handle_pubkey_request(&self) -> String { + format(format_args!("Hello {}!", "pubkey_request")) + } +} + +impl VASPSending for VASP { + fn handle_client_uma_lookup(&self, receiver: &str) -> String { + format(format_args!("Hello {}!", receiver)) + } + + fn handle_client_pay_req(&self, callback_uuid: &str) -> String { + format(format_args!("Hello {}!", callback_uuid)) + } + + fn handle_client_payment_confirm(&self, callback_uuid: &str) -> String { + format(format_args!("Hello {}!", callback_uuid)) + } +} + +impl VASPReceiving for VASP { + fn handle_well_known_lnurlp(&self, username: &str) -> String { + format(format_args!("Hello {}!", username)) + } + + fn handle_lnurl_payreq(&self, uuid: &str) -> String { + format(format_args!("Hello {}!", uuid)) + } + + fn handle_uma_payreq(&self, uuid: &str) -> String { + format(format_args!("Hello {}!", uuid)) + } +} diff --git a/lightspark-remote-signing/.gitignore b/lightspark-remote-signing/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/lightspark-remote-signing/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/lightspark-remote-signing/Cargo.toml b/lightspark-remote-signing/Cargo.toml new file mode 100644 index 0000000..3ede2e3 --- /dev/null +++ b/lightspark-remote-signing/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lightspark-remote-signing" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lightspark = { path = "../lightspark" } +bip39 = { "version" = "2.0.0", features = ["rand"]} +bitcoin = "0.30.1" +hex = "0.4.3" +rand_core = { "version" = "0.6.4", features = ["getrandom"] } +serde_json = "1.0.104" +log = "0.4.20" +serde = "1.0.183" diff --git a/lightspark-remote-signing/src/handler.rs b/lightspark-remote-signing/src/handler.rs new file mode 100644 index 0000000..90b9e73 --- /dev/null +++ b/lightspark-remote-signing/src/handler.rs @@ -0,0 +1,287 @@ +use lightspark::{ + objects::{ + id_and_signature::IdAndSignature, remote_signing_sub_event_type::RemoteSigningSubEventType, + webhook_event_type::WebhookEventType, + }, + webhooks::WebhookEvent, +}; +use log::info; +use serde::Deserialize; +use serde_json::from_value; + +use crate::{response::Response, signer::LightsparkSigner, validation::Validation, Error}; + +pub struct Handler +where + T: Validation, +{ + signer: LightsparkSigner, + validator: T, +} + +impl Handler +where + T: Validation, +{ + pub fn new(signer: LightsparkSigner, validator: T) -> Self { + Self { signer, validator } + } + + pub fn handle_remote_signing_webhook_msg( + &self, + event: &WebhookEvent, + ) -> Result { + if !matches!(event.event_type, WebhookEventType::RemoteSigning) { + return Err(Error::WebhookEventNotRemoteSigning); + } + + let data = &event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let sub_type: RemoteSigningSubEventType = from_value(data["sub_event_type"].clone()) + .map_err(|_| Error::WebhookEventDataMissing)?; + if !self.validator.should_sign(event) { + self.handle_decline_to_sign_messages(event) + } else { + match sub_type { + RemoteSigningSubEventType::Ecdh => self.handle_ecdh(event), + RemoteSigningSubEventType::SignInvoice => self.handle_sign_invoice(event), + RemoteSigningSubEventType::ReleasePaymentPreimage => { + self.handle_release_payment_preimage(event) + } + RemoteSigningSubEventType::GetPerCommitmentPoint => { + self.handle_get_per_commitment_point(event) + } + RemoteSigningSubEventType::ReleasePerCommitmentSecret => { + self.handle_release_per_commitment_secret(event) + } + RemoteSigningSubEventType::DeriveKeyAndSign => { + self.handle_derive_key_and_sign(event) + } + RemoteSigningSubEventType::RequestInvoicePaymentHash => { + self.handle_request_invoice_payment_hash(event) + } + } + } + } + + fn handle_request_invoice_payment_hash(&self, event: &WebhookEvent) -> Result { + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let invoice_id = data["invoice_id"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + let nonce = self.signer.generate_preimage_nonce(); + let nonce_str = hex::encode(&nonce); + + let payment_hash = self + .signer + .generate_preimage_hash(nonce) + .map_err(Error::SignerError)?; + let payment_hash_str = hex::encode(payment_hash); + Ok(Response::set_invoice_payment_hash_response( + invoice_id, + &payment_hash_str, + &nonce_str, + )) + } + + fn handle_decline_to_sign_messages(&self, event: &WebhookEvent) -> Result { + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + + let signing_jobs: Vec = serde_json::from_value(data["signing_jobs"].clone()) + .map_err(|_| Error::WebhookEventDataMissing)?; + + let payload_ids: Vec = signing_jobs.iter().map(|job| job.id.clone()).collect(); + Ok(Response::decline_to_sign_message_response(&payload_ids)) + } + + fn handle_ecdh(&self, event: &WebhookEvent) -> Result { + info!("Handling ECDH webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let node_id = &event.entity_id; + let public_key = data["public_key"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + let public_key_bytes = hex::decode(public_key).map_err(Error::PublicKeyDecodeError)?; + let ss = self + .signer + .ecdh(public_key_bytes.to_vec()) + .map_err(Error::SignerError)?; + let ss_str = hex::encode(ss); + Ok(Response::ecdh_response(node_id, &ss_str)) + } + + fn handle_sign_invoice(&self, event: &WebhookEvent) -> Result { + info!("Handling sign invoice webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let invoice_id = data["invoice_id"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + let invoice_hash = data["payreq_hash"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + let invoice_hash_bytes = hex::decode(invoice_hash).map_err(|_| Error::HexEncodingError)?; + let signature = self + .signer + .sign_invoice_hash(invoice_hash_bytes) + .map_err(Error::SignerError)?; + Ok(Response::sign_invoice_response( + invoice_id, + hex::encode(signature.get_signature()).as_str(), + signature.get_recovery_id(), + )) + } + + fn handle_release_payment_preimage(&self, event: &WebhookEvent) -> Result { + info!("Handling release payment preimage webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let nonce = data["preimage_nonce"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + let invoice_id = data["invoice_id"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + + let nonce_bytes = hex::decode(nonce).map_err(|_| Error::HexEncodingError)?; + let preimage = self + .signer + .generate_preimage(nonce_bytes) + .map_err(Error::SignerError)?; + + Ok(Response::release_payment_preimage_response( + invoice_id, + hex::encode(preimage).as_str(), + )) + } + + fn handle_get_per_commitment_point(&self, event: &WebhookEvent) -> Result { + info!("Handling get per commitment point webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let per_commitment_point_idx = data["per_commitment_point_idx"] + .as_u64() + .ok_or(Error::WebhookEventDataMissing)?; + + let derivation_path = data["derivation_path"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + + let channel_id = &event.entity_id; + + let per_commitment_point = self + .signer + .get_per_commitment_point(derivation_path.to_string(), per_commitment_point_idx) + .map_err(Error::SignerError)?; + + let commitment_point_str = hex::encode(per_commitment_point); + Ok(Response::get_channel_per_commitment_response( + channel_id, + commitment_point_str.as_str(), + per_commitment_point_idx, + )) + } + + fn handle_release_per_commitment_secret( + &self, + event: &WebhookEvent, + ) -> Result { + info!("Handling release per commitment secret webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + let per_commitment_point_idx = data["per_commitment_point_idx"] + .as_u64() + .ok_or(Error::WebhookEventDataMissing)?; + + let derivation_path = data["derivation_path"] + .as_str() + .ok_or(Error::WebhookEventDataMissing)?; + + let channel_id = &event.entity_id; + let commitment_secret = self + .signer + .release_per_commitment_secret(derivation_path.to_string(), per_commitment_point_idx) + .map_err(Error::SignerError)?; + + let commitment_secret_str = hex::encode(commitment_secret); + + Ok(Response::release_channel_per_commitment_secret_response( + channel_id, + &commitment_secret_str, + )) + } + + fn handle_derive_key_and_sign(&self, event: &WebhookEvent) -> Result { + info!("Handling derive key and sign webhook event"); + let data = event.data.as_ref().ok_or(Error::WebhookEventDataMissing)?; + + let signing_jobs: Vec = serde_json::from_value(data["signing_jobs"].clone()) + .map_err(|_| Error::WebhookEventDataMissing)?; + + let mut signatures: Vec = vec![]; + for signing_job in signing_jobs { + let signature = self + .signer + .derive_key_and_sign( + hex::decode(signing_job.message).map_err(|_| Error::HexEncodingError)?, + signing_job.derivation_path, + signing_job.is_raw, + signing_job + .add_tweak + .map(|tweak| hex::decode(tweak).map_err(|_| Error::HexEncodingError)) + .transpose()?, + signing_job + .mul_tweak + .map(|tweak| hex::decode(tweak).map_err(|_| Error::HexEncodingError)) + .transpose()?, + ) + .map_err(Error::SignerError)?; + + signatures.push(IdAndSignature { + id: signing_job.id, + signature: hex::encode(signature), + }); + } + Ok(Response::sign_messages_response(signatures)) + } +} + +#[derive(Clone, Deserialize, Debug)] +struct SigningJob { + id: String, + derivation_path: String, + message: String, + add_tweak: Option, + mul_tweak: Option, + is_raw: bool, +} + +#[cfg(test)] +mod tests { + use lightspark::webhooks::WebhookEvent; + + use crate::signer::{LightsparkSigner, Seed}; + + #[test] + fn test_handle_remote_signing_webhook_msg_ecdh() { + let data = "{\"event_type\": \"REMOTE_SIGNING\", \"event_id\": \"1615c8be5aa44e429eba700db2ed8ca5\", \"timestamp\": \"2023-05-17T23:56:47.874449+00:00\", \"entity_id\": \"lightning_node:01882c25-157a-f96b-0000-362d42b64397\", \"data\": {\"sub_event_type\": \"ECDH\", \"public_key\": \"027c4b09ffb985c298afe7e5813266cbfcb7780b480ac294b0b43dc21f2be3d13c\"}}"; + let hexdigest = "17db38526ce47682f4052e3182766fe2f23810ac538e32d5f20bbe1deb2e3519"; + let webhook_secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX"; + + let result = WebhookEvent::verify_and_parse( + data.as_bytes(), + hexdigest.to_string(), + webhook_secret.to_string(), + ) + .expect("Success case"); + + let seed = Seed::new("test".as_bytes().to_vec()); + let signer = LightsparkSigner::new(&seed, crate::signer::Network::Bitcoin).unwrap(); + let validator = crate::validation::PositiveValidator; + let handler = super::Handler::new(signer, validator); + let response = handler + .handle_remote_signing_webhook_msg(&result) + .expect("Success case"); + + let ss = response.variables["shared_secret"].as_str().unwrap(); + assert_eq!( + ss, + "930d00c9247dd9415b26855a5faafef14705460dfcc4c43fba2f2d899424d31b" + ); + } +} diff --git a/lightspark-remote-signing/src/lib.rs b/lightspark-remote-signing/src/lib.rs new file mode 100644 index 0000000..49b6414 --- /dev/null +++ b/lightspark-remote-signing/src/lib.rs @@ -0,0 +1,32 @@ +use std::fmt; + +pub mod handler; +pub mod response; +pub mod signer; +pub mod validation; + +#[derive(Debug)] +pub enum Error { + WebhookEventNotRemoteSigning, + WebhookEventDataMissing, + UnknownSubEventType, + PublicKeyDecodeError(hex::FromHexError), + HexEncodingError, + SignerError(signer::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Error::WebhookEventNotRemoteSigning => { + "Webhook event is not a remote signing event".to_string() + } + Error::WebhookEventDataMissing => "Webhook event data is missing".to_string(), + Error::UnknownSubEventType => "Unknown sub event type".to_string(), + Error::PublicKeyDecodeError(e) => format!("Error decoding public key: {}", e), + Error::HexEncodingError => "Error encoding hex".to_string(), + Error::SignerError(e) => format!("Error signing: {}", e), + }; + write!(f, "{}", msg) + } +} diff --git a/lightspark-remote-signing/src/response.rs b/lightspark-remote-signing/src/response.rs new file mode 100644 index 0000000..ad541ea --- /dev/null +++ b/lightspark-remote-signing/src/response.rs @@ -0,0 +1,233 @@ +use lightspark::objects::{ + decline_to_sign_messages_output, id_and_signature::IdAndSignature, + release_channel_per_commitment_secret_output, release_payment_preimage_output, + sign_invoice_output, sign_messages_output, update_channel_per_commitment_point_output, + update_node_shared_secret_output, +}; +use serde_json::{json, Value}; + +pub struct Response { + pub query: String, + pub variables: Value, +} + +const UPDATE_NODE_SHARED_SECRET_MUTATION: &str = "mutation UpdateNodeSharedSecret( + $node_id : ID! + $shared_secret : Hash32! + ) { + update_node_shared_secret(input: { + node_id: $node_id + shared_secret: $shared_secret + }) { + ...UpdateNodeSharedSecretOutputFragment + } + } +"; + +const UPDATE_CHANNEL_PER_COMMITMENT_POINT_MUTATION: &str = + "mutation UpdateChannelPerCommitmentPoint( + $channel_id : ID! + $per_commitment_point : PublicKey! + $per_commitment_point_index : Long! +) { + update_channel_per_commitment_point(input: { + channel_id: $channel_id + per_commitment_point_index: $per_commitment_point_index + per_commitment_point: $per_commitment_point + }) { + ...UpdateChannelPerCommitmentPointOutputFragment + } +}"; + +const RELEASE_CHANNEL_PER_COMMITMENT_SECRET_MUTATION: &str = + "mutation ReleaseChannelPerCommitmentSecret( + $channel_id: ID! + $per_commitment_secret: Hash32! +) { + release_channel_per_commitment_secret(input: { + channel_id: $channel_id + per_commitment_secret: $per_commitment_secret + }) { + ...ReleaseChannelPerCommitmentSecretOutputFragment + } +}"; + +const SIGN_INVOICE_MUTATION: &str = "mutation SignInvoice( + $invoice_id : ID! + $signature : Signature! + $recovery_id : Int! +) { + sign_invoice(input: { + invoice_id: $invoice_id + signature: $signature + recovery_id: $recovery_id + }) { + ...SignInvoiceOutputFragment + } +}"; + +const RELEASE_PAYMENT_PREIMAGE_MUTATION: &str = " +mutation ReleasePaymentPreimage( + $invoice_id: ID! + $payment_preimage: Hash32! +) { + release_payment_preimage(input: { + invoice_id: $invoice_id + payment_preimage: $payment_preimage + }) { + ...ReleasePaymentPreimageOutputFragment + } +}"; + +const SIGN_MESSAGES_MUTATION: &str = " +mutation SignMessages( + $signatures: [IdAndSignature!]! +) { + sign_messages(input: { + signatures: $signatures + }) { + ...SignMessagesOutputFragment + } +}"; + +const DECLINE_TO_SIGN_MESSAGES_MUTATION: &str = " +mutation DeclineToSignMessages($payload_ids: [ID!]!) { + decline_to_sign_messages(input: { + payload_ids: $payload_ids + }) { + ...DeclineToSignMessagesOutputFragment + } +}"; + +const SET_INVOICE_PAYMENT_HASH: &str = " +mutation SetInvoicePaymentHash( + $invoice_id: ID! + $payment_hash: Hash32! + $preimage_nonce: Hash32! +) { + set_invoice_payment_hash(input: { + invoice_id: $invoice_id + payment_hash: $payment_hash + preimage_nonce: $preimage_nonce + }) { + ...SetInvoicePaymentHashOutputFragment + } +}"; + +impl Response { + pub fn ecdh_response(node_id: &str, shared_secret: &str) -> Response { + let variables = json!({ + "node_id": node_id, + "shared_secret": shared_secret, + }); + let query = format!( + "{}\n{}", + UPDATE_NODE_SHARED_SECRET_MUTATION, + update_node_shared_secret_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn get_channel_per_commitment_response( + channel_id: &str, + per_commitment_point: &str, + per_commitment_point_index: u64, + ) -> Response { + let variables = json!({ + "channel_id": channel_id, + "per_commitment_point": per_commitment_point, + "per_commitment_point_index": per_commitment_point_index, + }); + let query = format!( + "{}\n{}", + UPDATE_CHANNEL_PER_COMMITMENT_POINT_MUTATION, + update_channel_per_commitment_point_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn release_channel_per_commitment_secret_response( + channel_id: &str, + per_commitment_secret: &str, + ) -> Response { + let variables = json!({ + "channel_id": channel_id, + "per_commitment_secret": per_commitment_secret, + }); + let query = format!( + "{}\n{}", + RELEASE_CHANNEL_PER_COMMITMENT_SECRET_MUTATION, + release_channel_per_commitment_secret_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn sign_invoice_response(invoice_id: &str, signature: &str, recovery_id: i32) -> Response { + let variables = json!({ + "invoice_id": invoice_id, + "signature": signature, + "recovery_id": recovery_id, + }); + let query = format!( + "{}\n{}", + SIGN_INVOICE_MUTATION, + sign_invoice_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn release_payment_preimage_response(invoice_id: &str, payment_preimage: &str) -> Response { + let variables = json!({ + "invoice_id": invoice_id, + "payment_preimage": payment_preimage, + }); + let query = format!( + "{}\n{}", + RELEASE_PAYMENT_PREIMAGE_MUTATION, + release_payment_preimage_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn sign_messages_response(signatures: Vec) -> Response { + let variables = json!({ + "signatures": signatures, + }); + let query = format!( + "{}\n{}", + SIGN_MESSAGES_MUTATION, + sign_messages_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn decline_to_sign_message_response(payload_ids: &[String]) -> Response { + let variables = json!({ + "payload_ids": payload_ids, + }); + let query = format!( + "{}\n{}", + DECLINE_TO_SIGN_MESSAGES_MUTATION, + decline_to_sign_messages_output::FRAGMENT + ); + Response { query, variables } + } + + pub fn set_invoice_payment_hash_response( + invoice_id: &str, + payment_hash: &str, + pre_image_nonce: &str, + ) -> Response { + let variables = json!({ + "invoice_id": invoice_id, + "payment_hash": payment_hash, + "preimage_nonce": pre_image_nonce, + }); + let query = format!( + "{}\n{}", + SET_INVOICE_PAYMENT_HASH, + sign_messages_output::FRAGMENT + ); + Response { query, variables } + } +} diff --git a/lightspark-remote-signing/src/signer.rs b/lightspark-remote-signing/src/signer.rs new file mode 100644 index 0000000..90db6b1 --- /dev/null +++ b/lightspark-remote-signing/src/signer.rs @@ -0,0 +1,540 @@ +use std::fmt; +use std::str::FromStr; + +use bitcoin::bip32::{DerivationPath, ExtendedPrivKey, ExtendedPubKey}; +use bitcoin::hashes::{sha512, Hash, HashEngine, Hmac, HmacEngine}; +use bitcoin::secp256k1::ecdh::SharedSecret; +use bitcoin::secp256k1::ecdsa::Signature; +use bitcoin::secp256k1::hashes::sha256; +use bitcoin::secp256k1::{Message, PublicKey, Scalar, Secp256k1, SecretKey}; +use rand_core::{OsRng, RngCore}; + +const NODE_KEY_PATH: &str = "m/0"; + +#[derive(Copy, Clone, Debug)] +pub enum Error { + Bip39Error(bip39::Error), + Secp256k1Error(bitcoin::secp256k1::Error), + KeyDerivationError, + EntropyLengthError, + KeyTweakError, +} + +#[derive(Copy, Clone, Debug)] +pub enum Network { + Bitcoin, + Testnet, + Regtest, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Bip39Error(err) => write!(f, "Bip39 error {}", err), + Self::Secp256k1Error(err) => write!(f, "Secp256k1 error {}", err), + Self::KeyDerivationError => write!(f, "Key derivation error"), + Self::EntropyLengthError => write!(f, "Entropy must be 32 bytes"), + Self::KeyTweakError => write!(f, "Key tweak error"), + } + } +} + +impl std::error::Error for Error {} + +#[derive(Clone)] +pub struct Mnemonic { + internal: bip39::Mnemonic, +} + +impl Mnemonic { + pub fn random() -> Result { + let internal = bip39::Mnemonic::generate(24).map_err(Error::Bip39Error)?; + Ok(Self { internal }) + } + + pub fn from_entropy(entropy: Vec) -> Result { + let slice = entropy.as_slice(); + let array: [u8; 32] = slice.try_into().map_err(|_| Error::EntropyLengthError)?; + let internal = bip39::Mnemonic::from_entropy(&array).map_err(Error::Bip39Error)?; + Ok(Self { internal }) + } + + pub fn from_phrase(phrase: String) -> Result { + let internal = + bip39::Mnemonic::parse_normalized(phrase.as_str()).map_err(Error::Bip39Error)?; + Ok(Self { internal }) + } + + pub fn as_string(&self) -> String { + self.internal.to_string() + } +} + +#[derive(Clone)] +pub struct Seed { + seed: Vec, +} + +impl Seed { + pub fn from_mnemonic(mnemonic: &Mnemonic) -> Self { + let seed = mnemonic.internal.to_seed("").to_vec(); + Self { seed } + } + + pub fn new(seed: Vec) -> Self { + Self { seed } + } + + pub fn as_bytes(&self) -> Vec { + self.seed.clone() + } +} + +#[derive(Clone)] +pub struct InvoiceSignature { + signature: Vec, + recovery_id: i32, +} + +impl InvoiceSignature { + pub fn get_signature(&self) -> Vec { + self.signature.clone() + } + + pub fn get_recovery_id(&self) -> i32 { + self.recovery_id + } +} + +pub struct LightsparkSigner { + master_private_key: ExtendedPrivKey, + node_private_key: ExtendedPrivKey, +} + +impl LightsparkSigner { + pub fn new(seed: &Seed, network: Network) -> Result { + let network: bitcoin::Network = match network { + Network::Bitcoin => bitcoin::Network::Bitcoin, + Network::Testnet => bitcoin::Network::Testnet, + Network::Regtest => bitcoin::Network::Regtest, + }; + let master_private_key = ExtendedPrivKey::new_master(network, seed.as_bytes().as_slice()) + .map_err(|_| Error::KeyDerivationError)?; + let secp = Secp256k1::new(); + let node_key_path = + DerivationPath::from_str(NODE_KEY_PATH).map_err(|_| Error::KeyDerivationError)?; + let node_private_key = master_private_key + .derive_priv(&secp, &node_key_path) + .map_err(|_| Error::KeyDerivationError)?; + Ok(Self { + master_private_key, + node_private_key, + }) + } + + pub fn from_bytes(seed: Vec, network: Network) -> Result { + let seed = Seed::new(seed); + Self::new(&seed, network) + } + + pub fn get_master_public_key(&self) -> Result { + let secp = Secp256k1::new(); + let pubkey = ExtendedPubKey::from_priv(&secp, &self.master_private_key); + Ok(pubkey.to_string()) + } + + pub fn derive_public_key(&self, derivation_path: String) -> Result { + let secp = Secp256k1::new(); + let path = + DerivationPath::from_str(&derivation_path).map_err(|_| Error::KeyDerivationError)?; + let private_key = self + .master_private_key + .derive_priv(&secp, &path) + .map_err(|_| Error::KeyDerivationError)?; + let pubkey = ExtendedPubKey::from_priv(&secp, &private_key); + Ok(pubkey.to_string()) + } + + pub fn derive_key_and_sign( + &self, + message: Vec, + derivation_path: String, + is_raw: bool, + add_tweak: Option>, + mul_tweak: Option>, + ) -> Result, Error> { + let secp = Secp256k1::new(); + let signing_key = self.derive_and_tweak_key(derivation_path, add_tweak, mul_tweak)?; + let signature: Signature = match is_raw { + true => { + let msg = Message::from_slice(message.as_slice()).map_err(Error::Secp256k1Error)?; + secp.sign_ecdsa(&msg, &signing_key) + } + false => { + let msg = Message::from_hashed_data::(message.as_slice()); + secp.sign_ecdsa(&msg, &signing_key) + } + }; + + Ok(signature.serialize_compact().to_vec()) + } + + pub fn ecdh(&self, public_key: Vec) -> Result, Error> { + let pubkey = PublicKey::from_slice(public_key.as_slice()).map_err(Error::Secp256k1Error)?; + let our_key = self.node_private_key.private_key; + let ss = SharedSecret::new(&pubkey, &our_key); + Ok(ss.as_ref().to_vec()) + } + + pub fn get_per_commitment_point( + &self, + derivation_path: String, + per_commitment_point_idx: u64, + ) -> Result, Error> { + let per_commitment_secret = + self.release_per_commitment_secret(derivation_path, per_commitment_point_idx)?; + let secret_key = SecretKey::from_slice(per_commitment_secret.as_slice()) + .map_err(Error::Secp256k1Error)?; + let public_key = secret_key.public_key(&Secp256k1::new()); + Ok(public_key.serialize().to_vec()) + } + + pub fn release_per_commitment_secret( + &self, + derivation_path: String, + per_commitment_point_idx: u64, + ) -> Result, Error> { + let key = self + .derive_key(derivation_path) + .map_err(|_| Error::KeyDerivationError)?; + let channel_seed = sha256::Hash::hash(&key.private_key[..]) + .as_byte_array() + .to_vec(); + let commitment_seed = self.build_commitment_seed(channel_seed); + Ok(self.build_commitment_secret(commitment_seed, per_commitment_point_idx)) + } + + pub fn generate_preimage_nonce(&self) -> Vec { + let mut rng = OsRng; + let mut nonce = [0u8; 32]; + rng.fill_bytes(&mut nonce); + nonce.to_vec() + } + + pub fn generate_preimage(&self, nonce: Vec) -> Result, Error> { + let key = self.derive_key("m/4h".to_owned())?; + let mut hmac_engine: HmacEngine = + HmacEngine::new(&key.private_key.secret_bytes()); + hmac_engine.input(b"invoice preimage"); + hmac_engine.input(nonce.as_slice()); + let hmac_result: Hmac = Hmac::from_engine(hmac_engine); + Ok(hmac_result[..32].into()) + } + + pub fn generate_preimage_hash(&self, nonce: Vec) -> Result, Error> { + let preimage = self.generate_preimage(nonce)?; + Ok(sha256::Hash::hash(preimage.as_slice()) + .as_byte_array() + .to_vec()) + } + + fn derive_and_tweak_key( + &self, + derivation_path: String, + add_tweak: Option>, + mul_tweak: Option>, + ) -> Result { + let derived_key = self + .derive_key(derivation_path) + .map_err(|_| Error::KeyDerivationError)?; + let add_tweak: Option<[u8; 32]> = add_tweak + .filter(|tweak| !tweak.is_empty()) + .map(|tweak| tweak.try_into().map_err(|_| Error::KeyTweakError)) + .transpose()?; + let mul_tweak: Option<[u8; 32]> = mul_tweak + .filter(|tweak| !tweak.is_empty()) + .map(|tweak| tweak.try_into().map_err(|_| Error::KeyTweakError)) + .transpose()?; + self.tweak_key(derived_key.private_key, add_tweak, mul_tweak) + } + + fn derive_key(&self, derivation_path: String) -> Result { + let secp = Secp256k1::new(); + let path = + DerivationPath::from_str(&derivation_path).map_err(|_| Error::KeyDerivationError)?; + let private_key = self + .master_private_key + .derive_priv(&secp, &path) + .map_err(|_| Error::KeyDerivationError)?; + Ok(private_key) + } + + fn build_commitment_seed(&self, seed: Vec) -> Vec { + let mut hasher = sha256::Hash::engine(); + hasher.input(seed.as_slice()); + hasher.input(&b"commitment seed"[..]); + sha256::Hash::from_engine(hasher).to_byte_array().to_vec() + } + + fn build_commitment_secret(&self, seed: Vec, idx: u64) -> Vec { + let mut res = seed; + for i in 0..48 { + let bitpos = 47 - i; + if idx & (1 << bitpos) == (1 << bitpos) { + res[bitpos / 8] ^= 1 << (bitpos & 7); + res = sha256::Hash::hash(&res).to_byte_array().to_vec(); + } + } + res + } + + fn tweak_key( + &self, + secret_key: SecretKey, + add_tweak: Option<[u8; 32]>, + mul_tweak: Option<[u8; 32]>, + ) -> Result { + let mut res: SecretKey = secret_key; + if let Some(mul_tweak) = mul_tweak { + let scalar = Scalar::from_be_bytes(mul_tweak).map_err(|_| Error::KeyTweakError)?; + res = res.mul_tweak(&scalar).map_err(Error::Secp256k1Error)?; + } + + if let Some(add_tweak) = add_tweak { + let scalar = Scalar::from_be_bytes(add_tweak).map_err(|_| Error::KeyTweakError)?; + res = res.add_tweak(&scalar).map_err(Error::Secp256k1Error)?; + } + + Ok(res) + } + + pub fn sign_invoice(&self, unsigned_invoice: String) -> Result { + let signing_key = self.node_private_key.private_key; + let msg = Message::from_hashed_data::(unsigned_invoice.as_bytes()); + let secp = Secp256k1::new(); + let sig = secp + .sign_ecdsa_recoverable(&msg, &signing_key) + .serialize_compact(); + let res = InvoiceSignature { + signature: sig.1.to_vec(), + recovery_id: sig.0.to_i32(), + }; + Ok(res) + } + + pub fn sign_invoice_hash(&self, invoice_hash: Vec) -> Result { + let signing_key = self.node_private_key.private_key; + let msg = Message::from_slice(invoice_hash.as_slice()).map_err(Error::Secp256k1Error)?; + let secp = Secp256k1::new(); + let sig = secp + .sign_ecdsa_recoverable(&msg, &signing_key) + .serialize_compact(); + let res = InvoiceSignature { + signature: sig.1.to_vec(), + recovery_id: sig.0.to_i32(), + }; + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex; + + #[test] + fn test_key_derivation() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let xprv = signer.derive_key("m".to_owned()).unwrap(); + let xprv_string = xprv.to_string(); + let expected_string = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"; + assert_eq!(xprv_string.as_str(), expected_string); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let xprv = signer.derive_key("m/0'".to_owned()).unwrap(); + let xprv_string = xprv.to_string(); + let expected_string = "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"; + assert_eq!(xprv_string.as_str(), expected_string); + } + + #[test] + fn test_public_key() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let public_key_string = signer.get_master_public_key().unwrap(); + let expected_string = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; + assert_eq!(public_key_string, expected_string); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let public_key_string = signer.derive_public_key("m/0'".to_owned()).unwrap(); + let expected_string = "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"; + assert_eq!(public_key_string, expected_string); + } + + #[test] + fn test_sign() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let public_key_string = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let xpub = signer.derive_public_key("m".to_owned()).unwrap(); + assert_eq!(xpub, public_key_string); + + let verification_key = ExtendedPubKey::from_str(public_key_string) + .unwrap() + .public_key; + + let message = b"Hello, world!"; + let signature_bytes = signer + .derive_key_and_sign(message.to_vec(), "m".to_owned(), false, None, None) + .unwrap(); + let signature = Signature::from_compact(signature_bytes.as_slice()).unwrap(); + let msg = Message::from_hashed_data::(message); + let secp = Secp256k1::new(); + assert!(secp + .verify_ecdsa(&msg, &signature, &verification_key) + .is_ok()); + } + + #[test] + fn test_ecdh() { + let seed1_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed1_bytes = hex::decode(seed1_hex_string).unwrap(); + let seed1 = Seed::new(seed1_bytes); + + let seed2_hex_string = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"; + let seed2_bytes = hex::decode(seed2_hex_string).unwrap(); + let seed2 = Seed::new(seed2_bytes); + + let signer1 = LightsparkSigner::new(&seed1, Network::Bitcoin).unwrap(); + let pub1 = signer1.derive_public_key("m/0".to_owned()).unwrap(); + let xpub1 = ExtendedPubKey::from_str(&pub1).unwrap(); + let pub1_bytes = xpub1.public_key.serialize(); + + let signer2 = LightsparkSigner::new(&seed2, Network::Bitcoin).unwrap(); + let pub2 = signer2.derive_public_key("m/0".to_owned()).unwrap(); + let xpub2 = ExtendedPubKey::from_str(&pub2).unwrap(); + let pub2_bytes = xpub2.public_key.serialize(); + + let secret_1 = signer1.ecdh(pub2_bytes.to_vec()).unwrap(); + let secret_2 = signer2.ecdh(pub1_bytes.to_vec()).unwrap(); + assert_eq!(secret_1, secret_2); + } + + #[test] + fn test_tweak() { + let base_hex_string = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + let base_bytes = hex::decode(base_hex_string).unwrap(); + let secrect_key = SecretKey::from_slice(base_bytes.as_slice()).unwrap(); + + let mul_tweak = "efbf7ba5a074276701798376950a64a90f698997cce0dff4d24a6d2785d20963"; + let mul_tweak_bytes = hex::decode(mul_tweak).unwrap(); + + let add_tweak = "8be02a96a97b9a3c1c9f59ebb718401128b72ec009d85ee1656319b52319b8ce"; + let add_tweak_bytes = hex::decode(add_tweak).unwrap(); + + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let key = signer + .tweak_key( + secrect_key, + Some(add_tweak_bytes.try_into().unwrap()), + Some(mul_tweak_bytes.try_into().unwrap()), + ) + .unwrap(); + + let result_hex = "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110"; + assert_eq!(format!("{}", key.display_secret()), result_hex); + } + + #[test] + fn test_preimage() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let nonce = signer.generate_preimage_nonce(); + let preimage = signer.generate_preimage(nonce.clone()); + let preimage_hash = sha256::Hash::hash(preimage.unwrap().as_slice()) + .as_byte_array() + .to_vec(); + let preimage_hash2 = signer.generate_preimage_hash(nonce).unwrap(); + assert_eq!(preimage_hash, preimage_hash2); + } + + #[test] + fn test_preimage_with_vectors() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + assert_eq!( + hex::encode(signer.generate_preimage([0u8; 32].to_vec()).unwrap()), + "ceb7494bb4dc84e5963a151f26faa2e759379aeb7b8cc9b02cf9753202d39381" + ); + + assert_eq!( + hex::encode(signer.generate_preimage([1u8; 32].to_vec()).unwrap()), + "d9a850ee1be830b3af70e88ce8085b5d23a24ca8b1dcb9164a4716f6a8771a85" + ); + } + + #[test] + fn test_commitment() { + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + let commitment_point = signer + .get_per_commitment_point("m/3/2104864975".to_owned(), 281474976710654) + .unwrap(); + let commitment_secret = signer + .release_per_commitment_secret("m/3/2104864975".to_owned(), 281474976710654) + .unwrap(); + + let secret_key = SecretKey::from_slice(commitment_secret.as_slice()).unwrap(); + let public_key = secret_key.public_key(&Secp256k1::new()).serialize(); + assert_eq!(commitment_point, public_key); + } + + #[test] + fn test_derive_and_sign() { + let msg = hex::decode("476bdd1db5d91897d00d75300eef50c0da7e0b2dada06dde93cbb5903b7e16b2") + .unwrap(); + let seed_hex_string = "000102030405060708090a0b0c0d0e0f"; + let seed_bytes = hex::decode(seed_hex_string).unwrap(); + let seed = Seed::new(seed_bytes); + let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap(); + + let signature_bytes = signer + .derive_key_and_sign(msg.clone(), "m/3/2106220917/0".to_owned(), true, None, None) + .unwrap(); + let pubkey = signer + .derive_public_key("m/3/2106220917/0".to_owned()) + .unwrap(); + let verification_key = ExtendedPubKey::from_str(&pubkey).unwrap().public_key; + + let msg = Message::from_slice(&msg).unwrap(); + let signature = Signature::from_compact(signature_bytes.as_slice()).unwrap(); + let secp = Secp256k1::new(); + assert!(secp + .verify_ecdsa(&msg, &signature, &verification_key) + .is_ok()); + } +} diff --git a/lightspark-remote-signing/src/validation.rs b/lightspark-remote-signing/src/validation.rs new file mode 100644 index 0000000..c5054d1 --- /dev/null +++ b/lightspark-remote-signing/src/validation.rs @@ -0,0 +1,13 @@ +use lightspark::webhooks::WebhookEvent; + +pub trait Validation { + fn should_sign(&self, webhook: &WebhookEvent) -> bool; +} + +pub struct PositiveValidator; + +impl Validation for PositiveValidator { + fn should_sign(&self, _: &WebhookEvent) -> bool { + true + } +} diff --git a/lightspark/.gitignore b/lightspark/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/lightspark/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/CHANGELOG.md b/lightspark/CHANGELOG.md similarity index 77% rename from CHANGELOG.md rename to lightspark/CHANGELOG.md index 6e2eebf..ba57faf 100644 --- a/CHANGELOG.md +++ b/lightspark/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# v0.6.0 +- Make UMA protocol a sepearte crate. See https://crates.io/crates/uma +- Upgrade to new graphql endpoint. + # v0.5.0 - Add UMA support. diff --git a/lightspark/Cargo.toml b/lightspark/Cargo.toml new file mode 100644 index 0000000..a528b74 --- /dev/null +++ b/lightspark/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "lightspark" +description = "Lightspark Rust SDK" +authors = ["Lightspark Group, Inc. "] +version = "0.6.0" +edition = "2021" +documentation = "https://app.lightspark.com/docs/sdk" +homepage = "https://www.lightspark.com/" +repository = "https://github.com/lightsparkdev/lightspark-rs" +license = "Apache-2.0" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11", features = ["blocking", "json"] } +futures = "0.3" +tokio = { version = "1.12.0", features = ["full"] } +base64 = "0.21.0" +serde_json = "1.0.94" +serde = { version = "1.0.155", features = ["derive"] } +regex = "1.7.1" +chrono = "0.4.24" +openssl = "0.10.48" +aes-gcm = "0.10.1" +rand = "0.8.5" +block-modes = "0.9.1" +os-version = "0.2.0" +version_check = "0.9.4" +hex = "0.4.3" +hmac = "0.12.1" +sha2 = "0.10.7" +bitcoin = "0.30.1" diff --git a/LICENSE b/lightspark/LICENSE similarity index 100% rename from LICENSE rename to lightspark/LICENSE diff --git a/lightspark/README.md b/lightspark/README.md new file mode 100644 index 0000000..9aed4cf --- /dev/null +++ b/lightspark/README.md @@ -0,0 +1,18 @@ +# Lightspark Rust SDK - v0.6.0 + +The Lightspark Rust SDK provides a convenient way to interact with the Lightspark services from applications written in the Rust language. + +***WARNING: This SDK is in version 0.6.0 (active development). It means that its APIs may not be fully stable. Please expect that changes to the APIs may happen until we move to v1.0.0.*** + +## Documentation + +The documentation for this SDK (installation, usage, etc.) is available at https://app.lightspark.com/docs/sdk + +## Sample code + +For your convenience, we included an example that shows you how to use the SDK. +Open the file `example/example.rs` and make sure to update the variables at the top of the page with your information, then run it using cargo: + +``` +cargo run --example example +``` diff --git a/examples/example.rs b/lightspark/examples/example.rs similarity index 99% rename from examples/example.rs rename to lightspark/examples/example.rs index d95a419..45431db 100644 --- a/examples/example.rs +++ b/lightspark/examples/example.rs @@ -1,6 +1,7 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved use chrono::{Duration, Utc}; +use lightspark::key::RSASigningKey; use lightspark::objects::bitcoin_network::BitcoinNetwork; use lightspark::objects::currency_amount::CurrencyAmount; @@ -34,7 +35,7 @@ async fn main() { // Create LightsparkClient let auth_provider = AccountAuthProvider::new(api_id, api_token); - let client = match LightsparkClient::new(auth_provider) { + let mut client = match LightsparkClient::::new(auth_provider) { Ok(value) => value, Err(err) => { println!("{}", err); diff --git a/src/client.rs b/lightspark/src/client.rs similarity index 90% rename from src/client.rs rename to lightspark/src/client.rs index 123333e..c159d67 100644 --- a/src/client.rs +++ b/lightspark/src/client.rs @@ -1,13 +1,16 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use std::cell::RefCell; use std::collections::HashMap; +use std::str::FromStr; +use bitcoin::bip32::{DerivationPath, ExtendedPrivKey}; +use bitcoin::secp256k1::Secp256k1; use openssl::sha::sha256; use serde_json::Value; -use crate::crypto::decrypt_private_key; +use crate::crypto::{decrypt_private_key, CryptoError}; use crate::error::Error; +use crate::key::OperationSigningKey; use crate::objects::account::Account; use crate::objects::api_token::ApiToken; use crate::objects::currency_amount::{self, CurrencyAmount}; @@ -29,20 +32,48 @@ use crate::request::auth_provider::AuthProvider; use crate::request::requester::{Requester, RequesterError}; use crate::types::get_entity::GetEntity; -pub struct LightsparkClient { +const SIGNING_KEY_PATH: &str = "m/5"; + +pub struct LightsparkClient { pub requester: Requester, - signing_keys: RefCell>>, + signing_keys: HashMap, } -impl LightsparkClient { +impl LightsparkClient { pub fn new(auth_provider: T) -> Result { let requester = Requester::new(auth_provider)?; Ok(LightsparkClient { requester, - signing_keys: RefCell::new(HashMap::new()), + signing_keys: HashMap::new(), }) } + pub fn provide_master_seed( + &mut self, + node_id: &str, + master_seed: Vec, + network: bitcoin_network::BitcoinNetwork, + ) -> Result<(), Error> { + let network: bitcoin::Network = match network { + bitcoin_network::BitcoinNetwork::Mainnet => bitcoin::Network::Bitcoin, + bitcoin_network::BitcoinNetwork::Testnet => bitcoin::Network::Testnet, + bitcoin_network::BitcoinNetwork::Regtest => bitcoin::Network::Regtest, + bitcoin_network::BitcoinNetwork::Signet => bitcoin::Network::Signet, + }; + let master_private_key = ExtendedPrivKey::new_master(network, &master_seed) + .map_err(|e| Error::CryptoError(CryptoError::Bip32Error(e)))?; + let secp = Secp256k1::new(); + let node_key_path = DerivationPath::from_str(SIGNING_KEY_PATH) + .map_err(|e| Error::CryptoError(CryptoError::Bip32Error(e)))?; + let signing_key = master_private_key + .derive_priv(&secp, &node_key_path) + .map_err(|e| Error::CryptoError(CryptoError::Bip32Error(e)))?; + + let key = K::new(signing_key.private_key.secret_bytes().to_vec()); + self.load_node_signing_key(node_id, key); + Ok(()) + } + pub async fn get_bitcoin_fee_estimates( &self, bitcoin_network: bitcoin_network::BitcoinNetwork, @@ -412,14 +443,16 @@ impl LightsparkClient { Ok(result) } - fn load_node_signing_key(&self, node_id: &str, signing_key: Vec) { - self.signing_keys - .borrow_mut() - .insert(node_id.to_owned(), signing_key); + fn load_node_signing_key(&mut self, node_id: &str, signing_key: K) { + self.signing_keys.insert(node_id.to_owned(), signing_key); } - fn get_node_signing_key(&self, node_id: &str) -> Option> { - return self.signing_keys.borrow_mut().get(node_id).cloned(); + fn get_node_signing_key(&self, node_id: &str) -> Result { + return self + .signing_keys + .get(node_id) + .cloned() + .ok_or(Error::SigningKeyNotFound); } pub async fn get_decoded_payment_request( @@ -460,7 +493,7 @@ impl LightsparkClient { } pub async fn recover_node_signing_key( - &self, + &mut self, node_id: &str, node_password: &str, ) -> Result, Error> { @@ -501,7 +534,8 @@ impl LightsparkClient { let decrypted_private_key = decrypt_private_key(cipher, encrypted_key, node_password) .map_err(Error::CryptoError)?; - self.load_node_signing_key(node_id, decrypted_private_key.clone()); + let key = K::new(decrypted_private_key.clone()); + self.load_node_signing_key(node_id, key); Ok(decrypted_private_key) } @@ -551,10 +585,10 @@ impl LightsparkClient { let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let signing_key = self.get_node_signing_key(node_id); + let signing_key = self.get_node_signing_key(node_id)?; let json = self .requester - .execute_graphql_signing(&operation, Some(value), signing_key) + .execute_graphql_signing(&operation, Some(value), Some(signing_key)) .await .map_err(Error::ClientError)?; @@ -606,11 +640,11 @@ impl LightsparkClient { let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let signing_key = self.get_node_signing_key(node_id); + let signing_key = self.get_node_signing_key(node_id)?; let json = self .requester - .execute_graphql_signing(&operation, Some(value), signing_key) + .execute_graphql_signing(&operation, Some(value), Some(signing_key)) .await .map_err(Error::ClientError)?; @@ -715,10 +749,10 @@ impl LightsparkClient { variables.insert("withdrawal_mode", withdrawal_mode.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let signing_key = self.get_node_signing_key(node_id); + let signing_key = self.get_node_signing_key(node_id)?; let json = self .requester - .execute_graphql_signing(&operation, Some(value), signing_key) + .execute_graphql_signing(&operation, Some(value), Some(signing_key)) .await .map_err(Error::ClientError)?; @@ -807,10 +841,10 @@ impl LightsparkClient { let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let signing_key = self.get_node_signing_key(node_id); + let signing_key = self.get_node_signing_key(node_id)?; let json = self .requester - .execute_graphql_signing(&mutation, Some(value), signing_key) + .execute_graphql_signing(&mutation, Some(value), Some(signing_key)) .await .map_err(Error::ClientError)?; diff --git a/src/crypto.rs b/lightspark/src/crypto.rs similarity index 96% rename from src/crypto.rs rename to lightspark/src/crypto.rs index 9ceb335..f5016cf 100644 --- a/src/crypto.rs +++ b/lightspark/src/crypto.rs @@ -27,6 +27,8 @@ pub enum CryptoError { JSONParsingError(Error), ParsingError(DecodeError), SigningError(ErrorStack), + Secp256k1Error(bitcoin::secp256k1::Error), + Bip32Error(bitcoin::bip32::Error), } impl fmt::Display for CryptoError { @@ -40,6 +42,8 @@ impl fmt::Display for CryptoError { Self::JSONParsingError(err) => write!(f, "JSON Parser error {}", err), Self::ParsingError(err) => write!(f, "Graphql Parsing error {}", err), Self::SigningError(err) => write!(f, "Signing error {}", err), + Self::Secp256k1Error(err) => write!(f, "Secp256k1 error {}", err), + Self::Bip32Error(err) => write!(f, "Bip32 error {}", err), } } } diff --git a/src/error.rs b/lightspark/src/error.rs similarity index 91% rename from src/error.rs rename to lightspark/src/error.rs index a2402e2..651b405 100644 --- a/src/error.rs +++ b/lightspark/src/error.rs @@ -12,6 +12,7 @@ pub enum Error { ConversionError(serde_json::Error), CryptoError(CryptoError), WebhookSignatureError, + SigningKeyNotFound, } impl fmt::Display for Error { @@ -25,6 +26,7 @@ impl fmt::Display for Error { Self::WebhookSignatureError => { write!(f, "Webhook message hash does not match signature") } + Self::SigningKeyNotFound => write!(f, "Signing key not found"), } } } diff --git a/lightspark/src/key.rs b/lightspark/src/key.rs new file mode 100644 index 0000000..f79c907 --- /dev/null +++ b/lightspark/src/key.rs @@ -0,0 +1,50 @@ +use base64::{self, Engine}; +use bitcoin::secp256k1::{hashes::sha256, Message, Secp256k1, SecretKey}; +use serde_json::json; + +use crate::crypto::{self, CryptoError}; + +pub trait OperationSigningKey: Clone { + fn new(key_bytes: Vec) -> Self; + fn sign_payload(&self, data: &[u8]) -> Result; +} + +#[derive(Debug, Clone)] +pub struct RSASigningKey { + key_bytes: Vec, +} + +impl OperationSigningKey for RSASigningKey { + fn new(key_bytes: Vec) -> Self { + Self { key_bytes } + } + + fn sign_payload(&self, data: &[u8]) -> Result { + crypto::sign_payload(&self.key_bytes, data) + } +} + +#[derive(Debug, Clone)] +pub struct Secp256k1SigningKey { + key_bytes: Vec, +} + +impl OperationSigningKey for Secp256k1SigningKey { + fn new(key_bytes: Vec) -> Self { + Self { key_bytes } + } + + fn sign_payload(&self, data: &[u8]) -> Result { + let secp = Secp256k1::new(); + let secret_key = + SecretKey::from_slice(&self.key_bytes).map_err(CryptoError::Secp256k1Error)?; + let message = Message::from_hashed_data::(data); + let sig = secp.sign_ecdsa(&message, &secret_key); + + Ok(json!({ + "v": 1, + "signature": base64::engine::general_purpose::STANDARD.encode(sig.serialize_der()), + }) + .to_string()) + } +} diff --git a/src/lib.rs b/lightspark/src/lib.rs similarity index 85% rename from src/lib.rs rename to lightspark/src/lib.rs index 23eab1d..1112f69 100644 --- a/src/lib.rs +++ b/lightspark/src/lib.rs @@ -9,11 +9,12 @@ //! ``` //! use lightspark::request::auth_provider::AccountAuthProvider; //! use lightspark::client::LightsparkClient; +//! use lightspark::key::RSASigningKey; //! //! let api_id = ""; //! let api_token = ""; //! let auth_provider = AccountAuthProvider::new(api_id.to_string(), api_token.to_string()); -//! let client = match LightsparkClient::new(auth_provider) { +//! let client = match LightsparkClient::::new(auth_provider) { //! Ok(value) => value, //! Err(err) => { //! println!("{}", err); @@ -27,13 +28,13 @@ //! See more examples in examples/example.rs //! /// The version of this library. -pub const VERSION: &str = "0.5.0"; +pub const VERSION: &str = "0.6.0"; pub mod client; pub mod crypto; pub mod error; +pub mod key; pub mod objects; pub mod request; pub mod types; -pub mod uma; pub mod webhooks; diff --git a/src/objects/account.rs b/lightspark/src/objects/account.rs similarity index 97% rename from src/objects/account.rs rename to lightspark/src/objects/account.rs index 9028d17..d3a9def 100644 --- a/src/objects/account.rs +++ b/lightspark/src/objects/account.rs @@ -416,6 +416,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -520,6 +521,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } @@ -916,6 +918,18 @@ impl Account { incoming_payment_payment_request: payment_request { id } + incoming_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } } ... on OutgoingPayment { __typename @@ -1077,6 +1091,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -1181,6 +1196,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } @@ -1190,6 +1206,18 @@ impl Account { __typename rich_text_text: text } + outgoing_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } } ... on RoutingTransaction { __typename @@ -1448,6 +1476,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -1552,6 +1581,7 @@ impl Account { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } @@ -1643,6 +1673,9 @@ impl Account { } } wallet_third_party_identifier: third_party_identifier + wallet_account: account { + id + } wallet_status: status } } diff --git a/src/objects/account_to_api_tokens_connection.rs b/lightspark/src/objects/account_to_api_tokens_connection.rs similarity index 100% rename from src/objects/account_to_api_tokens_connection.rs rename to lightspark/src/objects/account_to_api_tokens_connection.rs diff --git a/src/objects/account_to_channels_connection.rs b/lightspark/src/objects/account_to_channels_connection.rs similarity index 100% rename from src/objects/account_to_channels_connection.rs rename to lightspark/src/objects/account_to_channels_connection.rs diff --git a/src/objects/account_to_nodes_connection.rs b/lightspark/src/objects/account_to_nodes_connection.rs similarity index 100% rename from src/objects/account_to_nodes_connection.rs rename to lightspark/src/objects/account_to_nodes_connection.rs index 305ee7d..71dc3dc 100644 --- a/src/objects/account_to_nodes_connection.rs +++ b/lightspark/src/objects/account_to_nodes_connection.rs @@ -1,9 +1,9 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::page_info::PageInfo; -use serde::Deserialize; - use crate::objects::connection::Connection; use crate::objects::lightspark_node::LightsparkNodeEnum; +use serde::Deserialize; + +use crate::objects::page_info::PageInfo; use std::vec::Vec; /// A connection between an account and the nodes it manages. diff --git a/src/objects/account_to_payment_requests_connection.rs b/lightspark/src/objects/account_to_payment_requests_connection.rs similarity index 99% rename from src/objects/account_to_payment_requests_connection.rs rename to lightspark/src/objects/account_to_payment_requests_connection.rs index 3baaf2f..73ad2b9 100644 --- a/src/objects/account_to_payment_requests_connection.rs +++ b/lightspark/src/objects/account_to_payment_requests_connection.rs @@ -3,7 +3,6 @@ use crate::objects::connection::Connection; use crate::objects::page_info::PageInfo; use crate::objects::payment_request::PaymentRequestEnum; use serde::Deserialize; - use std::vec::Vec; #[derive(Clone, Deserialize)] diff --git a/src/objects/account_to_transactions_connection.rs b/lightspark/src/objects/account_to_transactions_connection.rs similarity index 99% rename from src/objects/account_to_transactions_connection.rs rename to lightspark/src/objects/account_to_transactions_connection.rs index 78d6334..cd2914d 100644 --- a/src/objects/account_to_transactions_connection.rs +++ b/lightspark/src/objects/account_to_transactions_connection.rs @@ -1,10 +1,9 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::page_info::PageInfo; -use serde::Deserialize; - use crate::objects::connection::Connection; use crate::objects::currency_amount::CurrencyAmount; +use crate::objects::page_info::PageInfo; use crate::objects::transaction::TransactionEnum; +use serde::Deserialize; use std::vec::Vec; #[derive(Clone, Deserialize)] diff --git a/src/objects/account_to_wallets_connection.rs b/lightspark/src/objects/account_to_wallets_connection.rs similarity index 100% rename from src/objects/account_to_wallets_connection.rs rename to lightspark/src/objects/account_to_wallets_connection.rs diff --git a/src/objects/api_token.rs b/lightspark/src/objects/api_token.rs similarity index 100% rename from src/objects/api_token.rs rename to lightspark/src/objects/api_token.rs diff --git a/src/objects/balances.rs b/lightspark/src/objects/balances.rs similarity index 100% rename from src/objects/balances.rs rename to lightspark/src/objects/balances.rs diff --git a/src/objects/bitcoin_network.rs b/lightspark/src/objects/bitcoin_network.rs similarity index 100% rename from src/objects/bitcoin_network.rs rename to lightspark/src/objects/bitcoin_network.rs diff --git a/src/objects/blockchain_balance.rs b/lightspark/src/objects/blockchain_balance.rs similarity index 100% rename from src/objects/blockchain_balance.rs rename to lightspark/src/objects/blockchain_balance.rs diff --git a/src/objects/channel.rs b/lightspark/src/objects/channel.rs similarity index 100% rename from src/objects/channel.rs rename to lightspark/src/objects/channel.rs diff --git a/src/objects/channel_closing_transaction.rs b/lightspark/src/objects/channel_closing_transaction.rs similarity index 100% rename from src/objects/channel_closing_transaction.rs rename to lightspark/src/objects/channel_closing_transaction.rs diff --git a/src/objects/channel_fees.rs b/lightspark/src/objects/channel_fees.rs similarity index 100% rename from src/objects/channel_fees.rs rename to lightspark/src/objects/channel_fees.rs diff --git a/src/objects/channel_opening_transaction.rs b/lightspark/src/objects/channel_opening_transaction.rs similarity index 100% rename from src/objects/channel_opening_transaction.rs rename to lightspark/src/objects/channel_opening_transaction.rs diff --git a/src/objects/channel_status.rs b/lightspark/src/objects/channel_status.rs similarity index 100% rename from src/objects/channel_status.rs rename to lightspark/src/objects/channel_status.rs diff --git a/src/objects/channel_to_transactions_connection.rs b/lightspark/src/objects/channel_to_transactions_connection.rs similarity index 100% rename from src/objects/channel_to_transactions_connection.rs rename to lightspark/src/objects/channel_to_transactions_connection.rs diff --git a/src/objects/crypto_sanctions_screening_provider.rs b/lightspark/src/objects/compliance_provider.rs similarity index 60% rename from src/objects/crypto_sanctions_screening_provider.rs rename to lightspark/src/objects/compliance_provider.rs index 85188f0..920cb87 100644 --- a/src/objects/crypto_sanctions_screening_provider.rs +++ b/lightspark/src/objects/compliance_provider.rs @@ -3,20 +3,20 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fmt; -/// This is an enum identifying a type of crypto sanctions screening provider. +/// This is an enum identifying a type of compliance provider. #[derive(Clone, Deserialize, Serialize)] -pub enum CryptoSanctionsScreeningProvider { +pub enum ComplianceProvider { #[serde(rename = "CHAINALYSIS")] Chainalysis, } -impl From for Value { - fn from(val: CryptoSanctionsScreeningProvider) -> Self { +impl From for Value { + fn from(val: ComplianceProvider) -> Self { Value::from(val.to_string()) } } -impl fmt::Display for CryptoSanctionsScreeningProvider { +impl fmt::Display for ComplianceProvider { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Chainalysis => write!(f, "CHAINALYSIS"), diff --git a/src/objects/connection.rs b/lightspark/src/objects/connection.rs similarity index 84% rename from src/objects/connection.rs rename to lightspark/src/objects/connection.rs index d1d724c..fa7852a 100644 --- a/src/objects/connection.rs +++ b/lightspark/src/objects/connection.rs @@ -9,6 +9,8 @@ use super::incoming_payment_to_attempts_connection::IncomingPaymentToAttemptsCon use super::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; use super::outgoing_payment_attempt_to_hops_connection::OutgoingPaymentAttemptToHopsConnection; use super::outgoing_payment_to_attempts_connection::OutgoingPaymentToAttemptsConnection; +use super::wallet_to_payment_requests_connection::WalletToPaymentRequestsConnection; +use super::wallet_to_transactions_connection::WalletToTransactionsConnection; use crate::objects::page_info::PageInfo; use serde::{Deserialize, Deserializer}; use serde_json::Value; @@ -35,6 +37,8 @@ pub enum ConnectionEnum { LightsparkNodeToChannelsConnection(LightsparkNodeToChannelsConnection), OutgoingPaymentAttemptToHopsConnection(OutgoingPaymentAttemptToHopsConnection), OutgoingPaymentToAttemptsConnection(OutgoingPaymentToAttemptsConnection), + WalletToPaymentRequestsConnection(WalletToPaymentRequestsConnection), + WalletToTransactionsConnection(WalletToTransactionsConnection), } impl<'de> Deserialize<'de> for ConnectionEnum { @@ -104,6 +108,20 @@ impl<'de> Deserialize<'de> for ConnectionEnum { })?; Ok(ConnectionEnum::OutgoingPaymentToAttemptsConnection(obj)) } + "WalletToPaymentRequestsConnection" => { + let obj = + WalletToPaymentRequestsConnection::deserialize(value).map_err(|err| { + serde::de::Error::custom(format!("Serde JSON Error {}", err)) + })?; + Ok(ConnectionEnum::WalletToPaymentRequestsConnection(obj)) + } + "WalletToTransactionsConnection" => { + let obj = + WalletToTransactionsConnection::deserialize(value).map_err(|err| { + serde::de::Error::custom(format!("Serde JSON Error {}", err)) + })?; + Ok(ConnectionEnum::WalletToTransactionsConnection(obj)) + } _ => Err(serde::de::Error::custom("unknown typename")), } diff --git a/src/objects/create_api_token_input.rs b/lightspark/src/objects/create_api_token_input.rs similarity index 100% rename from src/objects/create_api_token_input.rs rename to lightspark/src/objects/create_api_token_input.rs diff --git a/src/objects/create_api_token_output.rs b/lightspark/src/objects/create_api_token_output.rs similarity index 100% rename from src/objects/create_api_token_output.rs rename to lightspark/src/objects/create_api_token_output.rs diff --git a/src/objects/create_invoice_input.rs b/lightspark/src/objects/create_invoice_input.rs similarity index 88% rename from src/objects/create_invoice_input.rs rename to lightspark/src/objects/create_invoice_input.rs index eeeba43..dce3a4a 100644 --- a/src/objects/create_invoice_input.rs +++ b/lightspark/src/objects/create_invoice_input.rs @@ -16,8 +16,4 @@ pub struct CreateInvoiceInput { /// The expiry of the invoice in seconds. Default value is 86400 (1 day). pub expiry_secs: Option, - - pub payment_hash: Option, - - pub preimage_nonce: Option, } diff --git a/src/objects/create_invoice_output.rs b/lightspark/src/objects/create_invoice_output.rs similarity index 100% rename from src/objects/create_invoice_output.rs rename to lightspark/src/objects/create_invoice_output.rs diff --git a/src/objects/create_lnurl_invoice_input.rs b/lightspark/src/objects/create_lnurl_invoice_input.rs similarity index 89% rename from src/objects/create_lnurl_invoice_input.rs rename to lightspark/src/objects/create_lnurl_invoice_input.rs index e0f04e1..aa53e6f 100644 --- a/src/objects/create_lnurl_invoice_input.rs +++ b/lightspark/src/objects/create_lnurl_invoice_input.rs @@ -14,8 +14,4 @@ pub struct CreateLnurlInvoiceInput { /// The expiry of the invoice in seconds. Default value is 86400 (1 day). pub expiry_secs: Option, - - pub payment_hash: Option, - - pub preimage_nonce: Option, } diff --git a/src/objects/create_node_wallet_address_input.rs b/lightspark/src/objects/create_node_wallet_address_input.rs similarity index 100% rename from src/objects/create_node_wallet_address_input.rs rename to lightspark/src/objects/create_node_wallet_address_input.rs diff --git a/src/objects/create_node_wallet_address_output.rs b/lightspark/src/objects/create_node_wallet_address_output.rs similarity index 100% rename from src/objects/create_node_wallet_address_output.rs rename to lightspark/src/objects/create_node_wallet_address_output.rs diff --git a/src/objects/create_test_mode_invoice_input.rs b/lightspark/src/objects/create_test_mode_invoice_input.rs similarity index 100% rename from src/objects/create_test_mode_invoice_input.rs rename to lightspark/src/objects/create_test_mode_invoice_input.rs diff --git a/src/objects/create_test_mode_invoice_output.rs b/lightspark/src/objects/create_test_mode_invoice_output.rs similarity index 100% rename from src/objects/create_test_mode_invoice_output.rs rename to lightspark/src/objects/create_test_mode_invoice_output.rs diff --git a/src/objects/create_test_mode_payment_input.rs b/lightspark/src/objects/create_test_mode_payment_input.rs similarity index 100% rename from src/objects/create_test_mode_payment_input.rs rename to lightspark/src/objects/create_test_mode_payment_input.rs diff --git a/src/objects/create_test_mode_paymentoutput.rs b/lightspark/src/objects/create_test_mode_paymentoutput.rs similarity index 74% rename from src/objects/create_test_mode_paymentoutput.rs rename to lightspark/src/objects/create_test_mode_paymentoutput.rs index a4e74a6..4a61d18 100644 --- a/src/objects/create_test_mode_paymentoutput.rs +++ b/lightspark/src/objects/create_test_mode_paymentoutput.rs @@ -8,6 +8,10 @@ pub struct CreateTestModePaymentoutput { /// The payment that has been sent. #[serde(rename = "create_test_mode_paymentoutput_payment")] pub payment: EntityWrapper, + + /// The payment that has been received. + #[serde(rename = "create_test_mode_paymentoutput_incoming_payment")] + pub incoming_payment: EntityWrapper, } pub const FRAGMENT: &str = " @@ -16,5 +20,8 @@ fragment CreateTestModePaymentoutputFragment on CreateTestModePaymentoutput { create_test_mode_paymentoutput_payment: payment { id } + create_test_mode_paymentoutput_incoming_payment: incoming_payment { + id + } } "; diff --git a/lightspark/src/objects/create_uma_invoice_input.rs b/lightspark/src/objects/create_uma_invoice_input.rs new file mode 100644 index 0000000..ce93440 --- /dev/null +++ b/lightspark/src/objects/create_uma_invoice_input.rs @@ -0,0 +1,13 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct CreateUmaInvoiceInput { + pub node_id: String, + + pub amount_msats: i64, + + pub metadata_hash: String, + + pub expiry_secs: Option, +} diff --git a/src/objects/currency_amount.rs b/lightspark/src/objects/currency_amount.rs similarity index 100% rename from src/objects/currency_amount.rs rename to lightspark/src/objects/currency_amount.rs diff --git a/src/objects/currency_unit.rs b/lightspark/src/objects/currency_unit.rs similarity index 100% rename from src/objects/currency_unit.rs rename to lightspark/src/objects/currency_unit.rs diff --git a/lightspark/src/objects/decline_to_sign_messages_input.rs b/lightspark/src/objects/decline_to_sign_messages_input.rs new file mode 100644 index 0000000..8c210d8 --- /dev/null +++ b/lightspark/src/objects/decline_to_sign_messages_input.rs @@ -0,0 +1,9 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use serde::{Deserialize, Serialize}; +use std::vec::Vec; + +#[derive(Clone, Deserialize, Serialize)] +pub struct DeclineToSignMessagesInput { + /// List of payload ids to decline to sign because validation failed. + pub payload_ids: Vec, +} diff --git a/lightspark/src/objects/decline_to_sign_messages_output.rs b/lightspark/src/objects/decline_to_sign_messages_output.rs new file mode 100644 index 0000000..2558403 --- /dev/null +++ b/lightspark/src/objects/decline_to_sign_messages_output.rs @@ -0,0 +1,19 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::signable_payload::SignablePayload; +use serde::Deserialize; +use std::vec::Vec; + +#[derive(Clone, Deserialize)] +pub struct DeclineToSignMessagesOutput { + #[serde(rename = "decline_to_sign_messages_output_declined_payloads")] + pub declined_payloads: Vec, +} + +pub const FRAGMENT: &str = " +fragment DeclineToSignMessagesOutputFragment on DeclineToSignMessagesOutput { + __typename + decline_to_sign_messages_output_declined_payloads: declined_payloads { + id + } +} +"; diff --git a/src/objects/delete_api_token_input.rs b/lightspark/src/objects/delete_api_token_input.rs similarity index 100% rename from src/objects/delete_api_token_input.rs rename to lightspark/src/objects/delete_api_token_input.rs diff --git a/src/objects/delete_api_token_output.rs b/lightspark/src/objects/delete_api_token_output.rs similarity index 100% rename from src/objects/delete_api_token_output.rs rename to lightspark/src/objects/delete_api_token_output.rs diff --git a/src/objects/deposit.rs b/lightspark/src/objects/deposit.rs similarity index 100% rename from src/objects/deposit.rs rename to lightspark/src/objects/deposit.rs diff --git a/src/objects/entity.rs b/lightspark/src/objects/entity.rs similarity index 100% rename from src/objects/entity.rs rename to lightspark/src/objects/entity.rs diff --git a/src/objects/fee_estimate.rs b/lightspark/src/objects/fee_estimate.rs similarity index 100% rename from src/objects/fee_estimate.rs rename to lightspark/src/objects/fee_estimate.rs diff --git a/src/objects/fund_node_input.rs b/lightspark/src/objects/fund_node_input.rs similarity index 100% rename from src/objects/fund_node_input.rs rename to lightspark/src/objects/fund_node_input.rs diff --git a/src/objects/fund_node_output.rs b/lightspark/src/objects/fund_node_output.rs similarity index 100% rename from src/objects/fund_node_output.rs rename to lightspark/src/objects/fund_node_output.rs diff --git a/src/objects/graph_node.rs b/lightspark/src/objects/graph_node.rs similarity index 100% rename from src/objects/graph_node.rs rename to lightspark/src/objects/graph_node.rs diff --git a/src/objects/hop.rs b/lightspark/src/objects/hop.rs similarity index 100% rename from src/objects/hop.rs rename to lightspark/src/objects/hop.rs diff --git a/src/objects/htlc_attempt_failure_code.rs b/lightspark/src/objects/htlc_attempt_failure_code.rs similarity index 100% rename from src/objects/htlc_attempt_failure_code.rs rename to lightspark/src/objects/htlc_attempt_failure_code.rs diff --git a/src/objects/id_and_signature.rs b/lightspark/src/objects/id_and_signature.rs similarity index 100% rename from src/objects/id_and_signature.rs rename to lightspark/src/objects/id_and_signature.rs diff --git a/src/objects/incoming_payment.rs b/lightspark/src/objects/incoming_payment.rs similarity index 90% rename from src/objects/incoming_payment.rs rename to lightspark/src/objects/incoming_payment.rs index 05c158c..3aac007 100644 --- a/src/objects/incoming_payment.rs +++ b/lightspark/src/objects/incoming_payment.rs @@ -5,6 +5,7 @@ use crate::objects::entity::Entity; use crate::objects::incoming_payment_attempt_status::IncomingPaymentAttemptStatus; use crate::objects::incoming_payment_to_attempts_connection::IncomingPaymentToAttemptsConnection; use crate::objects::lightning_transaction::LightningTransaction; +use crate::objects::post_transaction_data::PostTransactionData; use crate::objects::transaction::Transaction; use crate::objects::transaction_status::TransactionStatus; use crate::request::requester::Requester; @@ -59,6 +60,10 @@ pub struct IncomingPayment { /// The optional payment request for this incoming payment, which will be null if the payment is sent through keysend. #[serde(rename = "incoming_payment_payment_request")] pub payment_request: Option, + + /// The post transaction data which can be used in KYT payment registration. + #[serde(rename = "incoming_payment_uma_post_transaction_data")] + pub uma_post_transaction_data: Option>, } impl LightningTransaction for IncomingPayment { @@ -155,6 +160,18 @@ fragment IncomingPaymentFragment on IncomingPayment { incoming_payment_payment_request: payment_request { id } + incoming_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } } "; diff --git a/src/objects/incoming_payment_attempt.rs b/lightspark/src/objects/incoming_payment_attempt.rs similarity index 100% rename from src/objects/incoming_payment_attempt.rs rename to lightspark/src/objects/incoming_payment_attempt.rs diff --git a/src/objects/incoming_payment_attempt_status.rs b/lightspark/src/objects/incoming_payment_attempt_status.rs similarity index 100% rename from src/objects/incoming_payment_attempt_status.rs rename to lightspark/src/objects/incoming_payment_attempt_status.rs diff --git a/src/objects/incoming_payment_to_attempts_connection.rs b/lightspark/src/objects/incoming_payment_to_attempts_connection.rs similarity index 100% rename from src/objects/incoming_payment_to_attempts_connection.rs rename to lightspark/src/objects/incoming_payment_to_attempts_connection.rs diff --git a/src/objects/invoice.rs b/lightspark/src/objects/invoice.rs similarity index 98% rename from src/objects/invoice.rs rename to lightspark/src/objects/invoice.rs index 6cff234..b162006 100644 --- a/src/objects/invoice.rs +++ b/lightspark/src/objects/invoice.rs @@ -227,6 +227,7 @@ fragment InvoiceFragment on Invoice { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -331,6 +332,7 @@ fragment InvoiceFragment on Invoice { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } diff --git a/src/objects/invoice_data.rs b/lightspark/src/objects/invoice_data.rs similarity index 98% rename from src/objects/invoice_data.rs rename to lightspark/src/objects/invoice_data.rs index 97fb4a5..b5dfb36 100644 --- a/src/objects/invoice_data.rs +++ b/lightspark/src/objects/invoice_data.rs @@ -1,12 +1,12 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::currency_amount::CurrencyAmount; use crate::objects::node::NodeEnum; -use crate::types::custom_date_formats::custom_date_format; -use chrono::{DateTime, Utc}; +use crate::objects::payment_request_data::PaymentRequestData; use serde::Deserialize; -use crate::objects::payment_request_data::PaymentRequestData; +use crate::objects::bitcoin_network::BitcoinNetwork; +use crate::types::custom_date_formats::custom_date_format; +use chrono::{DateTime, Utc}; /// This object represents the data associated with a BOLT #11 invoice. You can retrieve this object to receive the relevant data associated with a specific invoice. #[derive(Clone, Deserialize)] @@ -185,6 +185,7 @@ fragment InvoiceDataFragment on InvoiceData { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -289,6 +290,7 @@ fragment InvoiceDataFragment on InvoiceData { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } diff --git a/src/objects/invoice_type.rs b/lightspark/src/objects/invoice_type.rs similarity index 100% rename from src/objects/invoice_type.rs rename to lightspark/src/objects/invoice_type.rs diff --git a/src/objects/lightning_fee_estimate_for_invoice_input.rs b/lightspark/src/objects/lightning_fee_estimate_for_invoice_input.rs similarity index 100% rename from src/objects/lightning_fee_estimate_for_invoice_input.rs rename to lightspark/src/objects/lightning_fee_estimate_for_invoice_input.rs diff --git a/src/objects/lightning_fee_estimate_for_node_input.rs b/lightspark/src/objects/lightning_fee_estimate_for_node_input.rs similarity index 100% rename from src/objects/lightning_fee_estimate_for_node_input.rs rename to lightspark/src/objects/lightning_fee_estimate_for_node_input.rs diff --git a/src/objects/lightning_fee_estimate_output.rs b/lightspark/src/objects/lightning_fee_estimate_output.rs similarity index 100% rename from src/objects/lightning_fee_estimate_output.rs rename to lightspark/src/objects/lightning_fee_estimate_output.rs diff --git a/src/objects/lightning_transaction.rs b/lightspark/src/objects/lightning_transaction.rs similarity index 100% rename from src/objects/lightning_transaction.rs rename to lightspark/src/objects/lightning_transaction.rs diff --git a/src/objects/lightspark_node.rs b/lightspark/src/objects/lightspark_node.rs similarity index 94% rename from src/objects/lightspark_node.rs rename to lightspark/src/objects/lightspark_node.rs index dc4b851..a62d523 100644 --- a/src/objects/lightspark_node.rs +++ b/lightspark/src/objects/lightspark_node.rs @@ -1,16 +1,17 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::entity::Entity; -use crate::objects::node::Node; - use super::lightspark_node_with_o_s_k::LightsparkNodeWithOSK; -use super::lightspark_node_with_remote_signing::LightsparkNodeWithRemoteSigning; use crate::objects::blockchain_balance::BlockchainBalance; use crate::objects::currency_amount::CurrencyAmount; +use crate::objects::entity::Entity; use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::types::entity_wrapper::EntityWrapper; +use crate::objects::node::Node; use serde::{Deserialize, Deserializer}; use serde_json::Value; +use super::lightspark_node_with_remote_signing::LightsparkNodeWithRemoteSigning; +use crate::types::entity_wrapper::EntityWrapper; +use std::vec::Vec; + pub trait LightsparkNode: Node + Entity { /// The owner of this LightsparkNode. fn get_owner_id(&self) -> EntityWrapper; @@ -33,6 +34,9 @@ pub trait LightsparkNode: Node + Entity { /// The details of the balance of this node on the Bitcoin Network. fn get_blockchain_balance(&self) -> Option; + /// The utxos of the channels that are connected to this node. This is used in uma flow for pre-screening. + fn get_uma_prescreening_utxos(&self) -> Vec; + fn type_name(&self) -> &'static str; } diff --git a/src/objects/lightspark_node_owner.rs b/lightspark/src/objects/lightspark_node_owner.rs similarity index 100% rename from src/objects/lightspark_node_owner.rs rename to lightspark/src/objects/lightspark_node_owner.rs diff --git a/src/objects/lightspark_node_status.rs b/lightspark/src/objects/lightspark_node_status.rs similarity index 100% rename from src/objects/lightspark_node_status.rs rename to lightspark/src/objects/lightspark_node_status.rs diff --git a/src/objects/lightspark_node_to_channels_connection.rs b/lightspark/src/objects/lightspark_node_to_channels_connection.rs similarity index 100% rename from src/objects/lightspark_node_to_channels_connection.rs rename to lightspark/src/objects/lightspark_node_to_channels_connection.rs diff --git a/src/objects/lightspark_node_with_o_s_k.rs b/lightspark/src/objects/lightspark_node_with_o_s_k.rs similarity index 97% rename from src/objects/lightspark_node_with_o_s_k.rs rename to lightspark/src/objects/lightspark_node_with_o_s_k.rs index f9063a8..31f27a6 100644 --- a/src/objects/lightspark_node_with_o_s_k.rs +++ b/lightspark/src/objects/lightspark_node_with_o_s_k.rs @@ -1,27 +1,27 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::error::Error; +use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::blockchain_balance::BlockchainBalance; use crate::objects::channel_status::ChannelStatus; +use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; use crate::objects::lightspark_node::LightsparkNode; -use crate::objects::node::Node; +use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; use crate::objects::node_address_type::NodeAddressType; use crate::objects::secret::Secret; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; -use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use chrono::{DateTime, Utc}; use serde::Deserialize; use std::collections::HashMap; +use std::vec::Vec; -use crate::objects::bitcoin_network::BitcoinNetwork; -use crate::objects::currency_amount::CurrencyAmount; +use crate::error::Error; use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; +use crate::objects::node::Node; use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; -use chrono::{DateTime, Utc}; +use crate::request::requester::Requester; +use crate::types::entity_wrapper::EntityWrapper; use serde_json::Value; -use std::vec::Vec; /// This is a Lightspark node with OSK. #[derive(Clone, Deserialize)] @@ -96,6 +96,10 @@ pub struct LightsparkNodeWithOSK { #[serde(rename = "lightspark_node_with_o_s_k_blockchain_balance")] pub blockchain_balance: Option, + /// The utxos of the channels that are connected to this node. This is used in uma flow for pre-screening. + #[serde(rename = "lightspark_node_with_o_s_k_uma_prescreening_utxos")] + pub uma_prescreening_utxos: Vec, + /// The private key client is using to sign a GraphQL request which will be verified at server side. #[serde(rename = "lightspark_node_with_o_s_k_encrypted_signing_private_key")] pub encrypted_signing_private_key: Option, @@ -137,6 +141,11 @@ impl LightsparkNode for LightsparkNodeWithOSK { self.blockchain_balance.clone() } + /// The utxos of the channels that are connected to this node. This is used in uma flow for pre-screening. + fn get_uma_prescreening_utxos(&self) -> Vec { + self.uma_prescreening_utxos.clone() + } + fn type_name(&self) -> &'static str { "LightsparkNodeWithOSK" } @@ -316,6 +325,7 @@ fragment LightsparkNodeWithOSKFragment on LightsparkNodeWithOSK { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value diff --git a/src/objects/lightspark_node_with_remote_signing.rs b/lightspark/src/objects/lightspark_node_with_remote_signing.rs similarity index 97% rename from src/objects/lightspark_node_with_remote_signing.rs rename to lightspark/src/objects/lightspark_node_with_remote_signing.rs index c070889..a619ca0 100644 --- a/src/objects/lightspark_node_with_remote_signing.rs +++ b/lightspark/src/objects/lightspark_node_with_remote_signing.rs @@ -1,26 +1,26 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::error::Error; +use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::blockchain_balance::BlockchainBalance; use crate::objects::channel_status::ChannelStatus; +use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; use crate::objects::lightspark_node::LightsparkNode; -use crate::objects::node::Node; +use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; use crate::objects::node_address_type::NodeAddressType; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; -use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use chrono::{DateTime, Utc}; use serde::Deserialize; use std::collections::HashMap; +use std::vec::Vec; -use crate::objects::bitcoin_network::BitcoinNetwork; -use crate::objects::currency_amount::CurrencyAmount; +use crate::error::Error; use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; +use crate::objects::node::Node; use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; -use chrono::{DateTime, Utc}; +use crate::request::requester::Requester; +use crate::types::entity_wrapper::EntityWrapper; use serde_json::Value; -use std::vec::Vec; /// This is a Lightspark node with remote signing. #[derive(Clone, Deserialize)] @@ -94,6 +94,10 @@ pub struct LightsparkNodeWithRemoteSigning { /// The details of the balance of this node on the Bitcoin Network. #[serde(rename = "lightspark_node_with_remote_signing_blockchain_balance")] pub blockchain_balance: Option, + + /// The utxos of the channels that are connected to this node. This is used in uma flow for pre-screening. + #[serde(rename = "lightspark_node_with_remote_signing_uma_prescreening_utxos")] + pub uma_prescreening_utxos: Vec, } impl LightsparkNode for LightsparkNodeWithRemoteSigning { @@ -132,6 +136,11 @@ impl LightsparkNode for LightsparkNodeWithRemoteSigning { self.blockchain_balance.clone() } + /// The utxos of the channels that are connected to this node. This is used in uma flow for pre-screening. + fn get_uma_prescreening_utxos(&self) -> Vec { + self.uma_prescreening_utxos.clone() + } + fn type_name(&self) -> &'static str { "LightsparkNodeWithRemoteSigning" } @@ -311,6 +320,7 @@ fragment LightsparkNodeWithRemoteSigningFragment on LightsparkNodeWithRemoteSign currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } "; diff --git a/src/objects/mod.rs b/lightspark/src/objects/mod.rs similarity index 85% rename from src/objects/mod.rs rename to lightspark/src/objects/mod.rs index 8af8bf6..01dbfc9 100644 --- a/src/objects/mod.rs +++ b/lightspark/src/objects/mod.rs @@ -17,6 +17,7 @@ pub mod channel_fees; pub mod channel_opening_transaction; pub mod channel_status; pub mod channel_to_transactions_connection; +pub mod compliance_provider; pub mod connection; pub mod create_api_token_input; pub mod create_api_token_output; @@ -29,9 +30,11 @@ pub mod create_test_mode_invoice_input; pub mod create_test_mode_invoice_output; pub mod create_test_mode_payment_input; pub mod create_test_mode_paymentoutput; -pub mod crypto_sanctions_screening_provider; +pub mod create_uma_invoice_input; pub mod currency_amount; pub mod currency_unit; +pub mod decline_to_sign_messages_input; +pub mod decline_to_sign_messages_output; pub mod delete_api_token_input; pub mod delete_api_token_output; pub mod deposit; @@ -70,14 +73,21 @@ pub mod outgoing_payment_attempt; pub mod outgoing_payment_attempt_status; pub mod outgoing_payment_attempt_to_hops_connection; pub mod outgoing_payment_to_attempts_connection; +pub mod outgoing_payments_for_invoice_query_input; +pub mod outgoing_payments_for_invoice_query_output; pub mod page_info; pub mod pay_invoice_input; pub mod pay_invoice_output; +pub mod pay_uma_invoice_input; +pub mod payment_direction; pub mod payment_failure_reason; pub mod payment_request; pub mod payment_request_data; pub mod payment_request_status; pub mod permission; +pub mod post_transaction_data; +pub mod register_payment_input; +pub mod register_payment_output; pub mod release_channel_per_commitment_secret_input; pub mod release_channel_per_commitment_secret_output; pub mod release_payment_preimage_input; @@ -89,11 +99,13 @@ pub mod rich_text; pub mod risk_rating; pub mod routing_transaction; pub mod routing_transaction_failure_reason; -pub mod screen_bitcoin_addresses_input; -pub mod screen_bitcoin_addresses_output; +pub mod screen_node_input; +pub mod screen_node_output; pub mod secret; pub mod send_payment_input; pub mod send_payment_output; +pub mod set_invoice_payment_hash_input; +pub mod set_invoice_payment_hash_output; pub mod sign_invoice_input; pub mod sign_invoice_output; pub mod sign_messages_input; @@ -111,6 +123,8 @@ pub mod update_node_shared_secret_input; pub mod update_node_shared_secret_output; pub mod wallet; pub mod wallet_status; +pub mod wallet_to_payment_requests_connection; +pub mod wallet_to_transactions_connection; pub mod webhook_event_type; pub mod withdrawal; pub mod withdrawal_mode; diff --git a/src/objects/node.rs b/lightspark/src/objects/node.rs similarity index 100% rename from src/objects/node.rs rename to lightspark/src/objects/node.rs diff --git a/src/objects/node_address.rs b/lightspark/src/objects/node_address.rs similarity index 100% rename from src/objects/node_address.rs rename to lightspark/src/objects/node_address.rs diff --git a/src/objects/node_address_type.rs b/lightspark/src/objects/node_address_type.rs similarity index 100% rename from src/objects/node_address_type.rs rename to lightspark/src/objects/node_address_type.rs diff --git a/src/objects/node_to_addresses_connection.rs b/lightspark/src/objects/node_to_addresses_connection.rs similarity index 100% rename from src/objects/node_to_addresses_connection.rs rename to lightspark/src/objects/node_to_addresses_connection.rs diff --git a/src/objects/on_chain_transaction.rs b/lightspark/src/objects/on_chain_transaction.rs similarity index 100% rename from src/objects/on_chain_transaction.rs rename to lightspark/src/objects/on_chain_transaction.rs diff --git a/src/objects/outgoing_payment.rs b/lightspark/src/objects/outgoing_payment.rs similarity index 95% rename from src/objects/outgoing_payment.rs rename to lightspark/src/objects/outgoing_payment.rs index c56bca7..f2a709b 100644 --- a/src/objects/outgoing_payment.rs +++ b/lightspark/src/objects/outgoing_payment.rs @@ -1,24 +1,26 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::lightning_transaction::LightningTransaction; -use crate::objects::payment_request_data::PaymentRequestDataEnum; -use crate::types::custom_date_formats::custom_date_format; -use crate::types::entity_wrapper::EntityWrapper; -use crate::types::get_entity::GetEntity; use serde::Deserialize; use crate::error::Error; use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; +use crate::objects::lightning_transaction::LightningTransaction; use crate::objects::outgoing_payment_to_attempts_connection::OutgoingPaymentToAttemptsConnection; use crate::objects::payment_failure_reason::PaymentFailureReason; +use crate::objects::payment_request_data::PaymentRequestDataEnum; +use crate::objects::post_transaction_data::PostTransactionData; use crate::objects::rich_text::RichText; use crate::objects::transaction::Transaction; use crate::objects::transaction_status::TransactionStatus; use crate::request::requester::Requester; +use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; +use crate::types::entity_wrapper::EntityWrapper; +use crate::types::get_entity::GetEntity; use chrono::{DateTime, Utc}; use serde_json::Value; use std::collections::HashMap; +use std::vec::Vec; /// This object represents a Lightning Network payment sent from a Lightspark Node. You can retrieve this object to receive payment related information about any payment sent from your Lightspark Node on the Lightning Network. #[derive(Clone, Deserialize)] @@ -77,6 +79,10 @@ pub struct OutgoingPayment { /// If applicable, user-facing error message describing why the payment failed. #[serde(rename = "outgoing_payment_failure_message")] pub failure_message: Option, + + /// The post transaction data which can be used in KYT payment registration. + #[serde(rename = "outgoing_payment_uma_post_transaction_data")] + pub uma_post_transaction_data: Option>, } impl LightningTransaction for OutgoingPayment { @@ -311,6 +317,7 @@ fragment OutgoingPaymentFragment on OutgoingPayment { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { __typename secret_encrypted_value: encrypted_value @@ -415,6 +422,7 @@ fragment OutgoingPaymentFragment on OutgoingPayment { currency_amount_preferred_currency_value_approx: preferred_currency_value_approx } } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos } } } @@ -424,6 +432,18 @@ fragment OutgoingPaymentFragment on OutgoingPayment { __typename rich_text_text: text } + outgoing_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } } "; diff --git a/src/objects/outgoing_payment_attempt.rs b/lightspark/src/objects/outgoing_payment_attempt.rs similarity index 100% rename from src/objects/outgoing_payment_attempt.rs rename to lightspark/src/objects/outgoing_payment_attempt.rs diff --git a/src/objects/outgoing_payment_attempt_status.rs b/lightspark/src/objects/outgoing_payment_attempt_status.rs similarity index 100% rename from src/objects/outgoing_payment_attempt_status.rs rename to lightspark/src/objects/outgoing_payment_attempt_status.rs diff --git a/src/objects/outgoing_payment_attempt_to_hops_connection.rs b/lightspark/src/objects/outgoing_payment_attempt_to_hops_connection.rs similarity index 100% rename from src/objects/outgoing_payment_attempt_to_hops_connection.rs rename to lightspark/src/objects/outgoing_payment_attempt_to_hops_connection.rs diff --git a/src/objects/outgoing_payment_to_attempts_connection.rs b/lightspark/src/objects/outgoing_payment_to_attempts_connection.rs similarity index 100% rename from src/objects/outgoing_payment_to_attempts_connection.rs rename to lightspark/src/objects/outgoing_payment_to_attempts_connection.rs diff --git a/lightspark/src/objects/outgoing_payments_for_invoice_query_input.rs b/lightspark/src/objects/outgoing_payments_for_invoice_query_input.rs new file mode 100644 index 0000000..50f6ab6 --- /dev/null +++ b/lightspark/src/objects/outgoing_payments_for_invoice_query_input.rs @@ -0,0 +1,13 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::transaction_status::TransactionStatus; +use serde::{Deserialize, Serialize}; +use std::vec::Vec; + +#[derive(Clone, Deserialize, Serialize)] +pub struct OutgoingPaymentsForInvoiceQueryInput { + /// The encoded invoice that the outgoing payments paid to. + pub encoded_invoice: String, + + /// An optional filter to only query outgoing payments of given statuses. + pub statuses: Option>, +} diff --git a/lightspark/src/objects/outgoing_payments_for_invoice_query_output.rs b/lightspark/src/objects/outgoing_payments_for_invoice_query_output.rs new file mode 100644 index 0000000..580ff41 --- /dev/null +++ b/lightspark/src/objects/outgoing_payments_for_invoice_query_output.rs @@ -0,0 +1,19 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::outgoing_payment::OutgoingPayment; +use serde::Deserialize; +use std::vec::Vec; + +#[derive(Clone, Deserialize)] +pub struct OutgoingPaymentsForInvoiceQueryOutput { + #[serde(rename = "outgoing_payments_for_invoice_query_output_payments")] + pub payments: Vec, +} + +pub const FRAGMENT: &str = " +fragment OutgoingPaymentsForInvoiceQueryOutputFragment on OutgoingPaymentsForInvoiceQueryOutput { + __typename + outgoing_payments_for_invoice_query_output_payments: payments { + id + } +} +"; diff --git a/src/objects/page_info.rs b/lightspark/src/objects/page_info.rs similarity index 100% rename from src/objects/page_info.rs rename to lightspark/src/objects/page_info.rs diff --git a/src/objects/pay_invoice_input.rs b/lightspark/src/objects/pay_invoice_input.rs similarity index 100% rename from src/objects/pay_invoice_input.rs rename to lightspark/src/objects/pay_invoice_input.rs diff --git a/src/objects/pay_invoice_output.rs b/lightspark/src/objects/pay_invoice_output.rs similarity index 100% rename from src/objects/pay_invoice_output.rs rename to lightspark/src/objects/pay_invoice_output.rs diff --git a/lightspark/src/objects/pay_uma_invoice_input.rs b/lightspark/src/objects/pay_uma_invoice_input.rs new file mode 100644 index 0000000..8668aea --- /dev/null +++ b/lightspark/src/objects/pay_uma_invoice_input.rs @@ -0,0 +1,15 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct PayUmaInvoiceInput { + pub node_id: String, + + pub encoded_invoice: String, + + pub timeout_secs: i64, + + pub maximum_fees_msats: i64, + + pub amount_msats: Option, +} diff --git a/lightspark/src/objects/payment_direction.rs b/lightspark/src/objects/payment_direction.rs new file mode 100644 index 0000000..84342ff --- /dev/null +++ b/lightspark/src/objects/payment_direction.rs @@ -0,0 +1,29 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::fmt; + +/// This is an enum indicating the direction of the payment. +#[derive(Clone, Deserialize, Serialize)] +pub enum PaymentDirection { + #[serde(rename = "SENT")] + Sent, + + #[serde(rename = "RECEIVED")] + Received, +} + +impl From for Value { + fn from(val: PaymentDirection) -> Self { + Value::from(val.to_string()) + } +} + +impl fmt::Display for PaymentDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Sent => write!(f, "SENT"), + Self::Received => write!(f, "RECEIVED"), + } + } +} diff --git a/src/objects/payment_failure_reason.rs b/lightspark/src/objects/payment_failure_reason.rs similarity index 100% rename from src/objects/payment_failure_reason.rs rename to lightspark/src/objects/payment_failure_reason.rs diff --git a/src/objects/payment_request.rs b/lightspark/src/objects/payment_request.rs similarity index 100% rename from src/objects/payment_request.rs rename to lightspark/src/objects/payment_request.rs diff --git a/src/objects/payment_request_data.rs b/lightspark/src/objects/payment_request_data.rs similarity index 100% rename from src/objects/payment_request_data.rs rename to lightspark/src/objects/payment_request_data.rs diff --git a/src/objects/payment_request_status.rs b/lightspark/src/objects/payment_request_status.rs similarity index 100% rename from src/objects/payment_request_status.rs rename to lightspark/src/objects/payment_request_status.rs diff --git a/src/objects/permission.rs b/lightspark/src/objects/permission.rs similarity index 100% rename from src/objects/permission.rs rename to lightspark/src/objects/permission.rs diff --git a/lightspark/src/objects/post_transaction_data.rs b/lightspark/src/objects/post_transaction_data.rs new file mode 100644 index 0000000..dd9f47e --- /dev/null +++ b/lightspark/src/objects/post_transaction_data.rs @@ -0,0 +1,30 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::currency_amount::CurrencyAmount; +use serde::Deserialize; + +/// This object represents post-transaction data that could be used to register payment for KYT. +#[derive(Clone, Deserialize)] +pub struct PostTransactionData { + /// The utxo of the channel over which the payment went through in the format of :. + #[serde(rename = "post_transaction_data_utxo")] + pub utxo: String, + + /// The amount of funds transferred in the payment. + #[serde(rename = "post_transaction_data_amount")] + pub amount: CurrencyAmount, +} + +pub const FRAGMENT: &str = " +fragment PostTransactionDataFragment on PostTransactionData { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } +} +"; diff --git a/lightspark/src/objects/register_payment_input.rs b/lightspark/src/objects/register_payment_input.rs new file mode 100644 index 0000000..9d2976f --- /dev/null +++ b/lightspark/src/objects/register_payment_input.rs @@ -0,0 +1,15 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::compliance_provider::ComplianceProvider; +use crate::objects::payment_direction::PaymentDirection; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct RegisterPaymentInput { + pub provider: ComplianceProvider, + + pub payment_id: String, + + pub node_pubkey: String, + + pub direction: PaymentDirection, +} diff --git a/lightspark/src/objects/register_payment_output.rs b/lightspark/src/objects/register_payment_output.rs new file mode 100644 index 0000000..5105ef6 --- /dev/null +++ b/lightspark/src/objects/register_payment_output.rs @@ -0,0 +1,18 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::types::entity_wrapper::EntityWrapper; +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct RegisterPaymentOutput { + #[serde(rename = "register_payment_output_payment")] + pub payment: EntityWrapper, +} + +pub const FRAGMENT: &str = " +fragment RegisterPaymentOutputFragment on RegisterPaymentOutput { + __typename + register_payment_output_payment: payment { + id + } +} +"; diff --git a/src/objects/release_channel_per_commitment_secret_input.rs b/lightspark/src/objects/release_channel_per_commitment_secret_input.rs similarity index 88% rename from src/objects/release_channel_per_commitment_secret_input.rs rename to lightspark/src/objects/release_channel_per_commitment_secret_input.rs index 1b6450e..0cff1ef 100644 --- a/src/objects/release_channel_per_commitment_secret_input.rs +++ b/lightspark/src/objects/release_channel_per_commitment_secret_input.rs @@ -6,4 +6,6 @@ pub struct ReleaseChannelPerCommitmentSecretInput { pub channel_id: String, pub per_commitment_secret: String, + + pub per_commitment_index: i64, } diff --git a/src/objects/release_channel_per_commitment_secret_output.rs b/lightspark/src/objects/release_channel_per_commitment_secret_output.rs similarity index 100% rename from src/objects/release_channel_per_commitment_secret_output.rs rename to lightspark/src/objects/release_channel_per_commitment_secret_output.rs diff --git a/src/objects/release_payment_preimage_input.rs b/lightspark/src/objects/release_payment_preimage_input.rs similarity index 100% rename from src/objects/release_payment_preimage_input.rs rename to lightspark/src/objects/release_payment_preimage_input.rs diff --git a/src/objects/release_payment_preimage_output.rs b/lightspark/src/objects/release_payment_preimage_output.rs similarity index 100% rename from src/objects/release_payment_preimage_output.rs rename to lightspark/src/objects/release_payment_preimage_output.rs diff --git a/src/objects/remote_signing_sub_event_type.rs b/lightspark/src/objects/remote_signing_sub_event_type.rs similarity index 89% rename from src/objects/remote_signing_sub_event_type.rs rename to lightspark/src/objects/remote_signing_sub_event_type.rs index 84b276b..dac8286 100644 --- a/src/objects/remote_signing_sub_event_type.rs +++ b/lightspark/src/objects/remote_signing_sub_event_type.rs @@ -23,6 +23,9 @@ pub enum RemoteSigningSubEventType { #[serde(rename = "RELEASE_PAYMENT_PREIMAGE")] ReleasePaymentPreimage, + + #[serde(rename = "REQUEST_INVOICE_PAYMENT_HASH")] + RequestInvoicePaymentHash, } impl From for Value { @@ -40,6 +43,7 @@ impl fmt::Display for RemoteSigningSubEventType { Self::SignInvoice => write!(f, "SIGN_INVOICE"), Self::DeriveKeyAndSign => write!(f, "DERIVE_KEY_AND_SIGN"), Self::ReleasePaymentPreimage => write!(f, "RELEASE_PAYMENT_PREIMAGE"), + Self::RequestInvoicePaymentHash => write!(f, "REQUEST_INVOICE_PAYMENT_HASH"), } } } diff --git a/src/objects/request_withdrawal_input.rs b/lightspark/src/objects/request_withdrawal_input.rs similarity index 100% rename from src/objects/request_withdrawal_input.rs rename to lightspark/src/objects/request_withdrawal_input.rs diff --git a/src/objects/request_withdrawal_output.rs b/lightspark/src/objects/request_withdrawal_output.rs similarity index 100% rename from src/objects/request_withdrawal_output.rs rename to lightspark/src/objects/request_withdrawal_output.rs diff --git a/src/objects/rich_text.rs b/lightspark/src/objects/rich_text.rs similarity index 100% rename from src/objects/rich_text.rs rename to lightspark/src/objects/rich_text.rs diff --git a/src/objects/risk_rating.rs b/lightspark/src/objects/risk_rating.rs similarity index 100% rename from src/objects/risk_rating.rs rename to lightspark/src/objects/risk_rating.rs diff --git a/src/objects/routing_transaction.rs b/lightspark/src/objects/routing_transaction.rs similarity index 100% rename from src/objects/routing_transaction.rs rename to lightspark/src/objects/routing_transaction.rs diff --git a/src/objects/routing_transaction_failure_reason.rs b/lightspark/src/objects/routing_transaction_failure_reason.rs similarity index 100% rename from src/objects/routing_transaction_failure_reason.rs rename to lightspark/src/objects/routing_transaction_failure_reason.rs diff --git a/lightspark/src/objects/screen_node_input.rs b/lightspark/src/objects/screen_node_input.rs new file mode 100644 index 0000000..d4abe46 --- /dev/null +++ b/lightspark/src/objects/screen_node_input.rs @@ -0,0 +1,10 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::compliance_provider::ComplianceProvider; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct ScreenNodeInput { + pub provider: ComplianceProvider, + + pub node_pubkey: String, +} diff --git a/lightspark/src/objects/screen_node_output.rs b/lightspark/src/objects/screen_node_output.rs new file mode 100644 index 0000000..95c5a71 --- /dev/null +++ b/lightspark/src/objects/screen_node_output.rs @@ -0,0 +1,16 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::risk_rating::RiskRating; +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct ScreenNodeOutput { + #[serde(rename = "screen_node_output_rating")] + pub rating: RiskRating, +} + +pub const FRAGMENT: &str = " +fragment ScreenNodeOutputFragment on ScreenNodeOutput { + __typename + screen_node_output_rating: rating +} +"; diff --git a/src/objects/secret.rs b/lightspark/src/objects/secret.rs similarity index 100% rename from src/objects/secret.rs rename to lightspark/src/objects/secret.rs diff --git a/src/objects/send_payment_input.rs b/lightspark/src/objects/send_payment_input.rs similarity index 100% rename from src/objects/send_payment_input.rs rename to lightspark/src/objects/send_payment_input.rs diff --git a/src/objects/send_payment_output.rs b/lightspark/src/objects/send_payment_output.rs similarity index 100% rename from src/objects/send_payment_output.rs rename to lightspark/src/objects/send_payment_output.rs diff --git a/lightspark/src/objects/set_invoice_payment_hash_input.rs b/lightspark/src/objects/set_invoice_payment_hash_input.rs new file mode 100644 index 0000000..146e383 --- /dev/null +++ b/lightspark/src/objects/set_invoice_payment_hash_input.rs @@ -0,0 +1,14 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct SetInvoicePaymentHashInput { + /// The invoice that needs to be updated. + pub invoice_id: String, + + /// The 32-byte hash of the payment preimage. + pub payment_hash: String, + + /// The 32-byte nonce used to generate the invoice preimage. + pub preimage_nonce: String, +} diff --git a/lightspark/src/objects/set_invoice_payment_hash_output.rs b/lightspark/src/objects/set_invoice_payment_hash_output.rs new file mode 100644 index 0000000..b75b3cf --- /dev/null +++ b/lightspark/src/objects/set_invoice_payment_hash_output.rs @@ -0,0 +1,18 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::types::entity_wrapper::EntityWrapper; +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct SetInvoicePaymentHashOutput { + #[serde(rename = "set_invoice_payment_hash_output_invoice")] + pub invoice: EntityWrapper, +} + +pub const FRAGMENT: &str = " +fragment SetInvoicePaymentHashOutputFragment on SetInvoicePaymentHashOutput { + __typename + set_invoice_payment_hash_output_invoice: invoice { + id + } +} +"; diff --git a/src/objects/sign_invoice_input.rs b/lightspark/src/objects/sign_invoice_input.rs similarity index 100% rename from src/objects/sign_invoice_input.rs rename to lightspark/src/objects/sign_invoice_input.rs diff --git a/src/objects/sign_invoice_output.rs b/lightspark/src/objects/sign_invoice_output.rs similarity index 100% rename from src/objects/sign_invoice_output.rs rename to lightspark/src/objects/sign_invoice_output.rs diff --git a/src/objects/sign_messages_input.rs b/lightspark/src/objects/sign_messages_input.rs similarity index 100% rename from src/objects/sign_messages_input.rs rename to lightspark/src/objects/sign_messages_input.rs diff --git a/src/objects/sign_messages_output.rs b/lightspark/src/objects/sign_messages_output.rs similarity index 100% rename from src/objects/sign_messages_output.rs rename to lightspark/src/objects/sign_messages_output.rs diff --git a/src/objects/signable.rs b/lightspark/src/objects/signable.rs similarity index 100% rename from src/objects/signable.rs rename to lightspark/src/objects/signable.rs diff --git a/src/objects/signable_payload.rs b/lightspark/src/objects/signable_payload.rs similarity index 100% rename from src/objects/signable_payload.rs rename to lightspark/src/objects/signable_payload.rs diff --git a/src/objects/signable_payload_status.rs b/lightspark/src/objects/signable_payload_status.rs similarity index 83% rename from src/objects/signable_payload_status.rs rename to lightspark/src/objects/signable_payload_status.rs index 94aa3d1..0846664 100644 --- a/src/objects/signable_payload_status.rs +++ b/lightspark/src/objects/signable_payload_status.rs @@ -10,6 +10,9 @@ pub enum SignablePayloadStatus { #[serde(rename = "SIGNED")] Signed, + + #[serde(rename = "VALIDATION_FAILED")] + ValidationFailed, } impl From for Value { @@ -23,6 +26,7 @@ impl fmt::Display for SignablePayloadStatus { match self { Self::Created => write!(f, "CREATED"), Self::Signed => write!(f, "SIGNED"), + Self::ValidationFailed => write!(f, "VALIDATION_FAILED"), } } } diff --git a/src/objects/transaction.rs b/lightspark/src/objects/transaction.rs similarity index 100% rename from src/objects/transaction.rs rename to lightspark/src/objects/transaction.rs diff --git a/src/objects/transaction_failures.rs b/lightspark/src/objects/transaction_failures.rs similarity index 100% rename from src/objects/transaction_failures.rs rename to lightspark/src/objects/transaction_failures.rs diff --git a/src/objects/transaction_status.rs b/lightspark/src/objects/transaction_status.rs similarity index 100% rename from src/objects/transaction_status.rs rename to lightspark/src/objects/transaction_status.rs diff --git a/src/objects/transaction_type.rs b/lightspark/src/objects/transaction_type.rs similarity index 100% rename from src/objects/transaction_type.rs rename to lightspark/src/objects/transaction_type.rs diff --git a/src/objects/update_channel_per_commitment_point_input.rs b/lightspark/src/objects/update_channel_per_commitment_point_input.rs similarity index 100% rename from src/objects/update_channel_per_commitment_point_input.rs rename to lightspark/src/objects/update_channel_per_commitment_point_input.rs diff --git a/src/objects/update_channel_per_commitment_point_output.rs b/lightspark/src/objects/update_channel_per_commitment_point_output.rs similarity index 100% rename from src/objects/update_channel_per_commitment_point_output.rs rename to lightspark/src/objects/update_channel_per_commitment_point_output.rs diff --git a/src/objects/update_node_shared_secret_input.rs b/lightspark/src/objects/update_node_shared_secret_input.rs similarity index 100% rename from src/objects/update_node_shared_secret_input.rs rename to lightspark/src/objects/update_node_shared_secret_input.rs diff --git a/src/objects/update_node_shared_secret_output.rs b/lightspark/src/objects/update_node_shared_secret_output.rs similarity index 100% rename from src/objects/update_node_shared_secret_output.rs rename to lightspark/src/objects/update_node_shared_secret_output.rs diff --git a/lightspark/src/objects/wallet.rs b/lightspark/src/objects/wallet.rs new file mode 100644 index 0000000..cac903c --- /dev/null +++ b/lightspark/src/objects/wallet.rs @@ -0,0 +1,1074 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::error::Error; +use crate::objects::balances::Balances; +use crate::objects::currency_amount::CurrencyAmount; +use crate::objects::entity::Entity; +use crate::objects::lightspark_node_owner::LightsparkNodeOwner; +use crate::objects::transaction_status::TransactionStatus; +use crate::objects::transaction_type::TransactionType; +use crate::objects::wallet_status::WalletStatus; +use crate::objects::wallet_to_payment_requests_connection::WalletToPaymentRequestsConnection; +use crate::objects::wallet_to_transactions_connection::WalletToTransactionsConnection; +use crate::request::requester::Requester; +use crate::types::custom_date_formats::custom_date_format; +use crate::types::custom_date_formats::custom_date_format_option; +use crate::types::entity_wrapper::EntityWrapper; +use crate::types::get_entity::GetEntity; +use chrono::{DateTime, Utc}; +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashMap; +use std::vec::Vec; + +/// This object represents a Lightspark Wallet, tied to your Lightspark account. Wallets can be used to send or receive funds over the Lightning Network. You can retrieve this object to receive information about a specific wallet tied to your Lightspark account. +#[derive(Clone, Deserialize)] +pub struct Wallet { + /// The unique identifier of this entity across all Lightspark systems. Should be treated as an opaque string. + #[serde(rename = "wallet_id")] + pub id: String, + + /// The date and time when the entity was first created. + #[serde(with = "custom_date_format", rename = "wallet_created_at")] + pub created_at: DateTime, + + /// The date and time when the entity was last updated. + #[serde(with = "custom_date_format", rename = "wallet_updated_at")] + pub updated_at: DateTime, + + /// The date and time when the wallet user last logged in. + #[serde(with = "custom_date_format_option", rename = "wallet_last_login_at")] + pub last_login_at: Option>, + + /// The balances that describe the funds in this wallet. + #[serde(rename = "wallet_balances")] + pub balances: Option, + + /// The unique identifier of this wallet, as provided by the Lightspark Customer during login. + #[serde(rename = "wallet_third_party_identifier")] + pub third_party_identifier: String, + + /// The account this wallet belongs to. + #[serde(rename = "wallet_account")] + pub account: Option, + + /// The status of this wallet. + #[serde(rename = "wallet_status")] + pub status: WalletStatus, +} + +impl LightsparkNodeOwner for Wallet { + fn type_name(&self) -> &'static str { + "Wallet" + } +} + +impl Entity for Wallet { + /// The unique identifier of this entity across all Lightspark systems. Should be treated as an opaque string. + fn get_id(&self) -> String { + self.id.clone() + } + + /// The date and time when the entity was first created. + fn get_created_at(&self) -> DateTime { + self.created_at + } + + /// The date and time when the entity was last updated. + fn get_updated_at(&self) -> DateTime { + self.updated_at + } + + fn type_name(&self) -> &'static str { + "Wallet" + } +} + +impl GetEntity for Wallet { + fn get_entity_query() -> String { + format!( + " + query GetEntity($id: ID!) {{ + entity(id: $id) {{ + ... on Wallet {{ + ... WalletFragment + }} + }} + }} + + {}", + FRAGMENT + ) + } +} + +pub const FRAGMENT: &str = " +fragment WalletFragment on Wallet { + __typename + wallet_id: id + wallet_created_at: created_at + wallet_updated_at: updated_at + wallet_last_login_at: last_login_at + wallet_balances: balances { + __typename + balances_owned_balance: owned_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + balances_available_to_send_balance: available_to_send_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + balances_available_to_withdraw_balance: available_to_withdraw_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + wallet_third_party_identifier: third_party_identifier + wallet_account: account { + id + } + wallet_status: status +} +"; + +impl Wallet { + #[allow(clippy::too_many_arguments)] + pub async fn get_transactions( + &self, + requester: &Requester, + first: Option, + after: Option, + created_after_date: Option>, + created_before_date: Option>, + statuses: Option>, + types: Option>, + ) -> Result { + let query = "query FetchWalletToTransactionsConnection($entity_id: ID!, $first: Int, $after: ID, $created_after_date: DateTime, $created_before_date: DateTime, $statuses: [TransactionStatus!], $types: [TransactionType!]) { + entity(id: $entity_id) { + ... on Wallet { + transactions(, first: $first, after: $after, created_after_date: $created_after_date, created_before_date: $created_before_date, statuses: $statuses, types: $types) { + __typename + wallet_to_transactions_connection_count: count + wallet_to_transactions_connection_page_info: page_info { + __typename + page_info_has_next_page: has_next_page + page_info_has_previous_page: has_previous_page + page_info_start_cursor: start_cursor + page_info_end_cursor: end_cursor + } + wallet_to_transactions_connection_entities: entities { + __typename + ... on ChannelClosingTransaction { + __typename + channel_closing_transaction_id: id + channel_closing_transaction_created_at: created_at + channel_closing_transaction_updated_at: updated_at + channel_closing_transaction_status: status + channel_closing_transaction_resolved_at: resolved_at + channel_closing_transaction_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_closing_transaction_transaction_hash: transaction_hash + channel_closing_transaction_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_closing_transaction_block_hash: block_hash + channel_closing_transaction_block_height: block_height + channel_closing_transaction_destination_addresses: destination_addresses + channel_closing_transaction_num_confirmations: num_confirmations + channel_closing_transaction_channel: channel { + id + } + } + ... on ChannelOpeningTransaction { + __typename + channel_opening_transaction_id: id + channel_opening_transaction_created_at: created_at + channel_opening_transaction_updated_at: updated_at + channel_opening_transaction_status: status + channel_opening_transaction_resolved_at: resolved_at + channel_opening_transaction_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_opening_transaction_transaction_hash: transaction_hash + channel_opening_transaction_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_opening_transaction_block_hash: block_hash + channel_opening_transaction_block_height: block_height + channel_opening_transaction_destination_addresses: destination_addresses + channel_opening_transaction_num_confirmations: num_confirmations + channel_opening_transaction_channel: channel { + id + } + } + ... on Deposit { + __typename + deposit_id: id + deposit_created_at: created_at + deposit_updated_at: updated_at + deposit_status: status + deposit_resolved_at: resolved_at + deposit_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + deposit_transaction_hash: transaction_hash + deposit_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + deposit_block_hash: block_hash + deposit_block_height: block_height + deposit_destination_addresses: destination_addresses + deposit_num_confirmations: num_confirmations + deposit_destination: destination { + id + } + } + ... on IncomingPayment { + __typename + incoming_payment_id: id + incoming_payment_created_at: created_at + incoming_payment_updated_at: updated_at + incoming_payment_status: status + incoming_payment_resolved_at: resolved_at + incoming_payment_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + incoming_payment_transaction_hash: transaction_hash + incoming_payment_destination: destination { + id + } + incoming_payment_payment_request: payment_request { + id + } + incoming_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + } + ... on OutgoingPayment { + __typename + outgoing_payment_id: id + outgoing_payment_created_at: created_at + outgoing_payment_updated_at: updated_at + outgoing_payment_status: status + outgoing_payment_resolved_at: resolved_at + outgoing_payment_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + outgoing_payment_transaction_hash: transaction_hash + outgoing_payment_origin: origin { + id + } + outgoing_payment_destination: destination { + id + } + outgoing_payment_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + outgoing_payment_payment_request_data: payment_request_data { + __typename + ... on InvoiceData { + __typename + invoice_data_encoded_payment_request: encoded_payment_request + invoice_data_bitcoin_network: bitcoin_network + invoice_data_payment_hash: payment_hash + invoice_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + invoice_data_created_at: created_at + invoice_data_expires_at: expires_at + invoice_data_memo: memo + invoice_data_destination: destination { + __typename + ... on GraphNode { + __typename + graph_node_id: id + graph_node_created_at: created_at + graph_node_updated_at: updated_at + graph_node_alias: alias + graph_node_bitcoin_network: bitcoin_network + graph_node_color: color + graph_node_conductivity: conductivity + graph_node_display_name: display_name + graph_node_public_key: public_key + } + ... on LightsparkNodeWithOSK { + __typename + lightspark_node_with_o_s_k_id: id + lightspark_node_with_o_s_k_created_at: created_at + lightspark_node_with_o_s_k_updated_at: updated_at + lightspark_node_with_o_s_k_alias: alias + lightspark_node_with_o_s_k_bitcoin_network: bitcoin_network + lightspark_node_with_o_s_k_color: color + lightspark_node_with_o_s_k_conductivity: conductivity + lightspark_node_with_o_s_k_display_name: display_name + lightspark_node_with_o_s_k_public_key: public_key + lightspark_node_with_o_s_k_owner: owner { + id + } + lightspark_node_with_o_s_k_status: status + lightspark_node_with_o_s_k_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_total_local_balance: total_local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_blockchain_balance: blockchain_balance { + __typename + blockchain_balance_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_confirmed_balance: confirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_unconfirmed_balance: unconfirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_locked_balance: locked_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_required_reserve: required_reserve { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_available_balance: available_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos + lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { + __typename + secret_encrypted_value: encrypted_value + secret_cipher: cipher + } + } + ... on LightsparkNodeWithRemoteSigning { + __typename + lightspark_node_with_remote_signing_id: id + lightspark_node_with_remote_signing_created_at: created_at + lightspark_node_with_remote_signing_updated_at: updated_at + lightspark_node_with_remote_signing_alias: alias + lightspark_node_with_remote_signing_bitcoin_network: bitcoin_network + lightspark_node_with_remote_signing_color: color + lightspark_node_with_remote_signing_conductivity: conductivity + lightspark_node_with_remote_signing_display_name: display_name + lightspark_node_with_remote_signing_public_key: public_key + lightspark_node_with_remote_signing_owner: owner { + id + } + lightspark_node_with_remote_signing_status: status + lightspark_node_with_remote_signing_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_total_local_balance: total_local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_blockchain_balance: blockchain_balance { + __typename + blockchain_balance_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_confirmed_balance: confirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_unconfirmed_balance: unconfirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_locked_balance: locked_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_required_reserve: required_reserve { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_available_balance: available_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos + } + } + } + } + outgoing_payment_failure_reason: failure_reason + outgoing_payment_failure_message: failure_message { + __typename + rich_text_text: text + } + outgoing_payment_uma_post_transaction_data: uma_post_transaction_data { + __typename + post_transaction_data_utxo: utxo + post_transaction_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + } + ... on RoutingTransaction { + __typename + routing_transaction_id: id + routing_transaction_created_at: created_at + routing_transaction_updated_at: updated_at + routing_transaction_status: status + routing_transaction_resolved_at: resolved_at + routing_transaction_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + routing_transaction_transaction_hash: transaction_hash + routing_transaction_incoming_channel: incoming_channel { + id + } + routing_transaction_outgoing_channel: outgoing_channel { + id + } + routing_transaction_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + routing_transaction_failure_message: failure_message { + __typename + rich_text_text: text + } + routing_transaction_failure_reason: failure_reason + } + ... on Withdrawal { + __typename + withdrawal_id: id + withdrawal_created_at: created_at + withdrawal_updated_at: updated_at + withdrawal_status: status + withdrawal_resolved_at: resolved_at + withdrawal_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + withdrawal_transaction_hash: transaction_hash + withdrawal_fees: fees { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + withdrawal_block_hash: block_hash + withdrawal_block_height: block_height + withdrawal_destination_addresses: destination_addresses + withdrawal_num_confirmations: num_confirmations + withdrawal_origin: origin { + id + } + } + } + } + } + } +}"; + let mut variables: HashMap<&str, Value> = HashMap::new(); + variables.insert("entity_id", self.id.clone().into()); + variables.insert("first", first.into()); + variables.insert("after", after.into()); + variables.insert( + "created_after_date", + created_after_date.map(|dt| dt.to_rfc3339()).into(), + ); + variables.insert( + "created_before_date", + created_before_date.map(|dt| dt.to_rfc3339()).into(), + ); + variables.insert("statuses", statuses.into()); + variables.insert("types", types.into()); + + let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; + let result = requester + .execute_graphql(query, Some(value)) + .await + .map_err(Error::ClientError)?; + let json = result["entity"]["transactions"].clone(); + let result = serde_json::from_value(json).map_err(Error::JsonError)?; + Ok(result) + } + + pub async fn get_payment_requests( + &self, + requester: &Requester, + first: Option, + after: Option, + created_after_date: Option>, + created_before_date: Option>, + ) -> Result { + let query = "query FetchWalletToPaymentRequestsConnection($entity_id: ID!, $first: Int, $after: ID, $created_after_date: DateTime, $created_before_date: DateTime) { + entity(id: $entity_id) { + ... on Wallet { + payment_requests(, first: $first, after: $after, created_after_date: $created_after_date, created_before_date: $created_before_date) { + __typename + wallet_to_payment_requests_connection_count: count + wallet_to_payment_requests_connection_page_info: page_info { + __typename + page_info_has_next_page: has_next_page + page_info_has_previous_page: has_previous_page + page_info_start_cursor: start_cursor + page_info_end_cursor: end_cursor + } + wallet_to_payment_requests_connection_entities: entities { + __typename + ... on Invoice { + __typename + invoice_id: id + invoice_created_at: created_at + invoice_updated_at: updated_at + invoice_data: data { + __typename + invoice_data_encoded_payment_request: encoded_payment_request + invoice_data_bitcoin_network: bitcoin_network + invoice_data_payment_hash: payment_hash + invoice_data_amount: amount { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + invoice_data_created_at: created_at + invoice_data_expires_at: expires_at + invoice_data_memo: memo + invoice_data_destination: destination { + __typename + ... on GraphNode { + __typename + graph_node_id: id + graph_node_created_at: created_at + graph_node_updated_at: updated_at + graph_node_alias: alias + graph_node_bitcoin_network: bitcoin_network + graph_node_color: color + graph_node_conductivity: conductivity + graph_node_display_name: display_name + graph_node_public_key: public_key + } + ... on LightsparkNodeWithOSK { + __typename + lightspark_node_with_o_s_k_id: id + lightspark_node_with_o_s_k_created_at: created_at + lightspark_node_with_o_s_k_updated_at: updated_at + lightspark_node_with_o_s_k_alias: alias + lightspark_node_with_o_s_k_bitcoin_network: bitcoin_network + lightspark_node_with_o_s_k_color: color + lightspark_node_with_o_s_k_conductivity: conductivity + lightspark_node_with_o_s_k_display_name: display_name + lightspark_node_with_o_s_k_public_key: public_key + lightspark_node_with_o_s_k_owner: owner { + id + } + lightspark_node_with_o_s_k_status: status + lightspark_node_with_o_s_k_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_total_local_balance: total_local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_o_s_k_blockchain_balance: blockchain_balance { + __typename + blockchain_balance_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_confirmed_balance: confirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_unconfirmed_balance: unconfirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_locked_balance: locked_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_required_reserve: required_reserve { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_available_balance: available_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + lightspark_node_with_o_s_k_uma_prescreening_utxos: uma_prescreening_utxos + lightspark_node_with_o_s_k_encrypted_signing_private_key: encrypted_signing_private_key { + __typename + secret_encrypted_value: encrypted_value + secret_cipher: cipher + } + } + ... on LightsparkNodeWithRemoteSigning { + __typename + lightspark_node_with_remote_signing_id: id + lightspark_node_with_remote_signing_created_at: created_at + lightspark_node_with_remote_signing_updated_at: updated_at + lightspark_node_with_remote_signing_alias: alias + lightspark_node_with_remote_signing_bitcoin_network: bitcoin_network + lightspark_node_with_remote_signing_color: color + lightspark_node_with_remote_signing_conductivity: conductivity + lightspark_node_with_remote_signing_display_name: display_name + lightspark_node_with_remote_signing_public_key: public_key + lightspark_node_with_remote_signing_owner: owner { + id + } + lightspark_node_with_remote_signing_status: status + lightspark_node_with_remote_signing_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_total_local_balance: total_local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + lightspark_node_with_remote_signing_blockchain_balance: blockchain_balance { + __typename + blockchain_balance_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_confirmed_balance: confirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_unconfirmed_balance: unconfirmed_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_locked_balance: locked_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_required_reserve: required_reserve { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + blockchain_balance_available_balance: available_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + lightspark_node_with_remote_signing_uma_prescreening_utxos: uma_prescreening_utxos + } + } + } + invoice_status: status + invoice_amount_paid: amount_paid { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + } + } + } + } +}"; + let mut variables: HashMap<&str, Value> = HashMap::new(); + variables.insert("entity_id", self.id.clone().into()); + variables.insert("first", first.into()); + variables.insert("after", after.into()); + variables.insert( + "created_after_date", + created_after_date.map(|dt| dt.to_rfc3339()).into(), + ); + variables.insert( + "created_before_date", + created_before_date.map(|dt| dt.to_rfc3339()).into(), + ); + + let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; + let result = requester + .execute_graphql(query, Some(value)) + .await + .map_err(Error::ClientError)?; + let json = result["entity"]["payment_requests"].clone(); + let result = serde_json::from_value(json).map_err(Error::JsonError)?; + Ok(result) + } + + pub async fn get_total_amount_received( + &self, + requester: &Requester, + created_after_date: Option>, + created_before_date: Option>, + ) -> Result { + let query = "query FetchWalletTotalAmountReceived($entity_id: ID!, $created_after_date: DateTime, $created_before_date: DateTime) { + entity(id: $entity_id) { + ... on Wallet { + total_amount_received(, created_after_date: $created_after_date, created_before_date: $created_before_date) { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + } +}"; + let mut variables: HashMap<&str, Value> = HashMap::new(); + variables.insert("entity_id", self.id.clone().into()); + variables.insert( + "created_after_date", + created_after_date.map(|dt| dt.to_rfc3339()).into(), + ); + variables.insert( + "created_before_date", + created_before_date.map(|dt| dt.to_rfc3339()).into(), + ); + + let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; + let result = requester + .execute_graphql(query, Some(value)) + .await + .map_err(Error::ClientError)?; + let json = result["entity"]["total_amount_received"].clone(); + let result = serde_json::from_value(json).map_err(Error::JsonError)?; + Ok(result) + } + + pub async fn get_total_amount_sent( + &self, + requester: &Requester, + created_after_date: Option>, + created_before_date: Option>, + ) -> Result { + let query = "query FetchWalletTotalAmountSent($entity_id: ID!, $created_after_date: DateTime, $created_before_date: DateTime) { + entity(id: $entity_id) { + ... on Wallet { + total_amount_sent(, created_after_date: $created_after_date, created_before_date: $created_before_date) { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + } + } +}"; + let mut variables: HashMap<&str, Value> = HashMap::new(); + variables.insert("entity_id", self.id.clone().into()); + variables.insert( + "created_after_date", + created_after_date.map(|dt| dt.to_rfc3339()).into(), + ); + variables.insert( + "created_before_date", + created_before_date.map(|dt| dt.to_rfc3339()).into(), + ); + + let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; + let result = requester + .execute_graphql(query, Some(value)) + .await + .map_err(Error::ClientError)?; + let json = result["entity"]["total_amount_sent"].clone(); + let result = serde_json::from_value(json).map_err(Error::JsonError)?; + Ok(result) + } +} diff --git a/src/objects/wallet_status.rs b/lightspark/src/objects/wallet_status.rs similarity index 100% rename from src/objects/wallet_status.rs rename to lightspark/src/objects/wallet_status.rs diff --git a/lightspark/src/objects/wallet_to_payment_requests_connection.rs b/lightspark/src/objects/wallet_to_payment_requests_connection.rs new file mode 100644 index 0000000..8814e00 --- /dev/null +++ b/lightspark/src/objects/wallet_to_payment_requests_connection.rs @@ -0,0 +1,54 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::connection::Connection; +use crate::objects::page_info::PageInfo; +use crate::objects::payment_request::PaymentRequestEnum; +use serde::Deserialize; +use std::vec::Vec; + +#[derive(Clone, Deserialize)] +pub struct WalletToPaymentRequestsConnection { + /// The total count of objects in this connection, using the current filters. It is different from the number of objects returned in the current page (in the `entities` field). + #[serde(rename = "wallet_to_payment_requests_connection_count")] + pub count: i64, + + /// An object that holds pagination information about the objects in this connection. + #[serde(rename = "wallet_to_payment_requests_connection_page_info")] + pub page_info: PageInfo, + + /// The payment requests for the current page of this connection. + #[serde(rename = "wallet_to_payment_requests_connection_entities")] + pub entities: Vec, +} + +impl Connection for WalletToPaymentRequestsConnection { + /// The total count of objects in this connection, using the current filters. It is different from the number of objects returned in the current page (in the `entities` field). + fn get_count(&self) -> i64 { + self.count + } + + /// An object that holds pagination information about the objects in this connection. + fn get_page_info(&self) -> PageInfo { + self.page_info.clone() + } + + fn type_name(&self) -> &'static str { + "WalletToPaymentRequestsConnection" + } +} + +pub const FRAGMENT: &str = " +fragment WalletToPaymentRequestsConnectionFragment on WalletToPaymentRequestsConnection { + __typename + wallet_to_payment_requests_connection_count: count + wallet_to_payment_requests_connection_page_info: page_info { + __typename + page_info_has_next_page: has_next_page + page_info_has_previous_page: has_previous_page + page_info_start_cursor: start_cursor + page_info_end_cursor: end_cursor + } + wallet_to_payment_requests_connection_entities: entities { + id + } +} +"; diff --git a/lightspark/src/objects/wallet_to_transactions_connection.rs b/lightspark/src/objects/wallet_to_transactions_connection.rs new file mode 100644 index 0000000..5f1fbee --- /dev/null +++ b/lightspark/src/objects/wallet_to_transactions_connection.rs @@ -0,0 +1,54 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::connection::Connection; +use crate::objects::page_info::PageInfo; +use crate::objects::transaction::TransactionEnum; +use serde::Deserialize; +use std::vec::Vec; + +#[derive(Clone, Deserialize)] +pub struct WalletToTransactionsConnection { + /// The total count of objects in this connection, using the current filters. It is different from the number of objects returned in the current page (in the `entities` field). + #[serde(rename = "wallet_to_transactions_connection_count")] + pub count: i64, + + /// An object that holds pagination information about the objects in this connection. + #[serde(rename = "wallet_to_transactions_connection_page_info")] + pub page_info: PageInfo, + + /// The transactions for the current page of this connection. + #[serde(rename = "wallet_to_transactions_connection_entities")] + pub entities: Vec, +} + +impl Connection for WalletToTransactionsConnection { + /// The total count of objects in this connection, using the current filters. It is different from the number of objects returned in the current page (in the `entities` field). + fn get_count(&self) -> i64 { + self.count + } + + /// An object that holds pagination information about the objects in this connection. + fn get_page_info(&self) -> PageInfo { + self.page_info.clone() + } + + fn type_name(&self) -> &'static str { + "WalletToTransactionsConnection" + } +} + +pub const FRAGMENT: &str = " +fragment WalletToTransactionsConnectionFragment on WalletToTransactionsConnection { + __typename + wallet_to_transactions_connection_count: count + wallet_to_transactions_connection_page_info: page_info { + __typename + page_info_has_next_page: has_next_page + page_info_has_previous_page: has_previous_page + page_info_start_cursor: start_cursor + page_info_end_cursor: end_cursor + } + wallet_to_transactions_connection_entities: entities { + id + } +} +"; diff --git a/src/objects/webhook_event_type.rs b/lightspark/src/objects/webhook_event_type.rs similarity index 100% rename from src/objects/webhook_event_type.rs rename to lightspark/src/objects/webhook_event_type.rs diff --git a/src/objects/withdrawal.rs b/lightspark/src/objects/withdrawal.rs similarity index 100% rename from src/objects/withdrawal.rs rename to lightspark/src/objects/withdrawal.rs diff --git a/src/objects/withdrawal_mode.rs b/lightspark/src/objects/withdrawal_mode.rs similarity index 100% rename from src/objects/withdrawal_mode.rs rename to lightspark/src/objects/withdrawal_mode.rs diff --git a/src/objects/withdrawal_request.rs b/lightspark/src/objects/withdrawal_request.rs similarity index 100% rename from src/objects/withdrawal_request.rs rename to lightspark/src/objects/withdrawal_request.rs diff --git a/src/objects/withdrawal_request_status.rs b/lightspark/src/objects/withdrawal_request_status.rs similarity index 100% rename from src/objects/withdrawal_request_status.rs rename to lightspark/src/objects/withdrawal_request_status.rs diff --git a/src/objects/withdrawal_request_to_channel_closing_transactions_connection.rs b/lightspark/src/objects/withdrawal_request_to_channel_closing_transactions_connection.rs similarity index 100% rename from src/objects/withdrawal_request_to_channel_closing_transactions_connection.rs rename to lightspark/src/objects/withdrawal_request_to_channel_closing_transactions_connection.rs diff --git a/src/objects/withdrawal_request_to_channel_opening_transactions_connection.rs b/lightspark/src/objects/withdrawal_request_to_channel_opening_transactions_connection.rs similarity index 100% rename from src/objects/withdrawal_request_to_channel_opening_transactions_connection.rs rename to lightspark/src/objects/withdrawal_request_to_channel_opening_transactions_connection.rs diff --git a/src/request/auth_provider.rs b/lightspark/src/request/auth_provider.rs similarity index 100% rename from src/request/auth_provider.rs rename to lightspark/src/request/auth_provider.rs diff --git a/src/request/mod.rs b/lightspark/src/request/mod.rs similarity index 100% rename from src/request/mod.rs rename to lightspark/src/request/mod.rs diff --git a/src/request/requester.rs b/lightspark/src/request/requester.rs similarity index 86% rename from src/request/requester.rs rename to lightspark/src/request/requester.rs index 703faa2..89c91f1 100644 --- a/src/request/requester.rs +++ b/lightspark/src/request/requester.rs @@ -2,7 +2,13 @@ use std::{env, fmt}; -use crate::{crypto::sign_payload, error::Error, request::auth_provider::AuthProvider, VERSION}; +use crate::{ + crypto::CryptoError, + error::Error, + key::{OperationSigningKey, RSASigningKey}, + request::auth_provider::AuthProvider, + VERSION, +}; use chrono::{Duration, Utc}; use os_version::detect; use rand::RngCore; @@ -12,12 +18,14 @@ use reqwest::{ }; use serde_json::{json, to_string, Value}; -const DEFAULT_BASE_URL: &str = "https://api.lightspark.com/graphql/server/2023-04-04"; +const DEFAULT_BASE_URL: &str = "https://api.lightspark.com/graphql/server/2023-09-13"; #[derive(Debug)] pub enum RequesterError { ReqwestError(reqwest::Error), GraphqlError(String), + SigningError(CryptoError), + InvalidHeaderValue, } impl fmt::Display for RequesterError { @@ -25,6 +33,8 @@ impl fmt::Display for RequesterError { match self { Self::ReqwestError(err) => write!(f, "Network error {}", err), Self::GraphqlError(err) => write!(f, "Graphql error {}", err), + Self::SigningError(err) => write!(f, "Signing error {}", err), + Self::InvalidHeaderValue => write!(f, "Invalid header value"), } } } @@ -107,7 +117,7 @@ impl Requester { operation: &str, variables: Option, ) -> Result { - self.execute_graphql_signing(operation, variables, None) + self.execute_graphql_signing::(operation, variables, None) .await } @@ -121,11 +131,11 @@ impl Requester { /// * `operation` - graphql query or mutation to be executed. /// * `variables` - variable for the graphql. /// * `signing_key` - the node's private signing key. - pub async fn execute_graphql_signing( + pub async fn execute_graphql_signing( &self, operation: &str, variables: Option, - signing_key: Option>, + signing_key: Option, ) -> Result { let re = regex::Regex::new(r"\s*(?:query|mutation)\s+(\w+)").map_err(|_| { RequesterError::GraphqlError("The operation is not a query or a mutation".to_owned()) @@ -166,12 +176,14 @@ impl Requester { let json_string = to_string(&body) .map_err(|_| RequesterError::GraphqlError("Body malformat.".to_owned()))?; let payload: Vec = json_string.into_bytes(); - let signing = sign_payload(payload.as_slice(), key.as_slice()); - if let Ok(signing) = signing { - if let Ok(value) = HeaderValue::from_str(signing.as_str()) { - headers.insert("X-Lightspark-Signing", value); - } - } + let signing = key + .sign_payload(&payload) + .map_err(RequesterError::SigningError)?; + headers.insert( + "X-Lightspark-Signing", + HeaderValue::from_str(signing.as_str()) + .map_err(|_| RequesterError::InvalidHeaderValue)?, + ); } let response = self diff --git a/src/types/custom_date_formats.rs b/lightspark/src/types/custom_date_formats.rs similarity index 100% rename from src/types/custom_date_formats.rs rename to lightspark/src/types/custom_date_formats.rs diff --git a/src/types/entity_wrapper.rs b/lightspark/src/types/entity_wrapper.rs similarity index 100% rename from src/types/entity_wrapper.rs rename to lightspark/src/types/entity_wrapper.rs diff --git a/src/types/get_entity.rs b/lightspark/src/types/get_entity.rs similarity index 100% rename from src/types/get_entity.rs rename to lightspark/src/types/get_entity.rs diff --git a/src/types/mod.rs b/lightspark/src/types/mod.rs similarity index 100% rename from src/types/mod.rs rename to lightspark/src/types/mod.rs diff --git a/src/webhooks.rs b/lightspark/src/webhooks.rs similarity index 100% rename from src/webhooks.rs rename to lightspark/src/webhooks.rs diff --git a/src/objects/screen_bitcoin_addresses_input.rs b/src/objects/screen_bitcoin_addresses_input.rs deleted file mode 100644 index f5797f3..0000000 --- a/src/objects/screen_bitcoin_addresses_input.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::crypto_sanctions_screening_provider::CryptoSanctionsScreeningProvider; -use serde::{Deserialize, Serialize}; -use std::vec::Vec; - -#[derive(Clone, Deserialize, Serialize)] -pub struct ScreenBitcoinAddressesInput { - pub provider: CryptoSanctionsScreeningProvider, - - pub addresses: Vec, -} diff --git a/src/objects/screen_bitcoin_addresses_output.rs b/src/objects/screen_bitcoin_addresses_output.rs deleted file mode 100644 index 5070bce..0000000 --- a/src/objects/screen_bitcoin_addresses_output.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::objects::risk_rating::RiskRating; -use serde::Deserialize; -use std::vec::Vec; - -#[derive(Clone, Deserialize)] -pub struct ScreenBitcoinAddressesOutput { - #[serde(rename = "screen_bitcoin_addresses_output_ratings")] - pub ratings: Vec, -} - -pub const FRAGMENT: &str = " -fragment ScreenBitcoinAddressesOutputFragment on ScreenBitcoinAddressesOutput { - __typename - screen_bitcoin_addresses_output_ratings: ratings -} -"; diff --git a/src/objects/wallet.rs b/src/objects/wallet.rs deleted file mode 100644 index 629d20a..0000000 --- a/src/objects/wallet.rs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use crate::error::Error; -use crate::objects::balances::Balances; -use crate::objects::currency_amount::CurrencyAmount; -use crate::objects::entity::Entity; -use crate::objects::lightspark_node_owner::LightsparkNodeOwner; -use crate::objects::wallet_status::WalletStatus; -use crate::request::requester::Requester; -use crate::types::custom_date_formats::custom_date_format; -use crate::types::custom_date_formats::custom_date_format_option; -use crate::types::get_entity::GetEntity; -use chrono::{DateTime, Utc}; -use serde::Deserialize; -use serde_json::Value; -use std::collections::HashMap; - -/// This object represents a Lightspark Wallet, tied to your Lightspark account. Wallets can be used to send or receive funds over the Lightning Network. You can retrieve this object to receive information about a specific wallet tied to your Lightspark account. -#[derive(Clone, Deserialize)] -pub struct Wallet { - /// The unique identifier of this entity across all Lightspark systems. Should be treated as an opaque string. - #[serde(rename = "wallet_id")] - pub id: String, - - /// The date and time when the entity was first created. - #[serde(with = "custom_date_format", rename = "wallet_created_at")] - pub created_at: DateTime, - - /// The date and time when the entity was last updated. - #[serde(with = "custom_date_format", rename = "wallet_updated_at")] - pub updated_at: DateTime, - - /// The date and time when the wallet user last logged in. - #[serde(with = "custom_date_format_option", rename = "wallet_last_login_at")] - pub last_login_at: Option>, - - /// The balances that describe the funds in this wallet. - #[serde(rename = "wallet_balances")] - pub balances: Option, - - /// The unique identifier of this wallet, as provided by the Lightspark Customer during login. - #[serde(rename = "wallet_third_party_identifier")] - pub third_party_identifier: String, - - /// The status of this wallet. - #[serde(rename = "wallet_status")] - pub status: WalletStatus, -} - -impl LightsparkNodeOwner for Wallet { - fn type_name(&self) -> &'static str { - "Wallet" - } -} - -impl Entity for Wallet { - /// The unique identifier of this entity across all Lightspark systems. Should be treated as an opaque string. - fn get_id(&self) -> String { - self.id.clone() - } - - /// The date and time when the entity was first created. - fn get_created_at(&self) -> DateTime { - self.created_at - } - - /// The date and time when the entity was last updated. - fn get_updated_at(&self) -> DateTime { - self.updated_at - } - - fn type_name(&self) -> &'static str { - "Wallet" - } -} - -impl GetEntity for Wallet { - fn get_entity_query() -> String { - format!( - " - query GetEntity($id: ID!) {{ - entity(id: $id) {{ - ... on Wallet {{ - ... WalletFragment - }} - }} - }} - - {}", - FRAGMENT - ) - } -} - -pub const FRAGMENT: &str = " -fragment WalletFragment on Wallet { - __typename - wallet_id: id - wallet_created_at: created_at - wallet_updated_at: updated_at - wallet_last_login_at: last_login_at - wallet_balances: balances { - __typename - balances_owned_balance: owned_balance { - __typename - currency_amount_original_value: original_value - currency_amount_original_unit: original_unit - currency_amount_preferred_currency_unit: preferred_currency_unit - currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded - currency_amount_preferred_currency_value_approx: preferred_currency_value_approx - } - balances_available_to_send_balance: available_to_send_balance { - __typename - currency_amount_original_value: original_value - currency_amount_original_unit: original_unit - currency_amount_preferred_currency_unit: preferred_currency_unit - currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded - currency_amount_preferred_currency_value_approx: preferred_currency_value_approx - } - balances_available_to_withdraw_balance: available_to_withdraw_balance { - __typename - currency_amount_original_value: original_value - currency_amount_original_unit: original_unit - currency_amount_preferred_currency_unit: preferred_currency_unit - currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded - currency_amount_preferred_currency_value_approx: preferred_currency_value_approx - } - } - wallet_third_party_identifier: third_party_identifier - wallet_status: status -} -"; - -impl Wallet { - pub async fn get_total_amount_received( - &self, - requester: &Requester, - created_after_date: Option>, - created_before_date: Option>, - ) -> Result { - let query = "query FetchWalletTotalAmountReceived($entity_id: ID!, $created_after_date: DateTime, $created_before_date: DateTime) { - entity(id: $entity_id) { - ... on Wallet { - total_amount_received(, created_after_date: $created_after_date, created_before_date: $created_before_date) { - __typename - currency_amount_original_value: original_value - currency_amount_original_unit: original_unit - currency_amount_preferred_currency_unit: preferred_currency_unit - currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded - currency_amount_preferred_currency_value_approx: preferred_currency_value_approx - } - } - } -}"; - let mut variables: HashMap<&str, Value> = HashMap::new(); - variables.insert("entity_id", self.id.clone().into()); - variables.insert( - "created_after_date", - created_after_date.map(|dt| dt.to_rfc3339()).into(), - ); - variables.insert( - "created_before_date", - created_before_date.map(|dt| dt.to_rfc3339()).into(), - ); - - let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; - let json = result["entity"]["total_amount_received"].clone(); - let result = serde_json::from_value(json).map_err(Error::JsonError)?; - Ok(result) - } - - pub async fn get_total_amount_sent( - &self, - requester: &Requester, - created_after_date: Option>, - created_before_date: Option>, - ) -> Result { - let query = "query FetchWalletTotalAmountSent($entity_id: ID!, $created_after_date: DateTime, $created_before_date: DateTime) { - entity(id: $entity_id) { - ... on Wallet { - total_amount_sent(, created_after_date: $created_after_date, created_before_date: $created_before_date) { - __typename - currency_amount_original_value: original_value - currency_amount_original_unit: original_unit - currency_amount_preferred_currency_unit: preferred_currency_unit - currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded - currency_amount_preferred_currency_value_approx: preferred_currency_value_approx - } - } - } -}"; - let mut variables: HashMap<&str, Value> = HashMap::new(); - variables.insert("entity_id", self.id.clone().into()); - variables.insert( - "created_after_date", - created_after_date.map(|dt| dt.to_rfc3339()).into(), - ); - variables.insert( - "created_before_date", - created_before_date.map(|dt| dt.to_rfc3339()).into(), - ); - - let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; - let json = result["entity"]["total_amount_sent"].clone(); - let result = serde_json::from_value(json).map_err(Error::JsonError)?; - Ok(result) - } -} diff --git a/uma/Cargo.toml b/uma/Cargo.toml new file mode 100644 index 0000000..7981e3a --- /dev/null +++ b/uma/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "uma" +description = "UMA Protocol SDK for Rust" +authors = ["Lightspark Group, Inc. "] +version = "0.1.0" +edition = "2021" +documentation = "https://app.lightspark.com/docs/uma-sdk" +homepage = "https://www.lightspark.com/" +repository = "https://github.com/lightsparkdev/lightspark-rs" +license = "Apache-2.0" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11", features = ["blocking", "json"] } +futures = "0.3" +tokio = { version = "1.12.0", features = ["full"] } +base64 = "0.21.0" +serde_json = "1.0.94" +serde = { version = "1.0.155", features = ["derive"] } +sha2 = "0.10.7" +rand_core = "0.6.4" +ecies = "0.2.6" +bitcoin = "0.30.1" +url = "2.4.1" +chrono = "0.4.24" +hex = "0.4.3" diff --git a/uma/LICENSE b/uma/LICENSE new file mode 100644 index 0000000..cbb91e4 --- /dev/null +++ b/uma/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Lightspark Group, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/uma/README.md b/uma/README.md new file mode 100644 index 0000000..9f493b1 --- /dev/null +++ b/uma/README.md @@ -0,0 +1,3 @@ +# uma + +The UMA protocol implementation for Rust! Check out the [full documentation](https://app.lightspark.com/docs/uma-sdk/introduction) for more info. \ No newline at end of file diff --git a/src/uma/currency.rs b/uma/src/currency.rs similarity index 83% rename from src/uma/currency.rs rename to uma/src/currency.rs index b108b04..1ecb67b 100644 --- a/src/uma/currency.rs +++ b/uma/src/currency.rs @@ -1,3 +1,5 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/uma/src/kyc_status.rs b/uma/src/kyc_status.rs new file mode 100644 index 0000000..f9f5a62 --- /dev/null +++ b/uma/src/kyc_status.rs @@ -0,0 +1,18 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum KycStatus { + #[serde(rename = "UNKNOWN")] + KycStatusUnknown, + + #[serde(rename = "NOT_VERIFIED")] + KycStatusNotVerified, + + #[serde(rename = "PENDING")] + KycStatusPending, + + #[serde(rename = "VERIFIED")] + KycStatusVerified, +} diff --git a/uma/src/lib.rs b/uma/src/lib.rs new file mode 100644 index 0000000..05d8f6d --- /dev/null +++ b/uma/src/lib.rs @@ -0,0 +1,14 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + +/// The UMA protocol implementation for Rust. Check out +/// the full documentation: for more info. +pub mod currency; +pub mod kyc_status; +pub mod payer_data; +pub mod protocol; +pub mod public_key_cache; +pub mod uma; +pub mod version; + +#[cfg(test)] +mod uma_test; diff --git a/src/uma/payer_data.rs b/uma/src/payer_data.rs similarity index 86% rename from src/uma/payer_data.rs rename to uma/src/payer_data.rs index b0a4e3c..426c625 100644 --- a/src/uma/payer_data.rs +++ b/uma/src/payer_data.rs @@ -1,5 +1,9 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::kyc_status::KycStatus; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct PayerDataOptions { pub name_required: bool, @@ -112,14 +116,18 @@ pub struct CompliancePayerData { /// utxos is the list of UTXOs of the sender's channels that might be used to fund the payment. pub utxos: Vec, - /// is_kycd indicates whether VASP1 has KYC information about the sender. - #[serde(rename = "isKYCd")] - pub is_kycd: bool, + /// node_pubkey is the public key of the sender's node if known. + #[serde(rename = "nodePubkey")] + pub node_pubkey: Option, + + /// kyc_status indicates whether VASP1 has KYC information about the sender. + #[serde(rename = "kycStatus")] + pub kyc_status: KycStatus, - /// TrInfo is the travel rule information of the sender. This is encrypted with the receiver's - /// public encryption key. - #[serde(rename = "trInfo")] - pub tr_info: Option, + /// encrypted_travel_rule_info is the travel rule information of the sender. This is encrypted + /// with the receiver's public encryption key. + #[serde(rename = "encryptedTravelRuleInfo")] + pub encrypted_travel_rule_info: Option, // signature is the hex-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). pub signature: String, diff --git a/src/uma/protocol.rs b/uma/src/protocol.rs similarity index 67% rename from src/uma/protocol.rs rename to uma/src/protocol.rs index 7245303..156a6a1 100644 --- a/src/uma/protocol.rs +++ b/uma/src/protocol.rs @@ -1,8 +1,12 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + use std::fmt; use serde::{Deserialize, Serialize}; use url::Url; +use crate::kyc_status::KycStatus; + use super::{ currency::Currency, payer_data::{PayerData, PayerDataOptions}, @@ -36,15 +40,21 @@ pub struct LnurlpRequest { /// signature is the hex-encoded signature of sha256(receiver_address|nonce|timestamp). pub signature: String, - /// tr_status indicates VASP1 is a financial institution that requires travel rule information. - pub tr_status: bool, + /// is_subject_to_travel_rule indicates VASP1 is a financial institution that requires travel + /// rule information. + pub is_subject_to_travel_rule: bool, /// vasp_domain is the domain of the VASP that is sending the payment. It will be used by VASP2 /// to fetch the public keys of VASP1. pub vasp_domain: String, - // timestamp is the unix timestamp of when the request was sent. Used in the signature. + /// timestamp is the unix timestamp of when the request was sent. Used in the signature. pub timestamp: i64, + + /// uma_version is the version of the UMA protocol that VASP1 prefers to use for this + /// transaction. For the version negotiation flow, + /// see https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png + pub uma_version: String, } impl LnurlpRequest { @@ -69,8 +79,12 @@ impl LnurlpRequest { .append_pair("signature", &self.signature) .append_pair("vaspDomain", &self.vasp_domain) .append_pair("nonce", &self.nonce) - .append_pair("trStatus", &self.tr_status.to_string()) - .append_pair("timestamp", &self.timestamp.to_string()); + .append_pair( + "isSubjectToTravelRule", + &self.is_subject_to_travel_rule.to_string(), + ) + .append_pair("timestamp", &self.timestamp.to_string()) + .append_pair("umaVersion", &self.uma_version); Ok(lnurlp_url) } @@ -106,14 +120,21 @@ pub struct LnurlpResponse { pub required_payer_data: PayerDataOptions, pub compliance: LnurlComplianceResponse, + + /// UmaVersion is the version of the UMA protocol that VASP2 has chosen for this transaction + /// based on its own support and VASP1's specified preference in the LnurlpRequest. For the + /// version negotiation flow, see + /// https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png + #[serde(rename = "umaVersion")] + pub uma_version: String, } /// LnurlComplianceResponse is the `compliance` field of the LnurlpResponse. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct LnurlComplianceResponse { - /// is_kycd indicates whether VASP2 has KYC information about the receiver. - #[serde(rename = "isKYCd")] - pub is_kycd: bool, + /// kyc_status indicates whether VASP2 has KYC information about the receiver. + #[serde(rename = "kycStatus")] + pub kyc_status: KycStatus, /// signature is the hex-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). pub signature: String, @@ -126,9 +147,9 @@ pub struct LnurlComplianceResponse { #[serde(rename = "signatureTimestamp")] pub timestamp: i64, - /// tr_status indicates whether VASP2 is a financial institution that requires travel rule information. - #[serde(rename = "trStatus")] - pub tr_status: bool, + /// is_subject_to_travel_rule indicates whether VASP2 is a financial institution that requires travel rule information. + #[serde(rename = "isSubjectToTravelRule")] + pub is_subject_to_travel_rule: bool, /// receiver_identifier is the identifier of the receiver at VASP2. #[serde(rename = "receiverIdentifier")] @@ -148,7 +169,8 @@ impl LnurlpResponse { /// PayRequest is the request sent by the sender to the receiver to retrieve an invoice. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct PayRequest { - /// currency_code is the currency code that the receiver will receive for this payment. + /// currency_code is the ISO 3-digit currency code that the receiver will receive for this + /// payment. #[serde(rename = "currencyCode")] pub currency_code: String, @@ -206,31 +228,65 @@ pub struct Path { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct PayReqResponseCompliance { + /// node_pub_key is the public key of the receiver's node if known. + #[serde(rename = "nodePubKey")] + pub node_pub_key: Option, + + /// utxos is a list of UTXOs of channels over which the receiver will likely receive the + /// payment. pub utxos: Vec, + /// utxo_callback is the URL that the sender VASP will call to send UTXOs of the channel that + /// the sender used to send the payment once it completes. #[serde(rename = "utxoCallback")] pub utxo_callback: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct PayReqResponsePaymentInfo { + /// CurrencyCode is the ISO 3-digit currency code that the receiver will receive for this + /// payment. #[serde(rename = "currencyCode")] pub currency_code: String, + /// Multiplier is the conversion rate. It is the number of millisatoshis that the receiver will + /// receive for 1 unit of the specified currency. #[serde(rename = "multiplier")] pub multiplier: i64, + + /// ExchangeFeesMillisatoshi is the fees charged (in millisats) by the receiving VASP for this + /// transaction. This is separate from the Multiplier. + #[serde(rename = "exchangeFeesMillisatoshi")] + pub exchange_fees_millisatoshi: i64, } /// PubKeyResponse is sent from a VASP to another VASP to provide its public keys. /// It is the response to GET requests at `/.well-known/lnurlpubkey`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct PubKeyResponse { + /// signing_pub_key is used to verify signatures from a VASP. #[serde(rename = "signingPubKey")] pub signing_pub_key: Vec, + // encryption_pub_key is used to encrypt TR info sent to a VASP. #[serde(rename = "encryptionPubKey")] pub encryption_pub_key: Vec, + // expiration_timestamp [Optional] Seconds since epoch at which these pub keys must be refreshed. + // They can be safely cached until this expiration (or forever if null). #[serde(rename = "expirationTimestamp")] pub expiration_timestamp: Option, } + +/// UtxoWithAmount is a pair of utxo and amount transferred over that corresponding channel. +/// It can be used to register payment for KYT. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct UtxoWithAmount { + /// utxo The utxo of the channel over which the payment went through in the format of + /// :. + pub utxo: String, + + /// Amount The amount of funds transferred in the payment in mSats. + #[serde(rename = "amountMsats")] + pub amount: i64, +} diff --git a/src/uma/public_key_cache.rs b/uma/src/public_key_cache.rs similarity index 69% rename from src/uma/public_key_cache.rs rename to uma/src/public_key_cache.rs index 77987a6..c360587 100644 --- a/src/uma/public_key_cache.rs +++ b/uma/src/public_key_cache.rs @@ -1,10 +1,23 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + use super::protocol::PubKeyResponse; use std::collections::HashMap; +/// PublicKeyCache is an interface for a cache of public keys for other VASPs. +/// +/// Implementations of this interface should be thread-safe. pub trait PublicKeyCache { + /// fetch_public_key_for_vasp fetches the public key entry for a VASP if in the cache, otherwise + /// returns nil. fn fetch_public_key_for_vasp(&self, vasp_domain: &str) -> Option<&PubKeyResponse>; + + /// add_public_key_for_vasp adds a public key entry for a VASP to the cache. fn add_public_key_for_vasp(&mut self, vasp_domain: &str, public_key: &PubKeyResponse); + + /// remove_public_key_for_vasp removes a public key for a VASP from the cache. fn remove_public_key_for_vasp(&mut self, vasp_domain: &str); + + /// clear clears the cache. fn clear(&mut self); } diff --git a/src/uma/mod.rs b/uma/src/uma.rs similarity index 67% rename from src/uma/mod.rs rename to uma/src/uma.rs index 5c85975..878c82a 100644 --- a/src/uma/mod.rs +++ b/uma/src/uma.rs @@ -1,10 +1,4 @@ -pub mod currency; -pub mod payer_data; -pub mod protocol; -pub mod public_key_cache; - -#[cfg(test)] -mod uma_test; +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved use std::fmt; @@ -13,13 +7,15 @@ use bitcoin::secp256k1::{ }; use rand_core::{OsRng, RngCore}; -use self::{ +use crate::{ currency::Currency, + kyc_status::KycStatus, payer_data::{CompliancePayerData, PayerData, PayerDataOptions}, protocol::{ - LnurlComplianceResponse, LnurlpRequest, LnurlpResponse, PayReqResponse, + self, LnurlComplianceResponse, LnurlpRequest, LnurlpResponse, PayReqResponse, PayReqResponseCompliance, PayReqResponsePaymentInfo, PayRequest, PubKeyResponse, }, + public_key_cache, version, }; #[derive(Debug)] @@ -35,6 +31,8 @@ pub enum Error { InvalidHost, InvalidData(serde_json::Error), CreateInvoiceError(String), + InvalidUMAAddress, + InvalidVersion, } impl fmt::Display for Error { @@ -51,10 +49,21 @@ impl fmt::Display for Error { Self::InvalidHost => write!(f, "Invalid host"), Self::InvalidData(err) => write!(f, "Invalid data {}", err), Self::CreateInvoiceError(err) => write!(f, "Create invoice error {}", err), + Self::InvalidUMAAddress => write!(f, "Invalid UMA address"), + Self::InvalidVersion => write!(f, "Invalid version"), } } } +/// Fetches the public key for another VASP. +/// +/// If the public key is not in the cache, it will be fetched from the VASP's domain. +/// The public key will be cached for future use. +/// +/// # Arguments +/// +/// * `vasp_domain` - the domain of the VASP. +/// * `cache` - the PublicKeyCache cache to use. You can use the InMemoryPublicKeyCache struct, or implement your own persistent cache with any storage type. pub fn fetch_public_key_for_vasp( vasp_domain: &str, mut public_key_cache: T, @@ -111,6 +120,12 @@ fn verify_ecdsa(payload: &[u8], signature: &str, pub_key_bytes: &[u8]) -> Result .map_err(|_| Error::InvalidSignature) } +/// Verifies the signature on a uma pay request based on the public key of the VASP making the request. +/// +/// # Arguments +/// +/// * `pay_req` - the signed query to verify. +/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. pub fn verify_pay_req_signature( pay_req: &PayRequest, other_vasp_pub_key: &[u8], @@ -123,20 +138,35 @@ pub fn verify_pay_req_signature( ) } +/// Creates a signed uma request URL. +/// +/// # Arguments +/// +/// * `signing_private_key` - the private key of the VASP that is sending the payment. This will be used to sign the request. +/// * `receiver_address` - the address of the receiver of the payment (i.e. $bob@vasp2). +/// * `sender_vasp_domain` - the domain of the VASP that is sending the payment. It will be used by the receiver to fetch the public keys of the sender. +/// * `is_subject_to_travel_rule` - whether the sending VASP is a financial institution that requires travel rule information. +/// * `uma_version_override` - the version of the UMA protocol to use. If not specified, the latest version will be used. pub fn get_signed_lnurlp_request_url( signing_private_key: &[u8], receiver_address: &str, sender_vasp_domain: &str, - tr_status: bool, + is_subject_to_travel_rule: bool, + uma_version_override: Option<&str>, ) -> Result { let nonce = generate_nonce(); + let uma_version = match uma_version_override { + Some(version) => version.to_string(), + None => version::uma_protocol_version(), + }; let mut unsigned_request = LnurlpRequest { receiver_address: receiver_address.to_owned(), nonce, timestamp: chrono::Utc::now().timestamp(), signature: "".to_owned(), vasp_domain: sender_vasp_domain.to_owned(), - tr_status, + is_subject_to_travel_rule, + uma_version, }; let sig = sign_payload(&unsigned_request.signable_payload(), signing_private_key)?; @@ -147,10 +177,15 @@ pub fn get_signed_lnurlp_request_url( .map_err(Error::ProtocolError) } +/// Checks if the given URL is a valid UMA request. pub fn is_uma_lnurl_query(url: &url::Url) -> bool { parse_lnurlp_request(url).is_ok() } +/// Parses the message into an LnurlpRequest object. +/// +/// # Arguments +/// * `url` - the full URL of the uma request. pub fn parse_lnurlp_request(url: &url::Url) -> Result { let mut query = url.query_pairs(); let signature = query @@ -171,8 +206,8 @@ pub fn parse_lnurlp_request(url: &url::Url) -> Result { .ok_or(Error::MissingUrlParam("nonce".to_string()))?; let mut query = url.query_pairs(); - let tr_status = query - .find(|(key, _)| key == "trStatus") + let is_subject_to_travel_rule = query + .find(|(key, _)| key == "isSubjectToTravelRule") .map(|(_, value)| value.to_lowercase() == "true") .unwrap_or(false); @@ -183,6 +218,12 @@ pub fn parse_lnurlp_request(url: &url::Url) -> Result { .ok_or(Error::MissingUrlParam("timestamp".to_string()))? .map_err(|_| Error::MissingUrlParam("timestamp".to_string()))?; + let mut query = url.query_pairs(); + let uma_version = query + .find(|(key, _)| key == "umaVersion") + .map(|(_, value)| value) + .ok_or(Error::MissingUrlParam("umaVersion".to_string()))?; + let path_parts: Vec<&str> = url.path_segments().ok_or(Error::InvalidUrlPath)?.collect(); if path_parts.len() != 3 || path_parts[0] != ".well-known" || path_parts[1] != "lnurlp" { return Err(Error::InvalidUrlPath); @@ -200,10 +241,16 @@ pub fn parse_lnurlp_request(url: &url::Url) -> Result { receiver_address, nonce: nonce.into_owned(), timestamp, - tr_status, + is_subject_to_travel_rule, + uma_version: uma_version.into_owned(), }) } +/// Verifies the signature on an uma Lnurlp query based on the public key of the VASP making the request. +/// +/// # Arguments +/// * `query` - the signed query to verify. +/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. pub fn verify_uma_lnurl_query_signature( query: LnurlpRequest, other_vasp_pub_key: &[u8], @@ -226,14 +273,17 @@ pub fn get_lnurlp_response( max_sendable_sats: i64, payer_data_options: &PayerDataOptions, currency_options: &[Currency], - is_receiver_kycd: bool, + receiver_kyc_status: KycStatus, ) -> Result { let compliance_response = get_signed_compliance_respionse( query, private_key_bytes, requires_travel_rule_info, - is_receiver_kycd, + receiver_kyc_status, )?; + let uma_version = + version::select_lower_version(&query.uma_version, &version::uma_protocol_version()) + .map_err(|_| Error::InvalidVersion)?; Ok(LnurlpResponse { tag: "payRequest".to_string(), callback: callback.to_string(), @@ -243,14 +293,15 @@ pub fn get_lnurlp_response( currencies: currency_options.to_vec(), required_payer_data: payer_data_options.clone(), compliance: compliance_response, + uma_version, }) } fn get_signed_compliance_respionse( query: &LnurlpRequest, private_key_bytes: &[u8], - tr_status: bool, - is_receiver_kycd: bool, + is_subject_to_travel_rule: bool, + receiver_kyc_status: KycStatus, ) -> Result { let timestamp = chrono::Utc::now().timestamp(); let nonce = generate_nonce(); @@ -259,15 +310,20 @@ fn get_signed_compliance_respionse( let signature = sign_payload(payload_string.as_bytes(), private_key_bytes)?; Ok(LnurlComplianceResponse { - is_kycd: is_receiver_kycd, + kyc_status: receiver_kyc_status, signature, nonce, timestamp, - tr_status, + is_subject_to_travel_rule, receiver_identifier: query.receiver_address.clone(), }) } +/// Verifies the signature on an uma Lnurlp response based on the public key of the VASP making the request. +/// +/// # Arguments +/// * `response` - the signed response to verify. +/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. pub fn verify_uma_lnurlp_response_signature( response: LnurlpResponse, other_vasp_pub_key: &[u8], @@ -280,6 +336,31 @@ pub fn parse_lnurlp_response(bytes: &[u8]) -> Result { serde_json::from_slice(bytes).map_err(Error::InvalidData) } +/// Gets the domain of the VASP from an uma address. +pub fn get_vasp_domain_from_uma_address(uma_address: &str) -> Result { + let address_parts: Vec<&str> = uma_address.split('@').collect(); + if address_parts.len() != 2 { + Err(Error::InvalidUMAAddress) + } else { + Ok(address_parts[1].to_string()) + } +} + +/// Creates a signed uma pay request. +/// +/// # Arguments +/// * `receiver_encryption_pub_key` - the public key of the receiver of the payment. This will be used to encrypt the travel rule information. +/// * `sending_vasp_private_key` - the private key of the VASP that is sending the payment. This will be used to sign the request. +/// * `currency_code` - the currency code of the payment. +/// * `amount` - the amount of the payment in the smallest unit of the specified currency (i.e. cents for USD). +/// * `payer_identifier` - the identifier of the sender. For example, $alice@vasp1.com +/// * `payer_name` - the name of the sender. +/// * `payer_email` - the email of the sender. +/// * `tr_info` - the travel rule information to be encrypted. +/// * `payer_kyc_status` - the KYC status of the sender. +/// * `payer_uxtos` - the list of UTXOs of the sender's channels that might be used to fund the payment. +/// * `payer_node_pubkey` - If known, the public key of the sender's node. If supported by the receiving VASP's compliance provider, this will be used to pre-screen the sender's UTXOs for compliance purposes. +/// * `utxo_callback` - the URL that the receiver will use to fetch the sender's UTXOs. #[allow(clippy::too_many_arguments)] pub fn get_pay_request( receiver_encryption_pub_key: &[u8], @@ -290,8 +371,9 @@ pub fn get_pay_request( payer_name: Option<&str>, payer_email: Option<&str>, tr_info: Option<&str>, - is_payer_kycd: bool, + payer_kyc_status: KycStatus, payer_uxtos: &[String], + payer_node_pubkey: Option<&str>, utxo_callback: &str, ) -> Result { let compliance_data = get_signed_compliance_payer_data( @@ -299,8 +381,9 @@ pub fn get_pay_request( sending_vasp_private_key, payer_identifier, tr_info, - is_payer_kycd, + payer_kyc_status, payer_uxtos, + payer_node_pubkey, utxo_callback, )?; Ok(PayRequest { @@ -315,13 +398,15 @@ pub fn get_pay_request( }) } +#[allow(clippy::too_many_arguments)] fn get_signed_compliance_payer_data( receiver_encryption_pub_key: &[u8], sending_vasp_private_key: &[u8], payer_identifier: &str, tr_info: Option<&str>, - is_payer_kycd: bool, + payer_kyc_status: KycStatus, payer_uxtos: &[String], + payer_node_pubkey: Option<&str>, utxo_callback: &str, ) -> Result { let timestamp = chrono::Utc::now().timestamp(); @@ -336,8 +421,9 @@ fn get_signed_compliance_payer_data( Ok(CompliancePayerData { utxos: payer_uxtos.to_vec(), - is_kycd: is_payer_kycd, - tr_info: encrypted_tr_info, + node_pubkey: payer_node_pubkey.map(|s| s.to_string()), + kyc_status: payer_kyc_status, + encrypted_travel_rule_info: encrypted_tr_info, signature, signature_nonce: nonce, signature_timestamp: timestamp, @@ -355,14 +441,11 @@ pub fn parse_pay_request(bytes: &[u8]) -> Result { serde_json::from_slice(bytes).map_err(Error::InvalidData) } -pub trait LnurlInvoiceCreator { - fn create_lnurl_invoice( +pub trait UmaInvoiceCreator { + fn create_uma_invoice( &self, - node_id: &str, - master_seed_bytes: &[u8], amount_msat: i64, metadata: &str, - expiry_secs: Option, ) -> Result>; } @@ -370,28 +453,24 @@ pub trait LnurlInvoiceCreator { pub fn get_pay_req_response( query: &PayRequest, invoice_creator: &T, - node_id: &str, - node_master_seed_bytes: &[u8], metadata: &str, currency_code: &str, conversion_rate: i64, - expiry_secs: i32, + receiver_fees_millisats: i64, receiver_channel_utxos: &[String], + receiver_node_pub_key: Option<&str>, utxo_callback: &str, ) -> Result where - T: LnurlInvoiceCreator, + T: UmaInvoiceCreator, { let msats_amount = query.amount * conversion_rate; let encoded_payer_data = serde_json::to_string(&query.payer_data).map_err(Error::InvalidData)?; let encoded_invoice = invoice_creator - .create_lnurl_invoice( - node_id, - node_master_seed_bytes, + .create_uma_invoice( msats_amount, &format!("{}{{{}}}", metadata, encoded_payer_data), - Some(expiry_secs), ) .map_err(|e| Error::CreateInvoiceError(e.to_string()))?; @@ -399,12 +478,14 @@ where encoded_invoice, routes: [].to_vec(), compliance: PayReqResponseCompliance { + node_pub_key: receiver_node_pub_key.map(|s| s.to_string()), utxos: receiver_channel_utxos.to_vec(), utxo_callback: utxo_callback.to_string(), }, payment_info: PayReqResponsePaymentInfo { currency_code: currency_code.to_string(), multiplier: conversion_rate, + exchange_fees_millisatoshi: receiver_fees_millisats, }, }) } diff --git a/src/uma/uma_test.rs b/uma/src/uma_test.rs similarity index 69% rename from src/uma/uma_test.rs rename to uma/src/uma_test.rs index f349000..a395fa8 100644 --- a/src/uma/uma_test.rs +++ b/uma/src/uma_test.rs @@ -1,16 +1,18 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + #[cfg(test)] mod tests { use ecies::utils::generate_keypair; use crate::uma::{ - currency::Currency, get_lnurlp_response, get_pay_req_response, get_pay_request, - get_signed_lnurlp_request_url, is_uma_lnurl_query, parse_lnurlp_request, - parse_lnurlp_response, parse_pay_req_response, parse_pay_request, - payer_data::PayerDataOptions, protocol::LnurlpRequest, verify_pay_req_signature, - verify_uma_lnurl_query_signature, verify_uma_lnurlp_response_signature, - LnurlInvoiceCreator, + get_lnurlp_response, get_pay_req_response, get_pay_request, get_signed_lnurlp_request_url, + is_uma_lnurl_query, parse_lnurlp_request, parse_lnurlp_response, parse_pay_req_response, + parse_pay_request, verify_pay_req_signature, verify_uma_lnurl_query_signature, + verify_uma_lnurlp_response_signature, UmaInvoiceCreator, }; + use crate::{currency::Currency, payer_data::PayerDataOptions, protocol::LnurlpRequest}; + #[test] fn test_parse() { let timestamp = chrono::Utc::now().timestamp(); @@ -18,12 +20,13 @@ mod tests { receiver_address: "bob@vasp2.com".to_string(), nonce: "12345".to_string(), signature: "signature".to_string(), - tr_status: true, + is_subject_to_travel_rule: true, vasp_domain: "vasp1.com".to_string(), timestamp, + uma_version: "0.1".to_string(), }; - let url_string = format!("https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp={}",×tamp); + let url_string = format!("https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp={}",×tamp); let url = url::Url::parse(&url_string).unwrap(); let query = parse_lnurlp_request(&url).unwrap(); @@ -32,50 +35,54 @@ mod tests { #[test] fn test_is_uma_query_valid() { - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(is_uma_lnurl_query(&url)); } #[test] fn test_is_uma_query_missing_params() { - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&vaspDomain=vasp1.com×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); - // trStatus is optional + // isSubjectToTravelRule is optional assert!(is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true"; + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&vaspDomain=vasp1.com&isSubjectToTravelRule=true"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); let url_string = "https://vasp2.com/.well-known/lnurlp/bob"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); + + let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&isSubjectToTravelRule=true×tamp=12345678"; + let url = url::Url::parse(url_string).unwrap(); + assert!(!is_uma_lnurl_query(&url)); } #[test] fn test_is_uma_query_invalid_path() { - let url_string = "https://vasp2.com/.well-known/lnurla/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/.well-known/lnurla/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); - let url_string = "https://vasp2.com/?signature=signature&nonce=12345&vaspDomain=vasp1.com&trStatus=true×tamp=12345678"; + let url_string = "https://vasp2.com/?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; let url = url::Url::parse(url_string).unwrap(); assert!(!is_uma_lnurl_query(&url)); } @@ -84,9 +91,14 @@ mod tests { fn test_sign_and_verify_lnurlp_request() { let (sk, pk) = generate_keypair(); - let query_url = - get_signed_lnurlp_request_url(&sk.serialize(), "$bob@vasp2.com", "vasp1.com", true) - .unwrap(); + let query_url = get_signed_lnurlp_request_url( + &sk.serialize(), + "$bob@vasp2.com", + "vasp1.com", + true, + None, + ) + .unwrap(); let query = parse_lnurlp_request(&query_url).unwrap(); @@ -98,9 +110,14 @@ mod tests { fn test_sign_and_verify_lnurlp_request_invalid_signature() { let (sk, _) = generate_keypair(); - let query_url = - get_signed_lnurlp_request_url(&sk.serialize(), "$bob@vasp2.com", "vasp1.com", true) - .unwrap(); + let query_url = get_signed_lnurlp_request_url( + &sk.serialize(), + "$bob@vasp2.com", + "vasp1.com", + true, + None, + ) + .unwrap(); let query = parse_lnurlp_request(&query_url).unwrap(); @@ -114,9 +131,14 @@ mod tests { let (sk1, _) = generate_keypair(); let (sk2, pk2) = generate_keypair(); - let request = - get_signed_lnurlp_request_url(&sk1.serialize(), "$bob@vasp2.com", "vasp1.com", true) - .unwrap(); + let request = get_signed_lnurlp_request_url( + &sk1.serialize(), + "$bob@vasp2.com", + "vasp1.com", + true, + None, + ) + .unwrap(); let query = parse_lnurlp_request(&request).unwrap(); let metadata = create_metadata_for_bob().unwrap(); @@ -144,7 +166,7 @@ mod tests { compliance_required: true, }, ¤cy_options, - true, + crate::kyc_status::KycStatus::KycStatusVerified, ) .unwrap(); @@ -169,8 +191,9 @@ mod tests { None, None, Some("some TR info for VASP2"), - true, + crate::kyc_status::KycStatus::KycStatusVerified, &[], + None, "/api/lnurl/utxocallback?txid=1234", ) .unwrap(); @@ -182,7 +205,14 @@ mod tests { let result = verify_pay_req_signature(&payreq, &pk2.serialize()); assert!(result.is_ok()); - let cipher_text = hex::decode(payreq.payer_data.compliance.tr_info.unwrap()).unwrap(); + let cipher_text = hex::decode( + payreq + .payer_data + .compliance + .encrypted_travel_rule_info + .unwrap(), + ) + .unwrap(); let plain_text = ecies::decrypt(&sk1.serialize(), &cipher_text).unwrap(); assert_eq!(plain_text, b"some TR info for VASP2"); } @@ -201,8 +231,9 @@ mod tests { None, None, Some("some TR info for VASP2"), - true, + crate::kyc_status::KycStatus::KycStatusVerified, &[], + None, "/api/lnurl/utxocallback?txid=1234", ) .unwrap(); @@ -214,13 +245,12 @@ mod tests { let response = get_pay_req_response( &payreq, &client, - "node_id", - b"1", &metadata, "USD", 34150, - 60, + 100_000, &["abcdef12345".to_owned()], + None, "/api/lnurl/utxocallback?txid=1234", ) .unwrap(); @@ -233,14 +263,11 @@ mod tests { struct FakeInvoiceCreator {} - impl LnurlInvoiceCreator for FakeInvoiceCreator { - fn create_lnurl_invoice( + impl UmaInvoiceCreator for FakeInvoiceCreator { + fn create_uma_invoice( &self, - _node_id: &str, - _master_seed_bytes: &[u8], _amount_msat: i64, _metadata: &str, - _expiry_secs: Option, ) -> Result> { Ok("lntb100n1p0z9j".to_owned()) } diff --git a/uma/src/version.rs b/uma/src/version.rs new file mode 100644 index 0000000..b022e44 --- /dev/null +++ b/uma/src/version.rs @@ -0,0 +1,102 @@ +// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved + +use crate::uma; + +const MAJOR_VERSION: i32 = 0; +const MINOR_VERSION: i32 = 1; + +pub fn uma_protocol_version() -> String { + format!("{}.{}", MAJOR_VERSION, MINOR_VERSION) +} + +pub struct ParsedVersion { + pub major: i32, + pub minor: i32, +} + +impl ParsedVersion { + pub fn new(version: &str) -> Result { + let parts: Vec<&str> = version.split('.').collect(); + if parts.len() != 2 { + Err(uma::Error::InvalidVersion) + } else { + let major = parts[0] + .parse::() + .map_err(|_| uma::Error::InvalidVersion)?; + let minor = parts[1] + .parse::() + .map_err(|_| uma::Error::InvalidVersion)?; + Ok(Self { major, minor }) + } + } + + pub fn string_value(&self) -> String { + format!("{}.{}", self.major, self.minor) + } +} + +pub fn get_supported_major_version() -> Vec { + // NOTE: In the future, we may want to support multiple major versions in the same SDK, but for + // now, this keeps things simple. + vec![MAJOR_VERSION] +} + +pub fn get_highest_supported_version_for_major_version( + major_version: &i32, +) -> Option { + if *major_version != MAJOR_VERSION { + None + } else { + ParsedVersion::new(&uma_protocol_version()).ok() + } +} + +pub fn select_highest_supported_version( + other_vasp_supported_major_versions: &[i32], +) -> Option { + let supported_version = get_supported_major_version(); + let mut highest_version: Option = None; + for other_vasp_major_version in other_vasp_supported_major_versions { + if !supported_version.contains(other_vasp_major_version) { + continue; + } + + match highest_version { + Some(ref v) => { + if *other_vasp_major_version > v.major { + highest_version = Some( + get_highest_supported_version_for_major_version(other_vasp_major_version) + .unwrap(), + ); + } + } + None => { + highest_version = Some( + get_highest_supported_version_for_major_version(other_vasp_major_version) + .unwrap(), + ); + } + } + } + + highest_version.map(|v| v.string_value()) +} + +pub fn select_lower_version(version1: &str, version2: &str) -> Result { + let v1 = ParsedVersion::new(version1)?; + let v2 = ParsedVersion::new(version2)?; + + if v1.major > v2.major || (v1.major == v2.major && v1.minor > v2.minor) { + Ok(version2.to_string()) + } else { + Ok(version1.to_string()) + } +} + +pub fn is_version_supported(version: &str) -> bool { + let parsed_version = match ParsedVersion::new(version) { + Ok(parsed_version) => parsed_version, + Err(_) => return false, + }; + get_supported_major_version().contains(&parsed_version.major) +}