Skip to content

Commit

Permalink
[ABW-3862] Get Transaction Preview from SargonOS (#1369)
Browse files Browse the repository at this point in the history
Co-authored-by: Ghenadie Vasiliev-Pusca <ghenadie.vasiliev-pusca@rdx.works>
  • Loading branch information
matiasbzurovski and GhenadieVP authored Oct 28, 2024
1 parent 1317a4a commit 08fc480
Show file tree
Hide file tree
Showing 26 changed files with 98 additions and 137 deletions.
2 changes: 1 addition & 1 deletion RadixWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9051,7 +9051,7 @@
repositoryURL = "https://github.com/radixdlt/sargon";
requirement = {
kind = exactVersion;
version = 1.1.28;
version = 1.1.34;
};
};
A415574E2B757C5E0040AD4E /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@
{
"identity" : "sargon",
"kind" : "remoteSourceControl",
"location" : "https://github.com/radixdlt/sargon/",
"location" : "https://github.com/radixdlt/sargon",
"state" : {
"revision" : "7e9cbbaa253f9ccf27bf503b274ddbb0e4fbc52e",
"version" : "1.1.28"
"revision" : "e065e142c589fe89a8123b0345c185c79656c2ec",
"version" : "1.1.34"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ extension FactorSourceKind: Comparable {
case .offDeviceMnemonic: 2
case .securityQuestions: 3
case .trustedContact: 4
case .passphrase: 5
// we want to sign with device last, since it would allow for us to stop using
// ephemeral notary and allow us to implement a AutoPurgingMnemonicCache which
// deletes items after 1 sec, thus `device` must come last.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct GatewayAPIClient: Sendable, DependencyKey {
var getAccountLockerVaults: GetAccountLockerVaults

// MARK: Transaction
var transactionPreview: TransactionPreview
var streamTransactions: StreamTransactions
var prevalidateDeposit: PrevalidateDeposit
}
Expand Down Expand Up @@ -56,7 +55,6 @@ extension GatewayAPIClient {
typealias GetAccountLockerVaults = @Sendable (GatewayAPI.StateAccountLockerPageVaultsRequest) async throws -> GatewayAPI.StateAccountLockerPageVaultsResponse

// MARK: - Transaction
typealias TransactionPreview = @Sendable (GatewayAPI.TransactionPreviewRequest) async throws -> GatewayAPI.TransactionPreviewResponse
typealias StreamTransactions = @Sendable (GatewayAPI.StreamTransactionsRequest) async throws -> GatewayAPI.StreamTransactionsResponse
typealias PrevalidateDeposit = @Sendable (GatewayAPI.AccountDepositPreValidationRequest) async throws -> GatewayAPI.AccountDepositPreValidationResponse
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,6 @@ extension GatewayAPIClient {
request: request
) { $0.appendingPathComponent("/state/account-locker/page/vaults") }
},
transactionPreview: { transactionPreviewRequest in
try await post(
request: transactionPreviewRequest
) { $0.appendingPathComponent("transaction/preview") }
},
streamTransactions: { streamTransactionsRequest in
try await post(
request: streamTransactionsRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ extension GatewayAPIClient: TestDependencyKey {
getNonFungibleData: unimplemented("\(Self.self).getNonFungibleData"),
getAccountLockerTouchedAt: unimplemented("\(Self.self).getAccountLockerTouchedAt"),
getAccountLockerVaults: unimplemented("\(Self.self).GetAccountLockerVaults"),
transactionPreview: unimplemented("\(Self.self).transactionPreview"),
streamTransactions: unimplemented("\(Self.self).streamTransactions"),
prevalidateDeposit: unimplemented("\(Self.self).prevalidateDeposit")
)
Expand All @@ -42,7 +41,6 @@ extension GatewayAPIClient: TestDependencyKey {
getNonFungibleData: unimplemented("\(self).getNonFungibleData"),
getAccountLockerTouchedAt: unimplemented("\(Self.self).getAccountLockerTouchedAt"),
getAccountLockerVaults: unimplemented("\(Self.self).GetAccountLockerVaults"),
transactionPreview: unimplemented("\(self).transactionPreview"),
streamTransactions: unimplemented("\(self).streamTransactions"),
prevalidateDeposit: unimplemented("\(Self.self).prevalidateDeposit")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ struct SubmitTransactionClient: Sendable {
}

extension SubmitTransactionClient {
typealias SubmitTransaction = @Sendable (NotarizedTransaction) async throws -> IntentHash
typealias PollTransactionStatus = @Sendable (IntentHash) async throws -> Sargon.TransactionStatus
typealias SubmitTransaction = @Sendable (NotarizedTransaction) async throws -> TransactionIntentHash
typealias PollTransactionStatus = @Sendable (TransactionIntentHash) async throws -> Sargon.TransactionStatus
}

extension SubmitTransactionClient {
func hasTXBeenCommittedSuccessfully(_ intentHash: IntentHash) async throws -> Bool {
func hasTXBeenCommittedSuccessfully(_ intentHash: TransactionIntentHash) async throws -> Bool {
try await pollTransactionStatus(intentHash) == .success
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ extension TransactionFailure {
// case .failedToSubmitTX:
// return (errorKind: .failedToSubmitTransaction, message: nil)
// case let .invalidTXWasDuplicate(txID):
// return (errorKind: .submittedTransactionWasDuplicate, message: "IntentHash: \(txID)")
// return (errorKind: .submittedTransactionWasDuplicate, message: "TransactionIntentHash: \(txID)")
// }
//
// case let .failedToPoll(error):
// switch error {
// case let .invalidTXWasSubmittedButNotSuccessful(txID, status: .rejected):
// return (errorKind: .submittedTransactionHasRejectedTransactionStatus, message: "IntentHash: \(txID)")
// return (errorKind: .submittedTransactionHasRejectedTransactionStatus, message: "TransactionIntentHash: \(txID)")
// case let .invalidTXWasSubmittedButNotSuccessful(txID, status: .failed):
// return (errorKind: .submittedTransactionHasFailedTransactionStatus, message: "IntentHash: \(txID)")
// return (errorKind: .submittedTransactionHasFailedTransactionStatus, message: "TransactionIntentHash: \(txID)")
// case let .failedToPollTX(txID, _):
// return (errorKind: .failedToPollSubmittedTransaction, message: "IntentHash: \(txID)")
// return (errorKind: .failedToPollSubmittedTransaction, message: "TransactionIntentHash: \(txID)")
// case let .failedToGetTransactionStatus(txID, _):
// return (errorKind: .failedToPollSubmittedTransaction, message: "IntentHash: \(txID)")
// return (errorKind: .failedToPollSubmittedTransaction, message: "TransactionIntentHash: \(txID)")
// }
}
}
Expand All @@ -82,7 +82,7 @@ extension TransactionFailure {
case failedToRetrieveTXReceipt(String)
case failedToExtractTXReceiptBytes
case failedToGenerateTXReview(Error)
case manifestWithReservedInstructions([ReservedInstruction])
case manifestWithReservedInstructions(String)
case oneOfRecevingAccountsDoesNotAllowDeposits

var errorDescription: String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ struct NotarizeTransactionRequest: Sendable, Hashable {
struct NotarizeTransactionResponse: Sendable, Hashable {
let notarized: NotarizedTransaction
let intent: TransactionIntent
let txID: IntentHash
let txID: TransactionIntentHash
init(
notarized: NotarizedTransaction,
intent: TransactionIntent,
txID: IntentHash
txID: TransactionIntentHash
) {
self.notarized = notarized
self.intent = intent
Expand Down
119 changes: 40 additions & 79 deletions RadixWallet/Clients/TransactionClient/TransactionClient+Live.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extension TransactionClient {
try await .init(validating: addresses.asyncMap(identityFromComponentAddress))
}

let summary = manifest.summary
let summary = try manifest.summary

return try await MyEntitiesInvolvedInTransaction(
identitiesRequiringAuth: mapIdentity(summary.addressesOfPersonasRequiringAuth),
Expand Down Expand Up @@ -128,7 +128,7 @@ extension TransactionClient {
tipPercentage: request.makeTransactionHeaderInput.tipPercentage
)

return .init(header: header, manifest: request.manifest, message: request.message ?? Message.none)
return .init(header: header, manifest: request.manifest, message: request.message)
}

let notarizeTransaction: NotarizeTransaction = { request in
Expand All @@ -155,43 +155,35 @@ extension TransactionClient {
)
}

@Sendable
func analyseTransactionPreview(request: ManifestReviewRequest) async throws -> Sargon.TransactionToReview {
do {
return try await SargonOS.shared.analyseTransactionPreview(
instructions: request.unvalidatedManifest.transactionManifestString,
blobs: request.unvalidatedManifest.blobs,
message: request.message,
areInstructionsOriginatingFromHost: request.isWalletTransaction,
nonce: request.nonce,
notaryPublicKey: .ed25519(request.ephemeralNotaryPublicKey.intoSargon())
)
} catch {
throw TransactionFailure.fromCommonError(error as? CommonError)
}
}

let getTransactionReview: GetTransactionReview = { request in
let networkID = await gatewaysClient.getCurrentNetworkID()
// Get preview from SargonOS
let preview = try await analyseTransactionPreview(request: request)

let manifestToSign = try request.unvalidatedManifest.transactionManifest(onNetwork: networkID)
let networkID = await gatewaysClient.getCurrentNetworkID()

/// Get all transaction signers.
let transactionSigners = try await getTransactionSigners(.init(
networkID: networkID,
manifest: manifestToSign,
manifest: preview.transactionManifest,
ephemeralNotaryPublicKey: request.ephemeralNotaryPublicKey
))

/// Get the transaction preview
let transactionPreviewRequest = try await createTransactionPreviewRequest(
for: request,
networkID: networkID,
transactionManifest: manifestToSign,
transactionSigners: transactionSigners
)
let transactionPreviewResponse = try await gatewayAPIClient.transactionPreview(transactionPreviewRequest)
guard transactionPreviewResponse.receipt.status == .succeeded else {
throw TransactionFailure.fromFailedTXReviewResponse(transactionPreviewResponse)
}
guard let engineToolkitReceipt = transactionPreviewResponse.engineToolkitReceipt else {
throw TransactionFailure.failedToPrepareTXReview(.failedToExtractTXReceiptBytes)
}

/// Analyze the manifest
let analyzedManifestToReview = try manifestToSign.executionSummary(
engineToolkitReceipt: engineToolkitReceipt
)

/// Transactions created outside of the Wallet are not allowed to use reserved instructions
if !request.isWalletTransaction, !analyzedManifestToReview.reservedInstructions.isEmpty {
throw TransactionFailure.failedToPrepareTXReview(.manifestWithReservedInstructions(analyzedManifestToReview.reservedInstructions))
}

/// Get all of the expected signing factors.
let signingFactors = try await {
if let nonEmpty = NonEmpty<Set<AccountOrPersona>>(transactionSigners.intentSignerEntitiesOrEmpty()) {
Expand All @@ -207,7 +199,7 @@ extension TransactionClient {
/// If notary is signatory, count the signature of the notary that will be added.
let signaturesCount = transactionSigners.notaryIsSignatory ? 1 : signingFactors.expectedSignatureCount
var transactionFee = try TransactionFee(
executionSummary: analyzedManifestToReview,
executionSummary: preview.executionSummary,
signaturesCount: signaturesCount,
notaryIsSignatory: transactionSigners.notaryIsSignatory,
includeLockFee: false // Calculate without LockFee cost. It is yet to be determined if LockFe will be added or not
Expand All @@ -222,38 +214,15 @@ extension TransactionClient {
}

return TransactionToReview(
transactionManifest: manifestToSign,
analyzedManifestToReview: analyzedManifestToReview,
transactionManifest: preview.transactionManifest,
analyzedManifestToReview: preview.executionSummary,
networkID: networkID,
transactionFee: transactionFee,
transactionSigners: transactionSigners,
signingFactors: signingFactors
)
}

@Sendable
func createTransactionPreviewRequest(
for request: ManifestReviewRequest,
networkID: NetworkID,
transactionManifest: TransactionManifest,
transactionSigners: TransactionSigners
) async throws -> GatewayAPI.TransactionPreviewRequest {
let intent = try await buildTransactionIntent(.init(
networkID: networkID,
manifest: transactionManifest,
message: request.message,
nonce: request.nonce,
makeTransactionHeaderInput: request.makeTransactionHeaderInput,
transactionSigners: transactionSigners
))

return try .init(
rawManifest: transactionManifest,
header: intent.header,
transactionSigners: transactionSigners
)
}

let myInvolvedEntities: MyInvolvedEntities = { manifest in
let networkID = await gatewaysClient.getCurrentNetworkID()
return try await myEntitiesInvolvedInTransaction(
Expand Down Expand Up @@ -380,30 +349,22 @@ extension TransactionClient {
}

extension TransactionFailure {
static func fromFailedTXReviewResponse(_ response: GatewayAPI.TransactionPreviewResponse) -> Self {
let message = response.receipt.errorMessage ?? "Unknown reason"

// Quite rudimentary, but it is not worth making something smarter,
// as the GW will provide in the future strongly typed errors
let isFailureDueToDepositRules = message.contains("AccountError(DepositIsDisallowed") ||
message.contains("AccountError(NotAllBucketsCouldBeDeposited")

if isFailureDueToDepositRules {
return .failedToPrepareTXReview(.oneOfRecevingAccountsDoesNotAllowDeposits)
} else {
return .failedToPrepareTXReview(.failedToRetrieveTXReceipt(message))
}
}
}
static func fromCommonError(_ commonError: CommonError?) -> Self {
switch commonError {
case let .ReservedInstructionsNotAllowedInManifest(reservedInstructions):
.failedToPrepareTXReview(.manifestWithReservedInstructions(reservedInstructions))

private extension GatewayAPI.TransactionPreviewResponse {
var engineToolkitReceipt: String? {
guard
let dictionary = radixEngineToolkitReceipt?.value as? [String: Any],
let data = try? JSONSerialization.data(withJSONObject: dictionary, options: [])
else {
return nil
case .OneOfReceivingAccountsDoesNotAllowDeposits:
.failedToPrepareTXReview(.oneOfRecevingAccountsDoesNotAllowDeposits)

case .FailedTransactionPreview:
.failedToPrepareTXReview(.failedToRetrieveTXReceipt("Unknown reason"))

case .FailedToExtractTransactionReceiptBytes:
.failedToPrepareTXReview(.failedToExtractTXReceiptBytes)

default:
.failedToPrepareTXReview(.failedToRetrieveTXReceipt("Unknown reason"))
}
return String(data: data, encoding: .utf8)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct TransactionHistoryResponse: Sendable, Hashable {

// MARK: - TransactionHistoryItem
struct TransactionHistoryItem: Sendable, Hashable, Identifiable {
let id: IntentHash
let id: TransactionIntentHash
let time: Date
let message: String?
let manifestClass: GatewayAPI.ManifestClass?
Expand All @@ -48,7 +48,7 @@ struct TransactionHistoryItem: Sendable, Hashable, Identifiable {
let failed: Bool

init(
id: IntentHash,
id: TransactionIntentHash,
time: Date,
message: String? = nil,
manifestClass: GatewayAPI.ManifestClass? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ extension TransactionHistoryClient {
throw MissingIntentHash()
}

let txid = try IntentHash(hash)
let txid = try TransactionIntentHash(hash)

let manifestClass = info.manifestClasses?.first

Expand Down
2 changes: 1 addition & 1 deletion RadixWallet/Core/SharedModels/LedgerIdentifiable.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// MARK: - LedgerIdentifiable
enum LedgerIdentifiable: Sendable {
case address(Address)
case transaction(IntentHash)
case transaction(TransactionIntentHash)

static func address(of account: Account) -> Self {
.address(.account(account.address))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension DappMetadata {
extension Completion {
struct ViewState: Equatable {
/// `nil` is a valid value for Persona Data requests
let txID: IntentHash?
let txID: TransactionIntentHash?
let title: String
let subtitle: String
let showSwitchBackToBrowserMessage: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import SwiftUI
// MARK: - Completion
struct Completion: Sendable, FeatureReducer {
struct State: Sendable, Hashable {
let txID: IntentHash?
let txID: TransactionIntentHash?
let dappMetadata: DappMetadata
let p2pRoute: P2P.Route

init(
txID: IntentHash?,
txID: TransactionIntentHash?,
dappMetadata: DappMetadata,
p2pRoute: P2P.Route
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct DappInteractionCoordinator: Sendable, FeatureReducer {

enum DelegateAction: Sendable, Equatable {
case submit(WalletToDappInteractionResponse, DappMetadata)
case dismiss(DappMetadata, IntentHash)
case dismiss(DappMetadata, TransactionIntentHash)
case dismissSilently
}

Expand Down
Loading

0 comments on commit 08fc480

Please sign in to comment.