From 4c05bb7b4122c026aa4cd684e7ce6e9e8b9eb1ae Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:41:58 +0900 Subject: [PATCH] implement solana_signTransaction --- .../Services/WalletConnectorManager.swift | 19 ++++++- .../Services/WalletConnectorSigner.swift | 19 ++++++- .../Navigation/AssetNavigationView.swift | 2 +- .../SelectAssetSceneNavigationStack.swift | 2 +- .../Staking/StakeNavigationView.swift | 6 +- .../WalletConnectorNavigationStack.swift | 2 +- Gem/Swap/Navigation/SwapNavigationView.swift | 2 +- .../ConfirmTransferNavigationStack.swift | 8 +-- .../Navigation/RecipientNavigationView.swift | 4 +- .../Scenes/ConfirmTransferScene.swift | 6 +- .../ConfirmTransferViewModel.swift | 29 ++++++---- .../SelectedAssetNavigationStack.swift | 2 +- .../Sources/Solana/SolanaService.swift | 4 +- Packages/Primitives/Sources/Action.swift | 1 + .../Sources/TransferDataExtra.swift | 11 +++- .../Primitives/Sources/TransferDataType.swift | 7 +++ .../Signer/Sources/Chains/SolanaSigner.swift | 57 ++++++++++++++----- .../SwapService/Sources/SwapService.swift | 2 +- .../Sources/WalletConnectorService.swift | 7 +-- 19 files changed, 137 insertions(+), 53 deletions(-) diff --git a/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorManager.swift b/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorManager.swift index 9c0212f8..ffbc07b4 100644 --- a/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorManager.swift +++ b/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorManager.swift @@ -81,10 +81,27 @@ extension WalletConnectorManager: WalletConnectorInteractable { } public func signTransaction(transferData: WCTransferData) async throws -> String { - fatalError("") + try await self.presentPayload(transferData) } public func sendRawTransaction(transferData: WCTransferData) async throws -> String { fatalError("") } + + private func presentPayload(_ payload: WCTransferData) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + let transferDataCallback = TransferDataCallback(payload: payload) { result in + switch result { + case let .success(id): + continuation.resume(returning: id) + case .failure(let error): + continuation.resume(throwing: error) + } + } + Task { @MainActor [weak self] in + guard let self else { return } + self.presenter.isPresentingSheet = .transferData(transferDataCallback) + } + } + } } diff --git a/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorSigner.swift b/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorSigner.swift index 41bdaa19..e9c81603 100644 --- a/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorSigner.swift +++ b/Features/WalletConnector/Sources/WalletConnector/Services/WalletConnectorSigner.swift @@ -83,7 +83,24 @@ public final class WalletConnectorSigner: WalletConnectorSignable { } public func signTransaction(sessionId: String, chain: Chain, transaction: WalletConnectorTransaction) async throws -> String { - throw AnyError("Not supported yet") + let session = try store.getConnection(id: sessionId) + let wallet = try keystore.getWallet(session.wallet.walletId) + + switch transaction { + case .ethereum: throw AnyError("Not supported yet") + case .solana(let tx): + let transferData = TransferData( + type: .generic(asset: chain.asset, metadata: session.session.metadata, extra: TransferDataExtra(data: tx.data(using: .utf8), outputType: .signature)), + recipientData: RecipientData( + asset: chain.asset, + recipient: Recipient(name: .none, address: "", memo: .none), + amount: .none + ), + value: .zero, + canChangeValue: false + ) + return try await walletConnectorInteractor.signTransaction(transferData: WCTransferData(tranferData: transferData, wallet: wallet)) + } } public func sendTransaction(sessionId: String, chain: Chain, transaction: WalletConnectorTransaction) async throws -> String { diff --git a/Gem/Asset/Navigation/AssetNavigationView.swift b/Gem/Asset/Navigation/AssetNavigationView.swift index 73b3c219..db398eb4 100644 --- a/Gem/Asset/Navigation/AssetNavigationView.swift +++ b/Gem/Asset/Navigation/AssetNavigationView.swift @@ -48,7 +48,7 @@ struct AssetNavigationView: View { ConfirmTransferNavigationStack( wallet: wallet, transferData: data - ) { + ) { _ in transferData = .none } } diff --git a/Gem/Assets/Navigation/SelectAssetSceneNavigationStack.swift b/Gem/Assets/Navigation/SelectAssetSceneNavigationStack.swift index 2903aa01..ccbf3760 100644 --- a/Gem/Assets/Navigation/SelectAssetSceneNavigationStack.swift +++ b/Gem/Assets/Navigation/SelectAssetSceneNavigationStack.swift @@ -68,7 +68,7 @@ struct SelectAssetSceneNavigationStack: View { wallet: model.wallet, asset: input.asset, navigationPath: $navigationPath, - onComplete: { + onComplete: { _ in isPresentingSelectType = nil } ) diff --git a/Gem/Root/Navigation/Staking/StakeNavigationView.swift b/Gem/Root/Navigation/Staking/StakeNavigationView.swift index 660fd954..56b69755 100644 --- a/Gem/Root/Navigation/Staking/StakeNavigationView.swift +++ b/Gem/Root/Navigation/Staking/StakeNavigationView.swift @@ -19,13 +19,13 @@ struct StakeNavigationView: View { @Binding private var navigationPath: NavigationPath - private let onComplete: VoidAction - + private let onComplete: StringsAction + init( wallet: Wallet, assetId: AssetId, navigationPath: Binding, - onComplete: VoidAction + onComplete: StringsAction ) { self.wallet = wallet self.assetId = assetId diff --git a/Gem/Root/Navigation/WalletConnector/WalletConnectorNavigationStack.swift b/Gem/Root/Navigation/WalletConnector/WalletConnectorNavigationStack.swift index caa9a6b4..5164b1bf 100644 --- a/Gem/Root/Navigation/WalletConnector/WalletConnectorNavigationStack.swift +++ b/Gem/Root/Navigation/WalletConnector/WalletConnectorNavigationStack.swift @@ -36,7 +36,7 @@ struct WalletConnectorNavigationStack: View { .service(for: data.payload.tranferData.recipientData.asset.chain), walletsService: walletsService, confirmTransferDelegate: data.delegate, - onComplete: { presenter.complete(type: type) } + onComplete: { _ in presenter.complete(type: type) } ) ) case .signMessage(let data): diff --git a/Gem/Swap/Navigation/SwapNavigationView.swift b/Gem/Swap/Navigation/SwapNavigationView.swift index 66c375d2..8e3b1735 100644 --- a/Gem/Swap/Navigation/SwapNavigationView.swift +++ b/Gem/Swap/Navigation/SwapNavigationView.swift @@ -60,7 +60,7 @@ struct SwapNavigationView: View { service: ChainServiceFactory(nodeProvider: nodeService) .service(for: data.recipientData.asset.chain), walletsService: walletsService, - onComplete: { + onComplete: { _ in onSwapComplete(type: data.type) } ) diff --git a/Gem/Transfer/Navigation/ConfirmTransferNavigationStack.swift b/Gem/Transfer/Navigation/ConfirmTransferNavigationStack.swift index f3e296f8..bc5b5340 100644 --- a/Gem/Transfer/Navigation/ConfirmTransferNavigationStack.swift +++ b/Gem/Transfer/Navigation/ConfirmTransferNavigationStack.swift @@ -16,12 +16,12 @@ struct ConfirmTransferNavigationStack: View { private let wallet: Wallet private let transferData: TransferData - private let onComplete: VoidAction - + private let onComplete: StringsAction + public init( wallet: Wallet, transferData: TransferData, - onComplete: VoidAction + onComplete: StringsAction ) { self.wallet = wallet self.transferData = transferData @@ -44,7 +44,7 @@ struct ConfirmTransferNavigationStack: View { .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button(Localized.Common.cancel) { - onComplete?() + onComplete?([]) } .bold() } diff --git a/Gem/Transfer/Navigation/RecipientNavigationView.swift b/Gem/Transfer/Navigation/RecipientNavigationView.swift index de64af02..f33eddd0 100644 --- a/Gem/Transfer/Navigation/RecipientNavigationView.swift +++ b/Gem/Transfer/Navigation/RecipientNavigationView.swift @@ -60,7 +60,9 @@ struct RecipientNavigationView: View { service: ChainServiceFactory(nodeProvider: nodeService) .service(for: data.recipientData.asset.chain), walletsService: walletsService, - onComplete: onComplete + onComplete: { _ in + self.onComplete?() + } ) ) } diff --git a/Gem/Transfer/Scenes/ConfirmTransferScene.swift b/Gem/Transfer/Scenes/ConfirmTransferScene.swift index d3ac57f8..08f65ffb 100644 --- a/Gem/Transfer/Scenes/ConfirmTransferScene.swift +++ b/Gem/Transfer/Scenes/ConfirmTransferScene.swift @@ -203,10 +203,10 @@ extension ConfirmTransferScene { private func process(input: TransactionPreload, amount: TransferAmount) { Task { - await model.process(input: input, amount: amount) + let data = await model.process(input: input, amount: amount) await MainActor.run { if case .loaded(_) = model.confirmingState { - model.onCompleteAction() + model.onCompleteAction(data: data) } } } @@ -222,6 +222,6 @@ extension ConfirmTransferScene { data: .main, service: ChainServiceFactory(nodeProvider: NodeService.main).service(for: .bitcoin), walletsService: .main, - onComplete: { } + onComplete: { _ in } )) } diff --git a/Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift b/Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift index 70ec96f2..90c4a592 100644 --- a/Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift +++ b/Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift @@ -44,8 +44,8 @@ class ConfirmTransferViewModel { private let walletsService: WalletsService private let confirmTransferDelegate: TransferDataCallback.ConfirmTransferDelegate? - private let onComplete: VoidAction - + private let onComplete: StringsAction + init( wallet: Wallet, keystore: any Keystore, @@ -54,7 +54,7 @@ class ConfirmTransferViewModel { walletsService: WalletsService, explorerService: any ExplorerLinkFetchable = ExplorerService.standard, confirmTransferDelegate: TransferDataCallback.ConfirmTransferDelegate? = .none, - onComplete: VoidAction + onComplete: StringsAction ) { self.wallet = wallet self.keystore = keystore @@ -301,13 +301,16 @@ extension ConfirmTransferViewModel { } } - func process(input: TransactionPreload, amount: TransferAmount) async { + func process(input: TransactionPreload, amount: TransferAmount) async -> [String] { await MainActor.run { [self] in self.confirmingState = .loading } do { let signedData = try await sign(transferData: data, input: input, amount: amount) for data in signedData { + if self.data.type.outputSignature { + continue + } let hash = try await broadcast(data: data, options: broadcastOptions) let transaction = try getTransaction(input: input, amount: amount, hash: hash) try addTransaction(transaction: transaction) @@ -321,16 +324,18 @@ extension ConfirmTransferViewModel { await MainActor.run { [self] in self.confirmingState = .loaded(true) } + return signedData } catch { await MainActor.run { [self] in self.confirmingState = .error(error) } NSLog("confirm transaction error: \(error)") + return [] } } - func onCompleteAction() { - self.onComplete?() + func onCompleteAction(data: [String]) { + self.onComplete?(data) } } @@ -485,11 +490,13 @@ extension ConfirmTransferViewModel { // MARK: - Static extension ConfirmTransferViewModel { - static func sign(signer: Signer, - senderAddress: String, - transferData: TransferData, - input: TransactionPreload, - amount: TransferAmount) async throws -> [String] { + static func sign( + signer: Signer, + senderAddress: String, + transferData: TransferData, + input: TransactionPreload, + amount: TransferAmount + ) async throws -> [String] { let destinationAddress = transferData.recipientData.recipient.address let isMaxAmount = amount.useMaxAmount diff --git a/Gem/Wallet/Navigation/SelectedAssetNavigationStack.swift b/Gem/Wallet/Navigation/SelectedAssetNavigationStack.swift index 8d9a0284..9a4b67eb 100644 --- a/Gem/Wallet/Navigation/SelectedAssetNavigationStack.swift +++ b/Gem/Wallet/Navigation/SelectedAssetNavigationStack.swift @@ -30,7 +30,7 @@ struct SelectedAssetNavigationStack: View { self.wallet = wallet _isPresentingAssetSelectType = isPresentingAssetSelectType } - + var body: some View { NavigationStack(path: $navigationPath) { switch selectType.type { diff --git a/Packages/Blockchain/Sources/Solana/SolanaService.swift b/Packages/Blockchain/Sources/Solana/SolanaService.swift index 1635094c..e82b7ca0 100644 --- a/Packages/Blockchain/Sources/Solana/SolanaService.swift +++ b/Packages/Blockchain/Sources/Solana/SolanaService.swift @@ -203,9 +203,9 @@ extension SolanaService { options: options ) } - case .swap, .stake: + case .swap, .stake, .generic: return try await getBaseFee(type: input.type, gasPrice: input.gasPrice) - case .generic, .account: fatalError() + case .account: fatalError() } } } diff --git a/Packages/Primitives/Sources/Action.swift b/Packages/Primitives/Sources/Action.swift index 5949584e..97df0d1f 100644 --- a/Packages/Primitives/Sources/Action.swift +++ b/Packages/Primitives/Sources/Action.swift @@ -4,6 +4,7 @@ import Foundation public typealias VoidAction = (() -> Void)? public typealias StringAction = ((String) -> Void)? +public typealias StringsAction = (([String]) -> Void)? public typealias AssetAction = ((Asset) -> Void)? public typealias AssetIdAction = ((AssetId) -> Void) public typealias AssetIdBoolAction = ((AssetId, Bool) -> Void)? diff --git a/Packages/Primitives/Sources/TransferDataExtra.swift b/Packages/Primitives/Sources/TransferDataExtra.swift index 223b1cc7..fe7807e3 100644 --- a/Packages/Primitives/Sources/TransferDataExtra.swift +++ b/Packages/Primitives/Sources/TransferDataExtra.swift @@ -4,18 +4,25 @@ import Foundation import BigInt public struct TransferDataExtra: Equatable, Sendable { + public enum OutputType: Equatable, Sendable { + case encodedTx + case signature + } public let gasLimit: BigInt? public let gasPrice: GasPriceType? public let data: Data? - + public let outputType: OutputType? + public init( gasLimit: BigInt? = .none, gasPrice: GasPriceType? = .none, - data: Data? = .none + data: Data? = .none, + outputType: OutputType? = .encodedTx ) { self.gasLimit = gasLimit self.gasPrice = gasPrice self.data = data + self.outputType = outputType } } extension TransferDataExtra: Hashable {} diff --git a/Packages/Primitives/Sources/TransferDataType.swift b/Packages/Primitives/Sources/TransferDataType.swift index 1b59d177..cc87c6dd 100644 --- a/Packages/Primitives/Sources/TransferDataType.swift +++ b/Packages/Primitives/Sources/TransferDataType.swift @@ -105,4 +105,11 @@ public enum TransferDataType: Hashable, Equatable, Sendable { [asset.id] } } + + public var outputSignature: Bool { + return switch self { + case .generic(_, _, let extra): extra.outputType == .signature + default: false + } + } } diff --git a/Packages/Signer/Sources/Chains/SolanaSigner.swift b/Packages/Signer/Sources/Chains/SolanaSigner.swift index b294e612..e1e4a309 100644 --- a/Packages/Signer/Sources/Chains/SolanaSigner.swift +++ b/Packages/Signer/Sources/Chains/SolanaSigner.swift @@ -89,28 +89,55 @@ public struct SolanaSigner: Signable { } public func signData(input: Primitives.SignerInput, privateKey: Data) throws -> String { - guard case .generic(_, _, let extra) = input.type, - let string = String(data: extra.data!, encoding: .utf8), - let bytes = Base64.decode(string: string) else { + guard + case .generic(_, _, let extra) = input.type, + let string = String(data: extra.data!, encoding: .utf8), + let bytes = Base64.decode(string: string) + else { throw AnyError("not data input") } - return try signData(bytes: bytes, privateKey: privateKey) + return try signData(bytes: bytes, privateKey: privateKey, outputType: extra.outputType ?? .encodedTx) } - func signData(bytes: Data, privateKey: Data) throws -> String { - if bytes[0] != 1 { - throw AnyError("only support one signature") + func signData(bytes: Data, privateKey: Data, outputType: TransferDataExtra.OutputType) throws -> String { + var offset = 0 + // read number of signature neede + let numRequiredSignatures = bytes[offset] + offset = 1 + + // read all the signatures + var signatures: [Data] = [] + for _ in 0.. String { diff --git a/Services/SwapService/Sources/SwapService.swift b/Services/SwapService/Sources/SwapService.swift index d162d592..5e362aca 100644 --- a/Services/SwapService/Sources/SwapService.swift +++ b/Services/SwapService/Sources/SwapService.swift @@ -54,7 +54,7 @@ public final class SwapService { value: value, mode: .exactIn, options: GemSwapOptions( - slippage: .init(bps: swapConfig.defaultSlippageBps, mode: .exact), + slippage: getDefaultSlippage(chain: fromAsset.chain.rawValue), fee: getReferralFees(), preferredProviders: [] ) diff --git a/Services/WalletConnectorService/Sources/WalletConnectorService.swift b/Services/WalletConnectorService/Sources/WalletConnectorService.swift index 182ab8e1..41f8f889 100644 --- a/Services/WalletConnectorService/Sources/WalletConnectorService.swift +++ b/Services/WalletConnectorService/Sources/WalletConnectorService.swift @@ -170,8 +170,7 @@ extension WalletConnectorService { case .solanaSignMessage: return try await solanaSignMessage(request: request) case .solanaSignTransaction: - return .error(.invalidRequest) - //return try await solanaSignTransaction(request: request) + return try await solanaSignTransaction(request: request) } } @@ -280,8 +279,8 @@ extension WalletConnectorService { // solana private func solanaSignTransaction(request: WalletConnectSign.Request) async throws -> RPCResult { let transaction = try request.params.get(WCSolanaTransaction.self) - let transactionId = try await signer.sendTransaction(sessionId: request.topic, chain: .solana, transaction: .solana(transaction.transaction)) - return .response(AnyCodable(transactionId)) + let signature = try await signer.signTransaction(sessionId: request.topic, chain: .solana, transaction: .solana(transaction.transaction)) + return .response(AnyCodable(signature)) } private func solanaSignMessage(request: WalletConnectSign.Request) async throws -> RPCResult {