Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle toncoin deeplink address to open transfer tokens. #5444

Merged
merged 1 commit into from
Dec 12, 2023
Merged
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
2 changes: 0 additions & 2 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9152,7 +9152,6 @@
3A73FC9C258B1AF700FE4D34 /* MarketWatchlistViewController.swift in Sources */,
11B35B086B0D62A9D7A10CD0 /* AddTokenViewModel.swift in Sources */,
11B35A42BF19B93C6005FBD9 /* AddTokenService.swift in Sources */,
D0D90ADD2B1DA7DF0047C320 /* ScriptType.swift in Sources */,
11B35D550563934444558D15 /* AddTokenViewController.swift in Sources */,
58AAABE760739C0ECC6CA720 /* DownloadService.swift in Sources */,
11B3502E7AA00ACFE8EE8CD9 /* FormTextView.swift in Sources */,
Expand Down Expand Up @@ -10819,7 +10818,6 @@
11B35EF70120304F6D4F5561 /* MarketMultiSortHeaderView.swift in Sources */,
D05E969A2A26278D002CCD71 /* TronApproveTransactionRecord.swift in Sources */,
2FA5D40FA7CAC1BA01C0B373 /* CoinOverviewViewModel.swift in Sources */,
D0D90ADC2B1DA7DF0047C320 /* ScriptType.swift in Sources */,
2FA5DBE42827FF3D114DBF4B /* CoinOverviewService.swift in Sources */,
2FA5DF4BB73C12C8DDF42599 /* CoinOverviewModule.swift in Sources */,
D05E969D2A2627AF002CCD71 /* TronContractCallTransactionRecord.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,23 @@ extension AddressService {
}

func handleFetched(text: String) -> String {
let result = addressUriParser.parse(addressUri: text.trimmingCharacters(in: .whitespaces))
switch result {
case .invalidBlockchainType:
showUriErrorRelay.accept(UriError.invalidBlockchainType)
return ""
case .invalidTokenType:
showUriErrorRelay.accept(UriError.invalidTokenType)
return ""
case .noUri, .wrongUri:
let text = text.trimmingCharacters(in: .whitespacesAndNewlines)
set(text: text)
return text
case let .uri(data):

if let amount = data.amount {
do {
let result = try addressUriParser.parse(url: text.trimmingCharacters(in: .whitespaces))
if let amount = result.amount {
publishAmountRelay.accept(amount)
}
set(text: data.address)
return data.address
set(text: result.address)
return result.address
} catch {
switch error {
case AddressUriParser.ParseError.noUri, AddressUriParser.ParseError.wrongUri:
let text = text.trimmingCharacters(in: .whitespacesAndNewlines)
set(text: text)
return text
default:
showUriErrorRelay.accept(error)
return ""
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ class AddressUriParser {
return [prefix, s2].compactMap { $0 }.joined(separator: ":")
}

private func parameters(from queryItems: [URLQueryItem]) -> (handled: [AddressUri.Field: String], unhandled: [String: String]) {
var handled = [AddressUri.Field: String]()
var unhandled = [String: String]()

for item in queryItems {
guard let value = item.value else { continue }
if let field = AddressUri.Field(rawValue: item.name) {
handled[field] = value
} else {
unhandled[item.name] = value
}
}

return (handled: handled, unhandled: unhandled)
}

private func validate(parameters: [AddressUri.Field: String]) throws {
if let blockchainType,
let uid: String = parameters[.blockchainUid],
blockchainType != BlockchainType(uid: uid)
{
throw ParseError.invalidBlockchainType
}

if let uid: String = parameters[.tokenUid],
let tokenType,
tokenType != TokenType(id: uid)
{
throw ParseError.invalidTokenType
}
}

private func fullAddress(scheme: String, address: String, uriBlockchainUid: String? = nil) -> String {
// there is no explicit indication of the blockchain in the uri. We use the rules of the blockchain parser
guard let uriBlockchainUid else {
Expand All @@ -34,45 +66,54 @@ class AddressUriParser {
return pair(BlockchainType(uid: uriBlockchainUid), address)
}

func parse(addressUri: String) -> Result {
guard let components = URLComponents(string: addressUri), let scheme = components.scheme else {
return .noUri
}
if let validScheme = blockchainType?.uriScheme, components.scheme != validScheme {
return .invalidBlockchainType
private func handleDeepLink(url: String) -> AddressUri? {
guard let components = URLComponents(string: url), let scheme = components.scheme else {
return nil
}

var uri = AddressUri(scheme: scheme)
guard let parameters = components.queryItems else {
uri.address = fullAddress(scheme: scheme, address: components.path)
return .uri(uri)
}
// try to parse ton deeplink
if scheme == DeepLinkManager.tonDeepLinkScheme, let tonScheme = BlockchainType.ton.uriScheme {
var uri = AddressUri(scheme: tonScheme)
uri.address = components.path.stripping(prefix: "/")

for parameter in parameters {
guard let value = parameter.value else { continue }
if let field = AddressUri.Field(rawValue: parameter.name) {
uri.parameters[field] = parameter.value
} else {
uri.unhandledParameters[parameter.name] = value
var params = parameters(from: components.queryItems ?? [])
if let amount = params.handled[.amount] {
params.handled[.amount] = TonAdapter.amount(kitAmount: amount).description
}
params.handled[.blockchainUid] = BlockchainType.ton.uid

uri.parameters = params.handled
uri.unhandledParameters = params.unhandled

return uri
}

if let uid: String = uri.value(field: .blockchainUid),
let blockchainType,
blockchainType != BlockchainType(uid: uid)
{
return .invalidBlockchainType
return nil
}

func handleAddressUri(uri: String) throws -> AddressUri {
guard let components = URLComponents(string: uri), let scheme = components.scheme else {
throw ParseError.noUri
}

if let uid: String = uri.value(field: .tokenUid),
let tokenType,
tokenType != TokenType(id: uid)
{
return .invalidTokenType
if let validScheme = blockchainType?.uriScheme, components.scheme != validScheme {
throw ParseError.invalidBlockchainType
}

var uri = AddressUri(scheme: scheme)
guard let items = components.queryItems else {
uri.address = fullAddress(scheme: scheme, address: components.path)
return uri
}

let params = parameters(from: items)
try validate(parameters: params.handled)

uri.parameters = params.handled
uri.unhandledParameters = params.unhandled
uri.address = fullAddress(scheme: scheme, address: components.path, uriBlockchainUid: uri.parameters[.blockchainUid])
return .uri(uri)

return uri
}

static func hasUriPrefix(text: String) -> Bool {
Expand All @@ -81,6 +122,15 @@ class AddressUriParser {
}

extension AddressUriParser {
func parse(url: String) throws -> AddressUri {
// check if we try to parse deeplink address (like ton://transfer/<address>)
if let addressUri = handleDeepLink(url: url) {
return addressUri
}

return try handleAddressUri(uri: url)
}

func uri(_ addressUri: AddressUri) -> String {
var components = URLComponents()
components.scheme = blockchainType?.uriScheme
Expand All @@ -100,12 +150,11 @@ extension AddressUriParser {
}

extension AddressUriParser {
enum Result {
enum ParseError: Error {
case wrongUri
case invalidBlockchainType
case invalidTokenType
case noUri
case uri(AddressUri)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import ComponentKit
import Foundation
import RxRelay
import RxSwift

class DeepLinkManager {
static let tonDeepLinkScheme = "ton"

private let newSchemeRelay = BehaviorRelay<DeepLink?>(value: nil)
}

Expand All @@ -28,6 +31,16 @@ extension DeepLinkManager {
return true
}

if scheme == Self.tonDeepLinkScheme {
let parser = AddressParserFactory.parser(blockchainType: .ton, tokenType: nil)
do {
let address = try parser.parse(url: url.absoluteString)
newSchemeRelay.accept(.transfer(addressUri: address))
} catch {
HudHelper.instance.show(banner: .error(string: error.localizedDescription))
}
}

if scheme == "unstoppable.money", host == "coin" {
let uid = path.replacingOccurrences(of: "/", with: "")

Expand All @@ -43,5 +56,6 @@ extension DeepLinkManager {
enum DeepLink {
case walletConnect(url: String)
case coin(uid: String)
case transfer(addressUri: AddressUri)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum AppConfig {
.arbitrumOne: "0xA24c159C7f1E4A04dab7c364C2A8b87b3dBa4cd1",
.gnosis: "0xA24c159C7f1E4A04dab7c364C2A8b87b3dBa4cd1",
.fantom: "0xA24c159C7f1E4A04dab7c364C2A8b87b3dBa4cd1",
.ton: "UQAYLATDlfKgn3cKZAgznvowhXzpqgxrIicesxJfo9f6PN3k",
.tron: "TQzANCd363w5CjRWDtswm8Y5nFPAdnwekF",
.solana: "5gattKnvu5f1NDHBuZ6VfDXjRrJa9UcAArkZ3ys3e82F",
]
Expand Down
18 changes: 14 additions & 4 deletions UnstoppableWallet/UnstoppableWallet/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BlockchairApiKey</key>
<string>${blockchair_api_key}</string>
<key>ArbiscanApiKey</key>
<string>${arbiscan_api_key}</string>
<key>BlockchairApiKey</key>
<string>${blockchair_api_key}</string>
<key>BscscanApiKey</key>
<string>${bscscan_api_key}</string>
<key>CFBundleDevelopmentRegion</key>
Expand Down Expand Up @@ -34,6 +34,16 @@
<string>unstoppable.money</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>toncoin.deeplink</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ton</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
Expand Down Expand Up @@ -103,8 +113,6 @@
</array>
<key>OfficeMode</key>
<string>${OfficeMode}</string>
<key>oneInchApiKey</key>
<string>${one_inch_api_key}</string>
<key>OpenSeaApiKey</key>
<string>${open_sea_api_key}</string>
<key>OptimismEtherscanApiKey</key>
Expand Down Expand Up @@ -154,5 +162,7 @@
<string>${unstoppable_domains_api_key}</string>
<key>WallectConnectV2ProjectKey</key>
<string>${wallet_connect_v2_project_key}</string>
<key>oneInchApiKey</key>
<string>${one_inch_api_key}</string>
</dict>
</plist>
Loading