Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import Foundation
import CryptoKit

// MARK: - Supporting Models

struct StellarSignXDRResult: Codable {
let signedXDR: String
let signerAddress: String?
}

struct StellarSignAndSubmitXDRResult: Codable {
let tx_hash: String?

Check warning on line 12 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this constant "tx_hash" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCQ&open=AaAPZFP3Y0oOFI7PbZCQ&pullRequest=346
let signedXDR: String?
}

// MARK: - StellarTVFCollector

class StellarTVFCollector: ChainTVFCollector {
// MARK: - Constants

static let STELLAR_SIGN_XDR = "stellar_signXDR"

Check warning on line 21 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this constant "STELLAR_SIGN_XDR" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCR&open=AaAPZFP3Y0oOFI7PbZCR&pullRequest=346
static let STELLAR_SIGN_AND_SUBMIT_XDR = "stellar_signAndSubmitXDR"

Check warning on line 22 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this constant "STELLAR_SIGN_AND_SUBMIT_XDR" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCS&open=AaAPZFP3Y0oOFI7PbZCS&pullRequest=346

private static let pubnetPassphrase = "Public Global Stellar Network ; September 2015"

Check warning on line 24 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"passphrase" detected here, make sure this is not a hard-coded credential.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCT&open=AaAPZFP3Y0oOFI7PbZCT&pullRequest=346
private static let testnetPassphrase = "Test SDF Network ; September 2015"

// XDR EnvelopeType discriminants
private static let envelopeTypeTxV0: UInt32 = 0
private static let envelopeTypeTx: UInt32 = 2
private static let envelopeTypeTxFeeBump: UInt32 = 5

// DecoratedSignature with an ed25519 signature: hint (4) + length (4, =64) + signature (64)
private static let decoratedSignatureLength = 72
private static let ed25519SignatureLength: UInt32 = 64
private static let maxEnvelopeSignatures = 20

// MARK: - Supported Methods

private var supportedMethods: [String] {
[Self.STELLAR_SIGN_XDR, Self.STELLAR_SIGN_AND_SUBMIT_XDR]
}

func supportsMethod(_ method: String) -> Bool {
return supportedMethods.contains(method)
}

// MARK: - Implementation

func extractContractAddresses(rpcMethod: String, rpcParams: AnyCodable) -> [String]? {

Check warning on line 49 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "rpcMethod" or name it "_".

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCU&open=AaAPZFP3Y0oOFI7PbZCU&pullRequest=346

Check warning on line 49 in Sources/WalletConnectRelay/tvf/StellarTVFCollector.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "rpcParams" or name it "_".

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFP3Y0oOFI7PbZCV&open=AaAPZFP3Y0oOFI7PbZCV&pullRequest=346
// Stellar doesn't extract contract addresses for TVF in this implementation
return nil
}

func parseTxHashes(rpcMethod: String, rpcResult: RPCResult?, rpcParams: AnyCodable?) -> [String]? {
// If rpcResult is nil or is an error, we can't parse anything
guard let rpcResult = rpcResult, case .response(let anycodable) = rpcResult else {
return nil
}

// Only process Stellar transaction methods
guard supportedMethods.contains(rpcMethod) else {
return nil
}

// Extract from result wrapper (always under "result" key in JSON-RPC)
guard let wrapper = try? anycodable.get([String: AnyCodable].self),
let resultValue = wrapper["result"] else {
return nil
}

switch rpcMethod {
case Self.STELLAR_SIGN_AND_SUBMIT_XDR:
if let result = try? resultValue.get(StellarSignAndSubmitXDRResult.self),
let txHash = result.tx_hash {
return [txHash]
}
return nil

case Self.STELLAR_SIGN_XDR:
guard let result = try? resultValue.get(StellarSignXDRResult.self) else {
return nil
}
let chain = extractChain(from: rpcParams)
return Self.computeTransactionHash(signedXDR: result.signedXDR, chain: chain).map { [$0] }

default:
return nil
}
}

private func extractChain(from rpcParams: AnyCodable?) -> String? {
guard let rpcParams = rpcParams,
let params = try? rpcParams.get([String: AnyCodable].self),
let chainAny = params["chain"],
let chain = try? chainAny.get(String.self) else {
return nil
}
return chain
}

// MARK: - Hash Computation

/// Computes the Stellar transaction hash from a base64-encoded, signed TransactionEnvelope XDR
/// as `sha256(network_id || envelope_type || transaction_body)`. Signatures are computed over
/// the hash, so the trailing signature array is stripped rather than hashed. For fee-bump
/// envelopes this yields the canonical fee-bump hash.
///
/// - Parameters:
/// - signedXDR: base64-encoded TransactionEnvelope XDR (V0, V1 or fee-bump)
/// - chain: CAIP-2 chain id (`stellar:pubnet` / `stellar:testnet`), defaults to pubnet
/// - Returns: lowercase hex transaction hash (64 chars), or nil for malformed envelopes
static func computeTransactionHash(signedXDR: String, chain: String?) -> String? {
guard let bytes = Data(base64Encoded: signedXDR), bytes.count >= 8 else {
return nil
}

let discriminant = readUInt32BE(bytes, 0)
let envelopeType: UInt32
let bodyStart: Int
switch discriminant {
case envelopeTypeTxV0:
// V0 transactions are hashed as ENVELOPE_TYPE_TX over the envelope bytes INCLUDING
// the leading 4 zero bytes - they double as the legacy AccountID key-type tag
envelopeType = envelopeTypeTx
bodyStart = 0
case envelopeTypeTx:
envelopeType = envelopeTypeTx
bodyStart = 4
case envelopeTypeTxFeeBump:
envelopeType = envelopeTypeTxFeeBump
bodyStart = 4
default:
return nil
}

guard let signatureArrayOffset = findSignatureArrayOffset(bytes) else {
return nil
}

let reference = chain?.components(separatedBy: ":").last ?? "pubnet"
let passphrase: String
switch reference {
case "pubnet": passphrase = pubnetPassphrase
case "testnet": passphrase = testnetPassphrase
default: return nil
}

let networkId = Data(SHA256.hash(data: Data(passphrase.utf8)))
var payload = networkId
payload.append(contentsOf: [0, 0, 0, UInt8(envelopeType)])
payload.append(bytes.subdata(in: bodyStart..<signatureArrayOffset))

return SHA256.hash(data: payload).map { String(format: "%02x", $0) }.joined()
}

/// Locates the start of the trailing `DecoratedSignature signatures<20>` XDR array without
/// parsing the transaction body. Assumes ed25519 signatures (fixed 72-byte entries), which is
/// what the WalletConnect Stellar RPC spec mandates wallets emit.
private static func findSignatureArrayOffset(_ bytes: Data) -> Int? {
for signatureCount in 0...maxEnvelopeSignatures {
let offset = bytes.count - 4 - decoratedSignatureLength * signatureCount
if offset < 4 { break }
if readUInt32BE(bytes, offset) != UInt32(signatureCount) { continue }

var isValid = true
for i in 0..<signatureCount {
let entryOffset = offset + 4 + decoratedSignatureLength * i
// each entry's signature length field must be exactly 64 (ed25519)
if readUInt32BE(bytes, entryOffset + 4) != ed25519SignatureLength {
isValid = false
break
}
}
if isValid { return offset }
}
return nil
}

private static func readUInt32BE(_ bytes: Data, _ offset: Int) -> UInt32 {
let index = bytes.startIndex + offset
return (UInt32(bytes[index]) << 24)
| (UInt32(bytes[index + 1]) << 16)
| (UInt32(bytes[index + 2]) << 8)
| UInt32(bytes[index + 3])
}
}
3 changes: 2 additions & 1 deletion Sources/WalletConnectRelay/tvf/TVFCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public class TVFCollector: TVFCollectorProtocol {
StacksTVFCollector(),
SuiTVFCollector(),
PolkadotTVFCollector(),
TonTVFCollector()
TonTVFCollector(),
StellarTVFCollector()
]
}

Expand Down
144 changes: 144 additions & 0 deletions Tests/RelayerTests/StellarTVFCollectorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import XCTest
@testable import WalletConnectRelay

final class StellarTVFCollectorTests: XCTestCase {

private let stellarCollector = StellarTVFCollector()

// Stellar test vectors are real transactions fetched from Horizon
// (expected hash == the `hash` field of `GET /transactions/{hash}`).

private static let pubnetV1XDR =
"AAAAAgAAAACutgsH0wwp9iT1V1zWE8jbQAm7JNeTEx4zdvWD4Jtk8wAAAGQDymekAAAAHAAAAAEAAAAAAAAAAAAAAABqguWuAAAAAAAAAAEAAAAAAAAAAQAAAACutgsH0wwp9iT1V1zWE8jbQAm7JNeTEx4zdvWD4Jtk8wAAAAAAAAAAAJiWgAAAAAAAAAAB4Jtk8wAAAECrfMK7BzVXCay0QnEItO7dJ8Ix2wGaMnFfbWHW1tE6cezMinDXiDtlVBwoK2GjAbrE0h+eGDjDqWWaRS1XDrwE"
private static let pubnetV1Hash = "628ef4f404cba337f757a640260984830728c92101af0a051fb59fc8c79521c6"

private static let pubnetFeeBumpXDR =
"AAAABQAAAAA0mMHF8QGzwsMRBhe9i8PSIqxjNjKyQMyXZODBAdhAUwAAAAAAA5+BAAAAAgAAAAA6Hd6p+AA5GTO2bJqKN/hbBfWcOh2Ow8cnTY3iIFFVPAADnrgDoCvmAAAZVgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAADod3qn4ADkZM7Zsmoo3+FsF9Zw6HY7DxydNjeIgUVU8AAAAGAAAAAAAAAAB1/5EvQrxHWArEJHy9KH03yEtRE0DIeoyrbPMHLurCgQAAAAEd29yawAAAAMAAAASAAAAAAAAAAA6Hd6p+AA5GTO2bJqKN/hbBfWcOh2Ow8cnTY3iIFFVPAAAAA0AAAAgAAAAjpSFVoEyx/Ev5d2V/desEr+GEqMyXKvrs5wXFqwAAAAFAAAAAAIrSjwAAAAAAAAAAQAAAAAAAAABAAAAB9ssFCkNSWTjgF8lJ90TKTm6X7P8ysVrML+rj9CRARYnAAAAAwAAAAYAAAAB1/5EvQrxHWArEJHy9KH03yEtRE0DIeoyrbPMHLurCgQAAAAQAAAAAQAAAAIAAAAPAAAABUJsb2NrAAAAAAAAAwACsj8AAAAAAAAABgAAAAHX/kS9CvEdYCsQkfL0ofTfIS1ETQMh6jKts8wcu6sKBAAAABAAAAABAAAAAwAAAA8AAAAEUGFpbAAAABIAAAAAAAAAADod3qn4ADkZM7Zsmoo3+FsF9Zw6HY7DxydNjeIgUVU8AAAAAwACsj8AAAAAAAAABgAAAAHX/kS9CvEdYCsQkfL0ofTfIS1ETQMh6jKts8wcu6sKBAAAABQAAAABABvFygAAAAAAAAXIAAAAAAADnrgAAAABIFFVPAAAAEBbcalx54eMMHwWJz7tzgOoxIVmMl4pexbgwTLzxnyMtAhZ2nZlsF18jIMDBaubSNSNPi4YRHkSajpyOcdZOzsCAAAAAAAAAAEB2EBTAAAAQDOlpIeUB73BImAZJCSAt0cuKZXHlKG+TJ+j+fCeFe9bDc5wHzMlDjU6YlB6geCuxRi7QwTq4RgxrOIJ9HICfg8="
private static let pubnetFeeBumpHash = "5906453d5a367b4a8a1af9bbbc934904841718ec1ca1345874904e15f97bf83b"

private static let testnetV1XDR =
"AAAAAgAAAAAJDqKNhO2/XZAvmR1Wynm2lxfIUQwB6TDzqNVOlQgQTgAAm74AJGh0ABKiOwAAAAEAAAAAAAAAAAAAAABqgthtAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAABmtIg9IzJFxPz3yuidCMj3LiE2SB3XYOl8DvtohHRPVAAAAAJc2V0X3ByaWNlAAAAAAAAAwAAABIAAAAAAAAAAAkOoo2E7b9dkC+ZHVbKebaXF8hRDAHpMPOo1U6VCBBOAAAADwAAAAZFVEhVU0QAAAAAAAoAAAAAAAAAAAAAAARomVswAAAAAQAAAAAAAAAAAAAAAZrSIPSMyRcT898ronQjI9y4hNkgd12DpfA77aIR0T1QAAAACXNldF9wcmljZQAAAAAAAAMAAAASAAAAAAAAAAAJDqKNhO2/XZAvmR1Wynm2lxfIUQwB6TDzqNVOlQgQTgAAAA8AAAAGRVRIVVNEAAAAAAAKAAAAAAAAAAAAAAAEaJlbMAAAAAAAAAABAAAAAAAAAAQAAAAGAAAAAcbgeSm1T8h+V5r+/0u+qUVZIopr5hDeGKj1+u37faSgAAAAEAAAAAEAAAADAAAADwAAAAdIYXNSb2xlAAAAABIAAAAAAAAAAAkOoo2E7b9dkC+ZHVbKebaXF8hRDAHpMPOo1U6VCBBOAAAADwAAAAZPUkFDTEUAAAAAAAEAAAAGAAAAAcbgeSm1T8h+V5r+/0u+qUVZIopr5hDeGKj1+u37faSgAAAAFAAAAAEAAAAHve3Sc2JfmD0Hu+w96oFCzEX1XxFR0uE/NeSf2Vqmia8AAAAH4IWMslKp5yCKjCiGPIRV6yV0LdrLOEfRrCRSwjur0G8AAAABAAAABgAAAAGa0iD0jMkXE/PfK6J0IyPcuITZIHddg6XwO+2iEdE9UAAAABQAAAABADikCAAAAAAAAAJUAAAAAAAATa0AAAABlQgQTgAAAEDSMm2vdKNsB2TCk+Pbb6vSYgq6Zd5F0E4H5BfMi4lwWEcYFAq2Mp+e12wr1qU+Ni7+2BTqZUkb+uK7lVM8PqkN"
private static let testnetV1Hash = "c9b2d35ab15c055acb422872a8f1680a84ad6b8ad0d56271cfb74c083edf2007"

// MARK: - Helpers

private func makeSignXDRResponse(signedXDR: String) -> RPCResult {
let payload: [String: Any] = [
"result": [
"signedXDR": signedXDR,
"signerAddress": "stellar:pubnet:GCXLMCYH2MGCT5RE6VLVZVQTZDNUACN3ETLZGEY6GN3PLA7ATNSPGGJH"
]
]
return .response(AnyCodable(any: payload))
}

private func makeParams(chain: String?) -> AnyCodable {
var params: [String: Any] = ["xdr": "...", "account": "stellar:pubnet:G..."]
if let chain = chain {
params["chain"] = chain
}
return AnyCodable(any: params)
}

// MARK: - Method Support Tests

func testSupportsMethod() {
XCTAssertTrue(stellarCollector.supportsMethod("stellar_signXDR"))
XCTAssertTrue(stellarCollector.supportsMethod("stellar_signAndSubmitXDR"))
XCTAssertFalse(stellarCollector.supportsMethod("stellar_signMessage"))
XCTAssertFalse(stellarCollector.supportsMethod("eth_sendTransaction"))
}

// MARK: - Transaction Hash Parsing Tests

func testParseTxHashes_SignXDR_PubnetV1() {

Check warning on line 54 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_SignXDR_PubnetV1" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCW&open=AaAPZFTaY0oOFI7PbZCW&pullRequest=346
let rpcResult = makeSignXDRResponse(signedXDR: Self.pubnetV1XDR)

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: "stellar:pubnet")
)

XCTAssertEqual(hashes, [Self.pubnetV1Hash])
}

func testParseTxHashes_SignXDR_DefaultsToPubnet() {

Check warning on line 66 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_SignXDR_DefaultsToPubnet" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCX&open=AaAPZFTaY0oOFI7PbZCX&pullRequest=346
let rpcResult = makeSignXDRResponse(signedXDR: Self.pubnetV1XDR)

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: nil)
)

XCTAssertEqual(hashes, [Self.pubnetV1Hash])
}

func testParseTxHashes_SignXDR_FeeBump_YieldsCanonicalFeeBumpHash() {

Check warning on line 78 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_SignXDR_FeeBump_YieldsCanonicalFeeBumpHash" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCY&open=AaAPZFTaY0oOFI7PbZCY&pullRequest=346
let rpcResult = makeSignXDRResponse(signedXDR: Self.pubnetFeeBumpXDR)

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: "stellar:pubnet")
)

XCTAssertEqual(hashes, [Self.pubnetFeeBumpHash])
}

func testParseTxHashes_SignXDR_Testnet() {

Check warning on line 90 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_SignXDR_Testnet" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCZ&open=AaAPZFTaY0oOFI7PbZCZ&pullRequest=346
let rpcResult = makeSignXDRResponse(signedXDR: Self.testnetV1XDR)

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: "stellar:testnet")
)

XCTAssertEqual(hashes, [Self.testnetV1Hash])
}

func testParseTxHashes_SignAndSubmitXDR_ExtractsTxHash() {

Check warning on line 102 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_SignAndSubmitXDR_ExtractsTxHash" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCa&open=AaAPZFTaY0oOFI7PbZCa&pullRequest=346
let payload: [String: Any] = [
"result": [
"tx_hash": "6da5298ae2b4fd1567fa3f760e66c9fb9014e3ac72bf48af1ad8120f8423b961",
"signedXDR": "AAAAAg==",
"successful": true
]
]
let rpcResult = RPCResult.response(AnyCodable(any: payload))

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_AND_SUBMIT_XDR,
rpcResult: rpcResult,
rpcParams: nil
)

XCTAssertEqual(hashes, ["6da5298ae2b4fd1567fa3f760e66c9fb9014e3ac72bf48af1ad8120f8423b961"])
}

func testParseTxHashes_MalformedEnvelope_ReturnsNil() {

Check warning on line 121 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_MalformedEnvelope_ReturnsNil" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCb&open=AaAPZFTaY0oOFI7PbZCb&pullRequest=346
let rpcResult = makeSignXDRResponse(signedXDR: "q83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vAQ==")

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: "stellar:pubnet")
)

XCTAssertNil(hashes)
}

func testParseTxHashes_ErrorResult_ReturnsNil() {

Check warning on line 133 in Tests/RelayerTests/StellarTVFCollectorTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "testParseTxHashes_ErrorResult_ReturnsNil" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=reown-com_reown-swift&issues=AaAPZFTaY0oOFI7PbZCc&open=AaAPZFTaY0oOFI7PbZCc&pullRequest=346
let rpcResult = RPCResult.error(JSONRPCError(code: 4001, message: "User rejected"))

let hashes = stellarCollector.parseTxHashes(
rpcMethod: StellarTVFCollector.STELLAR_SIGN_XDR,
rpcResult: rpcResult,
rpcParams: makeParams(chain: "stellar:pubnet")
)

XCTAssertNil(hashes)
}
}
Loading