diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
index 14b8eeb45..204ab8061 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
@@ -482,6 +482,90 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Bool {
// Override point for customization after application launch.
+
return true
}
diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift
index d861b6ceb..ce02d82c9 100644
--- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift
+++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift
@@ -6,7 +6,10 @@ import WalletConnectSign
struct AccountDetails {
let chain: String
let methods: [String]
- let account: String
+ let address: String
+ var account: String {
+ "\(chain):\(address)"
+ }
}
diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
index e89ea8a8e..09c318daf 100644
--- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
+++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
@@ -70,7 +70,7 @@ final class SessionAccountPresenter: ObservableObject {
}
func copyUri() {
- UIPasteboard.general.string = sessionAccount.account
+ UIPasteboard.general.string = sessionAccount.address
}
}
@@ -128,17 +128,17 @@ extension SessionAccountPresenter.Errors: LocalizedError {
// MARK: - Transaction Stub
private enum Stub {
struct Transaction: Codable {
- let from, to, data, gas: String
+ let from, to, data, gasLimit: String
let gasPrice, value, nonce: String
}
static let tx = [Transaction(from: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
to: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
- data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
- gas: "0x76c0",
- gasPrice: "0x9184e72a000",
- value: "0x9184e72a",
- nonce: "0x117")]
+ data: "0x",
+ gasLimit: "0x5208",
+ gasPrice: "0x013e3d2ed4",
+ value: "0x00",
+ nonce: "0x09")]
static let eth_signTypedData = """
{
"types": {
diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift
index 939a9edb6..247bc800a 100644
--- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift
+++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift
@@ -16,7 +16,7 @@ struct SessionAccountView: View {
ScrollView {
VStack(spacing: 12) {
networkView(title: String(presenter.sessionAccount.chain.split(separator: ":").first ?? ""))
- accountView(address: presenter.sessionAccount.account)
+ accountView(address: presenter.sessionAccount.address)
methodsView(methods: presenter.sessionAccount.methods)
Spacer()
diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift
index c16bbe68c..8c95927cb 100644
--- a/Example/DApp/Modules/Sign/SignPresenter.swift
+++ b/Example/DApp/Modules/Sign/SignPresenter.swift
@@ -160,6 +160,13 @@ extension SignPresenter {
}
.store(in: &subscriptions)
+ Sign.instance.sessionSettlePublisher
+ .receive(on: DispatchQueue.main)
+ .sink { [unowned self] _ in
+ self.getSession()
+ }
+ .store(in: &subscriptions)
+
Sign.instance.authResponsePublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] response in
@@ -218,7 +225,7 @@ extension SignPresenter {
AccountDetails(
chain: account.blockchainIdentifier,
methods: Array(namespace.methods),
- account: account.address
+ address: account.address
)
)
}
diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift
index 7be67ee89..bad0d9b54 100644
--- a/Example/DApp/Modules/Sign/SignView.swift
+++ b/Example/DApp/Modules/Sign/SignView.swift
@@ -84,7 +84,7 @@ struct SignView: View {
.padding(12)
} else {
VStack {
- ForEach(presenter.accountsDetails, id: \.chain) { account in
+ ForEach(presenter.accountsDetails, id: \.account) { account in
Button {
presenter.presentSessionAccount(sessionAccount: account)
} label: {
diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj
index 988d32e57..1e805fb68 100644
--- a/Example/ExampleApp.xcodeproj/project.pbxproj
+++ b/Example/ExampleApp.xcodeproj/project.pbxproj
@@ -38,7 +38,9 @@
84474A0129B9EB74005F520B /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 84474A0029B9EB74005F520B /* Starscream */; };
84474A0229B9ECA2005F520B /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; };
8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 8448F1D327E4726F0000B866 /* WalletConnect */; };
+ 8454EF0A2C9421B600B5529E /* SmartAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454EF092C9421B600B5529E /* SmartAccountManager.swift */; };
845B8D8C2934B36C0084A966 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845B8D8B2934B36C0084A966 /* Account.swift */; };
+ 846CA5C82CB901F8005E8C15 /* Yttrium in Frameworks */ = {isa = PBXBuildFile; productRef = 846CA5C72CB901F8005E8C15 /* Yttrium */; };
846E359F2C00654F00E63DF4 /* ConfigModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E359E2C00654F00E63DF4 /* ConfigModule.swift */; };
846E35A22C0065AD00E63DF4 /* ConfigRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E35A12C0065AD00E63DF4 /* ConfigRouter.swift */; };
846E35A42C0065B600E63DF4 /* ConfigPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E35A32C0065B600E63DF4 /* ConfigPresenter.swift */; };
@@ -47,6 +49,7 @@
84733CD32C1C2A4B001B2850 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */; };
84733CD42C1C2C24001B2850 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B6A372B06697B00162B01 /* ProfilingService.swift */; };
84733CD52C1C2CEB001B2850 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE25D293F56D6004840D1 /* InputConfig.swift */; };
+ 84733CDA2C258BDB001B2850 /* SmartAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84733CD92C258BDB001B2850 /* SmartAccount.swift */; };
847BD1D62989492500076C90 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D12989492500076C90 /* MainViewController.swift */; };
847BD1D82989492500076C90 /* MainModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D32989492500076C90 /* MainModule.swift */; };
847BD1D92989492500076C90 /* MainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D42989492500076C90 /* MainPresenter.swift */; };
@@ -339,6 +342,7 @@
844511C92C8BA69D00A6A86C /* AppKitLab.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppKitLab.entitlements; sourceTree = ""; };
844749F329B9E5B9005F520B /* RelayIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RelayIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
844749F529B9E5B9005F520B /* RelayClientEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayClientEndToEndTests.swift; sourceTree = ""; };
+ 8454EF092C9421B600B5529E /* SmartAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartAccountManager.swift; sourceTree = ""; };
845AA7D929BA1EBA00F33739 /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = IntegrationTests.xctestplan; path = ExampleApp.xcodeproj/IntegrationTests.xctestplan; sourceTree = ""; };
845AA7DC29BB424800F33739 /* SmokeTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SmokeTests.xctestplan; sourceTree = ""; };
845B8D8B2934B36C0084A966 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; };
@@ -347,6 +351,7 @@
846E35A32C0065B600E63DF4 /* ConfigPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigPresenter.swift; sourceTree = ""; };
846E35A52C0065C100E63DF4 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; };
846E35A72C006C5600E63DF4 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
+ 84733CD92C258BDB001B2850 /* SmartAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartAccount.swift; sourceTree = ""; };
847BD1D12989492500076C90 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
847BD1D32989492500076C90 /* MainModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainModule.swift; sourceTree = ""; };
847BD1D42989492500076C90 /* MainPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainPresenter.swift; sourceTree = ""; };
@@ -629,6 +634,7 @@
A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */,
84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */,
8421447F2C81863A004FF494 /* ReownWalletKit in Frameworks */,
+ 846CA5C82CB901F8005E8C15 /* Yttrium in Frameworks */,
8421447D2C80A544004FF494 /* ReownAppKitUI in Frameworks */,
84CA52172C88965C0069BB33 /* ReownRouter in Frameworks */,
);
@@ -1008,6 +1014,8 @@
isa = PBXGroup;
children = (
A5D610CC2AB3592F00C20083 /* ListingsSertice */,
+ 84733CD92C258BDB001B2850 /* SmartAccount.swift */,
+ 8454EF092C9421B600B5529E /* SmartAccountManager.swift */,
);
path = BusinessLayer;
sourceTree = "";
@@ -1630,6 +1638,7 @@
8421447C2C80A544004FF494 /* ReownAppKitUI */,
8421447E2C81863A004FF494 /* ReownWalletKit */,
84CA52162C88965C0069BB33 /* ReownRouter */,
+ 846CA5C72CB901F8005E8C15 /* Yttrium */,
);
productName = ChatWallet;
productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */;
@@ -1708,6 +1717,7 @@
84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */,
84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */,
844511B82C8B6BC800A6A86C /* XCRemoteSwiftPackageReference "atlantis" */,
+ 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */,
);
productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */;
projectDirPath = "";
@@ -1989,6 +1999,7 @@
C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */,
C56EE24F293F566D004840D1 /* WalletView.swift in Sources */,
C55D34B22965FB750004314A /* SessionProposalView.swift in Sources */,
+ 84733CDA2C258BDB001B2850 /* SmartAccount.swift in Sources */,
C56EE248293F566D004840D1 /* ScanQR.swift in Sources */,
847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */,
C55D349B2965BC2F0004314A /* TagsView.swift in Sources */,
@@ -2020,6 +2031,7 @@
847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */,
A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */,
A50B6A382B06697B00162B01 /* ProfilingService.swift in Sources */,
+ 8454EF0A2C9421B600B5529E /* SmartAccountManager.swift in Sources */,
A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */,
C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */,
C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */,
@@ -2564,6 +2576,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = WalletApp/Other/Info.plist;
+ INFOPLIST_KEY_LSApplicationCategoryType = "";
INFOPLIST_KEY_NSCameraUsageDescription = "Camera access for scanning QR code";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -2602,6 +2615,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = WalletApp/Other/Info.plist;
+ INFOPLIST_KEY_LSApplicationCategoryType = "";
INFOPLIST_KEY_NSCameraUsageDescription = "Camera access for scanning QR code";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -2755,6 +2769,14 @@
minimumVersion = 1.24.0;
};
};
+ 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/WalletConnect/yttrium";
+ requirement = {
+ kind = upToNextMinorVersion;
+ minimumVersion = 0.1.0;
+ };
+ };
84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mixpanel/mixpanel-swift";
@@ -2880,6 +2902,11 @@
isa = XCSwiftPackageProductDependency;
productName = WalletConnect;
};
+ 846CA5C72CB901F8005E8C15 /* Yttrium */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */;
+ productName = Yttrium;
+ };
8486EDD22B4F2EA6008E53C3 /* SwiftMessages */ = {
isa = XCSwiftPackageProductDependency;
package = 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */;
diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 01ca2540d..866c577c3 100644
--- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/ProxymanApp/atlantis",
"state": {
"branch": null,
- "revision": "5145a7041ec71421d09653db87dcc80c81792004",
- "version": "1.24.0"
+ "revision": "523dd773538e1e20036cb2d28f8b9947448c2d20",
+ "version": "1.25.1"
}
},
{
@@ -96,8 +96,8 @@
"repositoryURL": "https://github.com/apple/swift-docc-plugin",
"state": {
"branch": null,
- "revision": "2eb22993b3dfd0c0d32729b357c8dabb6cd44680",
- "version": "1.4.2"
+ "revision": "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
+ "version": "1.4.3"
}
},
{
@@ -110,21 +110,21 @@
}
},
{
- "package": "swift-qrcode-generator",
- "repositoryURL": "https://github.com/dagronf/swift-qrcode-generator",
+ "package": "swift-dotenv",
+ "repositoryURL": "https://github.com/thebarndog/swift-dotenv.git",
"state": {
"branch": null,
- "revision": "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b",
- "version": "1.0.3"
+ "revision": "f6e7ca817d35eeccb20b62c87ee75963b01b29dc",
+ "version": "2.0.1"
}
},
{
- "package": "swift-snapshot-testing",
- "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing",
+ "package": "swift-qrcode-generator",
+ "repositoryURL": "https://github.com/dagronf/swift-qrcode-generator",
"state": {
"branch": null,
- "revision": "f29e2014f6230cf7d5138fc899da51c7f513d467",
- "version": "1.10.0"
+ "revision": "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b",
+ "version": "1.0.3"
}
},
{
@@ -189,6 +189,15 @@
"revision": "569255adcfff0b37e4cb8004aea29d0e2d6266df",
"version": "1.0.2"
}
+ },
+ {
+ "package": "yttrium",
+ "repositoryURL": "https://github.com/WalletConnect/yttrium",
+ "state": {
+ "branch": null,
+ "revision": "04d1589c42c510d0f2487e58b0af9619a6f070b4",
+ "version": "0.1.0"
+ }
}
]
},
diff --git a/Example/Shared/InputConfig.swift b/Example/Shared/InputConfig.swift
index b8ce51193..1deb5cf71 100644
--- a/Example/Shared/InputConfig.swift
+++ b/Example/Shared/InputConfig.swift
@@ -16,7 +16,15 @@ struct InputConfig {
static var mixpanelToken: String? {
return config(for: "MIXPANEL_TOKEN")
}
-
+
+ static var pimlicoBundlerUrl: String? {
+ return config(for: "PIMLICO_BUNDLER_URL")
+ }
+
+ static var rpcUrl: String? {
+ return config(for: "RPC_URL")
+ }
+
private static func config(for key: String) -> String? {
return Bundle.main.object(forInfoDictionaryKey: key) as? String
}
diff --git a/Example/Shared/Signer/ETHSigner.swift b/Example/Shared/Signer/ETHSigner.swift
index 232dcffc2..0d940c821 100644
--- a/Example/Shared/Signer/ETHSigner.swift
+++ b/Example/Shared/Signer/ETHSigner.swift
@@ -39,6 +39,24 @@ struct ETHSigner {
return AnyCodable(result)
}
+ func signHash(_ hashToSign: String) throws -> String {
+
+ let dataToSign: Bytes
+ if hashToSign.hasPrefix("0x") {
+ // Hex-encoded message, remove "0x" and convert
+ let messageData = Data(hex: String(hashToSign.dropFirst(2)))
+ dataToSign = messageData.bytes
+ } else {
+ // Plain text message, convert directly to data
+ let messageData = Data(hashToSign.utf8)
+ dataToSign = messageData.bytes
+ }
+
+ let (v, r, s) = try! privateKey.sign(hash: dataToSign)
+ let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16)
+ return result
+ }
+
func signTypedData(_ params: AnyCodable) -> AnyCodable {
let result = "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
return AnyCodable(result)
diff --git a/Example/Shared/Signer/Signer.swift b/Example/Shared/Signer/Signer.swift
index 3091c6c98..3075d82df 100644
--- a/Example/Shared/Signer/Signer.swift
+++ b/Example/Shared/Signer/Signer.swift
@@ -1,15 +1,87 @@
import Foundation
import Commons
import WalletConnectSign
+import Yttrium
+
+struct SendCallsParams: Codable {
+ let version: String
+ let from: String
+ let calls: [Call]
+
+ struct Call: Codable {
+ let to: String?
+ let value: String?
+ let data: String?
+ let chainId: String?
+ }
+}
+
+enum SmartAccountType {
+ case simple
+ case safe
+}
final class Signer {
enum Errors: Error {
case notImplemented
+ case unknownSmartAccountType
}
-
+
private init() {}
- static func sign(request: Request, importAccount: ImportAccount) throws -> AnyCodable {
+ static func sign(request: Request, importAccount: ImportAccount) async throws -> AnyCodable {
+ if let accountType = try await getRequestedSmartAccountType(request) {
+ return try await signWithSmartAccount(request: request, accountType: accountType)
+ } else {
+ return try signWithEOA(request: request, importAccount: importAccount)
+ }
+ }
+
+ private static func getRequestedSmartAccountType(_ request: Request) async throws -> SmartAccountType? {
+ let account = try await getRequestedAccount(request)
+ if account == nil {
+ return nil
+ }
+
+ let safeSmartAccountAddress = try await SmartAccountSafe.instance.getClient().getAddress()
+
+ if account?.lowercased() == safeSmartAccountAddress.lowercased() {
+ return .safe
+ }
+
+ return nil
+ }
+
+ private static func getRequestedAccount(_ request: Request) async throws -> String? {
+ // Attempt to decode params for transaction requests encapsulated in an array of dictionaries
+ if let paramsArray = try? request.params.get([AnyCodable].self),
+ let firstParam = paramsArray.first?.value as? [String: Any],
+ let account = firstParam["from"] as? String {
+ return account
+ }
+
+ // Attempt to decode params for signing message requests
+ if let paramsArray = try? request.params.get([AnyCodable].self) {
+ if request.method == "personal_sign" || request.method == "eth_signTypedData" {
+ // Typically, the account address is the second parameter for personal_sign and eth_signTypedData
+ if paramsArray.count > 1,
+ let account = paramsArray[1].value as? String {
+ return account
+ }
+ }
+ // Handle the `wallet_sendCalls` method
+ if request.method == "wallet_sendCalls" {
+ if let sendCallsParams = paramsArray.first?.value as? [String: Any],
+ let account = sendCallsParams["from"] as? String {
+ return account
+ }
+ }
+ }
+
+ return nil
+ }
+
+ private static func signWithEOA(request: Request, importAccount: ImportAccount) throws -> AnyCodable {
let signer = ETHSigner(importAccount: importAccount)
switch request.method {
@@ -24,9 +96,65 @@ final class Signer {
case "solana_signTransaction":
return SOLSigner.signTransaction(request.params)
-
+
+ default:
+ throw Errors.notImplemented
+ }
+ }
+
+ private static func signWithSmartAccount(request: Request, accountType: SmartAccountType) async throws -> AnyCodable {
+ let client: AccountClientProtocol
+ switch accountType {
+ case .safe:
+ client = await SmartAccountSafe.instance.getClient()
+ default:
+ fatalError("Only safe is currently supported")
+ }
+
+ switch request.method {
+ case "personal_sign":
+ let params = try request.params.get([String].self)
+ let message = params[0]
+ let signedMessage = try client.signMessage(message)
+ return AnyCodable(signedMessage)
+
+ case "eth_signTypedData":
+ let params = try request.params.get([String].self)
+ let message = params[0]
+ let signedMessage = try client.signMessage(message)
+ return AnyCodable(signedMessage)
+
+ case "eth_sendTransaction":
+ let params = try request.params.get([Yttrium.Transaction].self)
+ let result = try await client.sendTransactions(params)
+ return AnyCodable(result)
+
+ case "wallet_sendCalls":
+ let params = try request.params.get([SendCallsParams].self)
+ guard let calls = params.first?.calls else {
+ fatalError()
+ }
+
+ let transactions = calls.map {
+ Yttrium.Transaction(
+ to: $0.to!,
+ value: $0.value!,
+ data: $0.data!
+ )
+ }
+
+ let userOpHash = try await client.sendTransactions(transactions)
+
+ Task {
+ let userOpReceipt = try await SmartAccountSafe.instance.getClient().waitForUserOperationReceipt(userOperationHash: userOpHash)
+ guard let userOpReceiptSting = userOpReceipt.jsonString else { return }
+ AlertPresenter.present(message: userOpReceiptSting, type: .info)
+ }
+
+ return AnyCodable(userOpHash)
+
default:
- throw Signer.Errors.notImplemented
+ throw Errors.notImplemented
}
}
}
@@ -35,6 +163,7 @@ extension Signer.Errors: LocalizedError {
var errorDescription: String? {
switch self {
case .notImplemented: return "Requested method is not implemented"
+ case .unknownSmartAccountType: return "Unknown smart account type"
}
}
}
diff --git a/Example/WalletApp/ApplicationLayer/AppDelegate.swift b/Example/WalletApp/ApplicationLayer/AppDelegate.swift
index f2f961b36..993235565 100644
--- a/Example/WalletApp/ApplicationLayer/AppDelegate.swift
+++ b/Example/WalletApp/ApplicationLayer/AppDelegate.swift
@@ -8,6 +8,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
+ let entryPointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" // v0.7 on Sepolia
+ let chainId = 11155111 // Sepolia
+ SmartAccountSafe.instance.configure(entryPoint: entryPointAddress, chainId: chainId)
return true
}
@@ -39,5 +42,4 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
) {
print("Failed to register: \(error)")
}
-
}
diff --git a/Example/WalletApp/BusinessLayer/SmartAccount.swift b/Example/WalletApp/BusinessLayer/SmartAccount.swift
new file mode 100644
index 000000000..88ccede51
--- /dev/null
+++ b/Example/WalletApp/BusinessLayer/SmartAccount.swift
@@ -0,0 +1,94 @@
+import Foundation
+import Yttrium
+import WalletConnectUtils
+
+extension Yttrium.AccountClient {
+
+ func getAccount() async throws -> Account {
+ let chain = try Blockchain(namespace: "eip155", reference: chainId)
+ let address = try await getAddress()
+ return try Account(blockchain: chain, accountAddress: address)
+ }
+}
+
+class SmartAccountSafe {
+
+ static var instance = SmartAccountSafe()
+
+ private var client: AccountClient? {
+ didSet {
+ if let _ = client {
+ clientSetContinuation?.resume()
+ }
+ }
+ }
+
+ private var clientSetContinuation: CheckedContinuation?
+
+ private var config: Config?
+
+ private init() {
+
+ }
+
+ public func configure(entryPoint: String, chainId: Int) {
+ self.config = Config(
+ entryPoint: entryPoint,
+ chainId: chainId
+ )
+ }
+
+ public func register(owner: String, privateKey: String) {
+ guard let config = self.config else {
+ fatalError("Error - you must call SmartAccount.configure(entryPoint:chainId:onSign:) before accessing the shared instance.")
+ }
+ assert(owner.count == 40)
+
+ let localConfig = Yttrium.Config.local()
+
+ let pimlicoBundlerUrl = "https://\(InputConfig.pimlicoBundlerUrl!)"
+ let rpcUrl = "https://\(InputConfig.rpcUrl!)"
+ let pimlicoSepolia = Yttrium.Config(
+ endpoints: .init(
+ rpc: .init(baseURL: rpcUrl),
+ bundler: .init(baseURL: pimlicoBundlerUrl),
+ paymaster: .init(baseURL: pimlicoBundlerUrl)
+ )
+ )
+
+ let pickedConfig = if !(InputConfig.pimlicoBundlerUrl?.isEmpty ?? true) && !(InputConfig.rpcUrl?.isEmpty ?? true) {
+ pimlicoSepolia
+ } else {
+ localConfig
+ }
+
+ let client = AccountClient(
+ ownerAddress: owner,
+ entryPoint: config.entryPoint,
+ chainId: config.chainId,
+ config: pickedConfig,
+ safe: true
+ )
+ client.register(privateKey: privateKey)
+
+ self.client = client
+ }
+
+
+ public func getClient() async -> AccountClient {
+ if let client = client {
+ return client
+ }
+
+ await withCheckedContinuation { continuation in
+ self.clientSetContinuation = continuation
+ }
+
+ return client!
+ }
+
+ struct Config {
+ let entryPoint: String
+ let chainId: Int
+ }
+}
diff --git a/Example/WalletApp/BusinessLayer/SmartAccountManager.swift b/Example/WalletApp/BusinessLayer/SmartAccountManager.swift
new file mode 100644
index 000000000..60fec7c37
--- /dev/null
+++ b/Example/WalletApp/BusinessLayer/SmartAccountManager.swift
@@ -0,0 +1,42 @@
+import Foundation
+
+class SmartAccountManager {
+ enum Errors: Error {
+ case smartAccountNotEnabled
+ }
+
+ // Singleton instance
+ static let shared = SmartAccountManager()
+
+ // Use a private queue for thread-safe access to the isSmartAccountEnabled property
+ private let queue = DispatchQueue(label: "com.smartaccount.manager", attributes: .concurrent)
+
+ // A private backing variable for the thread-safe property
+ private var _isSmartAccountEnabled: Bool = false
+
+ // Thread-safe access for setting and getting isSmartAccountEnabled
+ var isSmartAccountEnabled: Bool {
+ get {
+ return queue.sync {
+ _isSmartAccountEnabled
+ }
+ }
+ set {
+ queue.async(flags: .barrier) {
+ self._isSmartAccountEnabled = newValue
+ }
+ }
+ }
+
+ // Private initializer to ensure it cannot be instantiated externally
+ private init() {}
+
+ // Function to get smart account addresses
+ func getSmartAccountsAddresses() async throws -> [String] {
+ guard isSmartAccountEnabled else {
+ throw Errors.smartAccountNotEnabled
+ }
+ let safeAccountAddress = try await SmartAccountSafe.instance.getClient().getAddress()
+ return [safeAccountAddress]
+ }
+}
diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist
index f542d355a..12593cb44 100644
--- a/Example/WalletApp/Other/Info.plist
+++ b/Example/WalletApp/Other/Info.plist
@@ -30,8 +30,12 @@
INSendMessageIntent
+ PIMLICO_BUNDLER_URL
+ $(PIMLICO_BUNDLER_URL)
PROJECT_ID
$(PROJECT_ID)
+ RPC_URL
+ $(RPC_URL)
SIMULATOR_IDENTIFIER
$(SIMULATOR_IDENTIFIER)
UIApplicationSceneManifest
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift
index d2186d935..06be96dd0 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift
@@ -1,4 +1,8 @@
import SwiftUI
+import Web3
+import Yttrium
+
+let mnemonic = "test test test test test test test test test test test junk"
final class MainModule {
@discardableResult
@@ -8,8 +12,45 @@ final class MainModule {
let presenter = MainPresenter(router: router, interactor: interactor, importAccount: importAccount, pushRegisterer: app.pushRegisterer, configurationService: app.configurationService)
let viewController = MainViewController(presenter: presenter)
+ configureSmartAccountOnSign(importAccount: importAccount)
router.viewController = viewController
return viewController
}
+
+ static func configureSmartAccountOnSign(importAccount: ImportAccount) {
+ let privateKey = importAccount.privateKey
+ let ownerAddress = String(importAccount.account.address.dropFirst(2))
+ SmartAccountSafe.instance.register(
+ owner: ownerAddress,
+ privateKey: privateKey
+ )
+// SmartAccount.instance.register(onSign: { (messageToSign: String) in
+// func dataToHash(_ data: Data) -> Bytes {
+// let prefix = "\u{19}Ethereum Signed Message:\n"
+// let prefixData = (prefix + String(data.count)).data(using: .utf8)!
+// let prefixedMessageData = prefixData + data
+// return .init(hex: prefixedMessageData.toHexString())
+// }
+//
+// let prvKey = try! EthereumPrivateKey(hexPrivateKey: importAccount.privateKey)
+//
+// // Determine if the message is hex-encoded or plain text
+// let dataToSign: Bytes
+// if messageToSign.hasPrefix("0x") {
+// // Hex-encoded message, remove "0x" and convert
+// let messageData = Data(hex: String(messageToSign.dropFirst(2)))
+// dataToSign = dataToHash(messageData)
+// } else {
+// // Plain text message, convert directly to data
+// let messageData = Data(messageToSign.utf8)
+// dataToSign = dataToHash(messageData)
+// }
+//
+// // Sign the data
+// let (v, r, s) = try! prvKey.sign(message: .init(Data(dataToSign)))
+// let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16)
+// return .success(result)
+// })
+ }
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
index 372a3437d..dfc484842 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
@@ -19,7 +19,7 @@ final class MainPresenter {
return [
router.walletViewController(importAccount: importAccount),
router.notificationsViewController(importAccount: importAccount),
- router.settingsViewController()
+ router.settingsViewController(importAccount: importAccount)
]
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
index c85eb61b9..f1ea36065 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
@@ -22,8 +22,8 @@ final class MainRouter {
.wrapToNavigationController()
}
- func settingsViewController() -> UIViewController {
- return SettingsModule.create(app: app)
+ func settingsViewController(importAccount: ImportAccount) -> UIViewController {
+ return SettingsModule.create(app: app, importAccount: importAccount)
.wrapToNavigationController()
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
index c5580bda0..68a010e41 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
@@ -4,16 +4,26 @@ import ReownWalletKit
import ReownRouter
final class SessionProposalInteractor {
- func approve(proposal: Session.Proposal, account: Account) async throws -> Bool {
+ func approve(proposal: Session.Proposal, EOAAccount: Account) async throws -> Bool {
// Following properties are used to support all the required and optional namespaces for the testing purposes
let supportedMethods = Set(proposal.requiredNamespaces.flatMap { $0.value.methods } + (proposal.optionalNamespaces?.flatMap { $0.value.methods } ?? []))
let supportedEvents = Set(proposal.requiredNamespaces.flatMap { $0.value.events } + (proposal.optionalNamespaces?.flatMap { $0.value.events } ?? []))
let supportedRequiredChains = proposal.requiredNamespaces["eip155"]?.chains ?? []
let supportedOptionalChains = proposal.optionalNamespaces?["eip155"]?.chains ?? []
- var supportedChains = supportedRequiredChains + supportedOptionalChains
+ var supportedChains = supportedRequiredChains + supportedOptionalChains
- let supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: account.address)! }
+ var supportedAccounts: [Account]
+ var sessionProperties = [String: String]()
+
+ if SmartAccountManager.shared.isSmartAccountEnabled {
+ let sepolia = Blockchain("eip155:11155111")!
+ let smartAccountAddresses = try await SmartAccountManager.shared.getSmartAccountsAddresses()
+ supportedAccounts = smartAccountAddresses.map { Account(blockchain: sepolia, address: $0)! }
+ sessionProperties = getSessionProperties(addresses: smartAccountAddresses)
+ } else {
+ supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: EOAAccount.address)! }
+ }
/* Use only supported values for production. I.e:
let supportedMethods = ["eth_signTransaction", "personal_sign", "eth_signTypedData", "eth_sendTransaction", "eth_sign"]
@@ -40,7 +50,8 @@ final class SessionProposalInteractor {
AlertPresenter.present(message: error.localizedDescription, type: .error)
return false
}
- _ = try await WalletKit.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties)
+
+ _ = try await WalletKit.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: sessionProperties)
if let uri = proposal.proposer.redirect?.native {
ReownRouter.goBack(uri: uri)
return false
@@ -56,4 +67,34 @@ final class SessionProposalInteractor {
ReownRouter.goBack(uri: uri)
}
}
+
+ private func getSessionProperties(addresses: [String]) -> [String: String] {
+ var addressCapabilities: [String] = []
+
+ // Iterate over the addresses and construct JSON strings for each address
+ for address in addresses {
+ let capability = """
+ "\(address)":{
+ "0xaa36a7":{
+ "atomicBatch":{
+ "supported":true
+ }
+ }
+ }
+ """
+ addressCapabilities.append(capability)
+ }
+
+ // Join all the address capabilities into one JSON-like structure
+ let sepoliaAtomicBatchCapabilities = "{\(addressCapabilities.joined(separator: ","))}"
+
+ let sessionProperties: [String: String] = [
+ "bundler_name": "pimlico",
+ "capabilities": sepoliaAtomicBatchCapabilities
+ ]
+
+ print(sessionProperties)
+ return sessionProperties
+ }
}
+
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
index c9c658c83..8060bf9d3 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
@@ -36,7 +36,7 @@ final class SessionProposalPresenter: ObservableObject {
func onApprove() async throws {
do {
ActivityIndicatorManager.shared.start()
- let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account)
+ let showConnected = try await interactor.approve(proposal: sessionProposal, EOAAccount: importAccount.account)
showConnected ? showConnectedSheet.toggle() : router.dismiss()
ActivityIndicatorManager.shared.stop()
} catch {
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
index b33e79088..2d186abd6 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
@@ -6,7 +6,7 @@ import ReownRouter
final class SessionRequestInteractor {
func respondSessionRequest(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool {
do {
- let result = try Signer.sign(request: sessionRequest, importAccount: importAccount)
+ let result = try await Signer.sign(request: sessionRequest, importAccount: importAccount)
try await WalletKit.instance.respond(
topic: sessionRequest.topic,
requestId: sessionRequest.id,
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift
index a29152e51..04db03bfa 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift
@@ -3,10 +3,10 @@ import SwiftUI
final class SettingsModule {
@discardableResult
- static func create(app: Application) -> UIViewController {
+ static func create(app: Application, importAccount: ImportAccount) -> UIViewController {
let router = SettingsRouter(app: app)
let interactor = SettingsInteractor()
- let presenter = SettingsPresenter(interactor: interactor, router: router, accountStorage: app.accountStorage)
+ let presenter = SettingsPresenter(interactor: interactor, router: router, accountStorage: app.accountStorage, importAccount: importAccount)
let view = SettingsView().environmentObject(presenter)
let viewController = SceneViewController(viewModel: presenter, content: view)
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift
index 937e80b01..630080d24 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift
@@ -2,22 +2,50 @@ import UIKit
import Combine
import WalletConnectNetworking
import ReownWalletKit
+import Yttrium
final class SettingsPresenter: ObservableObject {
private let interactor: SettingsInteractor
+ private let importAccount: ImportAccount
private let router: SettingsRouter
private let accountStorage: AccountStorage
-
private var disposeBag = Set()
+ @Published var smartAccount: String = "Loading..."
+ @Published var smartAccountSafe: String = "Loading..."
- init(interactor: SettingsInteractor, router: SettingsRouter, accountStorage: AccountStorage) {
+ init(interactor: SettingsInteractor, router: SettingsRouter, accountStorage: AccountStorage, importAccount: ImportAccount) {
defer { setupInitialState() }
self.interactor = interactor
self.router = router
self.accountStorage = accountStorage
+ self.importAccount = importAccount
+ fetchSmartAccountSafe()
+ }
+
+ func fetchSmartAccountSafe() {
+ Task {
+ do {
+ let smartAccount = try await getSmartAccountSafe()
+ DispatchQueue.main.async {
+ self.smartAccountSafe = smartAccount
+ }
+ } catch {
+ DispatchQueue.main.async {
+ self.smartAccountSafe = "Failed to load"
+ }
+ print("Failed to get smart account safe: \(error)")
+ }
+ }
}
+ func enableSmartAccount(_ enable: Bool) {
+ SmartAccountManager.shared.isSmartAccountEnabled = enable
+ }
+
+ private func getSmartAccountSafe() async throws -> String {
+ try await SmartAccountSafe.instance.getClient().getAccount().absoluteString
+ }
var account: String {
guard let importAccount = accountStorage.importAccount else { return .empty }
@@ -43,6 +71,26 @@ final class SettingsPresenter: ObservableObject {
router.presentBrowser()
}
+ func sendTransaction() async throws -> String {
+ let client = await SmartAccountSafe.instance.getClient()
+
+ let prepareSendTransactions = try await client.prepareSendTransactions([.init(
+ to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
+ value: "0",
+ data: "0x68656c6c6f"
+ )])
+
+ let owner = client.ownerAddress
+
+ let signer = ETHSigner(importAccount: importAccount)
+
+ let signature = try signer.signHash(prepareSendTransactions.hash)
+
+ let ownerSignature = OwnerSignature(owner: owner, signature: signature)
+
+ return try await client.doSendTransaction(signatures: [ownerSignature], params: prepareSendTransactions.doSendTransactionParams)
+ }
+
func logoutPressed() async throws {
guard let account = accountStorage.importAccount?.account else { return }
try? await interactor.notifyUnregister(account: account)
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift
index 30835d1ab..938702d6f 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift
@@ -2,11 +2,11 @@ import SwiftUI
import AsyncButton
import ReownAppKitUI
-struct SettingsView: View {
+struct SettingsView: View {
@EnvironmentObject var viewModel: SettingsPresenter
-
@State private var copyAlert: Bool = false
+ @State private var isSmartAccountEnabled: Bool = false // State for the toggle switch
var body: some View {
ScrollView {
@@ -16,7 +16,26 @@ struct SettingsView: View {
Group {
header(title: "Account")
row(title: "CAIP-10", subtitle: viewModel.account)
+ row(title: "Smart Account Safe", subtitle: viewModel.smartAccountSafe)
row(title: "Private key", subtitle: viewModel.privateKey)
+
+ // New Smart Account Toggle Row
+ HStack {
+ Text("Smart Account")
+ .foregroundColor(.Foreground100)
+ .font(.paragraph700)
+
+ Spacer()
+
+ Toggle("", isOn: $isSmartAccountEnabled)
+ .onChange(of: isSmartAccountEnabled) { newValue in
+ viewModel.enableSmartAccount(newValue)
+ }
+ .labelsHidden()
+ }
+ .padding(.horizontal, 12)
+ .padding(.vertical, 16)
+ .background(Color.Foreground100.opacity(0.05).cornerRadius(12))
}
.padding(.horizontal, 20)
@@ -40,6 +59,20 @@ struct SettingsView: View {
}
.frame(height: 44.0)
+ AsyncButton {
+ try await sendTransactionSafe()
+ } label: {
+ Text("Send Transaction Safe")
+ .foregroundColor(.green)
+ .frame(maxWidth: .infinity)
+ }
+ .frame(height: 44.0)
+ .overlay(
+ RoundedRectangle(cornerRadius: Radius.m)
+ .stroke(Color.green, lineWidth: 1)
+ )
+ .padding(.bottom, 24)
+
AsyncButton {
try await viewModel.logoutPressed()
} label: {
@@ -62,9 +95,15 @@ struct SettingsView: View {
}
.onAppear {
viewModel.objectWillChange.send()
+ isSmartAccountEnabled = SmartAccountManager.shared.isSmartAccountEnabled
}
}
+ @discardableResult
+ func sendTransactionSafe() async throws -> String {
+ try await viewModel.sendTransaction()
+ }
+
func header(title: String) -> some View {
HStack {
Text(title)
@@ -108,10 +147,10 @@ struct SettingsView: View {
}
func separator() -> some View {
- Rectangle()
- .foregroundColor(.Foreground100.opacity(0.05))
- .frame(maxWidth: .infinity)
- .frame(height: 1)
- .padding(.top, 8)
+ Rectangle()
+ .foregroundColor(.Foreground100.opacity(0.05))
+ .frame(maxWidth: .infinity)
+ .frame(height: 1)
+ .padding(.top, 8)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift
index f97cfe29f..c5480c919 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift
@@ -24,9 +24,9 @@ final class WelcomePresenter: ObservableObject {
func onImport() {
guard let account = ImportAccount(input: input)
else { return input = .empty }
-
importAccount(account)
}
+
}
// MARK: Private functions
diff --git a/Package.resolved b/Package.resolved
index acfc8dd9a..64598b065 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -28,6 +28,15 @@
"version": "1.0.0"
}
},
+ {
+ "package": "swift-dotenv",
+ "repositoryURL": "https://github.com/thebarndog/swift-dotenv.git",
+ "state": {
+ "branch": null,
+ "revision": "f6e7ca817d35eeccb20b62c87ee75963b01b29dc",
+ "version": "2.0.1"
+ }
+ },
{
"package": "swift-qrcode-generator",
"repositoryURL": "https://github.com/dagronf/swift-qrcode-generator",
@@ -37,6 +46,15 @@
"version": "1.0.3"
}
},
+ {
+ "package": "swift-snapshot-testing",
+ "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing",
+ "state": {
+ "branch": null,
+ "revision": "f29e2014f6230cf7d5138fc899da51c7f513d467",
+ "version": "1.10.0"
+ }
+ },
{
"package": "SwiftImageReadWrite",
"repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite",
@@ -45,6 +63,15 @@
"revision": "5596407d1cf61b953b8e658fa8636a471df3c509",
"version": "1.1.6"
}
+ },
+ {
+ "package": "CoinbaseWalletSDK",
+ "repositoryURL": "https://github.com/WalletConnect/wallet-mobile-sdk",
+ "state": {
+ "branch": null,
+ "revision": "b6dfb7d6b8447c7c5b238a10443a1ac28223f38f",
+ "version": "1.0.0"
+ }
}
]
},
diff --git a/Package.swift b/Package.swift
index 3a37d537d..0ae1fb5af 100644
--- a/Package.swift
+++ b/Package.swift
@@ -2,6 +2,38 @@
import PackageDescription
+// Determine if Yttrium should be used in debug (local) mode
+let yttriumDebug = false
+
+
+// Define dependencies array
+var dependencies: [Package.Dependency] = [
+ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
+ .package(url: "https://github.com/WalletConnect/QRCode", from: "14.3.1"),
+ .package(name: "CoinbaseWalletSDK", url: "https://github.com/MobileWalletProtocol/wallet-mobile-sdk", .upToNextMinor(from: "1.0.0")),
+// .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", .upToNextMinor(from: "1.10.0")),
+]
+//var yttriumTarget: Target!
+//// Conditionally add Yttrium dependency
+//if yttriumDebug {
+// var yttriumSwiftSettings: [SwiftSetting] = []
+// dependencies.append(.package(path: "../yttrium/crates/ffi/YttriumCore"))
+// yttriumSwiftSettings.append(.define("YTTRIUM_DEBUG"))
+// yttriumTarget = .target(
+// name: "YttriumWrapper",
+// dependencies: [.product(name: "YttriumCore", package: "YttriumCore")],
+// path: "Sources/YttriumWrapper",
+// swiftSettings: yttriumSwiftSettings
+// )
+//} else {
+// dependencies.append(.package(url: "https://github.com/reown-com/yttrium", .upToNextMinor(from: "0.1.0")))
+// yttriumTarget = .target(
+// name: "YttriumWrapper",
+// dependencies: [.product(name: "Yttrium", package: "yttrium")],
+// path: "Sources/YttriumWrapper"
+// )
+//}
+
let package = Package(
name: "reown",
platforms: [
@@ -42,14 +74,12 @@ let package = Package(
targets: ["ReownAppKit"]),
.library(
name: "ReownAppKitUI",
- targets: ["ReownAppKitUI"])
- ],
- dependencies: [
- .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
- .package(url: "https://github.com/WalletConnect/QRCode", from: "14.3.1"),
- .package(name: "CoinbaseWalletSDK", url: "https://github.com/MobileWalletProtocol/wallet-mobile-sdk", .upToNextMinor(from: "1.0.0")),
- .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", .upToNextMinor(from: "1.10.0")),
+ targets: ["ReownAppKitUI"]),
+// .library(
+// name: "YttriumWrapper",
+// targets: ["YttriumWrapper"])
],
+ dependencies: dependencies,
targets: [
.target(
name: "WalletConnectSign",
@@ -156,6 +186,7 @@ let package = Package(
name: "ReownAppKitBackport",
path: "Sources/ReownAppKitBackport"
),
+// yttriumTarget,
.testTarget(
name: "WalletConnectSignTests",
dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]),
diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift
index ceda7ba04..07ed632d7 100644
--- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift
+++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift
@@ -3,13 +3,13 @@ import Combine
public class Serializer: Serializing {
- enum Errors: Error, CustomStringConvertible {
+ public enum Errors: Error, CustomStringConvertible {
case symmetricKeyForTopicNotFound(String)
case publicKeyForTopicNotFound
case invalidType2Envelope
case topicNotFound
- var description: String {
+ public var description: String {
switch self {
case .symmetricKeyForTopicNotFound(let topic):
return "Error: Symmetric key for topic '\(topic)' was not found."
diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json
index f212de827..1f6b5d28d 100644
--- a/Sources/WalletConnectRelay/PackageConfig.json
+++ b/Sources/WalletConnectRelay/PackageConfig.json
@@ -1 +1 @@
-{"version": "1.0.4"}
+{"version": "1.0.5"}
diff --git a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
index a1c617e49..990e1ed8a 100644
--- a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
+++ b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
@@ -22,10 +22,20 @@ class DeleteSessionService {
let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message)
logger.debug("Will delete session for reason: message: \(reason.message) code: \(reason.code)")
let request = RPCRequest(method: protocolMethod.method, params: reason)
- try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
+
+ do {
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
+ } catch let error as Serializer.Errors {
+ switch error {
+ case .symmetricKeyForTopicNotFound, .publicKeyForTopicNotFound:
+ logger.debug("Encountered Serializer error during session deletion: \(error)")
+ default:
+ throw error
+ }
+ }
+
sessionStore.delete(topic: topic)
logger.debug("Session disconnected")
kms.deleteSymmetricKey(for: topic)
networkingInteractor.unsubscribe(topic: topic)
- }
-}
+ }}
diff --git a/Sources/WalletConnectUtils/Account.swift b/Sources/WalletConnectUtils/Account.swift
index 7d5c2cbee..eb1b7615d 100644
--- a/Sources/WalletConnectUtils/Account.swift
+++ b/Sources/WalletConnectUtils/Account.swift
@@ -11,7 +11,9 @@
[CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md
*/
public struct Account: Equatable, Hashable {
-
+ public enum Errors: Error {
+ case invalidInitFormat
+ }
/// A blockchain namespace. Usually describes an ecosystem or standard.
public let namespace: String
@@ -71,6 +73,13 @@ public struct Account: Equatable, Hashable {
public init?(blockchain: Blockchain, address: String) {
self.init("\(blockchain.absoluteString):\(address)")
}
+
+ public init(blockchain: Blockchain, accountAddress: String) throws {
+ guard let instance = Self("\(blockchain.absoluteString):\(accountAddress)") else {
+ throw Errors.invalidInitFormat
+ }
+ self = instance
+ }
}
extension Account: LosslessStringConvertible {
diff --git a/Sources/WalletConnectUtils/Blockchain.swift b/Sources/WalletConnectUtils/Blockchain.swift
index 8d920f01d..9e839bdb9 100644
--- a/Sources/WalletConnectUtils/Blockchain.swift
+++ b/Sources/WalletConnectUtils/Blockchain.swift
@@ -9,7 +9,9 @@
[CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md
*/
public struct Blockchain: Equatable, Hashable {
-
+ public enum Errors: Error {
+ case invalidInitFormat
+ }
/// A blockchain namespace. Usually describes an ecosystem or standard.
public let namespace: String
@@ -43,6 +45,13 @@ public struct Blockchain: Equatable, Hashable {
public init?(namespace: String, reference: String) {
self.init("\(namespace):\(reference)")
}
+
+ public init(namespace: String, reference: Int) throws {
+ guard let instance = Self("\(namespace):\(reference)") else {
+ throw Errors.invalidInitFormat
+ }
+ self = instance
+ }
}
extension Blockchain: LosslessStringConvertible {
diff --git a/Sources/YttriumWrapper/YttriumWrapper.swift b/Sources/YttriumWrapper/YttriumWrapper.swift
new file mode 100644
index 000000000..b37426a43
--- /dev/null
+++ b/Sources/YttriumWrapper/YttriumWrapper.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+#if YTTRIUM_DEBUG
+@_exported import YttriumCore
+#else
+@_exported import Yttrium
+#endif