branta 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Run tests with coverage
28
+ run: |
29
+ coverage run -m pytest tests/ --ignore=tests/test_integration.py -v
30
+ coverage report
31
+ coverage xml
32
+ env:
33
+ BRANTA_SKIP_INTEGRATION: "1"
34
+
35
+ - name: Upload coverage
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: coverage-${{ matrix.python-version }}
39
+ path: coverage.xml
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ .venv/
7
+ venv/
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ .eggs/
12
+ .pytest_cache/
13
+ .coverage
14
+ coverage.xml
15
+ htmlcov/
16
+ .mypy_cache/
17
+ *.egg
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [3.2.0] - 2026-06-26
8
+
9
+ ### Added
10
+ - `PaymentBuilder.set_child_platform(name, logo_url, logo_light_url)` — allows master-switch platforms to associate a child platform with a payment on POST
11
+ - `Payment.child_platform` field (optional `Platform`) to hold the child platform data before submission
12
+ - Removed tracked `__pycache__` files and ensured they are covered by `.gitignore`
13
+
14
+ ## [3.1.0] - 2026-06-25
15
+
16
+ ### Added
17
+ - Initial release of the Branta Python SDK
18
+ - Feature-parity port of `branta-js` 3.1.4 and `branta-dotnet` 3.1.6
19
+ - `BrantaService` with `get_payments`, `get_payments_by_qr_code`, `add_payment`, and `is_api_key_valid`
20
+ - `PaymentBuilder` fluent builder for constructing `Payment` objects
21
+ - `QRParser` for parsing Bitcoin and Lightning URI QR codes
22
+ - AES-256-GCM encryption with deterministic and random nonce modes
23
+ - Zero-knowledge (ZK) destination support for Bitcoin addresses, BOLT-11, Ark, and silent payments
24
+ - Metadata DEK-envelope encryption
25
+ - `PrivacyMode.Strict` (default) and `PrivacyMode.Loose` enforcement
26
+ - HMAC-SHA256 signing support for parent platform use
27
+ - Full type annotations and async/await throughout
28
+ - Unit and integration tests with full branch coverage
branta-3.2.0/CLAUDE.md ADDED
@@ -0,0 +1,49 @@
1
+ # Branta Python SDK
2
+
3
+ Feature-parity Python port of the Branta SDK. Mirrors `branta-js` and `branta-dotnet` — any public API change in one must be reflected here and in the other two.
4
+
5
+ ## Package layout
6
+
7
+ - `branta/` — top-level package; re-exports all public symbols
8
+ - `branta/enums.py` — `BrantaServerBaseUrl`, `DestinationType`, `PrivacyMode`
9
+ - `branta/exceptions.py` — `BrantaPaymentException`, `QRParseException`
10
+ - `branta/models.py` — `Payment`, `Destination`, `Platform`, `PaymentsResult`, `AddPaymentResult`
11
+ - `branta/options.py` — `BrantaClientOptions`
12
+ - `branta/extensions.py` — shared helper functions (hash, fragment, type detection)
13
+ - `branta/v2/service.py` — `BrantaService` (orchestrates ZK encrypt/decrypt around HTTP)
14
+ - `branta/v2/client.py` — `BrantaClient` (raw HTTP; do not call directly from consumer code)
15
+ - `branta/v2/builder.py` — `PaymentBuilder`
16
+ - `branta/v2/parser.py` — `QRParser`
17
+ - `branta/v2/encryption.py` — `AesEncryption` (static), `AesEncryptionService` (instance wrapper)
18
+ - `branta/v2/secret_generator.py` — `GuidSecretGenerator`
19
+ - `branta/v2/serialization.py` — snake_case JSON serialization helpers
20
+ - `tests/` — mirrors `branta/`; unit tests use mocked client + AES; integration tests hit live endpoints
21
+
22
+ ## Setup
23
+
24
+ ```bash
25
+ pip install -e ".[dev]"
26
+ ```
27
+
28
+ ## Commands
29
+
30
+ - `pytest tests/ --ignore=tests/test_integration.py` — run unit tests (no network)
31
+ - `pytest tests/test_integration.py` — run integration tests (requires network)
32
+ - `coverage run -m pytest tests/ --ignore=tests/test_integration.py && coverage report` — with coverage
33
+ - `pip install -e .` / `pip publish` — publish to PyPI (requires `twine`)
34
+
35
+ ## Key behaviors to preserve
36
+
37
+ - **`PrivacyMode.Strict` is the default.** `get_payments` throws `BrantaPaymentException` for plain bitcoin addresses; `get_payments_by_qr_code` returns an empty `PaymentsResult`. `add_payment` throws if any destination has `is_zk=False`.
38
+ - **`verify_url` is always returned**, even on a miss. Format: `{base_url}/v2/verify/{lookup}` plus `#k-{zk_id}={key}` fragments.
39
+ - **ZK encryption:** Bitcoin addresses use a random secret (GUID); hash-ZK types (bolt11, ark, silent_payment) use a deterministic key from SHA256(normalized value). `add_payment` mutates `payment.destinations[*].value` to the encrypted form before POSTing.
40
+ - **Metadata DEK-envelope:** If a payment has metadata and any ZK destination, a separate DEK is generated, metadata is encrypted with it, and the DEK is encrypted per-destination.
41
+ - **Never surface lookup failures.** Swallow decryption errors; leave `is_encrypted=True` and value unchanged.
42
+ - **Domain validation.** `platform_logo_url` must match `base_url` domain.
43
+
44
+ ## Conventions
45
+
46
+ - Async throughout — `BrantaService` and `BrantaClient` methods are all `async def`.
47
+ - Python snake_case for all public attribute and method names.
48
+ - Inject `client`, `aes_encryption`, `secret_generator` into `BrantaService` for testing.
49
+ - Keep feature-parity with `branta-js` and `branta-dotnet`: any new method, option, or enum value added here must be reflected in both.
branta-3.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Branta Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
branta-3.2.0/PKG-INFO ADDED
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: branta
3
+ Version: 3.2.0
4
+ Summary: Python SDK for the Branta V2 API — payment destination lookup and registration with zero-knowledge encryption support
5
+ Project-URL: Homepage, https://branta.pro
6
+ Project-URL: Repository, https://github.com/BrantaOps/branta-python
7
+ Project-URL: Changelog, https://github.com/BrantaOps/branta-python/blob/main/CHANGELOG.md
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: bitcoin,branta,crypto,lightning,payments
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: aiohttp>=3.9.0
22
+ Requires-Dist: cryptography>=42.0.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: coverage>=7.0.0; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Branta Python SDK
30
+
31
+ Python SDK for the [Branta](https://branta.pro) V2 API — payment destination lookup and registration with zero-knowledge encryption support.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install branta
37
+ ```
38
+
39
+ ## Integration Guide
40
+
41
+ ### Quick start
42
+
43
+ ```python
44
+ import asyncio
45
+ from branta.enums import BrantaServerBaseUrl, PrivacyMode
46
+ from branta.options import BrantaClientOptions
47
+ from branta.v2 import BrantaService
48
+
49
+ options = BrantaClientOptions(
50
+ base_url=BrantaServerBaseUrl.Production,
51
+ privacy=PrivacyMode.Strict,
52
+ )
53
+ service = BrantaService(options)
54
+
55
+ async def main():
56
+ result = await service.get_payments_by_qr_code("bitcoin:bc1q...")
57
+ if result.payments:
58
+ print(result.payments[0].get_default_value())
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ ### Privacy modes
64
+
65
+ | Mode | Behaviour |
66
+ |------|-----------|
67
+ | `PrivacyMode.Strict` (default) | Only ZK lookups. `get_payments()` raises `BrantaPaymentException` for plain addresses. `get_payments_by_qr_code()` returns empty for plain addresses. `add_payment()` raises if any destination has `is_zk=False`. |
68
+ | `PrivacyMode.Loose` | Both plain and ZK lookups are permitted. |
69
+
70
+ ### Looking up a payment by QR code
71
+
72
+ ```python
73
+ result = await service.get_payments_by_qr_code(qr_text)
74
+ # result.payments — list of Payment objects (empty if not found)
75
+ # result.verify_url — always populated; share with the payer to verify
76
+ ```
77
+
78
+ Prefer `get_payments_by_qr_code` for QR-driven flows. It handles multi-destination payloads (`branta_id` / `branta_secret` fragments) automatically.
79
+
80
+ ### Looking up a payment by destination value
81
+
82
+ ```python
83
+ # Plain bitcoin address (requires PrivacyMode.Loose or will raise):
84
+ result = await service.get_payments("bc1q...")
85
+
86
+ # ZK-encrypted bitcoin address with secret:
87
+ result = await service.get_payments(encrypted_address, destination_encryption_key="my-secret")
88
+
89
+ # BOLT-11 invoice (hash-ZK — works in strict mode):
90
+ result = await service.get_payments("lnbc...")
91
+ ```
92
+
93
+ ### Registering a payment
94
+
95
+ ```python
96
+ from branta.enums import DestinationType
97
+
98
+ builder = service.create_payment_builder()
99
+ payment = (
100
+ builder
101
+ .add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
102
+ .add_destination("lnbc...", DestinationType.Bolt11).set_zk()
103
+ .set_description("Donation")
104
+ .add_metadata("email", "donor@example.com")
105
+ .build()
106
+ )
107
+
108
+ result = await service.add_payment(payment, BrantaClientOptions(
109
+ base_url=BrantaServerBaseUrl.Production,
110
+ default_api_key="your-api-key",
111
+ privacy=PrivacyMode.Strict,
112
+ ))
113
+ # result.payment — the registered payment from the server
114
+ # result.secret — the random encryption key for the bitcoin address
115
+ # result.verify_url — share this URL to verify the payment
116
+ ```
117
+
118
+ ### Validating an API key
119
+
120
+ ```python
121
+ is_valid = await service.is_api_key_valid(BrantaClientOptions(
122
+ base_url=BrantaServerBaseUrl.Production,
123
+ default_api_key="your-api-key",
124
+ ))
125
+ ```
126
+
127
+ ### Per-call option overrides
128
+
129
+ Every public method accepts an optional `BrantaClientOptions` parameter that overrides the service's default options for that call only:
130
+
131
+ ```python
132
+ service = BrantaService(default_options)
133
+ result = await service.get_payments("lnbc...", options=override_options)
134
+ ```
135
+
136
+ ## ZK destination types
137
+
138
+ | Type | Encryption |
139
+ |------|-----------|
140
+ | `BitcoinAddress` | Random secret (GUID) per payment |
141
+ | `Bolt11` | Deterministic: SHA256 of lowercase invoice |
142
+ | `ArkAddress` | Deterministic: SHA256 of lowercase address |
143
+ | `SilentPayment` | Deterministic: SHA256 of lowercase address |
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ pip install -e ".[dev]"
149
+ pytest tests/ --ignore=tests/test_integration.py # unit tests
150
+ pytest tests/test_integration.py # integration (requires network)
151
+ coverage run -m pytest tests/ --ignore=tests/test_integration.py && coverage report
152
+ ```
branta-3.2.0/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Branta Python SDK
2
+
3
+ Python SDK for the [Branta](https://branta.pro) V2 API — payment destination lookup and registration with zero-knowledge encryption support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install branta
9
+ ```
10
+
11
+ ## Integration Guide
12
+
13
+ ### Quick start
14
+
15
+ ```python
16
+ import asyncio
17
+ from branta.enums import BrantaServerBaseUrl, PrivacyMode
18
+ from branta.options import BrantaClientOptions
19
+ from branta.v2 import BrantaService
20
+
21
+ options = BrantaClientOptions(
22
+ base_url=BrantaServerBaseUrl.Production,
23
+ privacy=PrivacyMode.Strict,
24
+ )
25
+ service = BrantaService(options)
26
+
27
+ async def main():
28
+ result = await service.get_payments_by_qr_code("bitcoin:bc1q...")
29
+ if result.payments:
30
+ print(result.payments[0].get_default_value())
31
+
32
+ asyncio.run(main())
33
+ ```
34
+
35
+ ### Privacy modes
36
+
37
+ | Mode | Behaviour |
38
+ |------|-----------|
39
+ | `PrivacyMode.Strict` (default) | Only ZK lookups. `get_payments()` raises `BrantaPaymentException` for plain addresses. `get_payments_by_qr_code()` returns empty for plain addresses. `add_payment()` raises if any destination has `is_zk=False`. |
40
+ | `PrivacyMode.Loose` | Both plain and ZK lookups are permitted. |
41
+
42
+ ### Looking up a payment by QR code
43
+
44
+ ```python
45
+ result = await service.get_payments_by_qr_code(qr_text)
46
+ # result.payments — list of Payment objects (empty if not found)
47
+ # result.verify_url — always populated; share with the payer to verify
48
+ ```
49
+
50
+ Prefer `get_payments_by_qr_code` for QR-driven flows. It handles multi-destination payloads (`branta_id` / `branta_secret` fragments) automatically.
51
+
52
+ ### Looking up a payment by destination value
53
+
54
+ ```python
55
+ # Plain bitcoin address (requires PrivacyMode.Loose or will raise):
56
+ result = await service.get_payments("bc1q...")
57
+
58
+ # ZK-encrypted bitcoin address with secret:
59
+ result = await service.get_payments(encrypted_address, destination_encryption_key="my-secret")
60
+
61
+ # BOLT-11 invoice (hash-ZK — works in strict mode):
62
+ result = await service.get_payments("lnbc...")
63
+ ```
64
+
65
+ ### Registering a payment
66
+
67
+ ```python
68
+ from branta.enums import DestinationType
69
+
70
+ builder = service.create_payment_builder()
71
+ payment = (
72
+ builder
73
+ .add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
74
+ .add_destination("lnbc...", DestinationType.Bolt11).set_zk()
75
+ .set_description("Donation")
76
+ .add_metadata("email", "donor@example.com")
77
+ .build()
78
+ )
79
+
80
+ result = await service.add_payment(payment, BrantaClientOptions(
81
+ base_url=BrantaServerBaseUrl.Production,
82
+ default_api_key="your-api-key",
83
+ privacy=PrivacyMode.Strict,
84
+ ))
85
+ # result.payment — the registered payment from the server
86
+ # result.secret — the random encryption key for the bitcoin address
87
+ # result.verify_url — share this URL to verify the payment
88
+ ```
89
+
90
+ ### Validating an API key
91
+
92
+ ```python
93
+ is_valid = await service.is_api_key_valid(BrantaClientOptions(
94
+ base_url=BrantaServerBaseUrl.Production,
95
+ default_api_key="your-api-key",
96
+ ))
97
+ ```
98
+
99
+ ### Per-call option overrides
100
+
101
+ Every public method accepts an optional `BrantaClientOptions` parameter that overrides the service's default options for that call only:
102
+
103
+ ```python
104
+ service = BrantaService(default_options)
105
+ result = await service.get_payments("lnbc...", options=override_options)
106
+ ```
107
+
108
+ ## ZK destination types
109
+
110
+ | Type | Encryption |
111
+ |------|-----------|
112
+ | `BitcoinAddress` | Random secret (GUID) per payment |
113
+ | `Bolt11` | Deterministic: SHA256 of lowercase invoice |
114
+ | `ArkAddress` | Deterministic: SHA256 of lowercase address |
115
+ | `SilentPayment` | Deterministic: SHA256 of lowercase address |
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ pip install -e ".[dev]"
121
+ pytest tests/ --ignore=tests/test_integration.py # unit tests
122
+ pytest tests/test_integration.py # integration (requires network)
123
+ coverage run -m pytest tests/ --ignore=tests/test_integration.py && coverage report
124
+ ```
@@ -0,0 +1,45 @@
1
+ from branta.enums import BrantaServerBaseUrl, DestinationType, PrivacyMode
2
+ from branta.exceptions import BrantaPaymentException, QRParseException
3
+ from branta.extensions import (
4
+ get_api_key,
5
+ get_base_url,
6
+ get_hash_zk_type,
7
+ get_hmac_secret,
8
+ get_privacy,
9
+ get_url,
10
+ is_ark,
11
+ is_bolt11,
12
+ is_silent_payment,
13
+ to_normalized_hash,
14
+ to_url_fragment,
15
+ )
16
+ from branta.models import AddPaymentResult, Destination, Payment, PaymentsResult, Platform
17
+ from branta.options import BrantaClientOptions
18
+ from branta.v2.encryption import AesEncryption, AesEncryptionService
19
+
20
+ __all__ = [
21
+ "BrantaServerBaseUrl",
22
+ "DestinationType",
23
+ "PrivacyMode",
24
+ "BrantaPaymentException",
25
+ "QRParseException",
26
+ "BrantaClientOptions",
27
+ "Payment",
28
+ "Destination",
29
+ "Platform",
30
+ "PaymentsResult",
31
+ "AddPaymentResult",
32
+ "AesEncryption",
33
+ "AesEncryptionService",
34
+ "get_url",
35
+ "get_base_url",
36
+ "get_privacy",
37
+ "get_api_key",
38
+ "get_hmac_secret",
39
+ "get_hash_zk_type",
40
+ "is_bolt11",
41
+ "is_ark",
42
+ "is_silent_payment",
43
+ "to_normalized_hash",
44
+ "to_url_fragment",
45
+ ]
@@ -0,0 +1,23 @@
1
+ from enum import Enum
2
+
3
+
4
+ class BrantaServerBaseUrl(str, Enum):
5
+ Staging = "https://staging.guardrail.branta.pro"
6
+ Production = "https://guardrail.branta.pro"
7
+ Localhost = "http://localhost:3000"
8
+
9
+
10
+ class PrivacyMode(str, Enum):
11
+ Strict = "strict"
12
+ Loose = "loose"
13
+
14
+
15
+ class DestinationType(str, Enum):
16
+ BitcoinAddress = "bitcoin_address"
17
+ Bolt11 = "bolt11"
18
+ Bolt12 = "bolt12"
19
+ LnUrl = "ln_url"
20
+ TetherAddress = "tether_address"
21
+ LnAddress = "ln_address"
22
+ ArkAddress = "ark_address"
23
+ SilentPayment = "silent_payment"
@@ -0,0 +1,6 @@
1
+ class BrantaPaymentException(Exception):
2
+ pass
3
+
4
+
5
+ class QRParseException(Exception):
6
+ pass
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from typing import TYPE_CHECKING, Dict, Optional
5
+
6
+ from branta.enums import DestinationType, PrivacyMode
7
+
8
+ if TYPE_CHECKING:
9
+ from branta.options import BrantaClientOptions
10
+
11
+
12
+ def get_url(server: BrantaServerBaseUrl) -> str:
13
+ return server.value
14
+
15
+
16
+ def get_base_url(
17
+ default_options: Optional[BrantaClientOptions],
18
+ override_options: Optional[BrantaClientOptions],
19
+ ) -> str:
20
+ base_url = None
21
+ if override_options is not None and override_options.base_url is not None:
22
+ base_url = override_options.base_url
23
+ elif default_options is not None and default_options.base_url is not None:
24
+ base_url = default_options.base_url
25
+ if base_url is None:
26
+ raise ValueError("Branta: base_url is a required option.")
27
+ return get_url(base_url)
28
+
29
+
30
+ def get_privacy(
31
+ default_options: Optional[BrantaClientOptions],
32
+ override_options: Optional[BrantaClientOptions],
33
+ fallback: PrivacyMode = PrivacyMode.Strict,
34
+ ) -> PrivacyMode:
35
+ if override_options is not None and override_options.privacy is not None:
36
+ return override_options.privacy
37
+ if default_options is not None and default_options.privacy is not None:
38
+ return default_options.privacy
39
+ return fallback
40
+
41
+
42
+ def get_api_key(
43
+ default_options: Optional[BrantaClientOptions],
44
+ override_options: Optional[BrantaClientOptions],
45
+ ) -> Optional[str]:
46
+ if override_options is not None and override_options.default_api_key is not None:
47
+ return override_options.default_api_key
48
+ if default_options is not None and default_options.default_api_key is not None:
49
+ return default_options.default_api_key
50
+ return None
51
+
52
+
53
+ def get_hmac_secret(
54
+ default_options: Optional[BrantaClientOptions],
55
+ override_options: Optional[BrantaClientOptions],
56
+ ) -> Optional[str]:
57
+ if override_options is not None and override_options.hmac_secret is not None:
58
+ return override_options.hmac_secret
59
+ if default_options is not None and default_options.hmac_secret is not None:
60
+ return default_options.hmac_secret
61
+ return None
62
+
63
+
64
+ def is_bolt11(value: str) -> bool:
65
+ lower = value.lower()
66
+ return lower.startswith("lnbc") or lower.startswith("lntb") or lower.startswith("lnbcrt")
67
+
68
+
69
+ def is_ark(value: str) -> bool:
70
+ return value.lower().startswith("ark1")
71
+
72
+
73
+ def is_silent_payment(value: str) -> bool:
74
+ lower = value.lower()
75
+ return lower.startswith("sp1") or lower.startswith("tsp1")
76
+
77
+
78
+ def get_hash_zk_type(value: str) -> Optional[DestinationType]:
79
+ if is_bolt11(value):
80
+ return DestinationType.Bolt11
81
+ if is_ark(value):
82
+ return DestinationType.ArkAddress
83
+ if is_silent_payment(value):
84
+ return DestinationType.SilentPayment
85
+ return None
86
+
87
+
88
+ def to_normalized_hash(value: str) -> str:
89
+ normalized = value.lower()
90
+ return hashlib.sha256(normalized.encode("utf-8")).hexdigest().upper()
91
+
92
+
93
+ def to_url_fragment(keys: Dict[str, str]) -> str:
94
+ if not keys:
95
+ return ""
96
+ fragments = [f"k-{k}={v}" for k, v in keys.items()]
97
+ return "#" + "&".join(fragments)
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List, Optional
5
+
6
+ from branta.enums import DestinationType
7
+
8
+
9
+ @dataclass
10
+ class Destination:
11
+ value: str
12
+ is_primary: bool = False
13
+ is_zk: bool = False
14
+ is_encrypted: Optional[bool] = None
15
+ type: Optional[DestinationType] = None
16
+ zk_id: Optional[str] = None
17
+ encrypted_dek: Optional[str] = None
18
+
19
+
20
+ @dataclass
21
+ class Platform:
22
+ name: Optional[str] = None
23
+ logo_url: Optional[str] = None
24
+ logo_light_url: Optional[str] = None
25
+
26
+
27
+ @dataclass
28
+ class Payment:
29
+ destinations: List[Destination] = field(default_factory=list)
30
+ description: Optional[str] = None
31
+ created_date: Optional[str] = None
32
+ ttl: Optional[int] = None
33
+ metadata: Optional[str] = None
34
+ platform: Optional[str] = None
35
+ platform_logo_url: Optional[str] = None
36
+ platform_logo_light_url: Optional[str] = None
37
+ parent_platform: Optional[Platform] = None
38
+ child_platform: Optional[Platform] = None
39
+ btc_pay_server_plugin_version: Optional[str] = None
40
+ is_metadata_decrypted: Optional[bool] = None
41
+
42
+ def get_default_value(self) -> str:
43
+ if not self.destinations:
44
+ raise ValueError("Payment has no destinations")
45
+ return self.destinations[0].value
46
+
47
+
48
+ @dataclass
49
+ class PaymentsResult:
50
+ payments: List[Payment]
51
+ verify_url: str
52
+
53
+
54
+ @dataclass
55
+ class AddPaymentResult:
56
+ payment: Payment
57
+ secret: str
58
+ verify_url: str
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from branta.enums import BrantaServerBaseUrl, PrivacyMode
7
+
8
+
9
+ @dataclass
10
+ class BrantaClientOptions:
11
+ base_url: BrantaServerBaseUrl
12
+ privacy: Optional[PrivacyMode] = None
13
+ default_api_key: Optional[str] = None
14
+ hmac_secret: Optional[str] = None