Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coinjoin): privacy level UI #613

Merged
merged 24 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
89babdb
fix: don't show close button for Screen presentation mode
Syn-McJ Sep 16, 2023
23fec12
feat: Uphold changes (WiP)
tikhop Sep 17, 2023
3045fbc
Merge pull request #602 from Syn-McJ/fix/currency-picker-crash
Syn-McJ Sep 19, 2023
28b24d6
chore: refactor CoinbaseEntryPointViewController into IntegrationView…
Syn-McJ Sep 20, 2023
b15abd7
feat: Uphold logged out state
Syn-McJ Sep 20, 2023
e62ff92
feat: powered by topper badge
Syn-McJ Sep 21, 2023
b25da8f
feat: attaching authentication flow to the new UI
Syn-McJ Sep 21, 2023
d4ad914
fix: balance view
Syn-McJ Sep 22, 2023
77ed3d7
feat: integrate Topper widget
Syn-McJ Sep 22, 2023
0aee649
chore: cleanup
Syn-McJ Sep 22, 2023
51c5105
chore: refactor specific integration extensions out of the integratio…
Syn-McJ Sep 22, 2023
557166a
feat: submit OTP automatically when 6 digits is entered
Syn-McJ Sep 23, 2023
f6de2d2
Merge pull request #605 from Syn-McJ/feature/uphold-align-portal
Syn-McJ Sep 25, 2023
44ded98
feat: update translations
Syn-McJ Sep 26, 2023
18efef8
Merge pull request #606 from Syn-McJ/feature/update-translations
Syn-McJ Oct 2, 2023
60f915f
chore: improve HTTPClient errors
Syn-McJ Oct 6, 2023
a9105a5
feat: migrate to v3 buy endpoint
Syn-McJ Oct 6, 2023
5f36ba9
feat: deposit to cash account
Syn-McJ Oct 6, 2023
76fabca
fix: deauth button
Syn-McJ Oct 6, 2023
d57cbf3
Merge pull request #609 from Syn-McJ/chore/coinbase-buy-api
Syn-McJ Oct 10, 2023
ff9820c
Merge branch 'develop' into feature/coinjoin-levels
Syn-McJ Oct 18, 2023
f290695
feat: levels UI
Syn-McJ Oct 19, 2023
71df6df
feat: levels changing and dialogs
Syn-McJ Oct 21, 2023
b3c6fb5
Merge branch 'feature/dashpay' into feature/coinjoin-levels
Syn-McJ Oct 24, 2023
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
822 changes: 725 additions & 97 deletions DashWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "icon.buy-dash.png",
"filename" : "􀙨.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon.buy-dash@2x.png",
"filename" : "􀙨@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon.buy-dash@3x.png",
"filename" : "􀙨@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "icon.buy-dash.png",
"filename" : "􁃘.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon.buy-dash@2x.png",
"filename" : "􁃘@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon.buy-dash@3x.png",
"filename" : "􁃘@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "icon.buy-dash.png",
"filename" : "buy.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon.buy-dash@2x.png",
"filename" : "buy@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon.buy-dash@3x.png",
"filename" : "buy@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "icon.convert-crypto.png",
"filename" : "convert.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon.convert-crypto@2x.png",
"filename" : "convert@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon.convert-crypto@3x.png",
"filename" : "convert@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "sell.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "sell@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "sell@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "integration.transfer.disabled.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "integration.transfer.disabled@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "integration.transfer.disabled@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "transfer.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "transfer@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "transfer@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
18 changes: 16 additions & 2 deletions DashWallet/Sources/Infrastructure/Networking/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,30 @@ private func JSONResponseDataFormatter(_ data: Data) -> String {

// MARK: - HTTPClientError

enum HTTPClientError: Error {
enum HTTPClientError: LocalizedError {
case statusCode(Moya.Response)
case mapping(Moya.Response)
case moya(MoyaError)
case decoder(DecodingError)

var localizedDescription: String {
var errorDescription: String? {
switch self {
case .statusCode(let response):
return "\(response.debugDescription)\nError: \(response.errorDescription ?? "")"
case .mapping(let response):
return "\(response.debugDescription)"
case .moya(let error):
return "\(String(describing: error.errorDescription))"
case .decoder(let error):
switch error {
case .typeMismatch(_, let context),
.valueNotFound(_, let context),
.keyNotFound(_, let context),
.dataCorrupted(let context):
return context.debugDescription
@unknown default:
return error.localizedDescription
}
}
}
}
Expand Down Expand Up @@ -216,9 +227,12 @@ extension Swift.Result where Success: Moya.Response, Failure: Error {
do {
let result = try jsonDecoder.decode(T.self, from: r.data)
return result
} catch let error as DecodingError {
throw HTTPClientError.decoder(error)
} catch {
throw HTTPClientError.mapping(r)
}

case .failure(let error):
throw error
}
Expand Down
77 changes: 40 additions & 37 deletions DashWallet/Sources/Models/Coinbase/Accounts/Account/CBAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ extension CBAccount {

let result: BaseDataResponse<CoinbaseTransaction> = try await httpClient
.request(.sendCoinsToWallet(accountId: accountId, verificationCode: verificationCode, dto: dto))
try? await refreshAccount() // Ignore if fails
let _ = try? await refreshAccount() // Ignore if fails

return result.data
} catch HTTPClientError.statusCode(let r) where r.statusCode == 402 {
Expand Down Expand Up @@ -164,46 +164,30 @@ extension CBAccount {

// MARK: Buy
extension CBAccount {
public func placeCoinbaseBuyOrder(amount: UInt64, paymentMethod: CoinbasePaymentMethod) async throws -> CoinbasePlaceBuyOrder {
let fiatCurrency = Coinbase.sendLimitCurrency
if let localNumber = try? Coinbase.shared.currencyExchanger.convertDash(amount: amount.dashAmount, to: fiatCurrency) {
if localNumber < kMinUSDAmountOrder {
let min = NSDecimalNumber(decimal: kMinUSDAmountOrder)
let localFormatter = NumberFormatter.fiatFormatter(currencyCode: fiatCurrency)
let str = localFormatter.string(from: min) ?? "$1.99"
throw Coinbase.Error.transactionFailed(.enteredAmountTooLow(minimumAmount: str))
} else if localNumber > Coinbase.shared.sendLimit {
throw Coinbase.Error.transactionFailed(.limitExceded)
}
public func placeCoinbaseBuyOrder(amount: UInt64) async throws -> CoinbasePlaceBuyOrder {
let fiatCurrency = Coinbase.defaultFiat

guard let localNumber = try? Coinbase.shared.currencyExchanger.convertDash(amount: amount.dashAmount, to: fiatCurrency) else {
throw Coinbase.Error.general(.rateNotFound)
}

// NOTE: Make sure we format the amount back into coinbase format (en_US)
let amount = amount.formattedDashAmountWithoutCurrencySymbol.coinbaseAmount()

let request = CoinbasePlaceBuyOrderRequest(amount: amount, currency: kDashCurrency, paymentMethod: paymentMethod.id, commit: false, quote: nil)

do {
try await authInterop.refreshTokenIfNeeded()
let result: BaseDataResponse<CoinbasePlaceBuyOrder> = try await httpClient.request(.placeBuyOrder(accountId, request))
return result.data
} catch HTTPClientError.statusCode(let r) {
if let error = r.error?.errors.first {
throw Coinbase.Error.transactionFailed(.message(error.message))
}

throw Coinbase.Error.unknownError
} catch {
throw error

if localNumber < kMinUSDAmountOrder {
let min = NSDecimalNumber(decimal: kMinUSDAmountOrder)
let localFormatter = NumberFormatter.fiatFormatter(currencyCode: fiatCurrency)
let str = localFormatter.string(from: min) ?? "$1.99"
throw Coinbase.Error.transactionFailed(.enteredAmountTooLow(minimumAmount: str))
} else if localNumber > Coinbase.shared.sendLimit {
throw Coinbase.Error.transactionFailed(.limitExceded)
}
}

public func commitCoinbaseBuyOrder(orderID: String) async throws -> CoinbasePlaceBuyOrder {
let formatter = NumberFormatter.decimalFormatter
formatter.maximumFractionDigits = 2
let amountStr = formatter.string(from: NSDecimalNumber(decimal: localNumber))!.coinbaseAmount()
let request = CoinbasePlaceBuyOrderRequest(clientOrderId: UUID(), productId: Coinbase.dashUSDPair, side: Coinbase.transactionTypeBuy, orderConfiguration: OrderConfiguration(marketMarketIoc: MarketMarketIoc(quoteSize: amountStr)))

do {
try await authInterop.refreshTokenIfNeeded()
let result: BaseDataResponse<CoinbasePlaceBuyOrder> = try await httpClient.request(.commitBuyOrder(accountId, orderID))
try? await refreshAccount() // Ignore if fails

return result.data
return try await httpClient.request<CoinbasePlaceBuyOrder>(.placeBuyOrder(request))
} catch HTTPClientError.statusCode(let r) {
if let error = r.error?.errors.first {
throw Coinbase.Error.transactionFailed(.message(error.message))
Expand Down Expand Up @@ -265,7 +249,7 @@ extension CBAccount {
do {
try await authInterop.refreshTokenIfNeeded()
let result: BaseDataResponse<CoinbaseSwapeTrade> = try await httpClient.request(.swapTradeCommit(orderID))
try? await refreshAccount() // Ignore if fails
let _ = try? await refreshAccount() // Ignore if fails

return result.data
} catch HTTPClientError.statusCode(let r) {
Expand All @@ -280,6 +264,25 @@ extension CBAccount {
}
}

// MARK: Deposit

extension CBAccount {
public func deposit(from paymentMethodId: String, amount: UInt64) async throws -> CoinbaseDepositResponse {
guard let localNumber = try? Coinbase.shared.currencyExchanger.convertDash(amount: amount.dashAmount, to: Coinbase.defaultFiat) else {
throw Coinbase.Error.general(.rateNotFound)
}

let formatter = NumberFormatter.decimalFormatter
formatter.maximumFractionDigits = 2
let amountStr = formatter.string(from: NSDecimalNumber(decimal: localNumber))!.coinbaseAmount()
try await authInterop.refreshTokenIfNeeded()
let request = CoinbaseDepositRequest(amount: amountStr, currency: Coinbase.defaultFiat, paymentMethod: paymentMethodId)
let result: BaseDataResponse<CoinbaseDepositResponse> = try await httpClient.request(.deposit(accountId: info.id, dto: request))

return result.data
}
}

// MARK: SourceViewDataProvider

extension CBAccount: SourceViewDataProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class AccountRepository {
var dashAccount: CBAccount? {
cachedAccounts[kDashAccount]
}

var usdAccount: CBAccount? {
cachedAccounts[Coinbase.dashUSDPair]
}

init(authInterop: CBAuthInterop) {
self.authInterop = authInterop
Expand Down
16 changes: 9 additions & 7 deletions DashWallet/Sources/Models/Coinbase/Accounts/AccountService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ class AccountService {
return tx
}

public func placeBuyOrder(for accountName: String, amount: UInt64, paymentMethod: CoinbasePaymentMethod) async throws -> CoinbasePlaceBuyOrder {
public func placeBuyOrder(for accountName: String, amount: UInt64) async throws -> CoinbasePlaceBuyOrder {
let account = try await account(by: accountName)
return try await account.placeCoinbaseBuyOrder(amount: amount, paymentMethod: paymentMethod)
return try await account.placeCoinbaseBuyOrder(amount: amount)
}

public func commitBuyOrder(accountName: String, orderID: String) async throws -> CoinbasePlaceBuyOrder {
public func deposit(to accountName: String, from paymentMethodId: String, amount: UInt64) async throws {
let account = try await account(by: accountName)

let order = try await account.commitCoinbaseBuyOrder(orderID: orderID)
return order
let result = try await account.deposit(from: paymentMethodId, amount: amount)

if result.status != "created" {
throw Coinbase.Error.general(.depositFailed)
}
}

func placeTradeOrder(from origin: CBAccount, to destination: CBAccount, amount: String) async throws -> CoinbaseSwapeTrade {
Expand Down
4 changes: 1 addition & 3 deletions DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@ extension CBAuth {
URLQueryItem(name: "account", value: Coinbase.account),
]

if let clientID = Coinbase.clientID as? String {
queryItems.append(URLQueryItem(name: "client_id", value: clientID))
}
queryItems.append(URLQueryItem(name: "client_id", value: Coinbase.clientID))

var urlComponents = URLComponents()
urlComponents.scheme = "https"
Expand Down
8 changes: 6 additions & 2 deletions DashWallet/Sources/Models/Coinbase/Coinbase+Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ extension Coinbase {
static let grantType = "authorization_code"
static let responseType = "code"
static let scope =
"wallet:accounts:read,wallet:user:read,wallet:payment-methods:read,wallet:buys:read,wallet:buys:create,wallet:transactions:transfer,wallet:transactions:request,wallet:transactions:read,wallet:supported-assets:read,wallet:sells:create,wallet:sells:read,wallet:transactions:send,wallet:addresses:read,wallet:addresses:create,wallet:trades:create,wallet:accounts:create"
static let sendLimitCurrency = "USD"
"wallet:accounts:read,wallet:user:read,wallet:payment-methods:read,wallet:buys:read,wallet:buys:create,wallet:transactions:transfer,wallet:transactions:request,wallet:transactions:read,wallet:supported-assets:read,wallet:sells:create,wallet:sells:read,wallet:transactions:send,wallet:addresses:read,wallet:addresses:create,wallet:trades:create,wallet:accounts:create,wallet:deposits:create"
static let defaultFiat = "USD"
static let sendLimitCurrency = defaultFiat
static let sendLimitAmount: Decimal = 1.0
static let sendLimitPeriod = "month"
static let account = "all"
static let buyFee = 0.006
static let dashUSDPair = "DASH-USD"
static let transactionTypeBuy = "BUY"

static let clientSecret: String = {
if let path = Bundle.main.path(forResource: "Coinbase-Info", ofType: "plist"),
Expand Down
Loading