Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@
repositoryURL = "https://github.com/synonymdev/bitkit-core";
requirement = {
kind = exactVersion;
version = "0.8.0-rc11";
version = "0.8.0-arkade.6";
};
};
96E493A22C942FD1000E8BC2 /* XCRemoteSwiftPackageReference "swift-secp256k1" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions Bitkit/AppScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,16 @@ enum IncomingPaykitPaymentRequestPresentationDispatcher {
return IncomingPaykitPaymentRequestPresentationFeedback(paymentDetailsPendingWasRequested: wasRequested)
}

/// Ends a presentation the user asked for once its payment details fail to resolve, so the send sheet stops loading
/// and the user sees why; the request stays in Payment Requests to try again. Automatic presentations keep retrying.
static func finishRequestedPresentationAfterResolutionFailure(
for request: PaykitPaymentRequest,
with manager: PaykitPaymentRequestManager
) -> IncomingPaykitPaymentRequestPresentationFeedback? {
guard manager.requestedPresentationId == request.id else { return nil }
return finishPendingPrivateLink(for: request, with: manager)
}

static func handleStateChange(
from previous: IncomingPaykitPaymentRequestPresentationState,
to current: IncomingPaykitPaymentRequestPresentationState,
Expand Down Expand Up @@ -1737,6 +1747,20 @@ struct AppScene: View {
return
}
guard case let .opened(paymentTarget, privatePaymentContext, endpoints) = result else {
let isWaitingForUpdatedList = if case .waitingForUpdatedPaymentList = result {
true
} else {
false
}
if !isWaitingForUpdatedList,
let feedback = IncomingPaykitPaymentRequestPresentationDispatcher.finishRequestedPresentationAfterResolutionFailure(
for: request,
with: paykitPaymentRequestManager
)
{
presentIncomingPaykitPaymentRequestFeedback(feedback, for: request)
return
}
deferIncomingPaykitPaymentRequestPresentation(
request,
reason: result.incomingPaymentRequestFailureReason ?? .resolutionFailed
Expand Down Expand Up @@ -1862,6 +1886,13 @@ struct AppScene: View {
return
} catch {
guard isCurrentIncomingPaymentRequestPreparation(preparation) else { return }
if let feedback = IncomingPaykitPaymentRequestPresentationDispatcher.finishRequestedPresentationAfterResolutionFailure(
for: request,
with: paykitPaymentRequestManager
) {
presentIncomingPaykitPaymentRequestFeedback(feedback, for: request)
return
}
deferIncomingPaykitPaymentRequestPresentation(request, reason: .resolutionFailed)
return
}
Expand Down
106 changes: 69 additions & 37 deletions Bitkit/Components/LightningChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@ struct LightningChannel: View {
let remoteBalance: UInt64
let status: ChannelStatus
let showLabels: Bool
/// False for a balance whose receiving isn't capped by a channel, such as Arkade: only the spending side is drawn.
let showReceiving: Bool

init(
capacity: UInt64,
localBalance: UInt64,
remoteBalance: UInt64,
status: ChannelStatus = .open,
showLabels: Bool = false
showLabels: Bool = false,
showReceiving: Bool = true
) {
self.capacity = capacity
self.localBalance = localBalance
self.remoteBalance = remoteBalance
self.status = status
self.showLabels = showLabels
self.showReceiving = showReceiving
}

private var spendingColor: Color {
Expand Down Expand Up @@ -90,7 +94,9 @@ struct LightningChannel: View {

Spacer()

CaptionMText(t("lightning__receiving_label"))
if showReceiving {
CaptionMText(t("lightning__receiving_label"))
}
}
}

Expand All @@ -106,49 +112,75 @@ struct LightningChannel: View {

Spacer()

HStack(spacing: 4) {
Image("arrow-down")
.resizable()
.frame(width: 14, height: 14)
.foregroundColor(receivingAvailableColor)
if showReceiving {
HStack(spacing: 4) {
Image("arrow-down")
.resizable()
.frame(width: 14, height: 14)
.foregroundColor(receivingAvailableColor)

BodySSBText(formatNumber(remoteBalance), textColor: receivingAvailableColor)
BodySSBText(formatNumber(remoteBalance), textColor: receivingAvailableColor)
}
}
}

HStack(spacing: 4) {
// Spending bar (left)
ZStack(alignment: .trailing) {
// Background
Rectangle()
.fill(spendingColor)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])

// Available
Rectangle()
.fill(spendingAvailableColor)
.frame(width: spendingPercentage * 100.0)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
}

// Receiving bar (right)
ZStack(alignment: .leading) {
// Background
Rectangle()
.fill(receivingColor)
.cornerRadius(8, corners: [.topRight, .bottomRight])

// Available
Rectangle()
.fill(receivingAvailableColor)
.frame(width: receivingPercentage * 100.0)
.cornerRadius(8, corners: [.topRight, .bottomRight])
}
if showReceiving {
channelBars
} else {
spendingBar
}
.frame(height: 16)
}
.opacity(status == .pending ? 0.5 : 1.0)
}

private var spendingBar: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(localBalance == 0 ? Color.gray6 : spendingColor)
.cornerRadius(8)

Rectangle()
.fill(spendingAvailableColor)
.frame(width: spendingPercentage * geometry.size.width)
.cornerRadius(8)
}
}
.frame(height: 16)
}

private var channelBars: some View {
HStack(spacing: 4) {
// Spending bar (left)
ZStack(alignment: .trailing) {
// Background
Rectangle()
.fill(spendingColor)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])

// Available
Rectangle()
.fill(spendingAvailableColor)
.frame(width: spendingPercentage * 100.0)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
}

// Receiving bar (right)
ZStack(alignment: .leading) {
// Background
Rectangle()
.fill(receivingColor)
.cornerRadius(8, corners: [.topRight, .bottomRight])

// Available
Rectangle()
.fill(receivingAvailableColor)
.frame(width: receivingPercentage * 100.0)
.cornerRadius(8, corners: [.topRight, .bottomRight])
}
}
.frame(height: 16)
}
}

/// Extension to apply rounded corners to specific corners
Expand Down
29 changes: 29 additions & 0 deletions Bitkit/Constants/Env.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ enum Env {
configValue("E2E_LOCAL_HOST") ?? "127.0.0.1"
}

/// The solver key of a local Arkade regtest stack; set, it turns Arkade on in dev builds.
static var arkadeRegtestSolverPubkey: String? {
configValue("ARKADE_REGTEST_SOLVER_PUBKEY")
}

static var arkadeRegtestHost: String {
configValue("ARKADE_REGTEST_HOST") ?? "localhost"
}

private static var e2eHomegateUrl: String {
infoPlistValue("E2E_HOMEGATE_URL") ?? "http://\(e2eLocalHost):6288"
}
Expand Down Expand Up @@ -272,6 +281,26 @@ enum Env {
.appendingPathComponent("wallet\(walletIndex)/ldk")
}

static func arkadeStorage(walletIndex: Int) -> URL {
appStorageUrl
.appendingPathComponent(networkName)
.appendingPathComponent("wallet\(walletIndex)/arkade")
}

/// A restored Arkade wallet data backup waiting to be imported when the Arkade wallet next starts.
static func arkadeExitRestore(walletIndex: Int) -> URL {
appStorageUrl
.appendingPathComponent(networkName)
.appendingPathComponent("wallet\(walletIndex)/arkade-restore/wallet-state.json")
}

/// Written after the Arkade wallet's first start on this install; until then it scans the operator for funds.
static func arkadeStartedMarker(walletIndex: Int) -> URL {
appStorageUrl
.appendingPathComponent(networkName)
.appendingPathComponent("wallet\(walletIndex)/arkade-restore/started")
}

static func bitkitCoreStorage(walletIndex: Int) -> URL {
appStorageUrl
.appendingPathComponent(networkName)
Expand Down
4 changes: 4 additions & 0 deletions Bitkit/MainNavView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ struct MainNavView: View {
case let .fundManualAmount(lnPeer): FundManualAmountView(lnPeer: lnPeer)
case let .fundManualConfirm(lnPeer, amountSats): FundManualConfirmView(lnPeer: lnPeer, amountSats: amountSats)
case .fundManualSuccess: FundManualSuccessView()
case .arkadeFundingAmount: ArkadeFundingAmountView()
case let .arkadeFundingConfirm(amountSats, isMaxAmount): ArkadeFundingConfirmView(amountSats: amountSats, isMaxAmount: isMaxAmount)
case let .arkadeFundingDeposit(txid): ArkadeFundingDepositView(txid: txid)
case let .lnurlChannel(channelData): LnurlChannel(channelData: channelData)
case .savingsIntro: SavingsIntroView()
case .savingsAvailability: SavingsAvailabilityView()
Expand Down Expand Up @@ -692,6 +695,7 @@ struct MainNavView: View {
case .addressTypePreference: AddressTypePreferenceView()
case .connections: LightningConnectionsView()
case let .connectionDetail(channelId): LightningConnectionDetailView(channelId: channelId)
case .arkadeConnectionDetail: ArkadeConnectionDetailView()
case let .closeConnection(channel: channel): CloseConnectionConfirmation(channel: channel)
case .node: NodeStateView()
case .electrumSettings: ElectrumSettingsScreen()
Expand Down
Loading
Loading