Skip to content

Commit 31b2b28

Browse files
committed
fix: use network-specific paykit onchain methods
1 parent c589229 commit 31b2b28

2 files changed

Lines changed: 116 additions & 39 deletions

File tree

Bitkit/Services/PublicPaykitService.swift

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,37 +69,96 @@ private actor PublicPaykitEndpointLock {
6969

7070
enum PublicPaykitService {
7171
private static let endpointLock = PublicPaykitEndpointLock()
72-
enum MethodId: String, Hashable {
72+
enum MethodId: String, Hashable, CaseIterable {
7373
case bitcoinLightningBolt11 = "btc-lightning-bolt11"
7474
case bitcoinLightningLnurl = "btc-lightning-lnurl"
7575
case bitcoinOnchainP2tr = "btc-bitcoin-p2tr"
7676
case bitcoinOnchainP2wpkh = "btc-bitcoin-p2wpkh"
7777
case bitcoinOnchainP2sh = "btc-bitcoin-p2sh"
7878
case bitcoinOnchainP2pkh = "btc-bitcoin-p2pkh"
79+
case testnetOnchainP2tr = "btc-testnet-p2tr"
80+
case testnetOnchainP2wpkh = "btc-testnet-p2wpkh"
81+
case testnetOnchainP2sh = "btc-testnet-p2sh"
82+
case testnetOnchainP2pkh = "btc-testnet-p2pkh"
83+
case signetOnchainP2tr = "btc-signet-p2tr"
84+
case signetOnchainP2wpkh = "btc-signet-p2wpkh"
85+
case signetOnchainP2sh = "btc-signet-p2sh"
86+
case signetOnchainP2pkh = "btc-signet-p2pkh"
87+
case regtestOnchainP2tr = "btc-regtest-p2tr"
88+
case regtestOnchainP2wpkh = "btc-regtest-p2wpkh"
89+
case regtestOnchainP2sh = "btc-regtest-p2sh"
90+
case regtestOnchainP2pkh = "btc-regtest-p2pkh"
7991

8092
static let payablePreferenceOrder: [MethodId] = [
8193
.bitcoinLightningBolt11,
8294
.bitcoinLightningLnurl,
83-
.bitcoinOnchainP2tr,
84-
.bitcoinOnchainP2wpkh,
85-
.bitcoinOnchainP2sh,
86-
.bitcoinOnchainP2pkh,
87-
]
95+
] + onchainPreferenceOrder
8896

8997
static let publishableMethodIds: [MethodId] = [
9098
.bitcoinLightningBolt11,
91-
.bitcoinOnchainP2tr,
92-
.bitcoinOnchainP2wpkh,
93-
.bitcoinOnchainP2sh,
94-
.bitcoinOnchainP2pkh,
95-
]
99+
] + onchainPreferenceOrder
96100

97101
static let onchainPreferenceOrder: [MethodId] = [
98102
.bitcoinOnchainP2tr,
103+
.testnetOnchainP2tr,
104+
.signetOnchainP2tr,
105+
.regtestOnchainP2tr,
99106
.bitcoinOnchainP2wpkh,
107+
.testnetOnchainP2wpkh,
108+
.signetOnchainP2wpkh,
109+
.regtestOnchainP2wpkh,
100110
.bitcoinOnchainP2sh,
111+
.testnetOnchainP2sh,
112+
.signetOnchainP2sh,
113+
.regtestOnchainP2sh,
101114
.bitcoinOnchainP2pkh,
115+
.testnetOnchainP2pkh,
116+
.signetOnchainP2pkh,
117+
.regtestOnchainP2pkh,
102118
]
119+
120+
var onchainNetwork: LDKNode.Network? {
121+
switch self {
122+
case .bitcoinOnchainP2tr, .bitcoinOnchainP2wpkh, .bitcoinOnchainP2sh, .bitcoinOnchainP2pkh:
123+
.bitcoin
124+
case .testnetOnchainP2tr, .testnetOnchainP2wpkh, .testnetOnchainP2sh, .testnetOnchainP2pkh:
125+
.testnet
126+
case .signetOnchainP2tr, .signetOnchainP2wpkh, .signetOnchainP2sh, .signetOnchainP2pkh:
127+
.signet
128+
case .regtestOnchainP2tr, .regtestOnchainP2wpkh, .regtestOnchainP2sh, .regtestOnchainP2pkh:
129+
.regtest
130+
case .bitcoinLightningBolt11, .bitcoinLightningLnurl:
131+
nil
132+
}
133+
}
134+
135+
static func onchainMethodId(network: LDKNode.Network, scriptType: OnchainScriptType) -> MethodId {
136+
switch (network, scriptType) {
137+
case (.bitcoin, .p2tr): .bitcoinOnchainP2tr
138+
case (.bitcoin, .p2wpkh): .bitcoinOnchainP2wpkh
139+
case (.bitcoin, .p2sh): .bitcoinOnchainP2sh
140+
case (.bitcoin, .p2pkh): .bitcoinOnchainP2pkh
141+
case (.testnet, .p2tr): .testnetOnchainP2tr
142+
case (.testnet, .p2wpkh): .testnetOnchainP2wpkh
143+
case (.testnet, .p2sh): .testnetOnchainP2sh
144+
case (.testnet, .p2pkh): .testnetOnchainP2pkh
145+
case (.signet, .p2tr): .signetOnchainP2tr
146+
case (.signet, .p2wpkh): .signetOnchainP2wpkh
147+
case (.signet, .p2sh): .signetOnchainP2sh
148+
case (.signet, .p2pkh): .signetOnchainP2pkh
149+
case (.regtest, .p2tr): .regtestOnchainP2tr
150+
case (.regtest, .p2wpkh): .regtestOnchainP2wpkh
151+
case (.regtest, .p2sh): .regtestOnchainP2sh
152+
case (.regtest, .p2pkh): .regtestOnchainP2pkh
153+
}
154+
}
155+
}
156+
157+
enum OnchainScriptType {
158+
case p2tr
159+
case p2wpkh
160+
case p2sh
161+
case p2pkh
103162
}
104163

105164
struct Endpoint: Equatable, Hashable {
@@ -243,22 +302,21 @@ enum PublicPaykitService {
243302
return "bitcoin:\(onchainEndpoint.paymentRequest)?lightning=\(encodedLightning)"
244303
}
245304

246-
static func onchainMethodId(for address: String) -> MethodId {
305+
static func onchainMethodId(for address: String, network: LDKNode.Network = Env.network) -> MethodId {
247306
let normalizedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
248-
249-
if normalizedAddress.hasPrefix("bc1p") || normalizedAddress.hasPrefix("tb1p") || normalizedAddress.hasPrefix("bcrt1p") {
250-
return .bitcoinOnchainP2tr
251-
}
252-
253-
if normalizedAddress.hasPrefix("bc1q") || normalizedAddress.hasPrefix("tb1q") || normalizedAddress.hasPrefix("bcrt1q") {
254-
return .bitcoinOnchainP2wpkh
255-
}
256-
257-
if normalizedAddress.hasPrefix("3") || normalizedAddress.hasPrefix("2") {
258-
return .bitcoinOnchainP2sh
307+
let scriptType: OnchainScriptType = if normalizedAddress.hasPrefix("bc1p") || normalizedAddress.hasPrefix("tb1p") || normalizedAddress
308+
.hasPrefix("bcrt1p")
309+
{
310+
.p2tr
311+
} else if normalizedAddress.hasPrefix("bc1q") || normalizedAddress.hasPrefix("tb1q") || normalizedAddress.hasPrefix("bcrt1q") {
312+
.p2wpkh
313+
} else if normalizedAddress.hasPrefix("3") || normalizedAddress.hasPrefix("2") {
314+
.p2sh
315+
} else {
316+
.p2pkh
259317
}
260318

261-
return .bitcoinOnchainP2pkh
319+
return MethodId.onchainMethodId(network: network, scriptType: scriptType)
262320
}
263321

264322
static func methodIdsToRemoveWhenUnpublishing(existingMethodIds: Set<MethodId>) -> [MethodId] {
@@ -410,7 +468,14 @@ enum PublicPaykitService {
410468

411469
return true
412470

413-
case .bitcoinOnchainP2tr, .bitcoinOnchainP2wpkh, .bitcoinOnchainP2sh, .bitcoinOnchainP2pkh:
471+
case .bitcoinOnchainP2tr, .bitcoinOnchainP2wpkh, .bitcoinOnchainP2sh, .bitcoinOnchainP2pkh,
472+
.testnetOnchainP2tr, .testnetOnchainP2wpkh, .testnetOnchainP2sh, .testnetOnchainP2pkh,
473+
.signetOnchainP2tr, .signetOnchainP2wpkh, .signetOnchainP2sh, .signetOnchainP2pkh,
474+
.regtestOnchainP2tr, .regtestOnchainP2wpkh, .regtestOnchainP2sh, .regtestOnchainP2pkh:
475+
guard endpoint.methodId.onchainNetwork == Env.network else {
476+
return false
477+
}
478+
414479
guard case let .onChain(invoice) = try? await decode(invoice: endpoint.paymentRequest) else {
415480
return false
416481
}

BitkitTests/PublicPaykitServiceTests.swift

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@testable import Bitkit
22
import Foundation
3+
import LDKNode
34
import XCTest
45

56
final class PublicPaykitServiceTests: XCTestCase {
@@ -43,6 +44,23 @@ final class PublicPaykitServiceTests: XCTestCase {
4344
XCTAssertEqual(endpoint?.value, "lnurl1example")
4445
}
4546

47+
func testParseEndpointReadsNetworkSpecificOnchainMethodIds() {
48+
XCTAssertEqual(
49+
PublicPaykitService.parseEndpoint(
50+
methodId: "btc-testnet-p2wpkh",
51+
endpointData: #"{"value":"tb1qexample"}"#
52+
)?.methodId,
53+
.testnetOnchainP2wpkh
54+
)
55+
XCTAssertEqual(
56+
PublicPaykitService.parseEndpoint(
57+
methodId: "btc-regtest-p2tr",
58+
endpointData: #"{"value":"bcrt1pexample"}"#
59+
)?.methodId,
60+
.regtestOnchainP2tr
61+
)
62+
}
63+
4664
func testParseEndpointRejectsNonSpecLegacyLnurlMethodId() {
4765
let endpoint = PublicPaykitService.parseEndpoint(
4866
methodId: "btc-lightning-lnurl-pay",
@@ -54,16 +72,8 @@ final class PublicPaykitServiceTests: XCTestCase {
5472

5573
func testKnownMethodIdsFollowPaymentEndpointIdentifierSpec() {
5674
let specPattern = #"^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$"#
57-
let methodIds: [PublicPaykitService.MethodId] = [
58-
.bitcoinLightningBolt11,
59-
.bitcoinLightningLnurl,
60-
.bitcoinOnchainP2tr,
61-
.bitcoinOnchainP2wpkh,
62-
.bitcoinOnchainP2sh,
63-
.bitcoinOnchainP2pkh,
64-
]
6575

66-
for methodId in methodIds {
76+
for methodId in PublicPaykitService.MethodId.allCases {
6777
XCTAssertNotNil(methodId.rawValue.range(of: specPattern, options: .regularExpression), "\(methodId.rawValue) must be asset-rail-endpoint")
6878
}
6979
}
@@ -122,11 +132,13 @@ final class PublicPaykitServiceTests: XCTestCase {
122132
XCTAssertEqual(request, "lnurl1example")
123133
}
124134

125-
func testOnchainMethodIdUsesAddressPrefix() {
126-
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "bc1pexample"), .bitcoinOnchainP2tr)
127-
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "tb1qexample"), .bitcoinOnchainP2wpkh)
128-
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "3Example"), .bitcoinOnchainP2sh)
129-
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "1Example"), .bitcoinOnchainP2pkh)
135+
func testOnchainMethodIdUsesAddressPrefixAndNetwork() {
136+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "bc1pexample", network: .bitcoin), .bitcoinOnchainP2tr)
137+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "tb1qexample", network: .testnet), .testnetOnchainP2wpkh)
138+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "bcrt1qexample", network: .regtest), .regtestOnchainP2wpkh)
139+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "3Example", network: .bitcoin), .bitcoinOnchainP2sh)
140+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "2Example", network: .regtest), .regtestOnchainP2sh)
141+
XCTAssertEqual(PublicPaykitService.onchainMethodId(for: "1Example", network: .bitcoin), .bitcoinOnchainP2pkh)
130142
}
131143

132144
func testPaymentLaunchResultFailureMessageKeys() {

0 commit comments

Comments
 (0)