-
Notifications
You must be signed in to change notification settings - Fork 180
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
#54 Public account type #65
Changes from all commits
d82fdd7
47ac3ae
877482a
9c7fc94
8545daf
6c254a0
09bc4c5
3662156
dc241ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe it would make sense to create a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, but IMHO only if we use the |
||
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) | ||
} | ||
} |
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) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add another
convenience init
to avoid passing the:
, It looks easy to forgetThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added two more
init
to make the type more flexible.