Skip to content
Draft
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 Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bitkitcore"
version = "0.8.0-swaps.2"
version = "0.8.0-swaps.4"
edition = "2021"
rust-version = "1.91"

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import PackageDescription
import Foundation

let tag = "v0.8.0-swaps.2"
let checksum = "69c085c480bab97cff77dbc4a654762a469d72598107937dcc0c59c73e6c4680"
let tag = "v0.8.0-swaps.4"
let checksum = "3ec9de280a2483ad059c3eaa366dfe639b1234d3335d6546f82c2f5176f82ed1"
let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip"

let localBinary = ProcessInfo.processInfo.environment["BITKIT_CORE_LOCAL"] == "1"
Expand Down
2 changes: 1 addition & 1 deletion bindings/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
group=com.synonym
version=0.8.0-swaps.2
version=0.8.0-swaps.4
215 changes: 208 additions & 7 deletions bindings/ios/bitkitcore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11652,6 +11652,11 @@ public struct PaykitRouteRequest {
* Fee rate for the wallet's own claim transaction.
*/
public var feeRateSatVb: UInt64
/**
* Routes through services the wallet holds a balance on (Spark), each already quoted by the
* app's engine for that service. They compete with the Pubky Swaps providers on fee.
*/
public var serviceLegs: [ServiceRouteLeg]

// Default memberwise initializers are never public by default, so we
// declare one manually.
Expand All @@ -11665,13 +11670,18 @@ public struct PaykitRouteRequest {
*/providers: [String],
/**
* Fee rate for the wallet's own claim transaction.
*/feeRateSatVb: UInt64) {
*/feeRateSatVb: UInt64,
/**
* Routes through services the wallet holds a balance on (Spark), each already quoted by the
* app's engine for that service. They compete with the Pubky Swaps providers on fee.
*/serviceLegs: [ServiceRouteLeg]) {
self.amountSat = amountSat
self.contactEndpoints = contactEndpoints
self.lightningSpendableSat = lightningSpendableSat
self.onchainSpendableSat = onchainSpendableSat
self.providers = providers
self.feeRateSatVb = feeRateSatVb
self.serviceLegs = serviceLegs
}
}

Expand Down Expand Up @@ -11700,6 +11710,9 @@ extension PaykitRouteRequest: Equatable, Hashable {
if lhs.feeRateSatVb != rhs.feeRateSatVb {
return false
}
if lhs.serviceLegs != rhs.serviceLegs {
return false
}
return true
}

Expand All @@ -11710,6 +11723,7 @@ extension PaykitRouteRequest: Equatable, Hashable {
hasher.combine(onchainSpendableSat)
hasher.combine(providers)
hasher.combine(feeRateSatVb)
hasher.combine(serviceLegs)
}
}

Expand All @@ -11729,7 +11743,8 @@ public struct FfiConverterTypePaykitRouteRequest: FfiConverterRustBuffer {
lightningSpendableSat: FfiConverterUInt64.read(from: &buf),
onchainSpendableSat: FfiConverterUInt64.read(from: &buf),
providers: FfiConverterSequenceString.read(from: &buf),
feeRateSatVb: FfiConverterUInt64.read(from: &buf)
feeRateSatVb: FfiConverterUInt64.read(from: &buf),
serviceLegs: FfiConverterSequenceTypeServiceRouteLeg.read(from: &buf)
)
}

Expand All @@ -11740,6 +11755,7 @@ public struct FfiConverterTypePaykitRouteRequest: FfiConverterRustBuffer {
FfiConverterUInt64.write(value.onchainSpendableSat, into: &buf)
FfiConverterSequenceString.write(value.providers, into: &buf)
FfiConverterUInt64.write(value.feeRateSatVb, into: &buf)
FfiConverterSequenceTypeServiceRouteLeg.write(value.serviceLegs, into: &buf)
}
}

Expand Down Expand Up @@ -12808,6 +12824,152 @@ public func FfiConverterTypeReverseSwapResponse_lower(_ value: ReverseSwapRespon
}


/**
* A one-leg route through a service the wallet holds a balance on, such as a Spark withdrawal
* to the contact's on-chain address. The app quotes it and runs it.
*/
public struct ServiceRouteLeg {
/**
* The service, such as `spark`.
*/
public var service: String
/**
* The contact endpoint the service delivers to. It must be one the contact published.
*/
public var endpoint: PaykitEndpoint
/**
* What the contact receives.
*/
public var deliveredSat: UInt64
/**
* What the wallet's balance on the service is debited.
*/
public var debitSat: UInt64
/**
* Who the wallet trusts to complete the leg: `trusted` for Spark's operators. Pubky Swaps
* legs are `hash`.
*/
public var atomicity: String
/**
* When the quote stops holding, or 0 when it does not expire.
*/
public var validUntilUnix: UInt64

// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(
/**
* The service, such as `spark`.
*/service: String,
/**
* The contact endpoint the service delivers to. It must be one the contact published.
*/endpoint: PaykitEndpoint,
/**
* What the contact receives.
*/deliveredSat: UInt64,
/**
* What the wallet's balance on the service is debited.
*/debitSat: UInt64,
/**
* Who the wallet trusts to complete the leg: `trusted` for Spark's operators. Pubky Swaps
* legs are `hash`.
*/atomicity: String,
/**
* When the quote stops holding, or 0 when it does not expire.
*/validUntilUnix: UInt64) {
self.service = service
self.endpoint = endpoint
self.deliveredSat = deliveredSat
self.debitSat = debitSat
self.atomicity = atomicity
self.validUntilUnix = validUntilUnix
}
}

#if compiler(>=6)
extension ServiceRouteLeg: Sendable {}
#endif


extension ServiceRouteLeg: Equatable, Hashable {
public static func ==(lhs: ServiceRouteLeg, rhs: ServiceRouteLeg) -> Bool {
if lhs.service != rhs.service {
return false
}
if lhs.endpoint != rhs.endpoint {
return false
}
if lhs.deliveredSat != rhs.deliveredSat {
return false
}
if lhs.debitSat != rhs.debitSat {
return false
}
if lhs.atomicity != rhs.atomicity {
return false
}
if lhs.validUntilUnix != rhs.validUntilUnix {
return false
}
return true
}

public func hash(into hasher: inout Hasher) {
hasher.combine(service)
hasher.combine(endpoint)
hasher.combine(deliveredSat)
hasher.combine(debitSat)
hasher.combine(atomicity)
hasher.combine(validUntilUnix)
}
}

extension ServiceRouteLeg: Codable {}



#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeServiceRouteLeg: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ServiceRouteLeg {
return
try ServiceRouteLeg(
service: FfiConverterString.read(from: &buf),
endpoint: FfiConverterTypePaykitEndpoint.read(from: &buf),
deliveredSat: FfiConverterUInt64.read(from: &buf),
debitSat: FfiConverterUInt64.read(from: &buf),
atomicity: FfiConverterString.read(from: &buf),
validUntilUnix: FfiConverterUInt64.read(from: &buf)
)
}

public static func write(_ value: ServiceRouteLeg, into buf: inout [UInt8]) {
FfiConverterString.write(value.service, into: &buf)
FfiConverterTypePaykitEndpoint.write(value.endpoint, into: &buf)
FfiConverterUInt64.write(value.deliveredSat, into: &buf)
FfiConverterUInt64.write(value.debitSat, into: &buf)
FfiConverterString.write(value.atomicity, into: &buf)
FfiConverterUInt64.write(value.validUntilUnix, into: &buf)
}
}


#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeServiceRouteLeg_lift(_ buf: RustBuffer) throws -> ServiceRouteLeg {
return try FfiConverterTypeServiceRouteLeg.lift(buf)
}

#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeServiceRouteLeg_lower(_ value: ServiceRouteLeg) -> RustBuffer {
return FfiConverterTypeServiceRouteLeg.lower(value)
}


/**
* Result from querying a single Bitcoin address.
*/
Expand Down Expand Up @@ -24077,6 +24239,11 @@ public enum PaykitRoute {
*/
case swap(quote: PubkySwapRouteQuote
)
/**
* A service the wallet holds a balance on delivers to the contact. The app runs the leg.
*/
case service(leg: ServiceRouteLeg
)
/**
* Nothing can pay this contact. `reason` says why, for the user.
*/
Expand Down Expand Up @@ -24105,7 +24272,10 @@ public struct FfiConverterTypePaykitRoute: FfiConverterRustBuffer {
case 2: return .swap(quote: try FfiConverterTypePubkySwapRouteQuote.read(from: &buf)
)

case 3: return .none(reason: try FfiConverterString.read(from: &buf)
case 3: return .service(leg: try FfiConverterTypeServiceRouteLeg.read(from: &buf)
)

case 4: return .none(reason: try FfiConverterString.read(from: &buf)
)

default: throw UniffiInternalError.unexpectedEnumCase
Expand All @@ -24126,8 +24296,13 @@ public struct FfiConverterTypePaykitRoute: FfiConverterRustBuffer {
FfiConverterTypePubkySwapRouteQuote.write(quote, into: &buf)


case let .none(reason):
case let .service(leg):
writeInt(&buf, Int32(3))
FfiConverterTypeServiceRouteLeg.write(leg, into: &buf)


case let .none(reason):
writeInt(&buf, Int32(4))
FfiConverterString.write(reason, into: &buf)

}
Expand Down Expand Up @@ -29354,6 +29529,31 @@ fileprivate struct FfiConverterSequenceTypePubkyProfileLink: FfiConverterRustBuf
}
}

#if swift(>=5.8)
@_documentation(visibility: private)
#endif
fileprivate struct FfiConverterSequenceTypeServiceRouteLeg: FfiConverterRustBuffer {
typealias SwiftType = [ServiceRouteLeg]

public static func write(_ value: [ServiceRouteLeg], into buf: inout [UInt8]) {
let len = Int32(value.count)
writeInt(&buf, len)
for item in value {
FfiConverterTypeServiceRouteLeg.write(item, into: &buf)
}
}

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ServiceRouteLeg] {
let len: Int32 = try readInt(&buf)
var seq = [ServiceRouteLeg]()
seq.reserveCapacity(Int(len))
for _ in 0 ..< len {
seq.append(try FfiConverterTypeServiceRouteLeg.read(from: &buf))
}
return seq
}
}

#if swift(>=5.8)
@_documentation(visibility: private)
#endif
Expand Down Expand Up @@ -31971,8 +32171,9 @@ public func pubkySwapsRestore(providers: [String], electrumUrl: String, payoutAd
}
/**
* Pick how to pay a Paykit contact. Returns `Direct` when a balance the wallet holds reaches
* one of the contact's endpoints, otherwise the cheapest firm quote from the trusted Pubky
* Swaps providers, or `None` with a reason. The user never picks the swap.
* one of the contact's endpoints, otherwise the cheaper of the request's service legs and the
* trusted Pubky Swaps providers' firm quotes, or `None` with a reason. The user never picks
* the route.
*
* Each candidate in `request.providers`, at most 30, is asked for its offer over iroh. iroh finds
* a candidate's address through n0's discovery servers, so n0 sees the keys looked up from the
Expand Down Expand Up @@ -33304,7 +33505,7 @@ private let initializationResult: InitializationResult = {
if (uniffi_bitkitcore_checksum_func_pubky_swaps_restore() != 21980) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_bitkitcore_checksum_func_pubky_swaps_route_paykit_payment() != 51456) {
if (uniffi_bitkitcore_checksum_func_pubky_swaps_route_paykit_payment() != 45030) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_bitkitcore_checksum_func_record_rbf_boost() != 15433) {
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3449,8 +3449,9 @@ pub async fn pubky_swaps_provider_edges(
}

/// Pick how to pay a Paykit contact. Returns `Direct` when a balance the wallet holds reaches
/// one of the contact's endpoints, otherwise the cheapest firm quote from the trusted Pubky
/// Swaps providers, or `None` with a reason. The user never picks the swap.
/// one of the contact's endpoints, otherwise the cheaper of the request's service legs and the
/// trusted Pubky Swaps providers' firm quotes, or `None` with a reason. The user never picks
/// the route.
///
/// Each candidate in `request.providers`, at most 30, is asked for its offer over iroh. iroh finds
/// a candidate's address through n0's discovery servers, so n0 sees the keys looked up from the
Expand Down
17 changes: 17 additions & 0 deletions src/modules/pubky_swaps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ exactly the amount asked for (`exact_out`): the lock covers the wallet's own cla
`pubky_swaps_route_paykit_payment` returns `Direct` when a balance reaches one of the contact's
endpoints, so the app only swaps when it must.

## Service routes

Services the wallet holds a balance on can also deliver to the contact: a Spark withdrawal pays an
on-chain address from Spending without a Pubky Swaps provider. The app quotes each such leg with
its own engine for the service (Spark's fee quote) and passes it in
`PaykitRouteRequest.service_legs`; the router never talks to Spark.

When no balance reaches the contact directly, the router keeps the cheapest service leg that
delivers exactly the amount to an endpoint the contact published and has not expired, asks the
providers for a swap quote, and returns whichever costs less in fees: `Service` with the leg, which
the app runs, or `Swap`. A swap wins a tie, since a hash lock needs no one's trust; a service leg
states who it trusts in `atomicity` (`trusted` for Spark).

Lightning through Spark needs no service leg: the app counts that balance as
Lightning-spendable, and `Direct` covers it. One route has one leg: a service paying a provider's
hold invoice is not routed yet.

Before the invoice reaches the app, the module checks the quote against the request
(`validate_quote`), the contract against the quote and the chain tip (`validate_accept`), rebuilds
the HTLC from its own keys (`verify_contract`), and binds the hold invoice to the quote and the
Expand Down
Loading
Loading