diff --git a/CHANGELOG.md b/CHANGELOG.md index ab32c71..efed831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [1.8.8] - 05.Oct.2024. +- Improve and extend SEP-07 (UriScheme) support +- Updated toml to ^0.16.0 +- Forwarded http client to EventSource +- minor fixes + ## [1.8.7] - 05.Sep.2024. - Add Soroban Contract Parser - contract spec xdr fixes diff --git a/README.md b/README.md index b07f2a8..2fb0d2e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The Soneso open source Stellar SDK for Flutter is build with Dart and provides A 1. Add the dependency to your pubspec.yaml file: ``` dependencies: - stellar_flutter_sdk: ^1.8.7 + stellar_flutter_sdk: ^1.8.8 ``` 2. Install it (command line or IDE): ``` diff --git a/documentation/sdk_examples/sep-0007-urischeme.md b/documentation/sdk_examples/sep-0007-urischeme.md index d3fb58b..276399e 100644 --- a/documentation/sdk_examples/sep-0007-urischeme.md +++ b/documentation/sdk_examples/sep-0007-urischeme.md @@ -74,42 +74,47 @@ print(url); //web+stellar:pay?destination=GDGUF4SCNINRDCRUIVOMDYGIMXOWVP3ZLMTL2OGQIWMFDDSECZSFQMQV&amount=123.21&asset_code=ANA&asset_issuer=GC4HC3AXQDNAMURMHVGMLFGLQELEQBCE4GI7IOKEAWAKBXY7SXXWBTLV ``` -**Check if URI Scheme is valid** +**Check if URL is a valid sep7 URL** ```dart -Future checkUIRSchemeIsValid(String url) async +final validationResult = uriScheme.isValidSep7Url(url); ``` -Checks if the received SEP-0007 URL is valid; signature and domain must be present and correct for the signer's keypair. -Returns true if valid, otherwise throws the corresponding URISchemeError. +Checks if the received SEP-0007 URL is valid; It does not check if it has been properly signed. -Example: +**Check if URL is a valid sep7 URL and if it has been properly signed** ```dart -URIScheme uriScheme = URIScheme(); -await uriScheme.checkUIRSchemeIsValid(url).then((response) { - // success -}).catchError((error) async { - if (error is URISchemeError && - error.type == URISchemeError.tomlSignatureMissing){ - // handle error - } -}); +final validationResult = await uriScheme.isValidSep7SignedUrl(url); +``` + +Checks if the received SEP-0007 URL is valid and properly signed. The url must contain the +`origin_domain` and `signature` query parameters. This function will make a http request +to load the toml data containing the signer's public key from the `origin_domain`. +The given signed url is considered as valid if it has been signed by the signer +listed in the `origin_domain` toml data. + +If you already know the signer's public key, you can check the signature by using: +```dart +bool verifySignature(String sep7Url, String signerPublicKey) ``` -Possible URISchemeErrors are: +**Parse URL** + +You can parse the url by using: ```dart -static const int invalidSignature = 0; -static const int invalidOriginDomain = 1; -static const int missingOriginDomain = 2; -static const int missingSignature = 3; -static const int tomlNotFoundOrInvalid = 4; -static const int tomlSignatureMissing = 5; +final parseResult = tryParseSep7Url(url); + +if (parseResult != null) { + final opType = parseResult.operationType; + final message = parseResult.queryParameters[URIScheme.messageParameterName]; + //... +} ``` **Sign URI** ```dart -String signURI(String url, KeyPair signerKeypair) +String addSignature(String sep7Url, KeyPair signerKeypair) ``` Signs the URIScheme compliant SEP-0007 url with the signer's key pair. Returns the signed url having the signature parameter attached. Be careful with this function, you should validate the url and ask the user for permission before using this function. @@ -121,7 +126,7 @@ print(url); // web+stellar:tx?xdr=AAAAAgAAAADNQvJCahsRijRFXMHgyGXdar95Wya9ONBFmFGORBZkWAAAAGQABwWpAAAAKwAAAAAAAAAAAAAAAQAAAAEAAAAAzULyQmobEYo0RVzB4Mhl3Wq%2FeVsmvTjQRZhRjkQWZFgAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAOd3d3LnNvbmVzby5jb20AAAAAAAAAAAAAAAAAAA%3D%3D&origin_domain=place.domain.com URIScheme uriScheme = URIScheme(); -url = uriScheme.signURI(url, signerKeyPair); +url = uriScheme.addSignature(url, signerKeyPair); print(url); // web+stellar:tx?xdr=AAAAAgAAAADNQvJCahsRijRFXMHgyGXdar95Wya9ONBFmFGORBZkWAAAAGQABwWpAAAAKwAAAAAAAAAAAAAAAQAAAAEAAAAAzULyQmobEYo0RVzB4Mhl3Wq%2FeVsmvTjQRZhRjkQWZFgAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAOd3d3LnNvbmVzby5jb20AAAAAAAAAAAAAAAAAAA%3D%3D&origin_domain=place.domain.com&signature=bIZ53bPKkNe0OoNK8PGLTnzHS%2FBCMzXTvwv1mc4DWc0XC4%2Bp197AmUB%2FIPL1UZAega7cLYv7%2F%2FaflB7CLGqZCw%3D%3D ``` @@ -130,7 +135,7 @@ print(url); ```dart Future signAndSubmitTransaction( - String url, KeyPair signerKeyPair, + String sep7TxUrl, KeyPair signerKeyPair, {Network? network}) async ``` Signs the given transaction and submits it to the callback url if available, otherwise it submits it to the stellar network. @@ -161,11 +166,39 @@ class SubmitUriSchemeTransactionResponse { **Get parameter value** ```dart -String? getParameterValue(String name, String url) +final parseResult = tryParseSep7Url(url); +if (parseResult != null) { + final message = parseResult.queryParameters[URIScheme.messageParameterName]; +} +``` + +**Get operation type** + +```dart +final parseResult = tryParseSep7Url(url); +if (parseResult != null) { + final opType = parseResult.operationType; + if (opType == URIScheme.operationTypePay) { + //... + } +} +``` + +**Compose and parse replace parameter** + +Takes a list of `UriSchemeReplacement` objects and parses it to a string that +could be used as a Sep-7 URI 'replace' param. + +```dart +String uriSchemeReplacementsToString(List replacements) ``` -Utility function that returns the value of the given url parameter from the specified SEP-0007 url. +Takes a Sep-7 URL-decoded `replace` string param and parses it to a list of +`UriSchemeReplacement` objects for easy of use: +```dart +List uriSchemeReplacementsFromString(String replace) +``` **More examples** diff --git a/lib/src/sep/0007/URIScheme.dart b/lib/src/sep/0007/URIScheme.dart index b5320e6..b08ceab 100644 --- a/lib/src/sep/0007/URIScheme.dart +++ b/lib/src/sep/0007/URIScheme.dart @@ -42,6 +42,10 @@ class URIScheme { memoReturnType ]; + static const replacementHintDelimiter = ";"; + static const replacementIdDelimiter = ":"; + static const replacementListDelimiter = ","; + static int messageMaxLength = 300; static int maxAllowedChainingNestedLevels = 7; @@ -70,41 +74,41 @@ class URIScheme { String result = "${uriSchemeName}$operationTypeTx?"; final Map queryParams = { - xdrParameterName: Uri.encodeQueryComponent(transactionEnvelopeXdrBase64) + xdrParameterName: Uri.encodeComponent(transactionEnvelopeXdrBase64) }; if (replace != null) { - queryParams[replaceParameterName] = Uri.encodeQueryComponent(replace); + queryParams[replaceParameterName] = Uri.encodeComponent(replace); } if (callback != null) { - queryParams[callbackParameterName] = Uri.encodeQueryComponent(callback); + queryParams[callbackParameterName] = Uri.encodeComponent(callback); } if (publicKey != null) { - queryParams[publicKeyParameterName] = Uri.encodeQueryComponent(publicKey); + queryParams[publicKeyParameterName] = Uri.encodeComponent(publicKey); } if (chain != null) { - queryParams[chainParameterName] = Uri.encodeQueryComponent(chain); + queryParams[chainParameterName] = Uri.encodeComponent(chain); } if (message != null) { - queryParams[publicKeyParameterName] = Uri.encodeQueryComponent(message); + queryParams[publicKeyParameterName] = Uri.encodeComponent(message); } if (networkPassphrase != null) { queryParams[networkPassphraseParameterName] = - Uri.encodeQueryComponent(networkPassphrase); + Uri.encodeComponent(networkPassphrase); } if (originDomain != null) { queryParams[originDomainParameterName] = - Uri.encodeQueryComponent(originDomain); + Uri.encodeComponent(originDomain); } if (signature != null) { - queryParams[signatureParameterName] = Uri.encodeQueryComponent(signature); + queryParams[signatureParameterName] = Uri.encodeComponent(signature); } for (MapEntry e in queryParams.entries) { @@ -144,46 +148,46 @@ class URIScheme { }; if (amount != null) { - queryParams[amountParameterName] = Uri.encodeQueryComponent(amount); + queryParams[amountParameterName] = Uri.encodeComponent(amount); } if (assetCode != null) { - queryParams[assetCodeParameterName] = Uri.encodeQueryComponent(assetCode); + queryParams[assetCodeParameterName] = Uri.encodeComponent(assetCode); } if (assetIssuer != null) { queryParams[assetIssuerParameterName] = - Uri.encodeQueryComponent(assetIssuer); + Uri.encodeComponent(assetIssuer); } if (memo != null) { - queryParams[memoParameterName] = Uri.encodeQueryComponent(memo); + queryParams[memoParameterName] = Uri.encodeComponent(memo); } if (memoType != null) { - queryParams[memoTypeParameterName] = Uri.encodeQueryComponent(memoType); + queryParams[memoTypeParameterName] = Uri.encodeComponent(memoType); } if (callback != null) { - queryParams[callbackParameterName] = Uri.encodeQueryComponent(callback); + queryParams[callbackParameterName] = Uri.encodeComponent(callback); } if (message != null) { - queryParams[messageParameterName] = Uri.encodeQueryComponent(message); + queryParams[messageParameterName] = Uri.encodeComponent(message); } if (networkPassphrase != null) { queryParams[networkPassphraseParameterName] = - Uri.encodeQueryComponent(networkPassphrase); + Uri.encodeComponent(networkPassphrase); } if (originDomain != null) { queryParams[originDomainParameterName] = - Uri.encodeQueryComponent(originDomain); + Uri.encodeComponent(originDomain); } if (signature != null) { - queryParams[signatureParameterName] = Uri.encodeQueryComponent(signature); + queryParams[signatureParameterName] = Uri.encodeComponent(signature); } for (MapEntry e in queryParams.entries) { @@ -242,7 +246,7 @@ class URIScheme { "Content-Type", () => "application/x-www-form-urlencoded"); String bodyStr = xdrParameterName + "=" + - Uri.encodeQueryComponent(absTransaction.toEnvelopeXdrBase64()); + Uri.encodeComponent(absTransaction.toEnvelopeXdrBase64()); SubmitUriSchemeTransactionResponse result = await httpClient .post(serverURI, body: bodyStr, headers: headers) .then((response) { @@ -316,7 +320,12 @@ class URIScheme { } var uri = Uri.tryParse(url); if (uri != null) { - return ParsedSep7UrlResult(uri.pathSegments.first, uri.queryParameters); + // must be modifiable + Map queryParameters = {}; + uri.queryParameters.forEach((key, value) { + queryParameters[key] = value; + }); + return ParsedSep7UrlResult(uri.pathSegments.first, queryParameters); } return null; } @@ -518,12 +527,6 @@ class URIScheme { String? memo; if (queryParameters.containsKey(memoParameterName)) { memo = queryParameters[memoParameterName]!; - if (memoType == null) { - return IsValidSep7UrlResult( - result: false, - reason: - "Parameter '$memoParameterName' requires parameter '$memoTypeParameterName'"); - } if (memoType == memoTextType) { try { MemoText(memo); @@ -817,7 +820,7 @@ class URIScheme { } final String urlSignatureLess = sep7Url.replaceAll( - "&$signatureParameterName=${Uri.encodeQueryComponent(signature)}", ""); + "&$signatureParameterName=${Uri.encodeComponent(signature)}", ""); final Uint8List payloadBytes = _getPayload(urlSignatureLess); return signerKeyPair.verify(payloadBytes, base64Decode(signature)); } @@ -833,7 +836,7 @@ class URIScheme { final Uint8List payloadBytes = _getPayload(url); final Uint8List signatureBytes = signerKeypair.sign(payloadBytes); final String base64Signature = base64Encode(signatureBytes); - return Uri.encodeQueryComponent(base64Signature); + return Uri.encodeComponent(base64Signature); } Uint8List _getPayload(String url) { @@ -851,6 +854,82 @@ class URIScheme { b.add(url8List); return b.toBytes(); } + + /// Takes a list of [UriSchemeReplacement] objects and parses it to a string that + /// could be used as a Sep-7 URI 'replace' param. + /// + /// This string identifies the fields to be replaced in the XDR using + /// the 'Txrep (SEP-0011)' representation, which should be specified in the format of: + /// txrep_tx_field_name_1:reference_identifier_1,txrep_tx_field_name_2:reference_identifier_2;reference_identifier_1:hint_1,reference_identifier_2:hint_2 + /// + /// @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0011.md + String uriSchemeReplacementsToString( + List replacements) { + if (replacements.isEmpty) { + return ""; + } + + String fields = ""; + String hints = ""; + replacements.forEach((var item) { + fields += + "${item.path}${replacementIdDelimiter}${item.id}${replacementListDelimiter}"; + final nextHint = "${item.id}${replacementIdDelimiter}${item.hint}${replacementListDelimiter}"; + if (!hints.contains(nextHint)) { + hints += nextHint; + } + }); + + return fields.substring(0, fields.length - 1) + + replacementHintDelimiter + + hints.substring(0, hints.length - 1); + } + + /// Takes a Sep-7 URL-decoded '[replace]' string param and parses it to a list of + /// [UriSchemeReplacement] objects for easy of use. + /// This string identifies the fields to be replaced in the XDR using + /// the 'Txrep (SEP-0011)' representation, which should be specified in the format of: + /// txrep_tx_field_name_1:reference_identifier_1,txrep_tx_field_name_2:reference_identifier_2;reference_identifier_1:hint_1,reference_identifier_2:hint_2 + /// + /// @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0011.md + List uriSchemeReplacementsFromString(String replace) { + if (replace.length == 0) { + return []; + } + + final fieldsAndHints = replace.split(replacementHintDelimiter); + var fieldsAndIds = List.empty(growable: true); + if (fieldsAndHints.isNotEmpty) { + fieldsAndIds = fieldsAndHints.first.split(replacementListDelimiter); + } + var idsAndHints = List.empty(growable: true); + if (fieldsAndHints.length > 1) { + idsAndHints = fieldsAndHints[1].split(replacementListDelimiter); + } + + Map fields = {}; + for (var item in fieldsAndIds) { + final fieldAndId = item.split(replacementIdDelimiter); + if (fieldAndId.length > 1) { + fields[fieldAndId.first] = fieldAndId[1]; + } + } + + Map hints = {}; + for (var item in idsAndHints) { + final idAndHint = item.split(replacementIdDelimiter); + if (idAndHint.length > 1) { + hints[idAndHint.first] = idAndHint[1]; + } + } + + var result = List.empty(growable: true); + fields.forEach((path, id) { + String hint = hints[id] ?? ""; + result.add(UriSchemeReplacement(id, path, hint)); + }); + return result; + } } class SubmitUriSchemeTransactionResponse { @@ -916,3 +995,11 @@ class ParsedSep7UrlResult { ParsedSep7UrlResult(this.operationType, this.queryParameters); } + +class UriSchemeReplacement { + String id; + String path; + String hint; + + UriSchemeReplacement(this.id, this.path, this.hint); +} diff --git a/lib/src/stellar_sdk.dart b/lib/src/stellar_sdk.dart index 39c881f..f054211 100644 --- a/lib/src/stellar_sdk.dart +++ b/lib/src/stellar_sdk.dart @@ -31,7 +31,7 @@ import 'requests/liquidity_pools_request_builder.dart'; /// Main class of the flutter stellar sdk. class StellarSDK { - static const versionNumber = "1.8.7"; + static const versionNumber = "1.8.8"; static final StellarSDK PUBLIC = StellarSDK("https://horizon.stellar.org"); static final StellarSDK TESTNET = diff --git a/pubspec.yaml b/pubspec.yaml index cddf675..e71994f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: stellar_flutter_sdk description: A stellar blockchain sdk that query's horizon, build, signs and submits transactions to the stellar network. -version: 1.8.7 +version: 1.8.8 homepage: https://github.com/Soneso/stellar_flutter_sdk environment: diff --git a/test/query_test.dart b/test/query_test.dart index e285da9..9c636c4 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -182,7 +182,7 @@ void main() { /// ! get Claimable Balance ID from BID result at claimable_balance_test.dart Page operationsPage = await sdk.operations .forClaimableBalance( - "000000002110ee2c5929217b791890e5aa7c1c558485eb3b88e985dfc05e17e6f7b9cc3e") + "00000000da4c619f56bc6e039d752c2755da943a1f1971e1fb23050ca9db97e68bc7672c") .limit(1) .order(RequestBuilderOrder.DESC) .execute(); @@ -195,7 +195,7 @@ void main() { /// ! get Claimable Balance ID from BID result at claimable_balance_test.dart Page transactionsPage = await sdk.transactions .forClaimableBalance( - "000000002110ee2c5929217b791890e5aa7c1c558485eb3b88e985dfc05e17e6f7b9cc3e") + "00000000da4c619f56bc6e039d752c2755da943a1f1971e1fb23050ca9db97e68bc7672c") .limit(1) .order(RequestBuilderOrder.DESC) .execute(); diff --git a/test/sep0007_test.dart b/test/sep0007_test.dart index 8e19222..9992cf3 100644 --- a/test/sep0007_test.dart +++ b/test/sep0007_test.dart @@ -12,10 +12,12 @@ void main() { "GDGUF4SCNINRDCRUIVOMDYGIMXOWVP3ZLMTL2OGQIWMFDDSECZSFQMQV"; final String secretSeed = "SBA2XQ5SRUW5H3FUQARMC6QYEPUYNSVCMM4PGESGVB2UIFHLM73TPXXF"; - final String originDomainParam = "&origin_domain=place.domain.com"; - final String callbackParam = "&callback=url:https://examplepost.com"; + final String originDomain = "place.domain.com"; + final String callbackUrl = "url:https://examplepost.com"; final KeyPair signerKeyPair = KeyPair.fromSecretSeed(secretSeed); final URIScheme uriScheme = URIScheme(); + final txXdr = Uri.encodeComponent( + "AAAAAgAAAACBv/Oc5CHGxiLZ4Xc4ehTB2jEB29pFIFnvyuLL6D0eQQAAAGQABE6rAAAAAQAAAAEAAAAAAAAAAAAAAABnAF3fAAAAAQAAAAtNZW1vIHN0cmluZwAAAAABAAAAAQAAAACBv/Oc5CHGxiLZ4Xc4ehTB2jEB29pFIFnvyuLL6D0eQQAAAAAAAAAAUsm2Z5rxXqY9/Fj7HVJq+jDt0ybXZ1AauYQyPzHrCqsAAAAAO6oMQAAAAAAAAAAA"); String requestToml() { return '''# Sample stellar.toml @@ -52,20 +54,24 @@ void main() { }); }); - test('test generate sign transaction url', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); + Transaction getTestTransaction() { SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); setOp.setSourceAccount(accountId); setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); + return TransactionBuilder(Account(accountId, BigInt.zero)) + .addOperation(setOp.build()) + .build(); + } + + test('test generate tx url', () { + final transaction = getTestTransaction(); String url = uriScheme.generateSignTransactionURI(transaction.toEnvelopeXdrBase64()); assert(url.startsWith( "web+stellar:tx?xdr=AAAAAgAAAADNQvJCahsRijRFXMHgyGXdar95Wya9O")); }); - test('test generate pay operation url', () async { + test('test generate pay url', () { String url = uriScheme.generatePayOperationURI(accountId, amount: "123.21", assetCode: "ANA", @@ -76,29 +82,19 @@ void main() { url); }); - test('check missing signature from URI scheme', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + test('test missing signature', () async { + final transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); final validationResult = await uriScheme.isValidSep7SignedUrl(url); assert(!validationResult.result); assert(validationResult.reason == "Missing parameter 'signature'"); }); - test('check missing domain from URI scheme', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); + test('test missing origin domain', () async { + final transaction = getTestTransaction(); String url = uriScheme.generateSignTransactionURI(transaction.toEnvelopeXdrBase64()); @@ -107,16 +103,11 @@ void main() { assert(validationResult.reason == "Missing parameter 'origin_domain'"); }); - test('generate signed Tx Test Url', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + test('test generate signed tx url', () { + final transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); url = uriScheme.addSignature(url, signerKeyPair); final parsedResult = uriScheme.tryParseSep7Url(url); @@ -125,16 +116,11 @@ void main() { .containsKey(URIScheme.signatureParameterName)); }); - test('validate Test Url', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + test('test signed transaction ok', () async { + Transaction transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); url = uriScheme.addSignature(url, signerKeyPair); final parsedResult = uriScheme.tryParseSep7Url(url); @@ -159,16 +145,16 @@ void main() { uriScheme.httpClient = http.Client(); }); - test('sign and submit transaction', () async { + test('test sign and submit transaction to stellar', () async { AccountResponse sourceAccount = await sdk.accounts.account(accountId); SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); setOp.setSourceAccount(accountId); setOp.setHomeDomain("www.soneso.com"); Transaction transaction = TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); url = uriScheme.addSignature(url, signerKeyPair); final parsedResult = uriScheme.tryParseSep7Url(url); @@ -182,17 +168,12 @@ void main() { assert(response.submitTransactionResponse!.success); }); - test('sign and submit transaction to callback', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam + - callbackParam; + test('test sign and submit transaction to callback', () async { + final transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain, + callback: callbackUrl); url = uriScheme.addSignature(url, signerKeyPair); final parsedResult = uriScheme.tryParseSep7Url(url); @@ -218,16 +199,11 @@ void main() { uriScheme.httpClient = http.Client(); }); - test('check Toml Signature Missing', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + test('test toml signature missing', () async { + final transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); url = uriScheme.addSignature(url, signerKeyPair); @@ -254,16 +230,11 @@ void main() { uriScheme.httpClient = http.Client(); }); - test('check Toml Signature Mismatch', () async { - AccountResponse sourceAccount = await sdk.accounts.account(accountId); - SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); - setOp.setSourceAccount(accountId); - setOp.setHomeDomain("www.soneso.com"); - Transaction transaction = - TransactionBuilder(sourceAccount).addOperation(setOp.build()).build(); - String url = uriScheme - .generateSignTransactionURI(transaction.toEnvelopeXdrBase64()) + - originDomainParam; + test('test toml signature invalid', () async { + final transaction = getTestTransaction(); + String url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + originDomain: originDomain); url = uriScheme.addSignature(url, signerKeyPair); @@ -294,9 +265,9 @@ void main() { uriScheme.httpClient = http.Client(); }); - test('test invalid sep7 url', () { - var url = "https://soneso.com/tx?xdr=AAAAAgAAAADNQvJCahsRijRFXMHgyGXdar95Wya9O"; - final txXdr = Uri.encodeQueryComponent("AAAAAgAAAACBv/Oc5CHGxiLZ4Xc4ehTB2jEB29pFIFnvyuLL6D0eQQAAAGQABE6rAAAAAQAAAAEAAAAAAAAAAAAAAABnAF3fAAAAAQAAAAtNZW1vIHN0cmluZwAAAAABAAAAAQAAAACBv/Oc5CHGxiLZ4Xc4ehTB2jEB29pFIFnvyuLL6D0eQQAAAAAAAAAAUsm2Z5rxXqY9/Fj7HVJq+jDt0ybXZ1AauYQyPzHrCqsAAAAAO6oMQAAAAAAAAAAA"); + test('test sep7 url validation', () { + var url = + "https://soneso.com/tx?xdr=AAAAAgAAAADNQvJCahsRijRFXMHgyGXdar95Wya9O"; var validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); @@ -305,7 +276,8 @@ void main() { url = "web+stellar:tx/pay?destination=$accountId"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Invalid number of path segments. Must only have one path segment"); + assert(validationResult.reason == + "Invalid number of path segments. Must only have one path segment"); url = "web+stellar:203842"; validationResult = uriScheme.isValidSep7Url(url); @@ -315,154 +287,227 @@ void main() { url = "web+stellar:tx?destination=$accountId"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Operation type tx must have a 'xdr' parameter"); + assert(validationResult.reason == + "Operation type tx must have a 'xdr' parameter"); url = "web+stellar:tx?xdr=12345673773"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The provided 'xdr' parameter is not a valid transaction envelope"); + assert(validationResult.reason == + "The provided 'xdr' parameter is not a valid transaction envelope"); url = "web+stellar:pay?xdr=$txXdr"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'xdr' for operation type 'pay'"); + assert(validationResult.reason == + "Unsupported parameter 'xdr' for operation type 'pay'"); url = "web+stellar:pay?amount=20"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Operation type pay must have a 'destination' parameter"); + assert(validationResult.reason == + "Operation type pay must have a 'destination' parameter"); url = "web+stellar:pay?destination=12345673773"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The provided 'destination' parameter is not a valid Stellar address"); + assert(validationResult.reason == + "The provided 'destination' parameter is not a valid Stellar address"); url = "web+stellar:tx?xdr=$txXdr&destination=$accountId"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'destination' for operation type 'tx'"); + assert(validationResult.reason == + "Unsupported parameter 'destination' for operation type 'tx'"); url = "web+stellar:tx?xdr=$txXdr&asset_code=USDC"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'asset_code' for operation type 'tx'"); + assert(validationResult.reason == + "Unsupported parameter 'asset_code' for operation type 'tx'"); url = "web+stellar:tx?xdr=$txXdr&asset_issuer=$accountId"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'asset_issuer' for operation type 'tx'"); + assert(validationResult.reason == + "Unsupported parameter 'asset_issuer' for operation type 'tx'"); url = "web+stellar:tx?xdr=$txXdr&memo=123"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'memo' for operation type 'tx'"); + assert(validationResult.reason == + "Unsupported parameter 'memo' for operation type 'tx'"); url = "web+stellar:tx?xdr=$txXdr&memo_type=id"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'memo_type' for operation type 'tx'"); + assert(validationResult.reason == + "Unsupported parameter 'memo_type' for operation type 'tx'"); url = "web+stellar:tx?xdr=$txXdr&pubkey=123434938"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The provided 'pubkey' parameter is not a valid Stellar public key"); + assert(validationResult.reason == + "The provided 'pubkey' parameter is not a valid Stellar public key"); url = "web+stellar:pay?destination=$accountId&replace=123"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'replace' for operation type 'pay'"); + assert(validationResult.reason == + "Unsupported parameter 'replace' for operation type 'pay'"); - url = "web+stellar:pay?destination=$accountId&msg=lksjafhdalkjsfhkldjahsflkjhasfhasdkjfhasdlfkjhdlkfhjasdlkjhfdskljhflkdsajhfladskjhflasdkjhfklasdjhfadslkjhfdlksjhflasdkjhflsdakjhfkasdjlhfljkdshfkjdshaflkjdhsalfkhdskjflhsadlkjfhdlskjhfasdlkfhdlsakjfhdlkjfhlaskdjhfldsajhfsldjkahflkjsdahflksjafhdalkjsfhkldjahsflkjhasfhasdkjfhasdlfkjhdlkfhjasdlkjhfdskljhflkdsajhfladskjhflasdkjhfklasdjhfadslkjhfdlksjhflasdkjhflsdakjhfkasdjlhfljkdshfkjdshaflkjdhsalfkhdskjflhsadlkjfhdlskjhfasdlkfhdlsakjfhdlkjfhlaskdjhfldsajhfsldjkahflkjsdahf"; + url = + "web+stellar:pay?destination=$accountId&msg=lksjafhdalkjsfhkldjahsflkjhasfhasdkjfhasdlfkjhdlkfhjasdlkjhfdskljhflkdsajhfladskjhflasdkjhfklasdjhfadslkjhfdlksjhflasdkjhflsdakjhfkasdjlhfljkdshfkjdshaflkjdhsalfkhdskjflhsadlkjfhdlskjhfasdlkfhdlsakjfhdlkjfhlaskdjhfldsajhfsldjkahflkjsdahflksjafhdalkjsfhkldjahsflkjhasfhasdkjfhasdlfkjhdlkfhjasdlkjhfdskljhflkdsajhfladskjhflasdkjhfklasdjhfadslkjhfdlksjhflasdkjhflsdakjhfkasdjlhfljkdshfkjdshaflkjdhsalfkhdskjflhsadlkjfhdlskjhfasdlkfhdlsakjfhdlkjfhlaskdjhfldsajhfsldjkahflkjsdahf"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The 'msg' parameter should be no longer than 300 characters"); + assert(validationResult.reason == + "The 'msg' parameter should be no longer than 300 characters"); url = "web+stellar:pay?destination=$accountId&origin_domain=911"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The 'origin_domain' parameter is not a fully qualified domain name"); + assert(validationResult.reason == + "The 'origin_domain' parameter is not a fully qualified domain name"); url = "web+stellar:pay?destination=$accountId&chain=911"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Unsupported parameter 'chain' for operation type 'pay'"); - - url = "web+stellar:pay?destination=$accountId&asset_code=19281209831092830912830917409238904231493827139871239847234"; - validationResult = uriScheme.isValidSep7Url(url); - assert(!validationResult.result); - assert(validationResult.reason == "The provided 'asset_code' parameter is not a valid Stellar asset code"); + assert(validationResult.reason == + "Unsupported parameter 'chain' for operation type 'pay'"); - url = "web+stellar:pay?destination=$accountId&asset_issuer=19281209831092830912830917409238904231493827139871239847234"; + url = + "web+stellar:pay?destination=$accountId&asset_code=19281209831092830912830917409238904231493827139871239847234"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "The provided 'asset_issuer' parameter is not a valid Stellar address"); + assert(validationResult.reason == + "The provided 'asset_code' parameter is not a valid Stellar asset code"); - url = "web+stellar:pay?destination=$accountId&memo=abracadabra"; + url = + "web+stellar:pay?destination=$accountId&asset_issuer=19281209831092830912830917409238904231493827139871239847234"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' requires parameter 'memo_type'"); + assert(validationResult.reason == + "The provided 'asset_issuer' parameter is not a valid Stellar address"); - url = "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=zulu"; + url = + "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=zulu"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); assert(validationResult.reason == "Unsupported 'memo_type' value 'zulu'"); - url = "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_ID"; + url = + "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_ID"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' of type 'MEMO_ID' has an invalid value"); + assert(validationResult.reason == + "Parameter 'memo' of type 'MEMO_ID' has an invalid value"); - url = "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_HASH"; + url = + "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_HASH"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' or type 'MEMO_HASH' must be base64 encoded"); + assert(validationResult.reason == + "Parameter 'memo' or type 'MEMO_HASH' must be base64 encoded"); - url = "web+stellar:pay?destination=$accountId&memo=YWxrc2RmajA5MzIxOTA0dWtkbm1sc2EgeDJlb2pmZGxzd2tkajg5YXMgd3PDtmRhc0pEQVNVOVVESiBBU0Rhc0RBc2R3cWVxdw%3D%3D&memo_type=MEMO_HASH"; + url = + "web+stellar:pay?destination=$accountId&memo=YWxrc2RmajA5MzIxOTA0dWtkbm1sc2EgeDJlb2pmZGxzd2tkajg5YXMgd3PDtmRhc0pEQVNVOVVESiBBU0Rhc0RBc2R3cWVxdw%3D%3D&memo_type=MEMO_HASH"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' of type 'MEMO_HASH' has an invalid value"); + assert(validationResult.reason == + "Parameter 'memo' of type 'MEMO_HASH' has an invalid value"); - url = "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_RETURN"; + url = + "web+stellar:pay?destination=$accountId&memo=abracadabra&memo_type=MEMO_RETURN"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' or type 'MEMO_RETURN' must be base64 encoded"); + assert(validationResult.reason == + "Parameter 'memo' or type 'MEMO_RETURN' must be base64 encoded"); - url = "web+stellar:pay?destination=$accountId&memo=YWxrc2RmajA5MzIxOTA0dWtkbm1sc2EgeDJlb2pmZGxzd2tkajg5YXMgd3PDtmRhc0pEQVNVOVVESiBBU0Rhc0RBc2R3cWVxdw%3D%3D&memo_type=MEMO_RETURN"; + url = + "web+stellar:pay?destination=$accountId&memo=YWxrc2RmajA5MzIxOTA0dWtkbm1sc2EgeDJlb2pmZGxzd2tkajg5YXMgd3PDtmRhc0pEQVNVOVVESiBBU0Rhc0RBc2R3cWVxdw%3D%3D&memo_type=MEMO_RETURN"; validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Parameter 'memo' of type 'MEMO_RETURN' has an invalid value"); + assert(validationResult.reason == + "Parameter 'memo' of type 'MEMO_RETURN' has an invalid value"); SetOptionsOperationBuilder setOp = SetOptionsOperationBuilder(); setOp.setSourceAccount(accountId); setOp.setHomeDomain("www.soneso.com"); Transaction transaction = - TransactionBuilder(Account(accountId, BigInt.zero)).addOperation(setOp.build()).build(); - url = uriScheme.generateSignTransactionURI(transaction.toEnvelopeXdrBase64()); - for(var i=0;i<10;i++) { - url = uriScheme.generateSignTransactionURI(transaction.toEnvelopeXdrBase64(), chain: url); + TransactionBuilder(Account(accountId, BigInt.zero)) + .addOperation(setOp.build()) + .build(); + url = + uriScheme.generateSignTransactionURI(transaction.toEnvelopeXdrBase64()); + for (var i = 0; i < 10; i++) { + url = uriScheme.generateSignTransactionURI( + transaction.toEnvelopeXdrBase64(), + chain: url); } validationResult = uriScheme.isValidSep7Url(url); assert(!validationResult.result); - assert(validationResult.reason == "Chaining more then 7 nested levels is not allowed"); + assert(validationResult.reason == + "Chaining more then 7 nested levels is not allowed"); - url = 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&memo_type=MEMO_TEXT&msg=pay%20me%20with%20lumens'; + url = + 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&memo_type=MEMO_TEXT&msg=pay%20me%20with%20lumens'; validationResult = uriScheme.isValidSep7Url(url); assert(validationResult.result); - url = 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.123&asset_code=USD&asset_issuer=GCRCUE2C5TBNIPYHMEP7NK5RWTT2WBSZ75CMARH7GDOHDDCQH3XANFOB&memo=hasysda987fs&memo_type=MEMO_TEXT&callback=url%3Ahttps%3A%2F%2FsomeSigningService.com%2Fhasysda987fs%3Fasset%3DUSD'; + url = + 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.123&asset_code=USD&asset_issuer=GCRCUE2C5TBNIPYHMEP7NK5RWTT2WBSZ75CMARH7GDOHDDCQH3XANFOB&memo=hasysda987fs&memo_type=MEMO_TEXT&callback=url%3Ahttps%3A%2F%2FsomeSigningService.com%2Fhasysda987fs%3Fasset%3DUSD'; validationResult = uriScheme.isValidSep7Url(url); assert(validationResult.result); - url = 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&memo_type=MEMO_TEXT&msg=pay%20me%20with%20lumens&origin_domain=someDomain.com&signature=tbsLtlK%2FfouvRWk2UWFP47yHYeI1g1NEC%2FfEQvuXG6V8P%2BbeLxplYbOVtTk1g94Wp97cHZ3pVJy%2FtZNYobl3Cw%3D%3D'; + url = + 'web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&memo_type=MEMO_TEXT&msg=pay%20me%20with%20lumens&origin_domain=someDomain.com&signature=tbsLtlK%2FfouvRWk2UWFP47yHYeI1g1NEC%2FfEQvuXG6V8P%2BbeLxplYbOVtTk1g94Wp97cHZ3pVJy%2FtZNYobl3Cw%3D%3D'; validationResult = uriScheme.isValidSep7Url(url); assert(validationResult.result); - url = 'web+stellar:tx?xdr=AAAAAP%2Byw%2BZEuNg533pUmwlYxfrq6%2FBoMJqiJ8vuQhf6rHWmAAAAZAB8NHAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABSFVHAAAAAABAH0wIyY3BJBS2qHdRPAV80M8hF7NBpxRjXyjuT9kEbH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAAAAAAAAA%3D&callback=url%3Ahttps%3A%2F%2FsomeSigningService.com%2Fa8f7asdfkjha&pubkey=GAU2ZSYYEYO5S5ZQSMMUENJ2TANY4FPXYGGIMU6GMGKTNVDG5QYFW6JS&msg=order%20number%2024'; + url = + 'web+stellar:tx?xdr=AAAAAP%2Byw%2BZEuNg533pUmwlYxfrq6%2FBoMJqiJ8vuQhf6rHWmAAAAZAB8NHAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABSFVHAAAAAABAH0wIyY3BJBS2qHdRPAV80M8hF7NBpxRjXyjuT9kEbH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAAAAAAAAA%3D&callback=url%3Ahttps%3A%2F%2FsomeSigningService.com%2Fa8f7asdfkjha&pubkey=GAU2ZSYYEYO5S5ZQSMMUENJ2TANY4FPXYGGIMU6GMGKTNVDG5QYFW6JS&msg=order%20number%2024'; validationResult = uriScheme.isValidSep7Url(url); assert(validationResult.result); - url = 'web+stellar:tx?xdr=AAAAAP%2Byw%2BZEuNg533pUmwlYxfrq6%2FBoMJqiJ8vuQhf6rHWmAAAAZAB8NHAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABSFVHAAAAAABAH0wIyY3BJBS2qHdRPAV80M8hF7NBpxRjXyjuT9kEbH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAAAAAAAAA%3D&replace=sourceAccount%3AX%3BX%3Aaccount%20on%20which%20to%20create%20the%20trustline'; + url = + 'web+stellar:tx?xdr=AAAAAP%2Byw%2BZEuNg533pUmwlYxfrq6%2FBoMJqiJ8vuQhf6rHWmAAAAZAB8NHAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABSFVHAAAAAABAH0wIyY3BJBS2qHdRPAV80M8hF7NBpxRjXyjuT9kEbH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAAAAAAAAA%3D&replace=sourceAccount%3AX%3BX%3Aaccount%20on%20which%20to%20create%20the%20trustline'; validationResult = uriScheme.isValidSep7Url(url); assert(validationResult.result); }); + + test('test replace param composing and parsing', () { + final first = UriSchemeReplacement( + 'X', 'sourceAccount', 'account from where you want to pay fees'); + final second = UriSchemeReplacement('Y', 'operations[0].sourceAccount', + 'account that needs the trustline and which will receive the new tokens'); + final third = UriSchemeReplacement('Y', 'operations[1].destination', + 'account that needs the trustline and which will receive the new tokens'); + + var replace = + uriScheme.uriSchemeReplacementsToString([first, second, third]); + var expected = + "sourceAccount:X,operations[0].sourceAccount:Y,operations[1].destination:Y;X:account from where you want to pay fees,Y:account that needs the trustline and which will receive the new tokens"; + assert(expected == replace); + + var url = "web+stellar:tx?xdr=$txXdr&replace=$replace"; + var validationResult = uriScheme.isValidSep7Url(url); + assert(validationResult.result); + + var replacements = uriScheme.uriSchemeReplacementsFromString(replace); + assert(replacements.length == 3); + final firstParsed = replacements.first; + assert(first.id == firstParsed.id); + assert(first.path == firstParsed.path); + assert(first.hint == firstParsed.hint); + + final secondParsed = replacements[1]; + assert(second.id == secondParsed.id); + assert(second.path == secondParsed.path); + assert(second.hint == secondParsed.hint); + + final thirdParsed = replacements[2]; + assert(third.id == thirdParsed.id); + assert(third.path == thirdParsed.path); + assert(third.hint == thirdParsed.hint); + }); }