Skip to content

Commit

Permalink
Merge pull request #65 from WalletConnect/#54-account-type
Browse files Browse the repository at this point in the history
#54 Public account type
  • Loading branch information
André Vants committed Feb 10, 2022
2 parents 112cb5b + dc241ad commit 35c010d
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Example/DApp/ClientDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ClientDelegate: WalletConnectClientDelegate {
onSessionResponse?(sessionResponse)
}

func didUpdate(sessionTopic: String, accounts: Set<String>) {
func didUpdate(sessionTopic: String, accounts: Set<Account>) {
}

func didUpgrade(sessionTopic: String, permissions: Session.Permissions) {
Expand Down
6 changes: 3 additions & 3 deletions Example/ExampleApp/Responder/ResponderViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ extension ResponderViewController: SessionViewControllerDelegate {
print("[RESPONDER] Approving session...")
let proposal = currentProposal!
currentProposal = nil
let accounts = proposal.permissions.blockchains.map {$0+":\(account)"}
client.approve(proposal: proposal, accounts: Set(accounts))
let accounts = Set(proposal.permissions.blockchains.compactMap { Account($0+":\(account)") })
client.approve(proposal: proposal, accounts: accounts)
}

func didRejectSession() {
Expand Down Expand Up @@ -196,7 +196,7 @@ extension ResponderViewController: WalletConnectClientDelegate {

}

func didUpdate(sessionTopic: String, accounts: Set<String>) {
func didUpdate(sessionTopic: String, accounts: Set<Account>) {

}

Expand Down
94 changes: 94 additions & 0 deletions Sources/WalletConnect/Account.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
A value that identifies an account in any given blockchain.
This structure parses account IDs according to [CAIP-10].
Account IDs are prefixed with a [CAIP-2] blockchain ID, delimited by a `':'` character, followed by the account address.
Specifying a blockchain account by using a chain-agnostic identifier is useful to allow interoperability between multiple
chains when using both wallets and decentralized applications.
[CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md
[CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md
*/
public struct Account: Equatable, Hashable {

/// A blockchain namespace. Usually describes an ecosystem or standard.
public let namespace: String

/// A reference string that identifies a blockchain within a given namespace.
public let reference: String

/// The account's address specific to the blockchain.
public let address: String

/// The CAIP-2 blockchain identifier of the account.
public var blockchainIdentifier: String {
"\(namespace):\(reference)"
}

/// The CAIP-10 account identifier absolute string.
public var absoluteString: String {
"\(namespace):\(reference):\(address)"
}

/// Returns whether the account conforms to CAIP-10.
public var isCAIP10Conformant: Bool {
String.conformsToCAIP10(absoluteString)
}

/**
Creates an account instance from the provided string.
This initializer returns nil if the string doesn't represent a valid account id in conformance with
[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md).
*/
public init?(_ string: String) {
guard String.conformsToCAIP10(string) else { return nil }
let splits = string.split(separator: ":")
self.init(namespace: String(splits[0]), reference: String(splits[1]), address: String(splits[2]))
}

/**
Creates an account instance from a chain ID and an address.
This initializer returns nil if the `chainIdentifier` parameter doesn't represent a valid chain id in conformance with
[CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) or if the `address` format is invalid.
*/
public init?(chainIdentifier: String, address: String) {
self.init("\(chainIdentifier):\(address)")
}

/**
Creates an account instance directly from the base components.
This initializer bypass any checks to CAIP conformance, make sure to pass valid values as parameters.
*/
public init(namespace: String, reference: String, address: String) {
self.namespace = namespace
self.reference = reference
self.address = address
}
}

extension Account: LosslessStringConvertible {
public var description: String {
return absoluteString
}
}

extension Account: Codable {

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let absoluteString = try container.decode(String.self)
guard let account = Account(absoluteString) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed CAIP-10 account identifier.")
}
self = account
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(absoluteString)
}
}
14 changes: 5 additions & 9 deletions Sources/WalletConnect/WalletConnectClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ public final class WalletConnectClient {
/// - Parameters:
/// - proposal: Session Proposal received from peer client in a WalletConnect delegate function: `didReceive(sessionProposal: Session.Proposal)`
/// - accounts: A Set of accounts that the dapp will be allowed to request methods executions on.
public func approve(proposal: Session.Proposal, accounts: Set<String>) {
sessionEngine.approve(proposal: proposal.proposal, accounts: accounts)
public func approve(proposal: Session.Proposal, accounts: Set<Account>) {
sessionEngine.approve(proposal: proposal.proposal, accounts: Set(accounts.map { $0.absoluteString }))
}

/// For the responder to reject a session proposal.
Expand All @@ -153,12 +153,8 @@ public final class WalletConnectClient {
/// - Parameters:
/// - topic: Topic of the session that is intended to be updated.
/// - accounts: Set of accounts that will be allowed to be used by the session after the update.
public func update(topic: String, accounts: Set<String>) {
do {
try sessionEngine.update(topic: topic, accounts: accounts)
} catch {
print("Error on session update call: \(error)")
}
public func update(topic: String, accounts: Set<Account>) throws {
try sessionEngine.update(topic: topic, accounts: Set(accounts.map { $0.absoluteString }))
}

/// For the responder to upgrade session permissions
Expand Down Expand Up @@ -305,7 +301,7 @@ public final class WalletConnectClient {
delegate?.didUpgrade(sessionTopic: topic, permissions: upgradedPermissions)
}
sessionEngine.onSessionUpdate = { [unowned self] topic, accounts in
delegate?.didUpdate(sessionTopic: topic, accounts: accounts)
delegate?.didUpdate(sessionTopic: topic, accounts: Set(accounts.compactMap { Account($0) }))
}
sessionEngine.onNotificationReceived = { [unowned self] topic, notification in
delegate?.didReceive(notification: notification, sessionTopic: topic)
Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnect/WalletConnectClientDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public protocol WalletConnectClientDelegate: AnyObject {
/// Tells the delegate that extra accounts has been included in session sequence
///
/// Function is executed on controller and non-controller client when both communicating peers have successfully included new accounts requested by the controller client.
func didUpdate(sessionTopic: String, accounts: Set<String>)
func didUpdate(sessionTopic: String, accounts: Set<Account>)

/// Tells the delegate that the client has settled a session.
///
Expand Down
4 changes: 2 additions & 2 deletions Tests/IntegrationTests/ClientDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ClientDelegate: WalletConnectClientDelegate {
var onSessionRejected: ((String, Reason)->())?
var onSessionDelete: (()->())?
var onSessionUpgrade: ((String, Session.Permissions)->())?
var onSessionUpdate: ((String, Set<String>)->())?
var onSessionUpdate: ((String, Set<Account>)->())?
var onNotificationReceived: ((Session.Notification, String)->())?
var onPairingUpdate: ((String, AppMetadata)->())?

Expand Down Expand Up @@ -42,7 +42,7 @@ class ClientDelegate: WalletConnectClientDelegate {
func didUpgrade(sessionTopic: String, permissions: Session.Permissions) {
onSessionUpgrade?(sessionTopic, permissions)
}
func didUpdate(sessionTopic: String, accounts: Set<String>) {
func didUpdate(sessionTopic: String, accounts: Set<Account>) {
onSessionUpdate?(sessionTopic, accounts)
}
func didReceive(notification: Session.Notification, sessionTopic: String) {
Expand Down
10 changes: 5 additions & 5 deletions Tests/IntegrationTests/ClientTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ final class ClientTests: XCTestCase {
func testNewSession() {
let proposerSettlesSessionExpectation = expectation(description: "Proposer settles session")
let responderSettlesSessionExpectation = expectation(description: "Responder settles session")
let account = "0x022c0c42a80bd19EA4cF0F94c4F9F96645759716"
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!

let permissions = Session.Permissions.stub()
let uri = try! proposer.client.connect(sessionPermissions: permissions)!
Expand Down Expand Up @@ -244,7 +244,7 @@ final class ClientTests: XCTestCase {
func testSuccessfulSessionUpgrade() {
let proposerSessionUpgradeExpectation = expectation(description: "Proposer upgrades session on responder request")
let responderSessionUpgradeExpectation = expectation(description: "Responder upgrades session on proposer response")
let account = "0x022c0c42a80bd19EA4cF0F94c4F9F96645759716"
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
let permissions = Session.Permissions.stub()
let upgradePermissions = Session.Permissions(blockchains: ["eip155:42"], methods: ["eth_sendTransaction"])
let uri = try! proposer.client.connect(sessionPermissions: permissions)!
Expand All @@ -271,16 +271,16 @@ final class ClientTests: XCTestCase {
func testSuccessfulSessionUpdate() {
let proposerSessionUpdateExpectation = expectation(description: "Proposer updates session on responder request")
let responderSessionUpdateExpectation = expectation(description: "Responder updates session on proposer response")
let account = "eip155:42:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716"
let updateAccounts: Set<String> = ["eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"]
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
let updateAccounts: Set<Account> = [Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdf")!]
let permissions = Session.Permissions.stub()
let uri = try! proposer.client.connect(sessionPermissions: permissions)!
try! responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposal: proposal, accounts: [account])
}
responder.onSessionSettled = { [unowned self] sessionSettled in
responder.client.update(topic: sessionSettled.topic, accounts: updateAccounts)
try? responder.client.update(topic: sessionSettled.topic, accounts: updateAccounts)
}
responder.onSessionUpdate = { _, accounts in
XCTAssertEqual(accounts, updateAccounts)
Expand Down
52 changes: 52 additions & 0 deletions Tests/WalletConnectTests/AccountTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import XCTest
@testable import WalletConnect

final class AccountTests: XCTestCase {

func testInitFromString() {
// Valid accounts
XCTAssertNotNil(Account("std:0:0"))
XCTAssertNotNil(Account("chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7"))

// Invalid accounts
XCTAssertNil(Account("std:0:$"))
XCTAssertNil(Account("std:$:0"))
XCTAssertNil(Account("st:0:0"))
}

func testInitFromChainAndAddress() {
// Valid accounts
XCTAssertNotNil(Account(chainIdentifier: "std:0", address: "0"))
XCTAssertNotNil(Account(chainIdentifier: "chainstd:8c3444cf8970a9e41a706fab93e7a6c4", address: "6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7"))

// Invalid accounts
XCTAssertNil(Account(chainIdentifier: "std:0", address: ""))
XCTAssertNil(Account(chainIdentifier: "std", address: "0"))
}

func testInitCAIP10Conformance() {
XCTAssertTrue(Account(namespace: "std", reference: "0", address: "0").isCAIP10Conformant)

XCTAssertFalse(Account(namespace: "st", reference: "0", address: "0").isCAIP10Conformant)
XCTAssertFalse(Account(namespace: "std", reference: "", address: "0").isCAIP10Conformant)
XCTAssertFalse(Account(namespace: "std", reference: "0", address: "").isCAIP10Conformant)
}

func testBlockchainIdentifier() {
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
XCTAssertEqual(account.blockchainIdentifier, "eip155:1")
}

func testAbsoluteString() {
let accountString = "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"
let account = Account(accountString)!
XCTAssertEqual(account.absoluteString, accountString)
}

func testCodable() throws {
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
let encoded = try JSONEncoder().encode(account)
let decoded = try JSONDecoder().decode(Account.self, from: encoded)
XCTAssertEqual(account, decoded)
}
}

0 comments on commit 35c010d

Please sign in to comment.