Skip to content

Commit

Permalink
implement solana_signTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
0xh3rman committed Jan 23, 2025
1 parent 06b5413 commit 4c05bb7
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Gem/Asset/Navigation/AssetNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct AssetNavigationView: View {
ConfirmTransferNavigationStack(
wallet: wallet,
transferData: data
) {
) { _ in
transferData = .none
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ struct SelectAssetSceneNavigationStack: View {
wallet: model.wallet,
asset: input.asset,
navigationPath: $navigationPath,
onComplete: {
onComplete: { _ in

Check failure on line 71 in Gem/Assets/Navigation/SelectAssetSceneNavigationStack.swift

View workflow job for this annotation

GitHub Actions / Build and Test iPhone simulator

contextual closure type '() -> Void' expects 0 arguments, but 1 was used in closure body
isPresentingSelectType = nil
}
)
Expand Down
6 changes: 3 additions & 3 deletions Gem/Root/Navigation/Staking/StakeNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<NavigationPath>,
onComplete: VoidAction
onComplete: StringsAction
) {
self.wallet = wallet
self.assetId = assetId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion Gem/Swap/Navigation/SwapNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
)
Expand Down
8 changes: 4 additions & 4 deletions Gem/Transfer/Navigation/ConfirmTransferNavigationStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -44,7 +44,7 @@ struct ConfirmTransferNavigationStack: View {
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(Localized.Common.cancel) {
onComplete?()
onComplete?([])
}
.bold()
}
Expand Down
4 changes: 3 additions & 1 deletion Gem/Transfer/Navigation/RecipientNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?()
}
)
)
}
Expand Down
6 changes: 3 additions & 3 deletions Gem/Transfer/Scenes/ConfirmTransferScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand All @@ -222,6 +222,6 @@ extension ConfirmTransferScene {
data: .main,
service: ChainServiceFactory(nodeProvider: NodeService.main).service(for: .bitcoin),
walletsService: .main,
onComplete: { }
onComplete: { _ in }
))
}
29 changes: 18 additions & 11 deletions Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Gem/Wallet/Navigation/SelectedAssetNavigationStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct SelectedAssetNavigationStack: View {
self.wallet = wallet
_isPresentingAssetSelectType = isPresentingAssetSelectType
}

var body: some View {
NavigationStack(path: $navigationPath) {
switch selectType.type {
Expand Down
4 changes: 2 additions & 2 deletions Packages/Blockchain/Sources/Solana/SolanaService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Packages/Primitives/Sources/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)?
11 changes: 9 additions & 2 deletions Packages/Primitives/Sources/TransferDataExtra.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
7 changes: 7 additions & 0 deletions Packages/Primitives/Sources/TransferDataType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
57 changes: 42 additions & 15 deletions Packages/Signer/Sources/Chains/SolanaSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<Int(numRequiredSignatures) {
signatures.append(Data(bytes[offset..<offset+64]))
offset += 64
}

let message = bytes[65...]
guard let signature = PrivateKey(data: privateKey)?.sign(digest: message, curve: .ed25519) else {

assert(offset == 1 + 64 * numRequiredSignatures)
guard signatures[0] == Data(repeating: 0x0, count: 64) else {
throw AnyError("user signature should be first")
}

// read message to sign
let message = bytes[offset...]
guard
let signature = PrivateKey(data: privateKey)?.sign(digest: message, curve: .ed25519)
else {
throw AnyError("fail to sign data")
}

var signed = Data([0x1])
signed.append(signature)
signed.append(message)
return signed.base64EncodedString()

switch outputType {
case .signature:
return signature.base64EncodedString()
case .encodedTx:
// update user's signature
signatures[0] = signature

var signed = Data([numRequiredSignatures])
for sig in signatures {
signed.append(sig)
}
return signed.base64EncodedString()
}
}

public func swap(input: SignerInput, privateKey: Data) throws -> String {
Expand Down
2 changes: 1 addition & 1 deletion Services/SwapService/Sources/SwapService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
)
Expand Down
Loading

0 comments on commit 4c05bb7

Please sign in to comment.