diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index c6a73db..3fb29ab 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -2563,6 +2563,204 @@ public func FfiConverterTypeUrDecoder_lower(_ value: UrDecoder) -> UnsafeMutable +public protocol UsdtDepositClientProtocol: AnyObject, Sendable { + + func detail(depositId: String, before: UInt64, mnemonic: String, passphrase: String?) async throws -> UsdtDepositDetail + + func history(before: UInt64, offset: UInt32, mnemonic: String, passphrase: String?) async throws -> UsdtDepositPage + + func networks() async throws -> [UsdtDepositNetwork] + + func receive(network: UsdtDepositNetwork, amount: UInt64, mnemonic: String, passphrase: String?) async throws -> UsdtDepositAddress + +} +open class UsdtDepositClient: UsdtDepositClientProtocol, @unchecked Sendable { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noPointer: NoPointer) { + self.pointer = nil + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_bitkitcore_fn_clone_usdtdepositclient(self.pointer, $0) } + } +public convenience init(address: String, serviceUrl: String)throws { + let pointer = + try rustCallWithError(FfiConverterTypeUsdtError_lift) { + uniffi_bitkitcore_fn_constructor_usdtdepositclient_new( + FfiConverterString.lower(address), + FfiConverterString.lower(serviceUrl),$0 + ) +} + self.init(unsafeFromRawPointer: pointer) +} + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_bitkitcore_fn_free_usdtdepositclient(pointer, $0) } + } + + + + +open func detail(depositId: String, before: UInt64, mnemonic: String, passphrase: String?)async throws -> UsdtDepositDetail { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_method_usdtdepositclient_detail( + self.uniffiClonePointer(), + FfiConverterString.lower(depositId),FfiConverterUInt64.lower(before),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(passphrase) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeUsdtDepositDetail_lift, + errorHandler: FfiConverterTypeUsdtError_lift + ) +} + +open func history(before: UInt64, offset: UInt32, mnemonic: String, passphrase: String?)async throws -> UsdtDepositPage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_method_usdtdepositclient_history( + self.uniffiClonePointer(), + FfiConverterUInt64.lower(before),FfiConverterUInt32.lower(offset),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(passphrase) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeUsdtDepositPage_lift, + errorHandler: FfiConverterTypeUsdtError_lift + ) +} + +open func networks()async throws -> [UsdtDepositNetwork] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_method_usdtdepositclient_networks( + self.uniffiClonePointer() + + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeUsdtDepositNetwork.lift, + errorHandler: FfiConverterTypeUsdtError_lift + ) +} + +open func receive(network: UsdtDepositNetwork, amount: UInt64, mnemonic: String, passphrase: String?)async throws -> UsdtDepositAddress { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_method_usdtdepositclient_receive( + self.uniffiClonePointer(), + FfiConverterTypeUsdtDepositNetwork_lower(network),FfiConverterUInt64.lower(amount),FfiConverterString.lower(mnemonic),FfiConverterOptionString.lower(passphrase) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeUsdtDepositAddress_lift, + errorHandler: FfiConverterTypeUsdtError_lift + ) +} + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUsdtDepositClient: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = UsdtDepositClient + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> UsdtDepositClient { + return UsdtDepositClient(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: UsdtDepositClient) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositClient { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: UsdtDepositClient, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositClient_lift(_ pointer: UnsafeMutableRawPointer) throws -> UsdtDepositClient { + return try FfiConverterTypeUsdtDepositClient.lift(pointer) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositClient_lower(_ value: UsdtDepositClient) -> UnsafeMutableRawPointer { + return FfiConverterTypeUsdtDepositClient.lower(value) +} + + + + + + public protocol UsdtWalletProtocol: AnyObject, Sendable { func balance() async throws -> UInt64 @@ -15814,31 +16012,548 @@ extension TxOutput: Equatable, Hashable { } } -extension TxOutput: Codable {} +extension TxOutput: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeTxOutput: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TxOutput { + return + try TxOutput( + scriptpubkey: FfiConverterString.read(from: &buf), + scriptpubkeyType: FfiConverterOptionString.read(from: &buf), + scriptpubkeyAddress: FfiConverterOptionString.read(from: &buf), + value: FfiConverterInt64.read(from: &buf), + n: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: TxOutput, into buf: inout [UInt8]) { + FfiConverterString.write(value.scriptpubkey, into: &buf) + FfiConverterOptionString.write(value.scriptpubkeyType, into: &buf) + FfiConverterOptionString.write(value.scriptpubkeyAddress, into: &buf) + FfiConverterInt64.write(value.value, into: &buf) + FfiConverterUInt32.write(value.n, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeTxOutput_lift(_ buf: RustBuffer) throws -> TxOutput { + return try FfiConverterTypeTxOutput.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeTxOutput_lower(_ value: TxOutput) -> RustBuffer { + return FfiConverterTypeTxOutput.lower(value) +} + + +/** + * Current state after accepting a scanned UR frame. + */ +public struct UrDecoderStatus { + /** + * Estimated completion from 0.0 through 1.0. + */ + public var progress: Double + /** + * Fountain source-fragment count, or 1 for a single-part UR. + */ + public var fragmentCount: UInt32 + /** + * Present once the complete message has been decoded. + */ + public var payload: UrPayload? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Estimated completion from 0.0 through 1.0. + */progress: Double, + /** + * Fountain source-fragment count, or 1 for a single-part UR. + */fragmentCount: UInt32, + /** + * Present once the complete message has been decoded. + */payload: UrPayload?) { + self.progress = progress + self.fragmentCount = fragmentCount + self.payload = payload + } +} + +#if compiler(>=6) +extension UrDecoderStatus: Sendable {} +#endif + + +extension UrDecoderStatus: Equatable, Hashable { + public static func ==(lhs: UrDecoderStatus, rhs: UrDecoderStatus) -> Bool { + if lhs.progress != rhs.progress { + return false + } + if lhs.fragmentCount != rhs.fragmentCount { + return false + } + if lhs.payload != rhs.payload { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(progress) + hasher.combine(fragmentCount) + hasher.combine(payload) + } +} + +extension UrDecoderStatus: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUrDecoderStatus: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrDecoderStatus { + return + try UrDecoderStatus( + progress: FfiConverterDouble.read(from: &buf), + fragmentCount: FfiConverterUInt32.read(from: &buf), + payload: FfiConverterOptionTypeUrPayload.read(from: &buf) + ) + } + + public static func write(_ value: UrDecoderStatus, into buf: inout [UInt8]) { + FfiConverterDouble.write(value.progress, into: &buf) + FfiConverterUInt32.write(value.fragmentCount, into: &buf) + FfiConverterOptionTypeUrPayload.write(value.payload, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUrDecoderStatus_lift(_ buf: RustBuffer) throws -> UrDecoderStatus { + return try FfiConverterTypeUrDecoderStatus.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUrDecoderStatus_lower(_ value: UrDecoderStatus) -> RustBuffer { + return FfiConverterTypeUrDecoderStatus.lower(value) +} + + +public struct UsdtDeposit { + public var id: String + public var network: String + public var asset: String + public var amount: UInt64? + public var sourceTx: String + public var status: String + public var code: String? + public var refundTx: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, network: String, asset: String, amount: UInt64?, sourceTx: String, status: String, code: String?, refundTx: String?) { + self.id = id + self.network = network + self.asset = asset + self.amount = amount + self.sourceTx = sourceTx + self.status = status + self.code = code + self.refundTx = refundTx + } +} + +#if compiler(>=6) +extension UsdtDeposit: Sendable {} +#endif + + +extension UsdtDeposit: Equatable, Hashable { + public static func ==(lhs: UsdtDeposit, rhs: UsdtDeposit) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.network != rhs.network { + return false + } + if lhs.asset != rhs.asset { + return false + } + if lhs.amount != rhs.amount { + return false + } + if lhs.sourceTx != rhs.sourceTx { + return false + } + if lhs.status != rhs.status { + return false + } + if lhs.code != rhs.code { + return false + } + if lhs.refundTx != rhs.refundTx { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(network) + hasher.combine(asset) + hasher.combine(amount) + hasher.combine(sourceTx) + hasher.combine(status) + hasher.combine(code) + hasher.combine(refundTx) + } +} + +extension UsdtDeposit: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUsdtDeposit: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDeposit { + return + try UsdtDeposit( + id: FfiConverterString.read(from: &buf), + network: FfiConverterString.read(from: &buf), + asset: FfiConverterString.read(from: &buf), + amount: FfiConverterOptionUInt64.read(from: &buf), + sourceTx: FfiConverterString.read(from: &buf), + status: FfiConverterString.read(from: &buf), + code: FfiConverterOptionString.read(from: &buf), + refundTx: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: UsdtDeposit, into buf: inout [UInt8]) { + FfiConverterString.write(value.id, into: &buf) + FfiConverterString.write(value.network, into: &buf) + FfiConverterString.write(value.asset, into: &buf) + FfiConverterOptionUInt64.write(value.amount, into: &buf) + FfiConverterString.write(value.sourceTx, into: &buf) + FfiConverterString.write(value.status, into: &buf) + FfiConverterOptionString.write(value.code, into: &buf) + FfiConverterOptionString.write(value.refundTx, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDeposit_lift(_ buf: RustBuffer) throws -> UsdtDeposit { + return try FfiConverterTypeUsdtDeposit.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDeposit_lower(_ value: UsdtDeposit) -> RustBuffer { + return FfiConverterTypeUsdtDeposit.lower(value) +} + + +public struct UsdtDepositAddress { + public var network: UsdtDepositNetwork + public var address: String + public var recipient: String + public var amount: UInt64 + public var estimatedReceived: UInt64 + public var minUsdCents: String? + public var maxUsdCents: String? + public var uri: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(network: UsdtDepositNetwork, address: String, recipient: String, amount: UInt64, estimatedReceived: UInt64, minUsdCents: String?, maxUsdCents: String?, uri: String) { + self.network = network + self.address = address + self.recipient = recipient + self.amount = amount + self.estimatedReceived = estimatedReceived + self.minUsdCents = minUsdCents + self.maxUsdCents = maxUsdCents + self.uri = uri + } +} + +#if compiler(>=6) +extension UsdtDepositAddress: Sendable {} +#endif + + +extension UsdtDepositAddress: Equatable, Hashable { + public static func ==(lhs: UsdtDepositAddress, rhs: UsdtDepositAddress) -> Bool { + if lhs.network != rhs.network { + return false + } + if lhs.address != rhs.address { + return false + } + if lhs.recipient != rhs.recipient { + return false + } + if lhs.amount != rhs.amount { + return false + } + if lhs.estimatedReceived != rhs.estimatedReceived { + return false + } + if lhs.minUsdCents != rhs.minUsdCents { + return false + } + if lhs.maxUsdCents != rhs.maxUsdCents { + return false + } + if lhs.uri != rhs.uri { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(network) + hasher.combine(address) + hasher.combine(recipient) + hasher.combine(amount) + hasher.combine(estimatedReceived) + hasher.combine(minUsdCents) + hasher.combine(maxUsdCents) + hasher.combine(uri) + } +} + +extension UsdtDepositAddress: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUsdtDepositAddress: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositAddress { + return + try UsdtDepositAddress( + network: FfiConverterTypeUsdtDepositNetwork.read(from: &buf), + address: FfiConverterString.read(from: &buf), + recipient: FfiConverterString.read(from: &buf), + amount: FfiConverterUInt64.read(from: &buf), + estimatedReceived: FfiConverterUInt64.read(from: &buf), + minUsdCents: FfiConverterOptionString.read(from: &buf), + maxUsdCents: FfiConverterOptionString.read(from: &buf), + uri: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: UsdtDepositAddress, into buf: inout [UInt8]) { + FfiConverterTypeUsdtDepositNetwork.write(value.network, into: &buf) + FfiConverterString.write(value.address, into: &buf) + FfiConverterString.write(value.recipient, into: &buf) + FfiConverterUInt64.write(value.amount, into: &buf) + FfiConverterUInt64.write(value.estimatedReceived, into: &buf) + FfiConverterOptionString.write(value.minUsdCents, into: &buf) + FfiConverterOptionString.write(value.maxUsdCents, into: &buf) + FfiConverterString.write(value.uri, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositAddress_lift(_ buf: RustBuffer) throws -> UsdtDepositAddress { + return try FfiConverterTypeUsdtDepositAddress.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositAddress_lower(_ value: UsdtDepositAddress) -> RustBuffer { + return FfiConverterTypeUsdtDepositAddress.lower(value) +} + + +public struct UsdtDepositDetail { + public var deposit: UsdtDeposit + public var order: UsdtDepositOrder? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(deposit: UsdtDeposit, order: UsdtDepositOrder?) { + self.deposit = deposit + self.order = order + } +} + +#if compiler(>=6) +extension UsdtDepositDetail: Sendable {} +#endif + + +extension UsdtDepositDetail: Equatable, Hashable { + public static func ==(lhs: UsdtDepositDetail, rhs: UsdtDepositDetail) -> Bool { + if lhs.deposit != rhs.deposit { + return false + } + if lhs.order != rhs.order { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(deposit) + hasher.combine(order) + } +} + +extension UsdtDepositDetail: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUsdtDepositDetail: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositDetail { + return + try UsdtDepositDetail( + deposit: FfiConverterTypeUsdtDeposit.read(from: &buf), + order: FfiConverterOptionTypeUsdtDepositOrder.read(from: &buf) + ) + } + + public static func write(_ value: UsdtDepositDetail, into buf: inout [UInt8]) { + FfiConverterTypeUsdtDeposit.write(value.deposit, into: &buf) + FfiConverterOptionTypeUsdtDepositOrder.write(value.order, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositDetail_lift(_ buf: RustBuffer) throws -> UsdtDepositDetail { + return try FfiConverterTypeUsdtDepositDetail.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositDetail_lower(_ value: UsdtDepositDetail) -> RustBuffer { + return FfiConverterTypeUsdtDepositDetail.lower(value) +} + + +public struct UsdtDepositOrder { + public var status: String + public var amountIn: UInt64? + public var amountOut: UInt64? + public var destinationTx: String? + public var refundTx: String? + public var code: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(status: String, amountIn: UInt64?, amountOut: UInt64?, destinationTx: String?, refundTx: String?, code: String?) { + self.status = status + self.amountIn = amountIn + self.amountOut = amountOut + self.destinationTx = destinationTx + self.refundTx = refundTx + self.code = code + } +} + +#if compiler(>=6) +extension UsdtDepositOrder: Sendable {} +#endif + + +extension UsdtDepositOrder: Equatable, Hashable { + public static func ==(lhs: UsdtDepositOrder, rhs: UsdtDepositOrder) -> Bool { + if lhs.status != rhs.status { + return false + } + if lhs.amountIn != rhs.amountIn { + return false + } + if lhs.amountOut != rhs.amountOut { + return false + } + if lhs.destinationTx != rhs.destinationTx { + return false + } + if lhs.refundTx != rhs.refundTx { + return false + } + if lhs.code != rhs.code { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(status) + hasher.combine(amountIn) + hasher.combine(amountOut) + hasher.combine(destinationTx) + hasher.combine(refundTx) + hasher.combine(code) + } +} + +extension UsdtDepositOrder: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTxOutput: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TxOutput { +public struct FfiConverterTypeUsdtDepositOrder: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositOrder { return - try TxOutput( - scriptpubkey: FfiConverterString.read(from: &buf), - scriptpubkeyType: FfiConverterOptionString.read(from: &buf), - scriptpubkeyAddress: FfiConverterOptionString.read(from: &buf), - value: FfiConverterInt64.read(from: &buf), - n: FfiConverterUInt32.read(from: &buf) + try UsdtDepositOrder( + status: FfiConverterString.read(from: &buf), + amountIn: FfiConverterOptionUInt64.read(from: &buf), + amountOut: FfiConverterOptionUInt64.read(from: &buf), + destinationTx: FfiConverterOptionString.read(from: &buf), + refundTx: FfiConverterOptionString.read(from: &buf), + code: FfiConverterOptionString.read(from: &buf) ) } - public static func write(_ value: TxOutput, into buf: inout [UInt8]) { - FfiConverterString.write(value.scriptpubkey, into: &buf) - FfiConverterOptionString.write(value.scriptpubkeyType, into: &buf) - FfiConverterOptionString.write(value.scriptpubkeyAddress, into: &buf) - FfiConverterInt64.write(value.value, into: &buf) - FfiConverterUInt32.write(value.n, into: &buf) + public static func write(_ value: UsdtDepositOrder, into buf: inout [UInt8]) { + FfiConverterString.write(value.status, into: &buf) + FfiConverterOptionUInt64.write(value.amountIn, into: &buf) + FfiConverterOptionUInt64.write(value.amountOut, into: &buf) + FfiConverterOptionString.write(value.destinationTx, into: &buf) + FfiConverterOptionString.write(value.refundTx, into: &buf) + FfiConverterOptionString.write(value.code, into: &buf) } } @@ -15846,100 +16561,79 @@ public struct FfiConverterTypeTxOutput: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTxOutput_lift(_ buf: RustBuffer) throws -> TxOutput { - return try FfiConverterTypeTxOutput.lift(buf) +public func FfiConverterTypeUsdtDepositOrder_lift(_ buf: RustBuffer) throws -> UsdtDepositOrder { + return try FfiConverterTypeUsdtDepositOrder.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTxOutput_lower(_ value: TxOutput) -> RustBuffer { - return FfiConverterTypeTxOutput.lower(value) +public func FfiConverterTypeUsdtDepositOrder_lower(_ value: UsdtDepositOrder) -> RustBuffer { + return FfiConverterTypeUsdtDepositOrder.lower(value) } -/** - * Current state after accepting a scanned UR frame. - */ -public struct UrDecoderStatus { - /** - * Estimated completion from 0.0 through 1.0. - */ - public var progress: Double - /** - * Fountain source-fragment count, or 1 for a single-part UR. - */ - public var fragmentCount: UInt32 - /** - * Present once the complete message has been decoded. - */ - public var payload: UrPayload? +public struct UsdtDepositPage { + public var deposits: [UsdtDeposit] + public var nextBefore: UInt64? + public var nextOffset: UInt32 // Default memberwise initializers are never public by default, so we // declare one manually. - public init( - /** - * Estimated completion from 0.0 through 1.0. - */progress: Double, - /** - * Fountain source-fragment count, or 1 for a single-part UR. - */fragmentCount: UInt32, - /** - * Present once the complete message has been decoded. - */payload: UrPayload?) { - self.progress = progress - self.fragmentCount = fragmentCount - self.payload = payload + public init(deposits: [UsdtDeposit], nextBefore: UInt64?, nextOffset: UInt32) { + self.deposits = deposits + self.nextBefore = nextBefore + self.nextOffset = nextOffset } } #if compiler(>=6) -extension UrDecoderStatus: Sendable {} +extension UsdtDepositPage: Sendable {} #endif -extension UrDecoderStatus: Equatable, Hashable { - public static func ==(lhs: UrDecoderStatus, rhs: UrDecoderStatus) -> Bool { - if lhs.progress != rhs.progress { +extension UsdtDepositPage: Equatable, Hashable { + public static func ==(lhs: UsdtDepositPage, rhs: UsdtDepositPage) -> Bool { + if lhs.deposits != rhs.deposits { return false } - if lhs.fragmentCount != rhs.fragmentCount { + if lhs.nextBefore != rhs.nextBefore { return false } - if lhs.payload != rhs.payload { + if lhs.nextOffset != rhs.nextOffset { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(progress) - hasher.combine(fragmentCount) - hasher.combine(payload) + hasher.combine(deposits) + hasher.combine(nextBefore) + hasher.combine(nextOffset) } } -extension UrDecoderStatus: Codable {} +extension UsdtDepositPage: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeUrDecoderStatus: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrDecoderStatus { +public struct FfiConverterTypeUsdtDepositPage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositPage { return - try UrDecoderStatus( - progress: FfiConverterDouble.read(from: &buf), - fragmentCount: FfiConverterUInt32.read(from: &buf), - payload: FfiConverterOptionTypeUrPayload.read(from: &buf) + try UsdtDepositPage( + deposits: FfiConverterSequenceTypeUsdtDeposit.read(from: &buf), + nextBefore: FfiConverterOptionUInt64.read(from: &buf), + nextOffset: FfiConverterUInt32.read(from: &buf) ) } - public static func write(_ value: UrDecoderStatus, into buf: inout [UInt8]) { - FfiConverterDouble.write(value.progress, into: &buf) - FfiConverterUInt32.write(value.fragmentCount, into: &buf) - FfiConverterOptionTypeUrPayload.write(value.payload, into: &buf) + public static func write(_ value: UsdtDepositPage, into buf: inout [UInt8]) { + FfiConverterSequenceTypeUsdtDeposit.write(value.deposits, into: &buf) + FfiConverterOptionUInt64.write(value.nextBefore, into: &buf) + FfiConverterUInt32.write(value.nextOffset, into: &buf) } } @@ -15947,15 +16641,15 @@ public struct FfiConverterTypeUrDecoderStatus: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrDecoderStatus_lift(_ buf: RustBuffer) throws -> UrDecoderStatus { - return try FfiConverterTypeUrDecoderStatus.lift(buf) +public func FfiConverterTypeUsdtDepositPage_lift(_ buf: RustBuffer) throws -> UsdtDepositPage { + return try FfiConverterTypeUsdtDepositPage.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrDecoderStatus_lower(_ value: UrDecoderStatus) -> RustBuffer { - return FfiConverterTypeUrDecoderStatus.lower(value) +public func FfiConverterTypeUsdtDepositPage_lower(_ value: UsdtDepositPage) -> RustBuffer { + return FfiConverterTypeUsdtDepositPage.lower(value) } @@ -23439,6 +24133,106 @@ extension UrPayload: Codable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum UsdtDepositNetwork { + + case ethereum + case solana + case polygon + case optimism + case base + case avalanche +} + + +#if compiler(>=6) +extension UsdtDepositNetwork: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUsdtDepositNetwork: FfiConverterRustBuffer { + typealias SwiftType = UsdtDepositNetwork + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtDepositNetwork { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .ethereum + + case 2: return .solana + + case 3: return .polygon + + case 4: return .optimism + + case 5: return .base + + case 6: return .avalanche + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: UsdtDepositNetwork, into buf: inout [UInt8]) { + switch value { + + + case .ethereum: + writeInt(&buf, Int32(1)) + + + case .solana: + writeInt(&buf, Int32(2)) + + + case .polygon: + writeInt(&buf, Int32(3)) + + + case .optimism: + writeInt(&buf, Int32(4)) + + + case .base: + writeInt(&buf, Int32(5)) + + + case .avalanche: + writeInt(&buf, Int32(6)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositNetwork_lift(_ buf: RustBuffer) throws -> UsdtDepositNetwork { + return try FfiConverterTypeUsdtDepositNetwork.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeUsdtDepositNetwork_lower(_ value: UsdtDepositNetwork) -> RustBuffer { + return FfiConverterTypeUsdtDepositNetwork.lower(value) +} + + +extension UsdtDepositNetwork: Equatable, Hashable {} + +extension UsdtDepositNetwork: Codable {} + + + + + + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. @@ -23541,11 +24335,17 @@ public enum UsdtError: Swift.Error { case InvalidAddress case WrongNetwork case InvalidCredentials + case ClockSkew case UnsupportedDelegation case InsufficientBalance case QuoteExpired case PendingTransfer case UnsupportedRoute + case DepositNeedsAttention + case DepositNotFound + case DepositAuthorizationRejected + case DepositAmountOutOfRange(minUsdCents: String?, maxUsdCents: String? + ) case NotConfigured case NetworkUnavailable case RateLimited @@ -23575,22 +24375,30 @@ public struct FfiConverterTypeUsdtError: FfiConverterRustBuffer { case 2: return .InvalidAddress case 3: return .WrongNetwork case 4: return .InvalidCredentials - case 5: return .UnsupportedDelegation - case 6: return .InsufficientBalance - case 7: return .QuoteExpired - case 8: return .PendingTransfer - case 9: return .UnsupportedRoute - case 10: return .NotConfigured - case 11: return .NetworkUnavailable - case 12: return .RateLimited - case 13: return .LogRangeTooLarge - case 14: return .TransactionRejected( + case 5: return .ClockSkew + case 6: return .UnsupportedDelegation + case 7: return .InsufficientBalance + case 8: return .QuoteExpired + case 9: return .PendingTransfer + case 10: return .UnsupportedRoute + case 11: return .DepositNeedsAttention + case 12: return .DepositNotFound + case 13: return .DepositAuthorizationRejected + case 14: return .DepositAmountOutOfRange( + minUsdCents: try FfiConverterOptionString.read(from: &buf), + maxUsdCents: try FfiConverterOptionString.read(from: &buf) + ) + case 15: return .NotConfigured + case 16: return .NetworkUnavailable + case 17: return .RateLimited + case 18: return .LogRangeTooLarge + case 19: return .TransactionRejected( reason: try FfiConverterString.read(from: &buf) ) - case 15: return .Storage( + case 20: return .Storage( reason: try FfiConverterString.read(from: &buf) ) - case 16: return .InvalidResponse + case 21: return .InvalidResponse default: throw UniffiInternalError.unexpectedEnumCase } @@ -23619,54 +24427,76 @@ public struct FfiConverterTypeUsdtError: FfiConverterRustBuffer { writeInt(&buf, Int32(4)) - case .UnsupportedDelegation: + case .ClockSkew: writeInt(&buf, Int32(5)) - case .InsufficientBalance: + case .UnsupportedDelegation: writeInt(&buf, Int32(6)) - case .QuoteExpired: + case .InsufficientBalance: writeInt(&buf, Int32(7)) - case .PendingTransfer: + case .QuoteExpired: writeInt(&buf, Int32(8)) - case .UnsupportedRoute: + case .PendingTransfer: writeInt(&buf, Int32(9)) - case .NotConfigured: + case .UnsupportedRoute: writeInt(&buf, Int32(10)) - case .NetworkUnavailable: + case .DepositNeedsAttention: writeInt(&buf, Int32(11)) - case .RateLimited: + case .DepositNotFound: writeInt(&buf, Int32(12)) - case .LogRangeTooLarge: + case .DepositAuthorizationRejected: writeInt(&buf, Int32(13)) - case let .TransactionRejected(reason): + case let .DepositAmountOutOfRange(minUsdCents,maxUsdCents): writeInt(&buf, Int32(14)) + FfiConverterOptionString.write(minUsdCents, into: &buf) + FfiConverterOptionString.write(maxUsdCents, into: &buf) + + + case .NotConfigured: + writeInt(&buf, Int32(15)) + + + case .NetworkUnavailable: + writeInt(&buf, Int32(16)) + + + case .RateLimited: + writeInt(&buf, Int32(17)) + + + case .LogRangeTooLarge: + writeInt(&buf, Int32(18)) + + + case let .TransactionRejected(reason): + writeInt(&buf, Int32(19)) FfiConverterString.write(reason, into: &buf) case let .Storage(reason): - writeInt(&buf, Int32(15)) + writeInt(&buf, Int32(20)) FfiConverterString.write(reason, into: &buf) case .InvalidResponse: - writeInt(&buf, Int32(16)) + writeInt(&buf, Int32(21)) } } @@ -24956,6 +25786,30 @@ fileprivate struct FfiConverterOptionTypeTrezorFeatures: FfiConverterRustBuffer } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeUsdtDepositOrder: FfiConverterRustBuffer { + typealias SwiftType = UsdtDepositOrder? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeUsdtDepositOrder.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeUsdtDepositOrder.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -26335,6 +27189,31 @@ fileprivate struct FfiConverterSequenceTypeTxOutput: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeUsdtDeposit: FfiConverterRustBuffer { + typealias SwiftType = [UsdtDeposit] + + public static func write(_ value: [UsdtDeposit], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeUsdtDeposit.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UsdtDeposit] { + let len: Int32 = try readInt(&buf) + var seq = [UsdtDeposit]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeUsdtDeposit.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -26485,6 +27364,31 @@ fileprivate struct FfiConverterSequenceTypeHardwareWalletTransport: FfiConverter } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeUsdtDepositNetwork: FfiConverterRustBuffer { + typealias SwiftType = [UsdtDepositNetwork] + + public static func write(_ value: [UsdtDepositNetwork], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeUsdtDepositNetwork.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UsdtDepositNetwork] { + let len: Int32 = try readInt(&buf) + var seq = [UsdtDepositNetwork]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeUsdtDepositNetwork.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -29916,6 +30820,18 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_method_urdecoder_reset() != 6027) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_method_usdtdepositclient_detail() != 48293) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_method_usdtdepositclient_history() != 60740) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_method_usdtdepositclient_networks() != 27890) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_method_usdtdepositclient_receive() != 27375) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_method_usdtwallet_balance() != 12328) { return InitializationResult.apiChecksumMismatch } @@ -29946,6 +30862,9 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_constructor_urdecoder_new() != 23014) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_constructor_usdtdepositclient_new() != 44626) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_constructor_usdtwallet_new() != 62148) { return InitializationResult.apiChecksumMismatch } diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index 7ebda6a..fc7c723 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -660,6 +660,41 @@ RustBuffer uniffi_bitkitcore_fn_method_urdecoder_receive(void*_Nonnull ptr, Rust void uniffi_bitkitcore_fn_method_urdecoder_reset(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_USDTDEPOSITCLIENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_USDTDEPOSITCLIENT +void*_Nonnull uniffi_bitkitcore_fn_clone_usdtdepositclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_USDTDEPOSITCLIENT +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FREE_USDTDEPOSITCLIENT +void uniffi_bitkitcore_fn_free_usdtdepositclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CONSTRUCTOR_USDTDEPOSITCLIENT_NEW +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CONSTRUCTOR_USDTDEPOSITCLIENT_NEW +void*_Nonnull uniffi_bitkitcore_fn_constructor_usdtdepositclient_new(RustBuffer address, RustBuffer service_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_DETAIL +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_DETAIL +uint64_t uniffi_bitkitcore_fn_method_usdtdepositclient_detail(void*_Nonnull ptr, RustBuffer deposit_id, uint64_t before, RustBuffer mnemonic, RustBuffer passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_HISTORY +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_HISTORY +uint64_t uniffi_bitkitcore_fn_method_usdtdepositclient_history(void*_Nonnull ptr, uint64_t before, uint32_t offset, RustBuffer mnemonic, RustBuffer passphrase +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_NETWORKS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_NETWORKS +uint64_t uniffi_bitkitcore_fn_method_usdtdepositclient_networks(void*_Nonnull ptr +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_RECEIVE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_METHOD_USDTDEPOSITCLIENT_RECEIVE +uint64_t uniffi_bitkitcore_fn_method_usdtdepositclient_receive(void*_Nonnull ptr, RustBuffer network, uint64_t amount, RustBuffer mnemonic, RustBuffer passphrase +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_USDTWALLET #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_CLONE_USDTWALLET void*_Nonnull uniffi_bitkitcore_fn_clone_usdtwallet(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status @@ -3466,6 +3501,30 @@ uint16_t uniffi_bitkitcore_checksum_method_urdecoder_receive(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_URDECODER_RESET uint16_t uniffi_bitkitcore_checksum_method_urdecoder_reset(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_DETAIL +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_DETAIL +uint16_t uniffi_bitkitcore_checksum_method_usdtdepositclient_detail(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_HISTORY +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_HISTORY +uint16_t uniffi_bitkitcore_checksum_method_usdtdepositclient_history(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_NETWORKS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_NETWORKS +uint16_t uniffi_bitkitcore_checksum_method_usdtdepositclient_networks(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_RECEIVE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTDEPOSITCLIENT_RECEIVE +uint16_t uniffi_bitkitcore_checksum_method_usdtdepositclient_receive(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_METHOD_USDTWALLET_BALANCE @@ -3526,6 +3585,12 @@ uint16_t uniffi_bitkitcore_checksum_method_usdtwallet_sync_history(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_CONSTRUCTOR_URDECODER_NEW uint16_t uniffi_bitkitcore_checksum_constructor_urdecoder_new(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_CONSTRUCTOR_USDTDEPOSITCLIENT_NEW +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_CONSTRUCTOR_USDTDEPOSITCLIENT_NEW +uint16_t uniffi_bitkitcore_checksum_constructor_usdtdepositclient_new(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_CONSTRUCTOR_USDTWALLET_NEW diff --git a/src/lib.rs b/src/lib.rs index 985a20e..9266c44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, - UsdtDestination, UsdtError, UsdtPaymentRequest, UsdtQuote, UsdtTransfer, UsdtTransferStatus, - UsdtWallet, + usdt_address, usdt_format_amount, usdt_parse_amount, usdt_parse_payment_request, UsdtDeposit, + UsdtDepositAddress, UsdtDepositClient, UsdtDepositDetail, UsdtDepositNetwork, UsdtDepositOrder, + UsdtDepositPage, UsdtDestination, UsdtError, UsdtPaymentRequest, UsdtQuote, UsdtTransfer, + UsdtTransferStatus, UsdtWallet, }; use bip39::Mnemonic; diff --git a/src/modules/usdt/README.md b/src/modules/usdt/README.md index c91e8b3..a40b635 100644 --- a/src/modules/usdt/README.md +++ b/src/modules/usdt/README.md @@ -85,3 +85,13 @@ Build iOS and Android sequentially with the repository scripts; Android temporar - [LayerZero message statuses](https://docs.layerzero.network/v2/tools/layerzeroscan/mainnet/messages/get-messagesstatus) Destination token addresses follow the official USDT0 ecosystem listings for [Polygon](https://usdt0.to/ecosystem/polygon), [Plasma](https://usdt0.to/ecosystem/plasma) and [Stable](https://usdt0.to/ecosystem/stable). + +## Incoming deposits + +`UsdtDepositClient` authenticates reusable deposit-address registration, indicative quotes, history and detail requests with the wallet account. Its credential-free service URL is separate from the chain and bundler URLs. The companion service holds the Rhino API key and pins the supported USDT routes to this wallet's Arbitrum One account. + +Supported source networks are Ethereum, Solana, Polygon, Optimism, Base and Avalanche. The sender makes an ordinary USDT transfer to the returned address and pays the source-network fee. Rhino prices each deposit when processing it; quoted receiving amounts are estimates, not guaranteed delivery or spendable balance. The wallet credits funds only through its Arbitrum history. + +History returns at most 100 rows per page. Start with `before = 0, offset = 0`, then pass the returned `next_before` and `next_offset` together. The timestamp stays fixed while paging within a 30-day window and advances backward when that window is exhausted. Missing optional metadata must not hide a pending deposit. Provider recovery states and completed refunds remain visible; refund initiation requires provider support. No client refund command or automatic paid retry is provided. + +A clock-skew error requires correcting the device clock. Amount-limit errors include available USD-cent bounds for display. Service enablement, funded delivery, repeated deposits, recovery and provider operating costs need separate acceptance before release. diff --git a/src/modules/usdt/deposits.rs b/src/modules/usdt/deposits.rs new file mode 100644 index 0000000..91da82b --- /dev/null +++ b/src/modules/usdt/deposits.rs @@ -0,0 +1,732 @@ +use super::{ + keys::{derive_owner_key, parse_address}, + rpc::{bounded_json, endpoint_client}, + user_operation::sign_hash, + UsdtError, +}; +use alloy_primitives::{eip191_hash_message, Address}; +use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; +use serde_json::{json, Value}; +use std::{sync::Arc, time::Duration}; +use zeroize::Zeroizing; + +const SOLANA_USDT: &str = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] +#[serde(rename_all = "lowercase")] +pub enum UsdtDepositNetwork { + Ethereum, + Solana, + Polygon, + Optimism, + Base, + Avalanche, +} + +#[derive(Clone, Debug, Deserialize, uniffi::Record)] +pub struct UsdtDepositAddress { + pub network: UsdtDepositNetwork, + pub address: String, + pub recipient: String, + #[serde(deserialize_with = "number")] + pub amount: u64, + #[serde(deserialize_with = "number")] + pub estimated_received: u64, + #[serde(default, deserialize_with = "deposit_limit")] + pub min_usd_cents: Option, + #[serde(default, deserialize_with = "deposit_limit")] + pub max_usd_cents: Option, + #[serde(skip)] + pub uri: String, +} + +#[derive(Clone, Debug, Deserialize, uniffi::Record)] +pub struct UsdtDeposit { + pub id: String, + pub network: String, + pub asset: String, + #[serde(default, deserialize_with = "optional_number")] + pub amount: Option, + pub source_tx: String, + pub status: String, + pub code: Option, + pub refund_tx: Option, +} + +#[derive(Clone, Debug, Deserialize, uniffi::Record)] +pub struct UsdtDepositPage { + pub deposits: Vec, + pub next_before: Option, + pub next_offset: u32, +} + +#[derive(Clone, Debug, Deserialize, uniffi::Record)] +pub struct UsdtDepositOrder { + pub status: String, + #[serde(default, deserialize_with = "optional_number")] + pub amount_in: Option, + #[serde(default, deserialize_with = "optional_number")] + pub amount_out: Option, + pub destination_tx: Option, + pub refund_tx: Option, + pub code: Option, +} + +#[derive(Clone, Debug, Deserialize, uniffi::Record)] +pub struct UsdtDepositDetail { + pub deposit: UsdtDeposit, + pub order: Option, +} + +#[derive(uniffi::Object)] +pub struct UsdtDepositClient { + address: Address, + client: reqwest::Client, + url: String, +} + +#[uniffi::export(async_runtime = "tokio")] +impl UsdtDepositClient { + #[uniffi::constructor] + pub fn new(address: String, service_url: String) -> Result, UsdtError> { + let client = endpoint_client(&service_url, Duration::from_secs(22))?; + Ok(Arc::new(Self { + address: parse_address(&address)?, + client, + url: service_url, + })) + } + + pub async fn networks(&self) -> Result, UsdtError> { + #[derive(Deserialize)] + struct Networks { + networks: Vec, + } + Ok(self + .response::(self.client.get(&self.url)) + .await? + .networks + .into_iter() + .filter_map(|network| match network.as_str() { + "ethereum" => Some(UsdtDepositNetwork::Ethereum), + "solana" => Some(UsdtDepositNetwork::Solana), + "polygon" => Some(UsdtDepositNetwork::Polygon), + "optimism" => Some(UsdtDepositNetwork::Optimism), + "base" => Some(UsdtDepositNetwork::Base), + "avalanche" => Some(UsdtDepositNetwork::Avalanche), + _ => None, + }) + .collect()) + } + + pub async fn receive( + &self, + network: UsdtDepositNetwork, + amount: u64, + mnemonic: String, + passphrase: Option, + ) -> Result { + let mnemonic = Zeroizing::new(mnemonic); + let passphrase = passphrase.map(Zeroizing::new); + if amount == 0 { + return Err(UsdtError::InvalidAmount); + } + let mut result: UsdtDepositAddress = self + .call( + json!({"action":"receive", "network":network, + "amount":amount.to_string()}), + mnemonic, + passphrase, + ) + .await?; + if result.network != network + || parse_address(&result.recipient).map_err(|_| UsdtError::InvalidResponse)? + != self.address + || result.amount != amount + || result.estimated_received == 0 + || result.estimated_received > amount + { + return Err(UsdtError::InvalidResponse); + } + validate_source_address(&result.address, network) + .map_err(|_| UsdtError::InvalidResponse)?; + if network != UsdtDepositNetwork::Solana + && parse_address(&result.address).map_err(|_| UsdtError::InvalidResponse)? + == self.address + { + return Err(UsdtError::InvalidResponse); + } + result.uri = match network.evm_token() { + Some((chain, token)) => format!( + "ethereum:{token}@{chain}/transfer?address={}", + result.address + ), + None => result.address.clone(), + }; + Ok(result) + } + + pub async fn history( + &self, + before: u64, + offset: u32, + mnemonic: String, + passphrase: Option, + ) -> Result { + let mnemonic = Zeroizing::new(mnemonic); + let passphrase = passphrase.map(Zeroizing::new); + if (before == 0 && offset != 0) || offset > 50_000 { + return Err(UsdtError::InvalidResponse); + } + let page: UsdtDepositPage = self + .call( + json!({"action":"history","before":before,"offset":offset}), + mnemonic, + passphrase, + ) + .await?; + let valid_cursor = match page.next_before { + None => page.next_offset == 0, + Some(next) if page.next_offset == 0 => next > 0 && (before == 0 || next < before), + Some(next) => { + next > 0 + && (before == 0 || next == before) + && !page.deposits.is_empty() + && page.next_offset as usize == offset as usize + page.deposits.len() + && page.next_offset <= 50_000 + } + }; + if page.deposits.len() > 100 || !valid_cursor { + return Err(UsdtError::InvalidResponse); + } + Ok(page) + } + + pub async fn detail( + &self, + deposit_id: String, + before: u64, + mnemonic: String, + passphrase: Option, + ) -> Result { + let result: UsdtDepositDetail = self + .call( + json!({"action":"detail","depositId":deposit_id,"before":before}), + mnemonic.into(), + passphrase.map(Into::into), + ) + .await?; + if result.deposit.id != deposit_id { + return Err(UsdtError::InvalidResponse); + } + Ok(result) + } +} + +impl UsdtDepositClient { + async fn call( + &self, + payload: Value, + mnemonic: Zeroizing, + passphrase: Option>, + ) -> Result { + let signed = self.authorize(payload, mnemonic, passphrase, super::wallet::now())?; + self.response(self.client.post(&self.url).json(&signed)) + .await + } + + fn authorize( + &self, + payload: Value, + mnemonic: Zeroizing, + passphrase: Option>, + timestamp: u64, + ) -> Result { + let key = derive_owner_key(mnemonic, passphrase, self.address)?; + let request = + json!({"owner":self.address.to_checksum(None),"timestamp":timestamp,"payload":payload}) + .to_string(); + let message = format!("Bitkit USDT deposits v1\n{request}"); + let signature = sign_hash(eip191_hash_message(message), &key)?; + drop(key); + Ok(json!({"request":request,"signature":signature})) + } + + async fn response( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request + .send() + .await + .map_err(|_| UsdtError::NetworkUnavailable)?; + let status = response.status(); + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(UsdtError::RateLimited); + } + let value = bounded_json(response, 262144, UsdtError::InvalidResponse).await; + if !status.is_success() { + let error = value.unwrap_or_default(); + return Err(match error["error"].as_str() { + Some("not_configured") => UsdtError::NotConfigured, + Some("provider_unavailable") => UsdtError::NetworkUnavailable, + Some("invalid_authorization") => UsdtError::DepositAuthorizationRejected, + Some("not_found") => UsdtError::DepositNotFound, + Some("clock_skew") => UsdtError::ClockSkew, + Some("amount_too_small" | "amount_too_large") => { + UsdtError::DepositAmountOutOfRange { + min_usd_cents: deposit_limit(&error["min_usd_cents"])?, + max_usd_cents: deposit_limit(&error["max_usd_cents"])?, + } + } + Some("amount_exceeds_liquidity") => UsdtError::UnsupportedRoute, + Some("route_unavailable") => UsdtError::UnsupportedRoute, + Some("operator_required") => UsdtError::DepositNeedsAttention, + _ if status == reqwest::StatusCode::UNAUTHORIZED + || status == reqwest::StatusCode::FORBIDDEN => + { + UsdtError::DepositAuthorizationRejected + } + _ if status.is_client_error() || status.is_redirection() => { + UsdtError::InvalidResponse + } + _ => UsdtError::NetworkUnavailable, + }); + } + serde_json::from_value(value?).map_err(Into::into) + } +} + +impl UsdtDepositNetwork { + fn evm_token(self) -> Option<(u64, &'static str)> { + Some(match self { + Self::Ethereum => (1, "0xdac17f958d2ee523a2206206994597c13d831ec7"), + Self::Polygon => (137, "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"), + Self::Optimism => (10, "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58"), + Self::Base => (8453, "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2"), + Self::Avalanche => (43114, "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7"), + Self::Solana => return None, + }) + } +} + +fn validate_source_address(value: &str, network: UsdtDepositNetwork) -> Result<(), UsdtError> { + if let Some((_, token)) = network.evm_token() { + let address = parse_address(value)?; + if address == parse_address(token)? { + return Err(UsdtError::InvalidAddress); + } + } else { + if !(32..=44).contains(&value.len()) || value == SOLANA_USDT { + return Err(UsdtError::InvalidAddress); + } + let bytes = bitcoin::base58::decode(value).map_err(|_| UsdtError::InvalidAddress)?; + if bytes.len() != 32 || bytes.iter().all(|b| *b == 0) { + return Err(UsdtError::InvalidAddress); + } + } + Ok(()) +} + +fn deposit_limit<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let value = Value::deserialize(deserializer)?; + Ok(value + .as_str() + .filter(|value| { + !value.is_empty() && value.len() <= 40 && value.bytes().all(|c| c.is_ascii_digit()) + }) + .map(str::to_owned)) +} + +fn number<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom) +} +fn optional_number<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + Option::::deserialize(deserializer)? + .map(|v| v.parse().map_err(serde::de::Error::custom)) + .transpose() +} + +#[cfg(test)] +mod tests { + use super::*; + const PHRASE: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + + #[test] + fn deposit_authorization_matches_vector_and_rejects_another_wallet() { + let vector: Value = + serde_json::from_str(include_str!("fixtures/deposit-signature.json")).unwrap(); + let request: Value = serde_json::from_str(vector["request"].as_str().unwrap()).unwrap(); + let client = UsdtDepositClient::new( + request["owner"].as_str().unwrap().into(), + "https://example.com/v1/usdt/deposits".into(), + ) + .unwrap(); + assert_eq!( + client + .authorize( + request["payload"].clone(), + PHRASE.to_owned().into(), + None, + 1_800_000_000 + ) + .unwrap(), + vector + ); + assert!(matches!( + client.authorize( + request["payload"].clone(), + PHRASE.to_owned().into(), + Some("other".to_owned().into()), + 1_800_000_000 + ), + Err(UsdtError::InvalidCredentials) + )); + } + + #[test] + fn deposit_addresses_and_transport_reject_wrong_networks() { + let solana = "6G41T4zUUYm47xgYBFoUioUGhigxS98Cj79y7C5nAf1L"; + for network in [ + UsdtDepositNetwork::Ethereum, + UsdtDepositNetwork::Polygon, + UsdtDepositNetwork::Optimism, + UsdtDepositNetwork::Base, + UsdtDepositNetwork::Avalanche, + ] { + assert!(validate_source_address(network.evm_token().unwrap().1, network).is_err()); + assert!( + validate_source_address("0x1111111111111111111111111111111111111111", network) + .is_ok() + ); + assert!(validate_source_address(solana, network).is_err()); + } + assert!(validate_source_address(solana, UsdtDepositNetwork::Solana).is_ok()); + for bad in [ + SOLANA_USDT, + "11111111111111111111111111111111", + "0x1111111111111111111111111111111111111111", + ] { + assert!(validate_source_address(bad, UsdtDepositNetwork::Solana).is_err()); + } + for url in [ + "http://example.com", + "https://user:password@example.com", + "https://example.com?key=secret", + ] { + assert!(UsdtDepositClient::new( + "0x1111111111111111111111111111111111111111".into(), + url.into() + ) + .is_err()); + } + } + + async fn service( + responses: Vec<(u16, Value)>, + ) -> (String, tokio::task::JoinHandle>) { + use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/v1/usdt/deposits", listener.local_addr().unwrap()); + let task = tokio::spawn(async move { + tokio::time::timeout(Duration::from_secs(10), async move { + let mut requests = Vec::new(); + for (status, value) in responses { + let (socket, _) = listener.accept().await.unwrap(); + let mut reader = BufReader::new(socket); + let mut first_line = String::new(); + reader.read_line(&mut first_line).await.unwrap(); + assert!(first_line == "GET /v1/usdt/deposits HTTP/1.1\r\n" || first_line == "POST /v1/usdt/deposits HTTP/1.1\r\n"); + let mut length = 0; + loop { + let mut line = String::new(); + assert_ne!(reader.read_line(&mut line).await.unwrap(), 0, "Request ended before its headers"); + if line == "\r\n" { + break; + } + if let Some(value) = line.to_ascii_lowercase().strip_prefix("content-length:") { + length = value.trim().parse().unwrap(); + } + } + let mut bytes = vec![0; length]; + reader.read_exact(&mut bytes).await.unwrap(); + if first_line.starts_with("POST") { + requests.push(serde_json::from_slice::(&bytes).unwrap()); + } + let body = value.to_string(); + reader.get_mut().write_all(format!("HTTP/1.1 {status} Response\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len()).as_bytes()).await.unwrap(); + } + requests + }).await.expect("Deposit fixture requests must complete") + }); + (url, task) + } + + #[tokio::test] + async fn service_responses_cover_receive_history_and_detail() { + let owner = super::super::usdt_address(PHRASE.into(), None).unwrap(); + let address = "0x1111111111111111111111111111111111111111"; + let deposit = json!({"id":"dep_one","network":"ethereum","asset":"USDT","source_tx":"0xsource","status":"held","code":null,"refund_tx":null}); + let (url, server) = service(vec![ + (200, json!({"networks":["ethereum","solana","polygon","optimism","base","avalanche","bitcoin"]})), + (200, json!({"network":"ethereum","address":address,"recipient":owner,"amount":"100000000","estimated_received":"98500000","min_usd_cents":"200","max_usd_cents":"10000","uri":123})), + (200, json!({"deposits":[deposit],"next_before":50,"next_offset":0})), + (200, json!({"deposit":deposit,"order":{"status":"held"}})), + ]).await; + let client = UsdtDepositClient::new(owner, url).unwrap(); + assert_eq!( + client.networks().await.unwrap(), + vec![ + UsdtDepositNetwork::Ethereum, + UsdtDepositNetwork::Solana, + UsdtDepositNetwork::Polygon, + UsdtDepositNetwork::Optimism, + UsdtDepositNetwork::Base, + UsdtDepositNetwork::Avalanche + ] + ); + let received = client + .receive( + UsdtDepositNetwork::Ethereum, + 100_000_000, + PHRASE.into(), + None, + ) + .await + .unwrap(); + assert_eq!(received.estimated_received, 98_500_000); + assert_eq!(received.min_usd_cents.as_deref(), Some("200")); + assert_eq!(received.max_usd_cents.as_deref(), Some("10000")); + assert!(received.uri.contains(address)); + let page = client.history(0, 0, PHRASE.into(), None).await.unwrap(); + assert_eq!(page.deposits[0].amount, None); + assert_eq!(page.next_before, Some(50)); + let detail = client + .detail("dep_one".into(), 0, PHRASE.into(), None) + .await + .unwrap(); + assert_eq!(detail.order.unwrap().amount_out, None); + let requests = server.await.unwrap(); + let expected = [ + json!({"action":"receive","network":"ethereum","amount":"100000000"}), + json!({"action":"history","before":0,"offset":0}), + json!({"action":"detail","depositId":"dep_one","before":0}), + ]; + assert_eq!(requests.len(), expected.len()); + for (signed, payload) in requests.into_iter().zip(expected) { + let request: Value = serde_json::from_str(signed["request"].as_str().unwrap()).unwrap(); + assert_eq!(request["payload"], payload); + assert_eq!( + signed, + client + .authorize( + payload, + PHRASE.to_string().into(), + None, + request["timestamp"].as_u64().unwrap() + ) + .unwrap() + ); + } + } + + #[tokio::test] + async fn history_pages_require_an_advancing_window_or_offset() { + let deposit = json!({"id":"deposit","network":"ethereum","asset":"USDT","source_tx":"0xsource","status":"pending"}); + for (next, offset, valid) in [ + (Some(1000), 101, true), + (Some(900), 0, true), + (None, 0, true), + (Some(1000), 100, false), + (Some(1000), 102, false), + (Some(1001), 101, false), + (Some(900), 101, false), + (None, 101, false), + ] { + let (url, server) = service(vec![( + 200, + json!({ + "deposits":[deposit],"next_before":next,"next_offset":offset + }), + )]) + .await; + let client = UsdtDepositClient::new( + super::super::usdt_address(PHRASE.into(), None).unwrap(), + url, + ) + .unwrap(); + assert_eq!( + client.history(1000, 100, PHRASE.into(), None).await.is_ok(), + valid + ); + let requests = server.await.unwrap(); + let signed: Value = + serde_json::from_str(requests[0]["request"].as_str().unwrap()).unwrap(); + assert_eq!( + signed["payload"], + json!({"action":"history","before":1000,"offset":100}) + ); + } + } + + #[tokio::test] + async fn service_errors_preserve_recovery_actions() { + for (code, expected) in [ + ("not_found", UsdtError::DepositNotFound), + ( + "invalid_authorization", + UsdtError::DepositAuthorizationRejected, + ), + ("clock_skew", UsdtError::ClockSkew), + ( + "amount_too_small", + UsdtError::DepositAmountOutOfRange { + min_usd_cents: None, + max_usd_cents: None, + }, + ), + ( + "amount_too_large", + UsdtError::DepositAmountOutOfRange { + min_usd_cents: None, + max_usd_cents: None, + }, + ), + ("amount_exceeds_liquidity", UsdtError::UnsupportedRoute), + ("operator_required", UsdtError::DepositNeedsAttention), + ("provider_unavailable", UsdtError::NetworkUnavailable), + ] { + let (url, server) = service(vec![( + 400, + json!({"error":code,"min_usd_cents":"200","max_usd_cents":"10000"}), + )]) + .await; + let owner = super::super::usdt_address(PHRASE.into(), None).unwrap(); + let client = UsdtDepositClient::new(owner, url).unwrap(); + let error = client.history(0, 0, PHRASE.into(), None).await.unwrap_err(); + assert_eq!( + std::mem::discriminant(&error), + std::mem::discriminant(&expected) + ); + if let UsdtError::DepositAmountOutOfRange { + min_usd_cents, + max_usd_cents, + } = error + { + assert_eq!(min_usd_cents.as_deref(), Some("200")); + assert_eq!(max_usd_cents.as_deref(), Some("10000")); + } + server.await.unwrap(); + } + for (status, expected) in [ + (429, UsdtError::RateLimited), + (401, UsdtError::DepositAuthorizationRejected), + (403, UsdtError::DepositAuthorizationRejected), + (404, UsdtError::InvalidResponse), + (502, UsdtError::NetworkUnavailable), + ] { + let (url, server) = service(vec![(status, json!({}))]).await; + let client = UsdtDepositClient::new( + super::super::usdt_address(PHRASE.into(), None).unwrap(), + url, + ) + .unwrap(); + let error = client.networks().await.unwrap_err(); + assert_eq!( + std::mem::discriminant(&error), + std::mem::discriminant(&expected) + ); + server.await.unwrap(); + } + } + + #[tokio::test] + async fn optional_limits_preserve_receive_and_amount_errors() { + for (limit, expected) in [ + (json!("200"), Some("200")), + (Value::Null, None), + (json!("unknown"), None), + (json!(200), None), + (json!(""), None), + (json!("1".repeat(41)), None), + ] { + let owner = super::super::usdt_address(PHRASE.into(), None).unwrap(); + let (url, server) = service(vec![ + (200, json!({"network":"ethereum","address":"0x1111111111111111111111111111111111111111","recipient":owner,"amount":"100000000","estimated_received":"98500000","min_usd_cents":limit,"max_usd_cents":limit})), + (400, json!({"error":"amount_too_small","min_usd_cents":limit,"max_usd_cents":limit})), + ]).await; + let client = UsdtDepositClient::new(owner, url).unwrap(); + let received = client + .receive( + UsdtDepositNetwork::Ethereum, + 100_000_000, + PHRASE.into(), + None, + ) + .await + .unwrap(); + assert_eq!(received.min_usd_cents.as_deref(), expected); + assert_eq!(received.max_usd_cents.as_deref(), expected); + let UsdtError::DepositAmountOutOfRange { + min_usd_cents, + max_usd_cents, + } = client + .receive( + UsdtDepositNetwork::Ethereum, + 100_000_000, + PHRASE.into(), + None, + ) + .await + .unwrap_err() + else { + panic!("Optional limits must not hide the amount error"); + }; + assert_eq!(min_usd_cents.as_deref(), expected); + assert_eq!(max_usd_cents.as_deref(), expected); + server.await.unwrap(); + } + } + + #[tokio::test] + async fn invalid_receive_terms_and_nonadvancing_pages_are_rejected() { + let owner = super::super::usdt_address(PHRASE.into(), None).unwrap(); + let mut responses = vec![ + ( + 200, + json!({"network":"ethereum","address":UsdtDepositNetwork::Ethereum.evm_token().unwrap().1,"recipient":owner,"amount":"100000000","estimated_received":"98500000"}), + ), + ( + 200, + json!({"network":"ethereum","address":"0x1111111111111111111111111111111111111111","recipient":"invalid","amount":"100000000","estimated_received":"98500000"}), + ), + ]; + for received in ["0", "100000001"] { + responses.push((200, json!({"network":"ethereum","address":"0x1111111111111111111111111111111111111111","recipient":owner,"amount":"100000000","estimated_received":received}))); + } + let invalid_receives = responses.len(); + responses.push((200, json!({"deposits":[],"next_before":0,"next_offset":0}))); + let (url, server) = service(responses).await; + let client = UsdtDepositClient::new(owner, url).unwrap(); + for _ in 0..invalid_receives { + assert!(matches!( + client + .receive( + UsdtDepositNetwork::Ethereum, + 100_000_000, + PHRASE.into(), + None + ) + .await, + Err(UsdtError::InvalidResponse) + )); + } + assert!(matches!( + client.history(0, 0, PHRASE.into(), None).await, + Err(UsdtError::InvalidResponse) + )); + server.await.unwrap(); + } +} diff --git a/src/modules/usdt/errors.rs b/src/modules/usdt/errors.rs index 3a5cc80..bc6be4e 100644 --- a/src/modules/usdt/errors.rs +++ b/src/modules/usdt/errors.rs @@ -10,6 +10,8 @@ pub enum UsdtError { WrongNetwork, #[error("Wallet credentials do not match this USDT account")] InvalidCredentials, + #[error("Set your device date and time automatically, then try again")] + ClockSkew, #[error("This account uses another wallet's smart account. Restore its delegation before sending with Bitkit")] UnsupportedDelegation, #[error("The USDT balance does not cover the amount and maximum fee")] @@ -20,6 +22,17 @@ pub enum UsdtError { PendingTransfer, #[error("The selected USDT payment route is unavailable")] UnsupportedRoute, + #[error("This deposit needs provider assistance. Check its recovery status")] + DepositNeedsAttention, + #[error("Deposit details changed. Refresh the deposit history and select it again")] + DepositNotFound, + #[error("The deposit service could not verify this request. Try again")] + DepositAuthorizationRejected, + #[error("The amount is outside this deposit route's limits. Review the minimum and maximum")] + DepositAmountOutOfRange { + min_usd_cents: Option, + max_usd_cents: Option, + }, #[error("USDT payments are not configured for this app build")] NotConfigured, #[error("The network could not be reached. Try again")] diff --git a/src/modules/usdt/fixtures/deposit-signature.json b/src/modules/usdt/fixtures/deposit-signature.json new file mode 100644 index 0000000..66fa60b --- /dev/null +++ b/src/modules/usdt/fixtures/deposit-signature.json @@ -0,0 +1,4 @@ +{ + "request": "{\"owner\":\"0x9858EfFD232B4033E47d90003D41EC34EcaEda94\",\"payload\":{\"action\":\"receive\",\"amount\":\"100000000\",\"network\":\"ethereum\"},\"timestamp\":1800000000}", + "signature": "0xe4a2d8340e92339d4f0627a6bf6a9bdaaae55498726ed75155a1d891761cd8cc23d6555526f69ce111c584dad43fbd974669dd9778f69c75c1b869065e05a0381b" +} diff --git a/src/modules/usdt/mod.rs b/src/modules/usdt/mod.rs index 9a56f06..f324cbf 100644 --- a/src/modules/usdt/mod.rs +++ b/src/modules/usdt/mod.rs @@ -1,5 +1,6 @@ mod account; mod amount; +mod deposits; mod errors; mod history; mod keys; @@ -13,6 +14,7 @@ mod user_operation; mod wallet; pub use amount::{usdt_format_amount, usdt_parse_amount}; +pub use deposits::*; pub use errors::UsdtError; pub use keys::usdt_address; pub use payment_request::usdt_parse_payment_request;