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

Smart Contract Wallets #403

Merged
merged 38 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ede8122
update package
nplasterer Sep 13, 2024
70c3a41
Merge branch 'main' of https://github.com/xmtp/xmtp-ios
nplasterer Sep 13, 2024
c72c33c
Merge branch 'main' of https://github.com/xmtp/xmtp-ios
nplasterer Sep 20, 2024
2f9cca8
add chain id and SCW check
nplasterer Sep 20, 2024
4e43786
add implementation
nplasterer Sep 20, 2024
ede8c29
fix a little formatting
nplasterer Sep 21, 2024
fd8ddc1
change defaults
nplasterer Sep 21, 2024
bc9fb62
Merge branch 'main' of https://github.com/xmtp/xmtp-ios
nplasterer Sep 22, 2024
65a4aaa
make a test release pod
nplasterer Sep 24, 2024
a366503
bump the latest libxmtp
nplasterer Sep 24, 2024
a46302b
fix up all the async tests
nplasterer Sep 24, 2024
16e2e6d
add installation timestamps and async members
nplasterer Sep 26, 2024
35f8053
Merge branch 'main' of https://github.com/xmtp/xmtp-ios into np/smart…
nplasterer Sep 26, 2024
f02d489
fix up the tests and bump the pod
nplasterer Sep 26, 2024
0e8ab5b
Merge branch 'np/stream-groups-logging' of https://github.com/xmtp/xm…
nplasterer Sep 26, 2024
e246103
bump to the next version
nplasterer Sep 26, 2024
f19b9b7
Merge branch 'main' of https://github.com/xmtp/xmtp-ios into np/smart…
nplasterer Sep 26, 2024
ef8dea9
bad merge
nplasterer Sep 26, 2024
03d4215
Merge branch 'main' of https://github.com/xmtp/xmtp-ios into np/smart…
nplasterer Oct 2, 2024
67a88fc
update the package
nplasterer Oct 2, 2024
929ac84
fix up bad merge
nplasterer Oct 2, 2024
a2d472a
Merge branch 'main' of https://github.com/xmtp/xmtp-ios into np/smart…
nplasterer Oct 9, 2024
63b79e1
make block number optional
nplasterer Oct 9, 2024
402ee51
add a test to reproduce the scw error
nplasterer Oct 9, 2024
d1f509d
update to latest libxmtp
nplasterer Oct 11, 2024
abe6af8
update the signers
nplasterer Oct 18, 2024
2cd20ce
update to the latest libxmtp functions
nplasterer Oct 18, 2024
2812b8a
fix the linter
nplasterer Oct 18, 2024
b03101f
get on a working version
nplasterer Oct 19, 2024
263bf64
check the chain id
nplasterer Oct 19, 2024
14b0cff
chain id is optional
nplasterer Oct 19, 2024
b986370
fix the lint issue
nplasterer Oct 20, 2024
87b8b42
tag
nplasterer Oct 20, 2024
4f36f8c
remove chain id from inbox id creation
nplasterer Oct 23, 2024
e704cea
update the SCW functionality and message listing
nplasterer Oct 23, 2024
39e52ef
small tweak to message listing
nplasterer Oct 23, 2024
b1678a9
get closer
nplasterer Oct 23, 2024
b169c71
small test clean up
nplasterer Oct 23, 2024
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 Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ let package = Package(
.package(url: "https://github.com/1024jp/GzipSwift", from: "5.2.0"),
.package(url: "https://github.com/bufbuild/connect-swift", exact: "0.12.0"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
.package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "0.5.9-beta1"),
.package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "0.5.10"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand Down
35 changes: 35 additions & 0 deletions Sources/XMTPTestHelpers/TestHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#if canImport(XCTest)
import Combine
import CryptoKit
import XCTest
@testable import XMTPiOS
import LibXMTP
Expand Down Expand Up @@ -67,6 +68,40 @@ public struct FakeWallet: SigningKey {
}
}

public struct FakeSCWWallet: SigningKey {
public var walletAddress: String
private var internalSignature: String

public init() throws {
// Simulate a wallet address (could be derived from a hash of some internal data)
self.walletAddress = UUID().uuidString // Using UUID for uniqueness in this fake example
self.internalSignature = Data(repeating: 0x01, count: 64).toHex // Fake internal signature
}

public var address: String {
walletAddress
}

public var type: WalletType {
WalletType.SCW
}

public var chainId: Int64? {
1
}

public static func generate() throws -> FakeSCWWallet {
return try FakeSCWWallet()
}

public func signSCW(message: String) async throws -> Data {
// swiftlint:disable force_unwrapping
let digest = SHA256.hash(data: message.data(using: .utf8)!)
// swiftlint:enable force_unwrapping
return Data(digest)
}
}

@available(iOS 15, *)
public struct Fixtures {
public var alice: PrivateKey!
Expand Down
67 changes: 52 additions & 15 deletions Sources/XMTPiOS/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,39 +161,64 @@ public final class Client {
}
}

// This is a V3 only feature
public static func createOrBuild(account: SigningKey, options: ClientOptions) async throws -> Client {
let inboxId = try await getOrCreateInboxId(options: options, address: account.address)

static func initializeClient(
accountAddress: String,
options: ClientOptions,
signingKey: SigningKey?,
inboxId: String
) async throws -> Client {
let (libxmtpClient, dbPath) = try await initV3Client(
accountAddress: account.address,
accountAddress: accountAddress,
options: options,
privateKeyBundleV1: nil,
signingKey: account,
signingKey: signingKey,
inboxId: inboxId
)

guard let v3Client = libxmtpClient else {
throw ClientError.noV3Client("Error no V3 client initialized")
}

let client = try Client(
address: account.address,
address: accountAddress,
v3Client: v3Client,
dbPath: dbPath,
installationID: v3Client.installationId().toHex,
inboxID: v3Client.inboxId(),
environment: options.api.env
)

let conversations = client.conversations
let contacts = client.contacts

for codec in (options.codecs) {
// Register codecs
for codec in options.codecs {
client.register(codec: codec)
}

return client
}

public static func createV3(account: SigningKey, options: ClientOptions) async throws -> Client {
let accountAddress = account.address
let inboxId = try await getOrCreateInboxId(options: options, address: accountAddress)

return try await initializeClient(
accountAddress: accountAddress,
options: options,
signingKey: account,
inboxId: inboxId
)
}

public static func buildV3(address: String, options: ClientOptions) async throws -> Client {
let inboxId = try await getOrCreateInboxId(options: options, address: address)

return try await initializeClient(
accountAddress: address,
options: options,
signingKey: nil,
inboxId: inboxId
)
}

static func initV3Client(
accountAddress: String,
options: ClientOptions?,
Expand Down Expand Up @@ -224,7 +249,7 @@ public final class Client {
let alias = "xmtp-\(options?.api.env.rawValue ?? "")-\(inboxId).db3"
let dbURL = directoryURL.appendingPathComponent(alias).path

var encryptionKey = options?.dbEncryptionKey
let encryptionKey = options?.dbEncryptionKey
if (encryptionKey == nil) {
throw ClientError.creationError("No encryption key passed for the database. Please store and provide a secure encryption key.")
}
Expand All @@ -246,8 +271,20 @@ public final class Client {
if let signatureRequest = v3Client.signatureRequest() {
if let signingKey = signingKey {
do {
let signedData = try await signingKey.sign(message: signatureRequest.signatureText())
try await signatureRequest.addEcdsaSignature(signatureBytes: signedData.rawData)
if signingKey.type == WalletType.SCW {
guard let chainId = signingKey.chainId else {
throw ClientError.creationError("Chain id must be present to sign Smart Contract Wallet")
}
let signedData = try await signingKey.signSCW(message: signatureRequest.signatureText())
try await signatureRequest.addScwSignature(signatureBytes: signedData,
address: signingKey.address,
chainId: UInt64(chainId),
blockNumber: signingKey.blockNumber.flatMap { $0 >= 0 ? UInt64($0) : nil })

} else {
let signedData = try await signingKey.sign(message: signatureRequest.signatureText())
try await signatureRequest.addEcdsaSignature(signatureBytes: signedData.rawData)
}
try await v3Client.registerIdentity(signatureRequest: signatureRequest)
} catch {
throw ClientError.creationError("Failed to sign the message: \(error.localizedDescription)")
Expand Down Expand Up @@ -651,7 +688,7 @@ public final class Client {
throw ClientError.noV3Client("Error no V3 client initialized")
}
do {
return Group(ffiGroup: try client.group(groupId: groupId.hexToData), client: self)
return Group(ffiGroup: try client.conversation(conversationId: groupId.hexToData), client: self)
} catch {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/XMTPiOS/Contacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public class ConsentList {
func groupState(groupId: String) async throws -> ConsentState {
if let client = client.v3Client {
return try await client.getConsentState(
entityType: .groupId,
entityType: .conversationId,
entity: groupId
).fromFFI
}
Expand Down
14 changes: 7 additions & 7 deletions Sources/XMTPiOS/Conversations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class GroupStreamCallback: FfiConversationCallback {
self.callback = callback
}

func onConversation(conversation: FfiGroup) {
func onConversation(conversation: FfiConversation) {
self.callback(conversation.fromFFI(client: client))
}
}
Expand Down Expand Up @@ -119,7 +119,7 @@ public actor Conversations {
guard let v3Client = client.v3Client else {
return 0
}
return try await v3Client.conversations().syncAllGroups()
return try await v3Client.conversations().syncAllConversations()
}

public func groups(createdAfter: Date? = nil, createdBefore: Date? = nil, limit: Int? = nil) async throws -> [Group] {
Expand All @@ -136,7 +136,7 @@ public actor Conversations {
if let limit {
options.limit = Int64(limit)
}
return try await v3Client.conversations().list(opts: options).map { $0.fromFFI(client: client) }
return try await v3Client.conversations().listGroups(opts: options).map { $0.fromFFI(client: client) }
}

public func streamGroups() async throws -> AsyncThrowingStream<Group, Error> {
Expand All @@ -150,7 +150,7 @@ public actor Conversations {
}
continuation.yield(group)
}
guard let stream = await self.client.v3Client?.conversations().stream(callback: groupCallback) else {
guard let stream = await self.client.v3Client?.conversations().streamGroups(callback: groupCallback) else {
continuation.finish(throwing: GroupError.streamingFailure)
return
}
Expand All @@ -175,7 +175,7 @@ public actor Conversations {
AsyncThrowingStream { continuation in
let ffiStreamActor = FfiStreamActor()
let task = Task {
let stream = await self.client.v3Client?.conversations().stream(
let stream = await self.client.v3Client?.conversations().streamGroups(
callback: GroupStreamCallback(client: self.client) { group in
guard !Task.isCancelled else {
continuation.finish()
Expand Down Expand Up @@ -435,7 +435,7 @@ public actor Conversations {
AsyncThrowingStream { continuation in
let ffiStreamActor = FfiStreamActor()
let task = Task {
let stream = await self.client.v3Client?.conversations().streamAllMessages(
let stream = await self.client.v3Client?.conversations().streamAllGroupMessages(
messageCallback: MessageCallback(client: self.client) { message in
guard !Task.isCancelled else {
continuation.finish()
Expand Down Expand Up @@ -500,7 +500,7 @@ public actor Conversations {
AsyncThrowingStream { continuation in
let ffiStreamActor = FfiStreamActor()
let task = Task {
let stream = await self.client.v3Client?.conversations().streamAllMessages(
let stream = await self.client.v3Client?.conversations().streamAllGroupMessages(
messageCallback: MessageCallback(client: self.client) { message in
guard !Task.isCancelled else {
continuation.finish()
Expand Down
6 changes: 3 additions & 3 deletions Sources/XMTPiOS/Extensions/Ffi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ extension FfiV2SubscribeRequest {

// MARK: Group

extension FfiGroup {
extension FfiConversation {
func fromFFI(client: Client) -> Group {
Group(ffiGroup: self, client: client)
}
}

extension FfiGroupMember {
extension FfiConversationMember {
var fromFFI: Member {
Member(ffiGroupMember: self)
}
Expand Down Expand Up @@ -230,7 +230,7 @@ extension FfiConsentState {
extension EntryType {
var toFFI: FfiConsentEntityType{
switch (self) {
case .group_id: return FfiConsentEntityType.groupId
case .group_id: return FfiConsentEntityType.conversationId
case .inbox_id: return FfiConsentEntityType.inboxId
case .address: return FfiConsentEntityType.address
}
Expand Down
54 changes: 32 additions & 22 deletions Sources/XMTPiOS/Group.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class StreamHolder {
}

public struct Group: Identifiable, Equatable, Hashable {
var ffiGroup: FfiGroup
var ffiGroup: FfiConversation
var client: Client
let streamHolder = StreamHolder()

Expand All @@ -39,7 +39,7 @@ public struct Group: Identifiable, Equatable, Hashable {
Topic.groupMessage(id).description
}

func metadata() throws -> FfiGroupMetadata {
func metadata() throws -> FfiConversationMetadata {
return try ffiGroup.groupMetadata()
}

Expand Down Expand Up @@ -230,12 +230,12 @@ public struct Group: Identifiable, Equatable, Hashable {
}

public func processMessage(envelopeBytes: Data) async throws -> DecodedMessage {
let message = try await ffiGroup.processStreamedGroupMessage(envelopeBytes: envelopeBytes)
let message = try await ffiGroup.processStreamedConversationMessage(envelopeBytes: envelopeBytes)
return try MessageV3(client: client, ffiMessage: message).decode()
}

public func processMessageDecrypted(envelopeBytes: Data) async throws -> DecryptedMessage {
let message = try await ffiGroup.processStreamedGroupMessage(envelopeBytes: envelopeBytes)
let message = try await ffiGroup.processStreamedConversationMessage(envelopeBytes: envelopeBytes)
return try MessageV3(client: client, ffiMessage: message).decrypt()
}

Expand Down Expand Up @@ -373,7 +373,8 @@ public struct Group: Identifiable, Equatable, Hashable {
sentBeforeNs: nil,
sentAfterNs: nil,
limit: nil,
deliveryStatus: nil
deliveryStatus: nil,
direction: nil
)

if let before {
Expand Down Expand Up @@ -402,16 +403,20 @@ public struct Group: Identifiable, Equatable, Hashable {
}()

options.deliveryStatus = status

let direction: FfiDirection? = {
switch direction {
case .ascending:
return FfiDirection.ascending
default:
return FfiDirection.descending
}
}()

let messages = try ffiGroup.findMessages(opts: options).compactMap { ffiMessage in
return MessageV3(client: self.client, ffiMessage: ffiMessage).decodeOrNull()
}
options.direction = direction

switch direction {
case .ascending:
return messages
default:
return messages.reversed()
return try ffiGroup.findMessages(opts: options).compactMap { ffiMessage in
return MessageV3(client: self.client, ffiMessage: ffiMessage).decodeOrNull()
}
}

Expand All @@ -426,7 +431,8 @@ public struct Group: Identifiable, Equatable, Hashable {
sentBeforeNs: nil,
sentAfterNs: nil,
limit: nil,
deliveryStatus: nil
deliveryStatus: nil,
direction: nil
)

if let before {
Expand Down Expand Up @@ -455,16 +461,20 @@ public struct Group: Identifiable, Equatable, Hashable {
}()

options.deliveryStatus = status

let direction: FfiDirection? = {
switch direction {
case .ascending:
return FfiDirection.ascending
default:
return FfiDirection.descending
}
}()

let messages = try ffiGroup.findMessages(opts: options).compactMap { ffiMessage in
options.direction = direction

return try ffiGroup.findMessages(opts: options).compactMap { ffiMessage in
return MessageV3(client: self.client, ffiMessage: ffiMessage).decryptOrNull()
}

switch direction {
case .ascending:
return messages
default:
return messages.reversed()
}
}
}
4 changes: 2 additions & 2 deletions Sources/XMTPiOS/Mls/Member.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public enum PermissionLevel {
}

public struct Member {
var ffiGroupMember: FfiGroupMember
var ffiGroupMember: FfiConversationMember

init(ffiGroupMember: FfiGroupMember) {
init(ffiGroupMember: FfiConversationMember) {
self.ffiGroupMember = ffiGroupMember
}

Expand Down
Loading
Loading