Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 351 additions & 7 deletions bindings/ios/bitkitcore.swift

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion bindings/ios/bitkitcoreFFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ void uniffi_bitkitcore_fn_free_usdtwallet(void*_Nonnull ptr, RustCallStatus *_No
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CONSTRUCTOR_USDTWALLET_NEW
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CONSTRUCTOR_USDTWALLET_NEW
void*_Nonnull uniffi_bitkitcore_fn_constructor_usdtwallet_new(RustBuffer address, RustBuffer storage_path, RustBuffer rpc_url, RustBuffer bundler_url, RustCallStatus *_Nonnull out_status
void*_Nonnull uniffi_bitkitcore_fn_constructor_usdtwallet_new(RustBuffer address, RustBuffer storage_path, RustBuffer rpc_url, RustBuffer bundler_url, RustBuffer bridge_url, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTWALLET_BALANCE
Expand All @@ -735,6 +735,11 @@ uint64_t uniffi_bitkitcore_fn_method_usdtwallet_create_payment_proof(void*_Nonnu
RustBuffer uniffi_bitkitcore_fn_method_usdtwallet_history(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTWALLET_ORCHESTRA_DESTINATIONS
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTWALLET_ORCHESTRA_DESTINATIONS
uint64_t uniffi_bitkitcore_fn_method_usdtwallet_orchestra_destinations(void*_Nonnull ptr
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTWALLET_QUOTE_TRANSFER
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTWALLET_QUOTE_TRANSFER
uint64_t uniffi_bitkitcore_fn_method_usdtwallet_quote_transfer(void*_Nonnull ptr, RustBuffer recipient, uint64_t amount, RustBuffer destination
Expand Down Expand Up @@ -1824,6 +1829,11 @@ uint64_t uniffi_bitkitcore_fn_func_usdt_parse_amount(RustBuffer value, RustCallS
RustBuffer uniffi_bitkitcore_fn_func_usdt_parse_payment_request(RustBuffer value, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_USDT_VALIDATE_RECIPIENT
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_USDT_VALIDATE_RECIPIENT
RustBuffer uniffi_bitkitcore_fn_func_usdt_validate_recipient(RustBuffer value, RustBuffer destination, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_VALIDATE_BITCOIN_ADDRESS
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_VALIDATE_BITCOIN_ADDRESS
RustBuffer uniffi_bitkitcore_fn_func_validate_bitcoin_address(RustBuffer address, RustCallStatus *_Nonnull out_status
Expand Down Expand Up @@ -3348,6 +3358,12 @@ uint16_t uniffi_bitkitcore_checksum_func_usdt_parse_amount(void
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_USDT_PARSE_PAYMENT_REQUEST
uint16_t uniffi_bitkitcore_checksum_func_usdt_parse_payment_request(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_USDT_VALIDATE_RECIPIENT
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_USDT_VALIDATE_RECIPIENT
uint16_t uniffi_bitkitcore_checksum_func_usdt_validate_recipient(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_VALIDATE_BITCOIN_ADDRESS
Expand Down Expand Up @@ -3570,6 +3586,12 @@ uint16_t uniffi_bitkitcore_checksum_method_usdtwallet_create_payment_proof(void
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTWALLET_HISTORY
uint16_t uniffi_bitkitcore_checksum_method_usdtwallet_history(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTWALLET_ORCHESTRA_DESTINATIONS
#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTWALLET_ORCHESTRA_DESTINATIONS
uint16_t uniffi_bitkitcore_checksum_method_usdtwallet_orchestra_destinations(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTWALLET_QUOTE_TRANSFER
Expand Down
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ pub use modules::onchain;
pub use modules::scanner::{DecodingError, LnurlPayData, Scanner};
pub use modules::seedqr::{decode_compact_seed_qr, decode_standard_seed_qr, SeedQrError};
pub use modules::usdt::{
usdt_address, usdt_format_amount, usdt_parse_amount, usdt_parse_payment_request, UsdtDeposit,
UsdtDepositAddress, UsdtDepositClient, UsdtDepositDetail, UsdtDepositNetwork, UsdtDepositOrder,
UsdtDepositPage, UsdtDestination, UsdtError, UsdtPaymentProof, UsdtPaymentProofBinding,
usdt_address, usdt_format_amount, usdt_parse_amount, usdt_parse_payment_request,
usdt_validate_recipient, UsdtBridgeProvider, UsdtDeposit, UsdtDepositAddress,
UsdtDepositClient, UsdtDepositDetail, UsdtDepositNetwork, UsdtDepositOrder, UsdtDepositPage,
UsdtDestination, UsdtError, UsdtOrchestraTransfer, UsdtPaymentProof, UsdtPaymentProofBinding,
UsdtPaymentRequest, UsdtQuote, UsdtTransfer, UsdtTransferStatus, UsdtVerifiedPayment,
UsdtWallet,
};
Expand Down
24 changes: 20 additions & 4 deletions src/modules/usdt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,20 @@ Storage is wallet-specific and must have one owning `UsdtWallet` object. Drop it

Both chain and bundler endpoints must be controlled, credential-free HTTPS URLs; HTTP is accepted only on loopback for fixtures. Provider keys belong on the server. Chain/bundler calls share an 80/minute budget with a burst of 20. Responses are bounded to 2 MiB, except protocol-projected receipts up to 16 MiB. The companion service documents provider requirements, receipt projection and deployment limits.

The outbound bridge API supports Ethereum (30101), Polygon (30109), Plasma (30383) and Stable (30396), alongside direct Arbitrum transfers. Native release flows expose Arbitrum only; bridge routes require explicit service enablement and destination acceptance. Plain deposits on another chain are not automatically forwarded. Recipient validation rejects the destination token and the pinned EntryPoint, paymaster and Simple7702 delegate addresses on every destination. Direct Arbitrum sends also reject the source OFT and helper.
The USDT0 outbound bridge supports Ethereum (30101), Polygon (30109), Plasma (30383) and Stable (30396), alongside direct Arbitrum transfers. An optional credential-free `bridge_url` enables Orchestra quotes and delivery tracking through the companion service. `orchestra_destinations()` returns enabled, available routes: Ethereum, Polygon, Plasma, Base, BNB Smart Chain, Solana and Tron. Callers combine these with their enabled USDT0 destinations and use `usdt_validate_recipient` for the selected network. Plain deposits on another chain are not automatically forwarded. Recipient validation rejects the destination token and the pinned EntryPoint, paymaster and Simple7702 delegate addresses on every destination. Direct Arbitrum sends also reject the source OFT and helper.

Bridge quotes include 10% native messaging-fee headroom and 20% token-conversion headroom, both within the displayed maximum USDT fee. Before signing or rebroadcasting, the stored native fee, helper liquidity and token approval are checked against current requirements without raising approved limits. The service reports OFT/helper execution reverts as sanitized RPC code `3`, which core maps to `UnsupportedRoute` during initial quoting. A reverted fee recheck for an already reviewed bridge quote requires a fresh quote (`QuoteExpired`); insufficient helper liquidity remains `UnsupportedRoute`. Provider outages remain retryable network errors. Delivery checks process up to three transfers concurrently outside the send lock, with a ten-second request budget, even when source recovery fails; failed lookups retain the last known status, while an explicit `INFLIGHT` or `CONFIRMING` update clears a previous needs-attention state.
USDT0 bridge quotes include 10% native messaging-fee headroom and 20% token-conversion headroom, both within the displayed maximum USDT fee. Before signing or rebroadcasting, the stored native fee, helper liquidity and token approval are checked against current requirements without raising approved limits. The service reports OFT/helper execution reverts as sanitized RPC code `3`, which core maps to `UnsupportedRoute` during initial quoting. A reverted fee recheck for an already reviewed bridge quote requires a fresh quote (`QuoteExpired`); insufficient helper liquidity remains `UnsupportedRoute`. Provider outages remain retryable network errors. Delivery checks process up to three transfers concurrently outside the send lock, with a ten-second request budget, even when source recovery fails; failed lookups retain the last known status, while an explicit `INFLIGHT` or `CONFIRMING` update clears a previous needs-attention state.

Bridges use the pinned OFT and TransactionValueHelper with zero account ETH, a finite USDT approval covering principal/fee, and atomic helper-allowance revocation. The deployed helper requires native liquidity and retains behaviors noted in its OpenZeppelin audit; its verified runtime is not the audit-remediated implementation. Source success means bridging, not delivered.
USDT0 bridges use the pinned OFT and TransactionValueHelper with zero account ETH, a finite USDT approval covering principal/fee, and atomic helper-allowance revocation. The deployed helper requires native liquidity and retains behaviors noted in its OpenZeppelin audit; its verified runtime is not the audit-remediated implementation. Source success means bridging, not delivered.

`Pending` means source execution is unresolved; `Failed` means the source payment failed or was proved unexecuted; `Replaced` means another operation consumed its nonce. `Bridging` means source execution succeeded and destination delivery is unresolved. For bridges, `Confirmed` means delivery was reported. `BridgeNeedsAttention` covers retryable delivery problems or missing message evidence; without a GUID, no delivery lookup is possible. `BridgeFailed` means LayerZero reports a burned or skipped message: delivery polling stops, while source transaction, GUID, amount and fees remain visible. Neither bridge status implies a refund. Terminal delivery states survive restart and source-history rescans for the same transaction and GUID.
`Pending` means source execution is unresolved; `Failed` means the source payment failed or was proved unexecuted; `Replaced` means another operation consumed its nonce. `Bridging` means source execution succeeded and destination delivery is unresolved. For bridges, `Confirmed` means delivery was reported. `BridgeNeedsAttention` covers retryable delivery problems or missing message evidence; without a GUID or an Orchestra tracking plan, no delivery lookup is possible. `BridgeFailed` means LayerZero reports a burned or skipped message: delivery polling stops, while source transaction, GUID, amount and fees remain visible. Neither bridge status implies a refund. Terminal delivery states survive restart and source-history rescans for the same transaction and GUID.

LayerZero status must match the operation GUID/pathway before confirmation; blocked delivery remains visible and never triggers an automatic paid retry.

Orchestra delivery and refund attribution trust the configured gateway and provider. Core bounds reported delivery by the sent principal. A reported refund also requires a successful canonical Arbitrum receipt with a matching USDT transfer and at least that amount in net credit to the wallet. Self-transfers and refund transactions already assigned to another payment cannot settle a refund. One refund transaction can settle at most one payment, even if a provider batches several refunds.

These checks prove receipt of funds, not which Orchestra order caused it. The current gateway contract does not pin a refund sender; an unrelated incoming payment cannot be distinguished from a refund without trusting the order association. `BridgeRefunded` retains that association across restart and receipt pruning. Funding and refund reorgs reopen the affected payment within the history revisit window.

Bridge history preserves the saved receiving amount or recovers the signed `minAmountLD` from calldata until a matching `OFTSent` event supplies the source-confirmed amount. The fallback is a minimum receiving amount, not proof of destination delivery.

RPC providers see queried addresses. Delivery checks use `bitkit_getBridgeMessages([sourceTransactionHash])` on the existing chain-service endpoint. The service queries LayerZero Scan without forwarding device headers, projects only message identity/pathway/status fields, and applies its shared request and response limits. LayerZero sees the service IP and the transaction hash; the service still sees the requesting device. Manually opening LayerZero Scan from transaction details connects the browser directly. No delivery requests are made for Arbitrum-only transfers.
Expand Down Expand Up @@ -102,6 +106,18 @@ Refunds require a user-approved address on the source network and provider eligi

Use a matching [bitkit-usdt-service](https://github.com/synonymdev/bitkit-usdt-service) deployment with `ORCHESTRA_API_KEY` and `ORCHESTRA_DEPOSIT_NETWORKS`. Enable each source only after funded delivery and provider recovery acceptance. See [Orchestra deposit addresses](https://docs.flashnet.xyz/orchestra/deposit-addresses).

### Orchestra outbound quotes and delivery

Core compares usable quotes concurrently and selects the highest estimated destination receipt per maximum total source debit, including the USDT paymaster fee. Ties prefer USDT0. An unavailable, expired or unaffordable provider does not disqualify the other. Direct Arbitrum and USDT0-only Stable transfers keep their existing execution paths. Provider selection is frozen in the quote; send and recovery never choose another route.

The entered amount is the source principal. Orchestra quotes are exact-input: routing costs are deducted from that principal, and the source transaction fee is additional. `received_amount` is an estimate, not a guaranteed exact output. Show the provider, estimated receipt, included routing cost, maximum source fee and maximum total debit before approval. All public amounts remain six-decimal USDT, including BSC's 18-decimal token; destination amounts are rounded down for display. Orchestra can use intermediate assets internally, but Core only signs a USDT transfer on Arbitrum. No destination gas token is requested from the sender.

The service returns a single-use quote funding address and a signed tracking ticket. Core persists both with the signed operation before broadcast. Successful funding becomes `Bridging`; `Confirmed` requires provider-reported delivery bound to that quote, source transaction and destination. This trusts Orchestra's delivery report, not an independently verified destination-chain proof. The original source transaction, gas fee, recipient and provider remain in activity. Delivery metadata includes the destination transaction or a refund transaction and amount. `BridgeRefunded` requires a successful canonical Arbitrum USDT receipt paying the refund to this account; it is not destination delivery.

Quotes can expire before an already-submitted operation executes. Never start another payment based on a timeout or expired quote: the original operation may still execute, and Orchestra may need to recover late funding. Tracking tickets remain usable after expiry and route disablement. Pending delivery retains its signed funding plan beyond the source-history revisit window. Seed-only history cannot reconstruct the provider ticket or external destination; app recovery must preserve these application records alongside the seed. The service signing secret must remain available for ticket verification.

Quoting shares the source account, destination address, network and amount with Orchestra even when USDT0 is ultimately selected. Only the selected quote is funded. No private key or mnemonic leaves Core.

## Request-bound payment proofs

`create_payment_proof` signs an executed direct Arbitrum payment using Paykit's `erc20-transfer-eip712` profile. The binding identifies the authenticated payer and payee, the app owning the accepted endpoint, request, reference, billing period and selected conversion quote. Persist this immutable binding and the quote/payment ID before `send`. Retry proof creation and delivery independently after execution; neither action sends funds. A pending payment returns `None`.
Expand Down
11 changes: 8 additions & 3 deletions src/modules/usdt/deposits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@ impl UsdtDepositNetwork {
}
}

fn validate_source_address(value: &str, network: UsdtDepositNetwork) -> Result<(), UsdtError> {
pub(super) fn validate_source_address(
value: &str,
network: UsdtDepositNetwork,
) -> Result<(), UsdtError> {
if network == UsdtDepositNetwork::Tron {
if value.len() != 34 || !value.starts_with('T') || !value.is_ascii() || value == TRON_USDT {
return Err(UsdtError::InvalidAddress);
Expand Down Expand Up @@ -380,12 +383,14 @@ fn deposit_limit<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<St
.map(str::to_owned))
}

fn number<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
pub(super) fn number<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
fn optional_number<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<u64>, D::Error> {
pub(super) fn optional_number<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<u64>, D::Error> {
Option::<String>::deserialize(deserializer)?
.map(|v| v.parse().map_err(serde::de::Error::custom))
.transpose()
Expand Down
1 change: 1 addition & 0 deletions src/modules/usdt/deposits/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fn deposit_addresses_and_transport_reject_wrong_networks() {
assert!(validate_source_address(
&super::super::UsdtDestination::Ethereum
.token()
.unwrap()
.to_checksum(None),
UsdtDepositNetwork::Ethereum
)
Expand Down
Loading
Loading