├── .github
└── ISSUE_TEMPLATE
│ └── BOUNTY.yml
├── .gitignore
├── .travis.yml
├── Example
├── AccountViewController.swift
├── Default-568h@2x.png
├── Podfile
├── Podfile.lock
├── Tests
│ ├── AccessKeySpec.swift
│ ├── AccountSpec.swift
│ ├── EnviromentConfig.swift
│ ├── FormatSpec.swift
│ ├── Info.plist
│ ├── KeyPairSpec.swift
│ ├── KeystoreSpec.swift
│ ├── MergeKeyStoreSpec.swift
│ ├── PromisesSpec.swift
│ ├── ProviderSpec.swift
│ ├── SerializeSpec.swift
│ ├── SignerSpec.swift
│ ├── TestUtils.swift
│ ├── Transaction.json
│ ├── WalletAccountSpec.swift
│ ├── Wasm.swift
│ └── main.wasm
├── nearclientios.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── nearclientios-Example.xcscheme
├── nearclientios.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── nearclientios
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
│ ├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Info.plist
│ └── WelcomeViewController.swift
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── _Pods.xcodeproj
├── nearclientios.h
├── nearclientios.podspec
├── nearclientios.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── nearclientios
├── Assets
└── .gitkeep
└── Sources
├── Account.swift
├── AccountCreator.swift
├── Connection.swift
├── Contract.swift
├── DefaultAuthService.swift
├── KeyStores
├── FileSystemKeyStore.swift
├── InMemoryKeyStore.swift
├── KeyStore.swift
├── KeychainKeyStore.swift
├── MergeKeyStore.swift
└── SecureEnclaveKeyStore.swift
├── Near.swift
├── Providers
├── JSONRPCProvider.swift
└── Provider.swift
├── Signer.swift
├── Transaction.swift
├── Utils
├── AppInfo.swift
├── Borsh
│ ├── BinaryReader.swift
│ ├── Borsh.swift
│ ├── BorshDecoder.swift
│ ├── BorshDeserialize.swift
│ ├── BorshEncoder.swift
│ └── BorshSerialize.swift
├── FileManager.swift
├── FixedLengthByteArray.swift
├── Format.swift
├── Int2x
│ ├── Int2X.swift
│ └── UInt2X.swift
├── KeyPair.swift
├── Network.swift
├── SHA256.swift
├── Serialize.swift
├── URL+Params.swift
└── Web.swift
└── WalletAccount.swift
/.github/ISSUE_TEMPLATE/BOUNTY.yml:
--------------------------------------------------------------------------------
1 | name: "Simple Bounty"
2 | description: "Use this template to create a HEROES Simple Bounty via Github bot"
3 | title: "Bounty: "
4 | labels: ["bounty"]
5 | assignees: heroes-bot-test
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Hi! Let's set up your bounty! Please don't change the template - @heroes-bot-test won't be able to help you.
11 |
12 | - type: dropdown
13 | id: type
14 | attributes:
15 | label: What talent are you looking for?
16 | options:
17 | - Marketing
18 | - Development
19 | - Design
20 | - Other
21 | - Content
22 | - Research
23 | - Audit
24 |
25 | - type: textarea
26 | id: description
27 | attributes:
28 | label: What you need to be done?
29 |
30 | - type: dropdown
31 | id: tags
32 | attributes:
33 | label: Tags
34 | description: Add tags that match the topic of the work
35 | multiple: true
36 | options:
37 | - API
38 | - Blockchain
39 | - Community
40 | - CSS
41 | - DAO
42 | - dApp
43 | - DeFi
44 | - Design
45 | - Documentation
46 | - HTML
47 | - Javascript
48 | - NFT
49 | - React
50 | - Rust
51 | - Smart contract
52 | - Typescript
53 | - UI/UX
54 | - web3
55 | - Translation
56 | - Illustration
57 | - Branding
58 | - Copywriting
59 | - Blogging
60 | - Editing
61 | - Video Creation
62 | - Social Media
63 | - Graphic Design
64 | - Transcription
65 | - Product Design
66 | - Artificial Intelligence
67 | - Quality Assurance
68 | - Risk Assessment
69 | - Security Audit
70 | - Bug Bounty
71 | - Code Review
72 | - Blockchain Security
73 | - Smart Contract Testing
74 | - Penetration Testing
75 | - Vulnerability Assessment
76 | - BOS
77 | - News
78 | - Hackathon
79 | - NEARCON2023
80 | - NEARWEEK
81 |
82 | - type: input
83 | id: deadline
84 | attributes:
85 | label: Deadline
86 | description: "Set a deadline for your bounty. Please enter the date in format: DD.MM.YYYY"
87 | placeholder: "19.05.2027"
88 |
89 | - type: dropdown
90 | id: currencyType
91 | attributes:
92 | label: Currency
93 | description: What is the currency you want to pay?
94 | options:
95 | - USDC.e
96 | - USDT.e
97 | - DAI
98 | - wNEAR
99 | - USDt
100 | - XP
101 | - marmaj
102 | - NEKO
103 | - JUMP
104 | - USDC
105 | - NEARVIDIA
106 | default: 0
107 | validations:
108 | required: true
109 |
110 | - type: input
111 | id: currencyAmount
112 | attributes:
113 | label: Amount
114 | description: How much it will be cost?
115 |
116 | - type: markdown
117 | attributes:
118 | value: "## Advanced settings"
119 |
120 | - type: checkboxes
121 | id: kyc
122 | attributes:
123 | label: KYC
124 | description: "Use HEROES' KYC Verification, only applicants who passed HEROES' KYC can apply and work on this bounty!"
125 | options:
126 | - label: Use KYC Verification
127 |
128 | - type: markdown
129 | attributes:
130 | value: |
131 | ### This cannot be changed once the bounty is live!
132 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## macOS
2 | .DS_Store
3 |
4 | ## User settings
5 | xcuserdata/
6 |
7 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
8 | *.xcscmblueprint
9 | *.xccheckout
10 |
11 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
12 | build/
13 | DerivedData/
14 | *.moved-aside
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 |
27 | ## App packaging
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | .build/
37 |
38 | # CocoaPods
39 | #
40 | # We recommend against adding the Pods directory to your .gitignore. However
41 | # you should judge for yourself, the pros and cons are mentioned at:
42 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
43 | #
44 | Pods/
45 | #
46 | # Add this line if you want to avoid checking in source code from the Xcode workspace
47 | # *.xcworkspace
48 |
49 | # Carthage
50 | #
51 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
52 | # Carthage/Checkouts
53 |
54 | Carthage/Build/
55 |
56 | # Accio dependency management
57 | Dependencies/
58 | .accio/
59 |
60 | # fastlane
61 | #
62 | # It is recommended to not store the screenshots in the git repo.
63 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
64 | # For more information about the recommended setup visit:
65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
66 |
67 | fastlane/report.xml
68 | fastlane/Preview.html
69 | fastlane/screenshots/**/*.png
70 | fastlane/test_output
71 |
72 | # Code Injection
73 | #
74 | # After new code Injection tools there's a generated folder /iOSInjectionProject
75 | # https://github.com/johnno1962/injectionforxcode
76 |
77 | iOSInjectionProject/
78 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | osx_image: xcode11.1
6 | language: swift
7 | cache: cocoapods
8 | podfile: Example/Podfile
9 | before_install:
10 | - gem install cocoapods
11 | - pod install --project-directory=Example
12 | script:
13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/nearclientios.xcworkspace -scheme nearclientios-Example -sdk iphonesimulator13.1 -destination 'platform=iOS Simulator,name=iPhone 8,OS=13.1' ONLY_ACTIVE_ARCH=NO | xcpretty
14 | - pod lib lint
15 |
--------------------------------------------------------------------------------
/Example/AccountViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountViewController.swift
3 | // nearclientios_Example
4 | //
5 | // Created by Viktor Siruk on 06.02.2020.
6 | // Copyright © 2020 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import nearclientios
11 |
12 | struct AccountStateField {
13 | let title: String
14 | let value: String
15 | }
16 |
17 | class AccountViewController: UITableViewController {
18 |
19 | private var walletAccount: WalletAccount?
20 | private var near: Near?
21 | private var accountState: AccountState?
22 | private var data = [AccountStateField]()
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | title = "Near account"
27 | setupSignOutButton()
28 | Task {
29 | accountState = try await fetchAccountState()
30 | await setupData(with: accountState!)
31 | }
32 | // Do any additional setup after loading the view, typically from a nib.
33 | }
34 |
35 | override func didReceiveMemoryWarning() {
36 | super.didReceiveMemoryWarning()
37 | // Dispose of any resources that can be recreated.
38 | }
39 | }
40 |
41 | extension AccountViewController {
42 | private func setupSignOutButton() {
43 | navigationItem.hidesBackButton = true
44 | let newBackButton = UIBarButtonItem(title: "Sign out", style: UIBarButtonItem.Style.plain, target: self, action: #selector(AccountViewController.back(sender:)))
45 | self.navigationItem.leftBarButtonItem = newBackButton
46 | }
47 |
48 | @objc private func back(sender: UIBarButtonItem) {
49 | Task {
50 | await walletAccount?.signOut()
51 | navigationController?.popViewController(animated: true)
52 | }
53 | }
54 | }
55 |
56 | enum AccountError: Error {
57 | case cannotFetchAccountState
58 | }
59 |
60 | extension AccountViewController {
61 | func setup(near: Near, wallet: WalletAccount) {
62 | self.near = near
63 | walletAccount = wallet
64 | }
65 |
66 | private func fetchAccountState() async throws -> AccountState {
67 | do {
68 | let account = try await near!.account(accountId: walletAccount!.getAccountId())
69 | let state = try await account.state()
70 | return state
71 | } catch {
72 | throw AccountError.cannotFetchAccountState
73 | }
74 | }
75 |
76 | private func setupData(with accountState: AccountState) async {
77 | data.append(AccountStateField(title: "Account ID", value: await walletAccount!.getAccountId()))
78 | let account = try! await near!.account(accountId: walletAccount!.getAccountId())
79 | let accountBalance = try! await account.getAccountBalance()
80 | let balance = "\(accountBalance.available.toNearAmount(fracDigits: 5)) Ⓝ"
81 | data.append(AccountStateField(title: "Balance", value: balance))
82 | data.append(AccountStateField(title: "Storage (used/paid)", value: "\(accountState.storageUsage.toStorageUnit())/\(accountState.storagePaidAt.toStorageUnit())"))
83 | await MainActor.run {
84 | tableView.reloadData()
85 | }
86 | }
87 | }
88 |
89 | extension AccountViewController {
90 | override func numberOfSections(in tableView: UITableView) -> Int {
91 | return 1
92 | }
93 |
94 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
95 | return data.count
96 | }
97 |
98 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
99 | var cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")
100 | if cell == nil {
101 | cell = UITableViewCell(style: .value1, reuseIdentifier: "cellIdentifier")
102 | }
103 | let field = data[indexPath.row]
104 | cell!.textLabel?.text = field.title
105 | cell!.detailTextLabel?.text = field.value
106 | return cell!
107 | }
108 | }
109 |
110 | extension Int {
111 | func toStorageUnit() -> String {
112 | let formatter = NumberFormatter()
113 | formatter.numberStyle = .decimal
114 | return formatter.string(from: NSNumber(value: self))!
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Example/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/near/near-api-swift/95a9c118065f072aba2fa92c476a8cb53b1559a6/Example/Default-568h@2x.png
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | target 'nearclientios_Example' do
4 | pod 'nearclientios', :path => '../'
5 |
6 | target 'nearclientios_Tests' do
7 | inherit! :search_paths
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AnyCodable-FlightSchool (0.6.3)
3 | - Base58Swift (2.1.10):
4 | - BigInt (~> 5.0.0)
5 | - BigInt (5.0.0)
6 | - KeychainAccess (4.2.2)
7 | - nearclientios (1.0.29):
8 | - AnyCodable-FlightSchool (~> 0.6.0)
9 | - Base58Swift (~> 2.1.10)
10 | - KeychainAccess (~> 4.2.2)
11 | - secp256k1.swift
12 | - TweetNacl (~> 1.0)
13 | - secp256k1.swift (0.1.4)
14 | - TweetNacl (1.0.2)
15 |
16 | DEPENDENCIES:
17 | - nearclientios (from `../`)
18 |
19 | SPEC REPOS:
20 | trunk:
21 | - AnyCodable-FlightSchool
22 | - Base58Swift
23 | - BigInt
24 | - KeychainAccess
25 | - secp256k1.swift
26 | - TweetNacl
27 |
28 | EXTERNAL SOURCES:
29 | nearclientios:
30 | :path: "../"
31 |
32 | SPEC CHECKSUMS:
33 | AnyCodable-FlightSchool: 1c9890be5884140c60da5fde1b66380dec4c244a
34 | Base58Swift: 53d551f0b33d9478fa63b3445e453a772d6b31a7
35 | BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8
36 | KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
37 | nearclientios: 33b7eb8dd9305cd2ca8f95864c9402519b2c1ee5
38 | secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
39 | TweetNacl: 3abf4d1d2082b0114e7a67410e300892448951e6
40 |
41 | PODFILE CHECKSUM: bf2ede311928de0026e7155c3c779a7c3a7fed9d
42 |
43 | COCOAPODS: 1.11.2
44 |
--------------------------------------------------------------------------------
/Example/Tests/AccessKeySpec.swift:
--------------------------------------------------------------------------------
1 | ////
2 | //// AccessKeySpec.swift
3 | //// nearclientios_Tests
4 | ////
5 | //// Created by Dmytro Kurochka on 02.12.2019.
6 | //// Copyright © 2019 CocoaPods. All rights reserved.
7 | ////
8 | //
9 | import XCTest
10 |
11 | @testable import nearclientios
12 | class AccessKeySpec: XCTestCase {
13 |
14 | static var near: Near!
15 | static var testAccount: Account!
16 |
17 | override class func setUp() {
18 | super.setUp()
19 | unsafeWaitFor {
20 | try! await setUpAll()
21 | }
22 | }
23 |
24 | class func setUpAll() async throws {
25 | near = try await TestUtils.setUpTestConnection()
26 | let masterAccount = try await self.near.account(accountId: testAccountName)
27 | let amount = INITIAL_BALANCE * UInt128(100)
28 | testAccount = try await TestUtils.createAccount(masterAccount: masterAccount, amount: amount)
29 | }
30 |
31 | var workingAccount: Account!
32 | var contractId: String!
33 | var contract: Contract!
34 |
35 | override func setUp() async throws {
36 | self.contractId = TestUtils.generateUniqueString(prefix: "test")
37 | self.workingAccount = try await TestUtils.createAccount(masterAccount: AccessKeySpec.testAccount)
38 | self.contract = try await TestUtils.deployContract(workingAccount: self.workingAccount,
39 | contractId: self.contractId)
40 | }
41 |
42 | func testMakeFunctionCallsUsingAcccessKey() async throws {
43 | let keyPair = try keyPairFromRandom()
44 | let publicKey = keyPair.getPublicKey()
45 | try await self.workingAccount.addKey(publicKey: publicKey,
46 | contractId: self.contractId,
47 | methodName: "",
48 | amount: UInt128(stringLiteral: "2000000000000000000000000"))
49 | // Override in the key store the workingAccount key to the given access key.
50 | let signer = AccessKeySpec.near.connection.signer as! InMemorySigner
51 | try await signer.keyStore.setKey(networkId: networkId,
52 | accountId: self.workingAccount.accountId,
53 | keyPair: keyPair)
54 | let setCallValue = TestUtils.generateUniqueString(prefix: "setCallPrefix")
55 | try await self.contract.change(methodName: .setValue, args: ["value": setCallValue])
56 | let testValue: String = try await self.contract.view(methodName: .getValue)
57 | XCTAssertEqual(testValue, setCallValue)
58 | }
59 |
60 | func testMakeFunctionCallsUsingSepc256k1AcccessKey() async throws {
61 | let keyPair = try keyPairFromRandom(curve: .SECP256k1)
62 | let publicKey = keyPair.getPublicKey()
63 | try await self.workingAccount.addKey(publicKey: publicKey,
64 | contractId: self.contractId,
65 | methodName: "",
66 | amount: UInt128(stringLiteral: "2000000000000000000000000"))
67 | // Override in the key store the workingAccount key to the given access key.
68 | let signer = AccessKeySpec.near.connection.signer as! InMemorySigner
69 | try await signer.keyStore.setKey(networkId: networkId,
70 | accountId: self.workingAccount.accountId,
71 | keyPair: keyPair)
72 | let setCallValue = TestUtils.generateUniqueString(prefix: "setCallPrefix")
73 | try await self.contract.change(methodName: .setValue, args: ["value": setCallValue])
74 | let testValue: String = try await self.contract.view(methodName: .getValue)
75 | XCTAssertEqual(testValue, setCallValue)
76 | }
77 |
78 | func testRemoveAccessKeyNoLongerWorks() async throws {
79 | let keyPair = try keyPairFromRandom()
80 | let publicKey = keyPair.getPublicKey()
81 | try await self.workingAccount.addKey(publicKey: publicKey,
82 | contractId: self.contractId,
83 | methodName: "",
84 | amount: UInt128(400000))
85 | try await self.workingAccount.deleteKey(publicKey: publicKey)
86 | // Override in the key store the workingAccount key to the given access key.
87 | let signer = AccessKeySpec.near.connection.signer as! InMemorySigner
88 | try await signer.keyStore.setKey(networkId: networkId,
89 | accountId: self.workingAccount.accountId,
90 | keyPair: keyPair)
91 |
92 | await AssertThrowsError(try await self.contract.change(methodName: .setValue, args: ["value": "test"]) as Any) { error in
93 | XCTAssertTrue(error is RPCError)
94 | }
95 | }
96 |
97 | func testViewAccountDetailsAfterAddingAccessKeys() async throws {
98 | let keyPair = try keyPairFromRandom()
99 | try await self.workingAccount.addKey(publicKey: keyPair.getPublicKey(),
100 | contractId: self.contractId,
101 | methodName: "",
102 | amount: UInt128(1000000000))
103 | let contract2 = try await TestUtils.deployContract(workingAccount: self.workingAccount,
104 | contractId: TestUtils.generateUniqueString(prefix: "test_contract2"))
105 | let keyPair2 = try keyPairFromRandom()
106 | try await self.workingAccount.addKey(publicKey: keyPair2.getPublicKey(),
107 | contractId: contract2.contractId,
108 | methodName: "",
109 | amount: UInt128(2000000000))
110 | let details = try await self.workingAccount.getAccountDetails()
111 | let expectedResult: [AuthorizedApp] = [AuthorizedApp(contractId: self.contractId,
112 | amount: UInt128(1000000000),
113 | publicKey: keyPair.getPublicKey().toString()),
114 | AuthorizedApp(contractId: contract2.contractId,
115 | amount: UInt128(2000000000),
116 | publicKey: keyPair2.getPublicKey().toString())]
117 |
118 | XCTAssertTrue(details.authorizedApps.contains(expectedResult[0]))
119 | XCTAssertTrue(details.authorizedApps.contains(expectedResult[1]))
120 | }
121 |
122 | func testLoadingAccountAfterAddingAFullKey() async throws {
123 | let keyPair = try keyPairFromRandom()
124 | // wallet calls this with an empty string for contract id and method
125 | try await self.workingAccount.addKey(publicKey: keyPair.getPublicKey(),
126 | contractId: "",
127 | methodName: "",
128 | amount: nil)
129 | let accessKeys = try await self.workingAccount.getAccessKeys()
130 | XCTAssertEqual(accessKeys.keys.count, 2)
131 | let addedKey = accessKeys.keys.first(where: {$0.publicKey == keyPair.getPublicKey().toString()})
132 | XCTAssertNotNil(addedKey)
133 | if case AccessKeyPermission.fullAccess(let permission) = addedKey!.accessKey.permission {
134 | XCTAssertEqual(permission, FullAccessPermission())
135 | } else {
136 | XCTFail("AccessKeyPermission in not FullAccess")
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Example/Tests/AccountSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 28.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import nearclientios
12 | class AccountSpec: XCTestCase {
13 | static var near: Near!
14 | static var workingAccount: Account!
15 |
16 | static let contractId = TestUtils.generateUniqueString(prefix: "test_contract")
17 | static var contract: Contract!
18 |
19 | override class func setUp() {
20 | super.setUp()
21 | unsafeWaitFor {
22 | do {
23 | try await setUpAll()
24 | } catch let error {
25 | print(error)
26 | }
27 | }
28 | }
29 |
30 | class func setUpAll() async throws {
31 | // Account setup
32 | near = try await TestUtils.setUpTestConnection()
33 | let masterAccount = try await near.account(accountId: testAccountName)
34 | let amount = INITIAL_BALANCE
35 | workingAccount = try await TestUtils.createAccount(masterAccount: masterAccount, amount: amount)
36 |
37 | // Contract setup
38 | let newPublicKey = try await near.connection.signer.createKey(accountId: contractId, networkId: networkId, curve: .ED25519)
39 | let data = Wasm().data
40 | try await workingAccount.createAndDeployContract(contractId: contractId, publicKey: newPublicKey, data: data.bytes, amount: HELLO_WASM_BALANCE)
41 | let options = ContractOptions(viewMethods: [.hello, .getValue, .getAllKeys, .returnHiWithLogs], changeMethods: [.setValue, .generateLogs, .triggerAssert, .testSetRemove], sender: nil)
42 | contract = Contract(account: workingAccount, contractId: contractId, options: options)
43 | }
44 |
45 | func testViewPredefinedAccountWithCorrectName() async throws {
46 | let status = try await AccountSpec.workingAccount.state()
47 | XCTAssertEqual(status.codeHash, "11111111111111111111111111111111")
48 | }
49 |
50 | func testCreateAccountAndViewNewAccount() async throws {
51 | let newAccountName = TestUtils.generateUniqueString(prefix: "test")
52 | let newAccountPublicKey = try PublicKey.fromString(encodedKey: "9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE")
53 | let workingState = try await AccountSpec.workingAccount.state()
54 | let amount = workingState.amount
55 | let newAmount = UInt128(stringLiteral: amount) / UInt128(100)
56 | try await AccountSpec.workingAccount.createAccount(newAccountId: newAccountName, publicKey: newAccountPublicKey, amount: newAmount)
57 | let newAccount = Account(connection: AccountSpec.near.connection, accountId: newAccountName)
58 | let state = try await newAccount.state()
59 | XCTAssertEqual(state.amount, "\(newAmount)")
60 | }
61 |
62 | func testCreateAccountAndViewNewAccountUsingSecp256k1Curve() async throws {
63 | let newAccountName = TestUtils.generateUniqueString(prefix: "test")
64 | let newAccountPublicKey = try PublicKey.fromString(encodedKey: "secp256k1:45KcWwYt6MYRnnWFSxyQVkuu9suAzxoSkUMEnFNBi9kDayTo5YPUaqMWUrf7YHUDNMMj3w75vKuvfAMgfiFXBy28")
65 | let workingState = try await AccountSpec.workingAccount.state()
66 | let amount = workingState.amount
67 | let newAmount = UInt128(stringLiteral: amount) / UInt128(100)
68 | try await AccountSpec.workingAccount.createAccount(newAccountId: newAccountName, publicKey: newAccountPublicKey, amount: newAmount)
69 | let newAccount = Account(connection: AccountSpec.near.connection, accountId: newAccountName)
70 | let state = try await newAccount.state()
71 | XCTAssertEqual(state.amount, "\(newAmount)")
72 | }
73 |
74 | func testSendMoney() async throws {
75 | let workingState = try await AccountSpec.workingAccount.state()
76 | let amountFraction = UInt128(stringLiteral: workingState.amount) / UInt128(100)
77 | let sender = try await TestUtils.createAccount(masterAccount: AccountSpec.workingAccount, amount: amountFraction)
78 | let receiver = try await TestUtils.createAccount(masterAccount: AccountSpec.workingAccount, amount: amountFraction)
79 | try await sender.sendMoney(receiverId: receiver.accountId, amount: UInt128(10000))
80 | try await receiver.fetchState()
81 | let state = try await receiver.state()
82 | XCTAssertEqual(state.amount, "\(amountFraction + UInt128(10000))")
83 | }
84 |
85 | func testDeleteAccount() async throws {
86 | let workingState = try await AccountSpec.workingAccount.state()
87 | let amountFraction = UInt128(stringLiteral: workingState.amount) / UInt128(100)
88 | let sender = try await TestUtils.createAccount(masterAccount: AccountSpec.workingAccount, amount: amountFraction)
89 | let receiver = try await TestUtils.createAccount(masterAccount: AccountSpec.workingAccount, amount: amountFraction)
90 | try await sender.deleteAccount(beneficiaryId: receiver.accountId)
91 | try await receiver.fetchState()
92 | let senderState = try await receiver.state()
93 | XCTAssertGreaterThan(UInt128(stringLiteral: senderState.amount), amountFraction)
94 |
95 | let reloaded = Account(connection: sender.connection, accountId: sender.accountId)
96 | await AssertThrowsError(try await reloaded.state()) { error in
97 | XCTAssertTrue(error is HTTPError)
98 | }
99 | }
100 |
101 | // Errors
102 | func testCreatingAnExistingAccountShouldThrow() async throws {
103 | await AssertThrowsError(try await AccountSpec.workingAccount.createAccount(newAccountId: AccountSpec.workingAccount.accountId, publicKey: PublicKey.fromString(encodedKey: "9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE"), amount: 100)) { error in
104 | XCTAssertTrue(error is TypedError)
105 | }
106 | }
107 |
108 | // With deploy contract
109 | func testMakeFunctionCallsViaAccount() async throws {
110 | let result: String = try await AccountSpec.workingAccount.viewFunction(contractId: AccountSpec.contractId, methodName: "hello", args: ["name": "trex"])
111 | XCTAssertEqual(result, "hello trex")
112 |
113 | let setCallValue = TestUtils.generateUniqueString(prefix: "setCallPrefix")
114 | let result2 = try await AccountSpec.workingAccount.functionCall(contractId: AccountSpec.contractId, methodName: "setValue", args: ["value": setCallValue], amount: 0)
115 | XCTAssertEqual(getTransactionLastResult(txResult: result2) as? String, setCallValue)
116 |
117 | let testSetCallValue: String = try await AccountSpec.workingAccount.viewFunction(contractId: AccountSpec.contractId, methodName: "getValue", args: [:])
118 | XCTAssertEqual(testSetCallValue, setCallValue)
119 | }
120 |
121 | func testMakeFunctionCallsViaAccountWithGas() async throws {
122 | let result: String = try await AccountSpec.contract.view(methodName: .hello, args: ["name": "trex"])
123 | XCTAssertEqual(result, "hello trex")
124 |
125 | let setCallValue = TestUtils.generateUniqueString(prefix: "setCallPrefix")
126 | let result2 = try await AccountSpec.contract.change(methodName: .setValue, args: ["value": setCallValue], gas: 1000000 * 1000000) as? String
127 | XCTAssertEqual(result2, setCallValue)
128 |
129 | let testSetCallValue: String = try await AccountSpec.contract.view(methodName: .getValue)
130 | XCTAssertEqual(testSetCallValue, setCallValue)
131 | }
132 |
133 | // func testShouldGetLogsFromMethodResult() async throws {
134 | // let logs = try await contract.change(methodName: .generateLogs)
135 | // expect(logs).to(equal([`[${contractId}]: LOG: log1`, `[${contractId}]: LOG: log2`]))]
136 | // }
137 |
138 | func testCanGetLogsFromViewCall() async throws {
139 | let result: String = try await AccountSpec.contract.view(methodName: .returnHiWithLogs)
140 | XCTAssertEqual(result, "Hi")
141 | //expect(logs).toEqual([`[${contractId}]: LOG: loooog1`, `[${contractId}]: LOG: loooog2`]);
142 | }
143 |
144 | func testCanGetAssertMessageFromMethodResult() async throws {
145 | await AssertThrowsError(try await AccountSpec.contract.change(methodName: .triggerAssert) as Any) { error in
146 | XCTAssertTrue(error is TypedError)
147 | // This method in the testing contract is just designed to test logging after failure.
148 | //expect(logs[0]).toEqual(`[${contractId}]: LOG: log before assert`)
149 | //expect(logs[1]).toMatch(new RegExp(`^\\[${contractId}\\]: ABORT: "?expected to fail"?,?
150 | }
151 | }
152 |
153 | func testAttemptSetRemove() async throws {
154 | do {
155 | try await AccountSpec.contract.change(methodName: .testSetRemove, args: ["value": "123"])
156 | } catch let error {
157 | XCTFail(error.localizedDescription)
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Example/Tests/EnviromentConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnviromentConfig.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 27.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal enum Environment: String {
12 | case production, development, local, test, testRemote = "test-remote", ci, ciStaging = "ci-staging"
13 | }
14 |
15 | internal struct EnvironmentConfig {
16 | let networkId: String
17 | let nodeUrl: URL
18 | let masterAccount: String
19 | }
20 |
21 | func getConfig(env: Environment) -> EnvironmentConfig {
22 | switch env {
23 | case .production, .development:
24 | return EnvironmentConfig(networkId: "default",
25 | nodeUrl: URL(string: "https://rpc.mainnet.near.org")!,
26 | masterAccount: "test.near")
27 | case .local:
28 | //process.env.HOME ?
29 | // "masterAccount": "\(process.env.HOME)/.near/validator_key.json"]
30 | return EnvironmentConfig(networkId: "local",
31 | nodeUrl: URL(string: "http://localhost:3030")!,
32 | masterAccount: "test.near")
33 | case .test:
34 | return EnvironmentConfig(networkId: "local",
35 | nodeUrl: URL(string: "http://localhost:3030")!,
36 | masterAccount: "test.near")
37 | case .testRemote, .ci:
38 | return EnvironmentConfig(networkId: "shared-test",
39 | nodeUrl: URL(string: "https://rpc.ci-testnet.near.org")!,
40 | masterAccount: "test.near")
41 | case .ciStaging:
42 | return EnvironmentConfig(networkId: "shared-test-staging",
43 | nodeUrl: URL(string: "http://staging-shared-test.nearprotocol.com:3030")!,
44 | masterAccount: "test.near")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Example/Tests/FormatSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormatSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Kevin McConnaughay on 3/22/22.
6 | // Copyright © 2022 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 |
12 | struct YNTestCase {
13 | let balance: String
14 | let fracDigits: Int?
15 | let expected: String
16 | let expectedDouble: Double
17 | }
18 |
19 | struct NYTestCase {
20 | let amount: String
21 | let expected: String
22 | }
23 |
24 | let yoctoToNearStringTestCases: [YNTestCase] = [
25 | YNTestCase(balance: "8999999999837087887", fracDigits: nil, expected: "0.000008999999999837087887", expectedDouble: 0.000008999999999837087887),
26 | YNTestCase(balance: "8099099999837087887", fracDigits: nil, expected: "0.000008099099999837087887", expectedDouble: 0.000008099099999837087887),
27 | YNTestCase(balance: "999998999999999837087887000", fracDigits: nil, expected: "999.998999999999837087887", expectedDouble: 999.998999999999837087887),
28 | YNTestCase(balance: "1" + String(repeating: "0", count: 13), fracDigits: nil, expected: "0.00000000001", expectedDouble: 0.00000000001),
29 | YNTestCase(balance: "9999989999999998370878870000000", fracDigits: nil, expected: "9,999,989.99999999837087887", expectedDouble: 9999989.99999999837087887),
30 | YNTestCase(balance: "000000000000000000000000", fracDigits: nil, expected: "0", expectedDouble: 0),
31 | YNTestCase(balance: "1000000000000000000000000", fracDigits: nil, expected: "1", expectedDouble: 1),
32 | YNTestCase(balance: "999999999999999999000000", fracDigits: nil, expected: "0.999999999999999999", expectedDouble: 0.999999999999999999),
33 | YNTestCase(balance: "999999999999999999000000", fracDigits: 10, expected: "1", expectedDouble: 1),
34 | YNTestCase(balance: "1003000000000000000000000", fracDigits: 3, expected: "1.003", expectedDouble: 1.003),
35 | YNTestCase(balance: "3000000000000000000000", fracDigits: 3, expected: "0.003", expectedDouble: 0.003),
36 | YNTestCase(balance: "3000000000000000000000", fracDigits: 4, expected: "0.003", expectedDouble: 0.003),
37 | YNTestCase(balance: "3500000000000000000000", fracDigits: 3, expected: "0.004", expectedDouble: 0.004),
38 | YNTestCase(balance: "03500000000000000000000", fracDigits: 3, expected: "0.004", expectedDouble: 0.004),
39 | YNTestCase(balance: "10000000999999997410000000", fracDigits: nil, expected: "10.00000099999999741", expectedDouble: 10.00000099999999741),
40 | YNTestCase(balance: "10100000999999997410000000", fracDigits: nil, expected: "10.10000099999999741", expectedDouble: 10.10000099999999741),
41 | YNTestCase(balance: "10040000999999997410000000", fracDigits: 2, expected: "10.04", expectedDouble: 10.04),
42 | YNTestCase(balance: "10999000999999997410000000", fracDigits: 2, expected: "11", expectedDouble: 11),
43 | YNTestCase(balance: "1000000100000000000000000000000", fracDigits: nil, expected: "1,000,000.1", expectedDouble: 1000000.1),
44 | YNTestCase(balance: "1000100000000000000000000000000", fracDigits: nil, expected: "1,000,100", expectedDouble: 1000100),
45 | YNTestCase(balance: "910000000000000000000000", fracDigits: 0, expected: "1", expectedDouble: 1)
46 | ]
47 |
48 | let nearStringToYoctoStringTestCases: [NYTestCase] = [
49 | NYTestCase(amount: "5.3", expected: "5300000000000000000000000"),
50 | NYTestCase(amount: "5", expected: "5000000000000000000000000"),
51 | NYTestCase(amount: "1", expected: "1000000000000000000000000"),
52 | NYTestCase(amount: "10", expected: "10000000000000000000000000"),
53 | NYTestCase(amount: "0.000008999999999837087887", expected: "8999999999837087887"),
54 | NYTestCase(amount: "0.000008099099999837087887", expected: "8099099999837087887"),
55 | NYTestCase(amount: "999.998999999999837087887000", expected: "999998999999999837087887000"),
56 | NYTestCase(amount: "0.000000000000001", expected: "1000000000"),
57 | NYTestCase(amount: "0", expected: "0"),
58 | NYTestCase(amount: "0.000", expected: "0"),
59 | NYTestCase(amount: "0.000001", expected: "1000000000000000000"),
60 | NYTestCase(amount: ".000001", expected: "1000000000000000000"),
61 | NYTestCase(amount: "000000.000001", expected: "1000000000000000000"),
62 | NYTestCase(amount: "1,000,000.1", expected: "1000000100000000000000000000000"),
63 | ]
64 |
65 | class FormatSpec: XCTestCase {
66 | func testFormatNearAmount() throws {
67 | for testCase in yoctoToNearStringTestCases {
68 | let balance = UInt128(stringLiteral: testCase.balance)
69 | let formatResult = balance.toNearAmount(fracDigits: testCase.fracDigits ?? NEAR_NOMINATION_EXP)
70 | let doubleResult = balance.toNearDouble(fracDigits: testCase.fracDigits ?? NEAR_NOMINATION_EXP)
71 | XCTAssertEqual(formatResult, testCase.expected)
72 | XCTAssertTrue(fabs(doubleResult - testCase.expectedDouble) < .ulpOfOne)
73 | }
74 | }
75 |
76 | func testFormatNearAmountFromString() throws {
77 | for testCase in yoctoToNearStringTestCases {
78 | let balance = testCase.balance
79 | let formatResult = testCase.fracDigits != nil ? balance.toNearAmount(fracDigits: testCase.fracDigits!) : balance.toNearAmount()
80 | XCTAssertEqual(formatResult, testCase.expected)
81 | }
82 | }
83 |
84 | func testFormatYoctoAmount() throws {
85 | for testCase in nearStringToYoctoStringTestCases {
86 | let formatResult = try testCase.amount.toYoctoNearString()
87 | XCTAssertEqual(formatResult, testCase.expected)
88 | }
89 |
90 | XCTAssertThrowsError(try "".toYoctoNearString())
91 | XCTAssertThrowsError(try ".".toYoctoNearString())
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Example/Tests/KeyPairSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyPairSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 27.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 |
12 | class KeyPairSpec: XCTestCase {
13 |
14 | func testSignAndVerify() {
15 | let keyPair = try! KeyPairEd25519(secretKey: "26x56YPzPDro5t2smQfGcYAPy3j7R2jB2NUb7xKbAGK23B6x4WNQPh3twb6oDksFov5X8ts5CtntUNbpQpAKFdbR")
16 | XCTAssertEqual(keyPair.getPublicKey().toString(), "ed25519:AYWv9RAN1hpSQA4p1DLhCNnpnNXwxhfH9qeHN8B4nJ59")
17 | let message = "message".data(using: .utf8)!.digest
18 | let signature = try! keyPair.sign(message: message)
19 | XCTAssertEqual(signature.signature.baseEncoded, "26gFr4xth7W9K7HPWAxq3BLsua8oTy378mC1MYFiEXHBBpeBjP8WmJEJo8XTBowetvqbRshcQEtBUdwQcAqDyP8T")
20 | }
21 |
22 | func testSignAndVerifyWithRandom() {
23 | let keyPair = try! KeyPairEd25519.fromRandom()
24 | let message = "message".data(using: .utf8)!.digest
25 | let signature = try! keyPair.sign(message: message)
26 | XCTAssertTrue(try! keyPair.verify(message: message, signature: signature.signature))
27 | }
28 |
29 | func testSignAndVerifyWithSepc256k1Random() {
30 | let keyPair = try! KeyPairSecp256k1.fromRandom()
31 | let message = "message".data(using: .utf8)!.digest
32 | let signature = try! keyPair.sign(message: message)
33 | XCTAssertTrue(try! keyPair.verify(message: message, signature: signature.signature))
34 | }
35 |
36 | func testSecp256k1InitFromSecret() {
37 | let keyPair = try! KeyPairSecp256k1(secretKey: "Cqmi5vHc59U1MHhq7JCxTSJentvVBYMcKGUA7s7kwnKn")
38 | XCTAssertEqual(keyPair.getPublicKey().toString(), "secp256k1:45KcWwYt6MYRnnWFSxyQVkuu9suAzxoSkUMEnFNBi9kDayTo5YPUaqMWUrf7YHUDNMMj3w75vKuvfAMgfiFXBy28")
39 | }
40 |
41 | func testInitFromSecret() {
42 | let keyPair = try! KeyPairEd25519(secretKey: "5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C")
43 | XCTAssertEqual(keyPair.getPublicKey().toString(), "ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW")
44 | }
45 |
46 | func testConvertToString() {
47 | let keyPair = try! KeyPairEd25519.fromRandom()
48 | let newKeyPair = try! keyPairFromString(encodedKey: keyPair.toString()) as! KeyPairEd25519
49 | XCTAssertEqual(newKeyPair.getSecretKey(), keyPair.getSecretKey())
50 | let keyString = "ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw"
51 | let keyPair2 = try! keyPairFromString(encodedKey: keyString)
52 | XCTAssertEqual(keyPair2.toString(), keyString)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Example/Tests/KeystoreSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeystoreSpec.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 26.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 | import LocalAuthentication
12 |
13 | let NETWORK_ID_SINGLE_KEY = "singlekeynetworkid"
14 | let ACCOUNT_ID_SINGLE_KEY = "singlekey_accountid"
15 | let secretKey = "2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw"
16 | let KEYPAIR_SINGLE_KEY = try! KeyPairEd25519(secretKey: secretKey)
17 |
18 | class KeyStoreSpec: XCTestCase {
19 | static var keyStores: [KeyStore] = []
20 | override class func setUp() {
21 | keyStores.append(InMemoryKeyStore())
22 | keyStores.append(UnencryptedFileSystemKeyStore(keyDir: "test-keys"))
23 | keyStores.append(KeychainKeyStore(keychain: .init(service: "test.keystore")))
24 | keyStores.append(SecureEnclaveKeyStore(keychain: .init(service: "testEnclave.keystore")))
25 | keyStores.append(MergeKeyStore(keyStores: [InMemoryKeyStore(), InMemoryKeyStore()]))
26 | }
27 |
28 | func withAllKeyStores(run: (_: KeyStore) async throws -> Void) async throws {
29 | for store in KeyStoreSpec.keyStores {
30 | try await run(store)
31 | }
32 | }
33 |
34 | override func setUp() async throws {
35 | try await withAllKeyStores(run: setUp(keyStore:))
36 | }
37 |
38 | func setUp(keyStore: KeyStore) async throws {
39 | try! await(keyStore.setKey(networkId: NETWORK_ID_SINGLE_KEY,
40 | accountId: ACCOUNT_ID_SINGLE_KEY,
41 | keyPair: KEYPAIR_SINGLE_KEY))
42 | }
43 |
44 | override func tearDown() async throws {
45 | try await withAllKeyStores(run: tearDown(keyStore:))
46 | }
47 |
48 | func tearDown(keyStore: KeyStore) async throws {
49 | try! await(keyStore.clear())
50 | }
51 |
52 | func testGetAllKeysWithEmptyNetworkReturnsEmptyList() async throws {
53 | try await withAllKeyStores(run: getAllKeysWithEmptyNetworkReturnsEmptyList)
54 | }
55 | func getAllKeysWithEmptyNetworkReturnsEmptyList(keyStore: KeyStore) async throws {
56 | let emptyList = try! await keyStore.getAccounts(networkId: "emptynetwork")
57 | XCTAssertEqual(emptyList.count, 0)
58 | }
59 |
60 | func testGetAllKeysWithSingleKeyInKeyStore() async throws {
61 | try await withAllKeyStores(run: getAllKeysWithSingleKeyInKeyStore)
62 | }
63 | func getAllKeysWithSingleKeyInKeyStore(keyStore: KeyStore) async throws {
64 | let accountIds = try! await keyStore.getAccounts(networkId: NETWORK_ID_SINGLE_KEY)
65 | XCTAssertEqual(accountIds, [ACCOUNT_ID_SINGLE_KEY])
66 | }
67 |
68 | func testGetNonExistingAccount() async throws {
69 | try await withAllKeyStores(run: getNonExistingAccount)
70 | }
71 | func getNonExistingAccount(keyStore: KeyStore) async throws {
72 | let account = try! await(keyStore.getKey(networkId: "somenetwork", accountId: "someaccount"))
73 | XCTAssertNil(account)
74 | }
75 |
76 | func testGetAccountIdFromNetworkWithSingleKey() async throws {
77 | try await withAllKeyStores(run: getAccountIdFromNetworkWithSingleKey)
78 | }
79 | func getAccountIdFromNetworkWithSingleKey(keyStore: KeyStore) async throws {
80 | let key = try! await keyStore.getKey(networkId: NETWORK_ID_SINGLE_KEY,
81 | accountId: ACCOUNT_ID_SINGLE_KEY) as? KeyPairEd25519
82 | XCTAssertEqual(key, KEYPAIR_SINGLE_KEY)
83 | }
84 |
85 | func testGetNetworks() async throws {
86 | try await withAllKeyStores(run: getNetworks)
87 | }
88 | func getNetworks(keyStore: KeyStore) async throws {
89 | let networks = try! await keyStore.getNetworks()
90 | XCTAssertEqual(networks, [NETWORK_ID_SINGLE_KEY])
91 | }
92 |
93 | func testAddTwoKeysToNetworkAndRetrieveThem() async throws {
94 | try await withAllKeyStores(run: addTwoKeysToNetworkAndRetrieveThem)
95 | }
96 | func addTwoKeysToNetworkAndRetrieveThem(keyStore: KeyStore) async throws {
97 | if keyStore is SecureEnclaveKeyStore && (keyStore as! SecureEnclaveKeyStore).requireUserPresence {
98 | (keyStore as! SecureEnclaveKeyStore).context = LAContext()
99 | }
100 | let networkId = "twoKeyNetwork"
101 | let accountId1 = "acc1"
102 | let accountId2 = "acc2"
103 | let key1Expected = try! keyPairFromRandom() as! KeyPairEd25519
104 | let key2Expected = try! keyPairFromRandom() as! KeyPairEd25519
105 | try! await keyStore.setKey(networkId: networkId, accountId: accountId1, keyPair: key1Expected)
106 | try! await keyStore.setKey(networkId: networkId, accountId: accountId2, keyPair: key2Expected)
107 | let key1 = try await keyStore.getKey(networkId: networkId, accountId: accountId1) as! KeyPairEd25519
108 | let key2 = try await keyStore.getKey(networkId: networkId, accountId: accountId2) as! KeyPairEd25519
109 | XCTAssertEqual(key1, key1Expected)
110 | XCTAssertEqual(key2, key2Expected)
111 | let accountIds = try! await keyStore.getAccounts(networkId: networkId)
112 | XCTAssertEqual(accountIds.sorted(), [accountId1, accountId2].sorted())
113 | let networks = try! await(keyStore.getNetworks())
114 | XCTAssertEqual(networks.sorted(), [NETWORK_ID_SINGLE_KEY, networkId].sorted())
115 | }
116 |
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/Example/Tests/MergeKeyStoreSpec.swift:
--------------------------------------------------------------------------------
1 |
2 | // MergeKeyStoreSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 26.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 |
12 | class MergeKeyStoreSpec: XCTestCase {
13 | private let stores: [KeyStore] = [InMemoryKeyStore(), InMemoryKeyStore()]
14 | private lazy var keyStore: KeyStore! = MergeKeyStore(keyStores: stores)
15 |
16 | override func tearDown() async throws {
17 | try! await(self.keyStore.clear())
18 | }
19 |
20 | func testLookUpKeyFromFallbackKeystoreIfNeeded() async throws {
21 | let key1 = try! keyPairFromRandom() as! KeyPairEd25519
22 | try! await self.stores[1].setKey(networkId: "network", accountId: "account", keyPair: key1)
23 | let key = try! await self.keyStore.getKey(networkId: "network", accountId: "account") as! KeyPairEd25519
24 | XCTAssertEqual(key, key1)
25 | }
26 |
27 | func testKeyLookupOrder() async throws {
28 | let key1 = try! keyPairFromRandom() as! KeyPairEd25519
29 | let key2 = try! keyPairFromRandom() as! KeyPairEd25519
30 | try! await self.stores[0].setKey(networkId: "network", accountId: "account", keyPair: key1)
31 | try! await self.stores[1].setKey(networkId: "network", accountId: "account", keyPair: key2)
32 | let key = try! await self.keyStore.getKey(networkId: "network", accountId: "account") as! KeyPairEd25519
33 | XCTAssertEqual(key, key1)
34 | }
35 |
36 | func testSetsKeysOnlyInFirstKeyStore() async throws {
37 | let key1 = try! keyPairFromRandom() as! KeyPairEd25519
38 | try! await self.keyStore.setKey(networkId: "network", accountId: "account", keyPair: key1)
39 | let account1 = try! await self.stores[0].getAccounts(networkId: "network")
40 | let account2 = try! await self.stores[1].getAccounts(networkId: "network")
41 | XCTAssertEqual(account1.count, 1)
42 | XCTAssertEqual(account2.count, 0)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Example/Tests/ProviderSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProviderSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 27.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 |
12 | class ProviderSpec: XCTestCase {
13 | private var provider: Provider!
14 | override func setUp() {
15 | let url = getConfig(env: .ci).nodeUrl
16 | self.provider = JSONRPCProvider(url: url)
17 | }
18 |
19 | func testFetchNodeStatus() async {
20 | let response = try! await self.provider.status()
21 | XCTAssertTrue(response.chainId.contains("ci-testnet"))
22 | }
23 |
24 | func testFetchNetworkInfo() async {
25 | let response = try! await self.provider.networkInfo()
26 | XCTAssertNotNil(response.peerMaxCount)
27 | }
28 |
29 | func testCorrectFinalTransactionResult() {
30 |
31 | let outcome = ExecutionOutcome(
32 | status: .successReceiptId("11112"),
33 | logs: [],
34 | receiptIds: ["11112"],
35 | gasBurnt: 1
36 | )
37 | let transactionOutcome = ExecutionOutcomeWithId(id: "11111", outcome: outcome)
38 | let firstRecipientOutcome = ExecutionOutcome(
39 | status: .successValue("e30="),
40 | logs: [],
41 | receiptIds: ["11112"],
42 | gasBurnt: 9001
43 | )
44 | let secondRecipientOutcome = ExecutionOutcome(
45 | status: .successValue(""),
46 | logs: [],
47 | receiptIds: [],
48 | gasBurnt: 0
49 | )
50 | let receipts = [ExecutionOutcomeWithId(id: "11112", outcome: firstRecipientOutcome),
51 | ExecutionOutcomeWithId(id: "11113", outcome: secondRecipientOutcome)]
52 | let transaction = Transaction(
53 | hash: "11111",
54 | publicKey: "11112",
55 | signature: "11113"
56 | )
57 |
58 | let result = FinalExecutionOutcome(
59 | transaction: transaction,
60 | status: .successValue("e30="),
61 | transactionOutcome: transactionOutcome,
62 | receiptsOutcome: receipts, receipts: nil
63 | )
64 | XCTAssertNotNil(getTransactionLastResult(txResult: result))
65 | }
66 |
67 | func testFinalTransactionResultWithNil() {
68 | let outcome = ExecutionOutcome(
69 | status: .successReceiptId("11112"),
70 | logs: [],
71 | receiptIds: ["11112"],
72 | gasBurnt: 1
73 | )
74 | let transactionOutcome = ExecutionOutcomeWithId(id: "11111", outcome: outcome)
75 | let firstRecipientOutcome = ExecutionOutcome(
76 | status: .failure(ExecutionError()),
77 | logs: [],
78 | receiptIds: ["11112"],
79 | gasBurnt: 9001
80 | )
81 | let secondRecipientOutcome = ExecutionOutcome(
82 | status: .successValue(""),
83 | logs: [],
84 | receiptIds: [],
85 | gasBurnt: 0
86 | )
87 | let receipts = [ExecutionOutcomeWithId(id: "11112", outcome: firstRecipientOutcome),
88 | ExecutionOutcomeWithId(id: "11113", outcome: secondRecipientOutcome)]
89 | let transaction = Transaction(
90 | hash: "11111",
91 | publicKey: "11112",
92 | signature: "11113"
93 | )
94 | let result = FinalExecutionOutcome(
95 | transaction: transaction,
96 | status: .failure(ExecutionError()),
97 | transactionOutcome: transactionOutcome,
98 | receiptsOutcome: receipts, receipts: nil)
99 | XCTAssertNil(getTransactionLastResult(txResult: result))
100 | }
101 |
102 | func testFetchBlockInfo() async throws {
103 | let status = try await self.provider.status()
104 |
105 | let height = status.syncInfo.latestBlockHeight - 1
106 | let blockHeight = BlockId.blockHeight(height)
107 | let response = try await provider.block(blockQuery: BlockReference.blockId(blockHeight))
108 | XCTAssertEqual(response.header.height, height)
109 |
110 | let sameBlock = try await provider.block(blockQuery: BlockReference.blockId(BlockId.blockHash(response.header.hash)))
111 | XCTAssertEqual(sameBlock.header.height, height)
112 |
113 | let optimisticBlock = try await provider.block(blockQuery: BlockReference.finality(Finality.optimistic))
114 | XCTAssertLessThan(optimisticBlock.header.height - height, 5)
115 |
116 | let finalBlock = try await provider.block(blockQuery: BlockReference.finality(Finality.final))
117 | XCTAssertLessThan(finalBlock.header.height - height, 5)
118 | }
119 |
120 | func testFetchBlockChanges() async throws {
121 | let status = try await self.provider.status()
122 | let latestHash = BlockId.blockHash(status.syncInfo.latestBlockHash)
123 | let blockQuery = BlockReference.blockId(latestHash)
124 | let response = try await self.provider.blockChanges(blockQuery: blockQuery)
125 | XCTAssertNotNil(response.blockHash)
126 | XCTAssertNotNil(response.changes)
127 |
128 | let latestHeight = BlockId.blockHeight(status.syncInfo.latestBlockHeight)
129 | let blockQuery2 = BlockReference.blockId(latestHeight)
130 | let response2 = try await self.provider.blockChanges(blockQuery: blockQuery2)
131 | XCTAssertNotNil(response2.blockHash)
132 | XCTAssertNotNil(response2.changes)
133 |
134 | let blockQuery3 = BlockReference.finality(Finality.final)
135 | let response3 = try await self.provider.blockChanges(blockQuery: blockQuery3)
136 | XCTAssertNotNil(response3.blockHash)
137 | XCTAssertNotNil(response3.changes)
138 | }
139 |
140 | func testFetchChunkInfo() async throws {
141 | let status = try await self.provider.status()
142 | let height = status.syncInfo.latestBlockHeight - 1
143 | let blockShardId = BlockShardId(blockId: BlockId.blockHeight(height), shardId: 0)
144 | let chunkId = ChunkId.blockShardId(blockShardId)
145 | let response = try await self.provider.chunk(chunkId: chunkId)
146 | XCTAssertEqual(response.header.shardId, 0)
147 |
148 | let sameChunk = try await self.provider.chunk(chunkId: ChunkId.chunkHash(response.header.chunkHash))
149 | XCTAssertEqual(sameChunk.header.chunkHash, response.header.chunkHash)
150 | XCTAssertEqual(sameChunk.header.shardId, 0)
151 | }
152 |
153 | func testGasPrice() async throws {
154 | let status = try await self.provider.status()
155 |
156 | let blockHeight = NullableBlockId.blockHeight(status.syncInfo.latestBlockHeight)
157 | let response1 = try await self.provider.gasPrice(blockId: blockHeight)
158 | XCTAssertGreaterThan(Int(response1.gasPrice) ?? 0, 0)
159 |
160 | let blockHash = NullableBlockId.blockHash(status.syncInfo.latestBlockHash)
161 | let response2 = try await self.provider.gasPrice(blockId: blockHash)
162 | XCTAssertGreaterThan(Int(response2.gasPrice) ?? 0, 0)
163 |
164 | let response3 = try await self.provider.gasPrice(blockId: NullableBlockId.null)
165 | XCTAssertGreaterThan(Int(response3.gasPrice) ?? 0, 0)
166 | }
167 |
168 | func testExperimentalGenesisConfig() async throws {
169 | let response = try await self.provider.experimentalGenesisConfig()
170 |
171 | XCTAssertNotNil(response.chainId)
172 | XCTAssertNotNil(response.genesisHeight)
173 | }
174 |
175 | func testExperimentalProtocolConfig() async throws {
176 | let status = try await self.provider.status()
177 | let latestHash = BlockId.blockHash(status.syncInfo.latestBlockHash)
178 | let blockQuery = BlockReference.blockId(latestHash)
179 | let response = try await self.provider.experimentalProtocolConfig(blockQuery: blockQuery)
180 |
181 | XCTAssertNotNil(response.chainId)
182 | XCTAssertNotNil(response.genesisHeight)
183 | XCTAssertNotNil(response.runtimeConfig)
184 | XCTAssertNotNil(response.runtimeConfig?.storageAmountPerByte)
185 | }
186 |
187 | func testFetchValidatorInfo() async throws {
188 | let validators = try await self.provider.validators(blockId: NullableBlockId.null)
189 | XCTAssertGreaterThanOrEqual(validators.currentValidators.count, 1)
190 | }
191 |
192 | func testAccessKeyChanges() async throws {
193 | let status = try await self.provider.status()
194 | let changes = try await provider.accessKeyChanges(accountIdArray: [testAccountName], blockQuery: BlockReference.blockId(BlockId.blockHash(status.syncInfo.latestBlockHash)))
195 | XCTAssertEqual(status.syncInfo.latestBlockHash, changes.blockHash)
196 | XCTAssertNotNil(changes.changes)
197 | }
198 |
199 | func testSingleAccessKeyChanges() async throws {
200 | let status = try await self.provider.status()
201 | let near = try await TestUtils.setUpTestConnection()
202 | let testAccount = try await near.account(accountId: testAccountName)
203 | let keyBox = try await testAccount.getAccessKeys()
204 | let publicKey = keyBox.keys.first?.publicKey
205 | let accessKeyWithPublicKey = AccessKeyWithPublicKey(accountId: testAccountName, publicKey: publicKey!)
206 |
207 | let changes = try await self.provider.singleAccessKeyChanges(accessKeyArray: [accessKeyWithPublicKey], blockQuery: BlockReference.blockId(BlockId.blockHash(status.syncInfo.latestBlockHash)))
208 | XCTAssertEqual(status.syncInfo.latestBlockHash, changes.blockHash)
209 | XCTAssertNotNil(changes.changes)
210 | }
211 |
212 | func testAccountChanges() async throws {
213 | let status = try await self.provider.status()
214 | let changes = try await self.provider.accountChanges(accountIdArray: [testAccountName], blockQuery: BlockReference.blockId(BlockId.blockHash(status.syncInfo.latestBlockHash)))
215 | XCTAssertEqual(status.syncInfo.latestBlockHash, changes.blockHash)
216 | XCTAssertNotNil(changes.changes)
217 | }
218 |
219 | func testContractStateChanges() async throws {
220 | let status = try await self.provider.status()
221 | let changes = try await self.provider.contractStateChanges(accountIdArray: [testAccountName], blockQuery: BlockReference.blockId(BlockId.blockHash(status.syncInfo.latestBlockHash)), keyPrefix: nil)
222 | XCTAssertEqual(status.syncInfo.latestBlockHash, changes.blockHash)
223 | XCTAssertNotNil(changes.changes)
224 | }
225 |
226 | func testContractCodeChanges() async throws {
227 | let status = try await self.provider.status()
228 | let changes = try await self.provider.contractCodeChanges(accountIdArray: [testAccountName], blockQuery: BlockReference.blockId(BlockId.blockHash(status.syncInfo.latestBlockHash)))
229 | XCTAssertEqual(status.syncInfo.latestBlockHash, changes.blockHash)
230 | XCTAssertNotNil(changes.changes)
231 | }
232 |
233 | func testTransactionStatus() async throws {
234 | let near = try await TestUtils.setUpTestConnection()
235 | let testAccount = try await near.account(accountId: testAccountName)
236 | let sender = try await TestUtils.createAccount(masterAccount: testAccount)
237 | let receiver = try await TestUtils.createAccount(masterAccount: testAccount)
238 | let outcome = try await sender.sendMoney(receiverId: receiver.accountId, amount: UInt128(1))
239 | let response = try await self.provider.txStatus(txHash: outcome.transactionOutcome.id.baseDecoded, accountId: sender.accountId)
240 | XCTAssertEqual(response, outcome)
241 | }
242 |
243 | func testTransactionStatusWithReceipts() async throws {
244 | let near = try await TestUtils.setUpTestConnection()
245 | let testAccount = try await near.account(accountId: testAccountName)
246 | let sender = try await TestUtils.createAccount(masterAccount: testAccount)
247 | let receiver = try await TestUtils.createAccount(masterAccount: testAccount)
248 | let outcome = try await sender.sendMoney(receiverId: receiver.accountId, amount: UInt128(1))
249 | let response = try await self.provider.experimentalTxStatusWithReceipts(txHash: outcome.transactionOutcome.id.baseDecoded, accountId: sender.accountId)
250 | XCTAssertNil(outcome.receipts)
251 | XCTAssertNotNil(response.receipts)
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/Example/Tests/SignerSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignerSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 28.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import nearclientios
11 |
12 | class SignerSpec: XCTestCase {
13 | func testNoKeyThrowsError() async throws {
14 | let signer = InMemorySigner(keyStore: InMemoryKeyStore())
15 | await AssertThrowsError(try await signer.signMessage(message: "message".baseDecoded, accountId: "user", networkId: "network")) { error in
16 | XCTAssertTrue(error is InMemorySignerError)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Example/Tests/TestUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UtilsSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 28.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | @testable import nearclientios
10 | import XCTest
11 |
12 | let networkId = "unittest"
13 | let testAccountName = "test.near"
14 |
15 | let INITIAL_BALANCE = UInt128(stringLiteral: "500000000000000000000000000")
16 | let HELLO_WASM_BALANCE = UInt128(stringLiteral: "10000000000000000000000000")
17 |
18 | // Length of a random account. Set to 40 because in the protocol minimal allowed top-level account length should be at
19 | // least 32.
20 | let RANDOM_ACCOUNT_LENGTH = 40;
21 |
22 | enum TestUtils {}
23 |
24 | func unsafeWaitFor(_ f: @escaping () async -> ()) {
25 | let sema = DispatchSemaphore(value: 0)
26 | async {
27 | await f()
28 | sema.signal()
29 | }
30 | sema.wait()
31 | }
32 |
33 | extension TestUtils {
34 |
35 | static func setUpTestConnection() async throws -> Near {
36 | let keyStore = InMemoryKeyStore()
37 | let keyPair = try keyPairFromString(encodedKey: "ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw")
38 | try! await(keyStore.setKey(networkId: networkId, accountId: testAccountName, keyPair: keyPair))
39 | let environment = getConfig(env: .ci)
40 | let config = NearConfig(networkId: networkId,
41 | nodeUrl: environment.nodeUrl,
42 | masterAccount: nil,
43 | keyPath: nil,
44 | helperUrl: nil,
45 | initialBalance: nil,
46 | providerType: .jsonRPC(environment.nodeUrl),
47 | signerType: .inMemory(keyStore),
48 | keyStore: keyStore,
49 | contractName: "contractId",
50 | walletUrl: environment.nodeUrl.absoluteString)
51 | return try await connect(config: config)
52 | }
53 |
54 | // Generate some unique string with a given prefix using the alice nonce.
55 | static func generateUniqueString(prefix: String) -> String {
56 | var result = prefix + "-\(Int(Date().timeIntervalSince1970 * 1000))" + "-\(Int.random(in: 0..<1000000))"
57 | let add_symbols = max(RANDOM_ACCOUNT_LENGTH - result.count, 1)
58 | for _ in 0.. Account {
66 | try await masterAccount.fetchState()
67 | let newAccountName = generateUniqueString(prefix: "test")
68 | let newPublicKey = try await(masterAccount.connection.signer.createKey(accountId: newAccountName,
69 | networkId: networkId, curve: .ED25519))
70 | _ = try await masterAccount.createAccount(newAccountId: newAccountName, publicKey: newPublicKey, amount: amount)
71 | return Account(connection: masterAccount.connection, accountId: newAccountName)
72 | }
73 |
74 | static func deployContract(workingAccount: Account, contractId: String, amount: UInt128 = HELLO_WASM_BALANCE) async throws -> Contract {
75 | let newPublicKey = try await workingAccount.connection.signer.createKey(accountId: contractId, networkId: networkId, curve: .ED25519)
76 | let data = Wasm().data
77 | _ = try await workingAccount.createAndDeployContract(contractId: contractId,
78 | publicKey: newPublicKey,
79 | data: data.bytes,
80 | amount: amount)
81 | let options = ContractOptions(viewMethods: [.getValue, .getLastResult],
82 | changeMethods: [.setValue, .callPromise],
83 | sender: nil)
84 | let contract = Contract(account: workingAccount, contractId: contractId, options: options)
85 | return contract
86 | }
87 | }
88 |
89 | extension XCTest {
90 | func AssertThrowsError(
91 | _ expression: @autoclosure () async throws -> Any,
92 | _ message: @autoclosure () -> String = "",
93 | file: StaticString = #filePath,
94 | line: UInt = #line,
95 | _ errorHandler: (_ error: Error) -> Void = { _ in }
96 | ) async {
97 | do {
98 | _ = try await expression()
99 | XCTFail(message(), file: file, line: line)
100 | } catch {
101 | errorHandler(error)
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Example/Tests/Transaction.json:
--------------------------------------------------------------------------------
1 | {"type": "Transaction",
2 | "data": "0000000000795cb7b5f57222e742d1759092f0e20071a0cd2bf30e1f681d800e67935e168801000000000000001000000073747564696f2d76776375396534316d4def837b838543990f3380af8e2a3817ddf70fe9960135b2add25a679b2a01ed01000000020a0000006164644d6573736167650b0000007b2274657874223a22227d80841e000000000000000000000000000000000000000000"}
3 |
--------------------------------------------------------------------------------
/Example/Tests/WalletAccountSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WalletAccountSpec.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 28.11.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KeychainAccess
11 | @testable import nearclientios
12 |
13 | internal class MockAuthService: ExternalAuthService {
14 | var urls: [URL] = []
15 |
16 | func openURL(_ url: URL, presentingViewController: UIViewController) -> Bool {
17 | urls.append(url)
18 | return true
19 | }
20 | }
21 |
22 | class WalletAccountSpec: XCTestCase {
23 |
24 | var walletAccount: WalletAccount!
25 | var keyStore: KeyStore!
26 | let walletUrl = "http://example.com/wallet"
27 | var authService: MockAuthService!
28 | var nearFake: Near!
29 | var testStorage: Keychain!
30 |
31 | override func setUp() {
32 | self.keyStore = InMemoryKeyStore()
33 | self.nearFake = try Near(config: NearConfig(networkId: "networkId",
34 | nodeUrl: URL(string: self.walletUrl)!,
35 | masterAccount: nil,
36 | keyPath: nil,
37 | helperUrl: nil,
38 | initialBalance: nil,
39 | providerType: .jsonRPC(URL(string: self.walletUrl)!),
40 | signerType: .inMemory(self.keyStore),
41 | keyStore: self.keyStore,
42 | contractName: "contractId",
43 | walletUrl: self.walletUrl))
44 | self.testStorage = Keychain(service: "TEST_WALLET_STORAGE_SERVICE")
45 | self.authService = MockAuthService()
46 | self.walletAccount = try! WalletAccount(near: self.nearFake,
47 | authService: self.authService,
48 | storage: self.testStorage)
49 | }
50 |
51 | override func tearDown() {
52 | try! self.testStorage.removeAll()
53 | }
54 |
55 | func testNotSignedInByDefault() async {
56 | let signedIn = await walletAccount.isSignedIn()
57 | XCTAssertFalse(signedIn)
58 | }
59 |
60 | func testCanRequestSignIn() async throws {
61 | let viewController = UIViewController()
62 | try await self.walletAccount.requestSignIn(contractId: "signInContract",
63 | title: "signInTitle", presentingViewController: viewController)
64 | let accounts = try await self.keyStore.getAccounts(networkId: "networkId")
65 | XCTAssertEqual(accounts.count, 1)
66 | XCTAssertTrue(accounts[0].hasPrefix("pending_key"))
67 | XCTAssertEqual(self.authService.urls.count, 1)
68 |
69 | let newUrl = self.authService.urls.last!
70 | XCTAssertEqual(newUrl.scheme, "http")
71 | XCTAssertEqual(newUrl.host, "example.com")
72 | let params = newUrl.queryParameters!
73 | XCTAssertEqual(params["referrer"], "signInTitle")
74 | XCTAssertEqual(params["contract_id"], "signInContract")
75 | XCTAssertEqual(params["success_url"], "\(APP_SCHEME)://success")
76 | XCTAssertEqual(params["failure_url"], "\(APP_SCHEME)://fail")
77 | let keyPair = try await self.keyStore.getKey(networkId: "networkId", accountId: accounts[0])
78 | XCTAssertEqual(params["public_key"], keyPair?.getPublicKey().toString())
79 | }
80 |
81 | func testCompleteSignIn() async throws {
82 | let keyPair = try keyPairFromRandom() as! KeyPairEd25519
83 | try await self.keyStore.setKey(networkId: "networkId",
84 | accountId: "pending_key" + keyPair.getPublicKey().toString(),
85 | keyPair: keyPair)
86 | let public_key = keyPair.getPublicKey().toString()
87 | let url = URL(string: "\(APP_SCHEME)://success?account_id=near.account&public_key=\(public_key)")!
88 | try await self.walletAccount.completeSignIn(url: url)
89 | let testKeyPair = try await self.keyStore.getKey(networkId: "networkId",
90 | accountId: "near.account")
91 | XCTAssertEqual(testKeyPair as? KeyPairEd25519, keyPair)
92 | let signedIn = await self.walletAccount.isSignedIn()
93 | XCTAssertTrue(signedIn)
94 | let accountId = await self.walletAccount.getAccountId()
95 | XCTAssertEqual(accountId, "near.account")
96 | }
97 |
98 | }
99 |
100 |
101 | // override func spec() {
102 | // describe("WalletAccountSpec") {
103 | //
104 | // it("can complete sign in") {
105 | // do {
106 | // let keyPair = try keyPairFromRandom() as! KeyPairEd25519
107 | // try await(self.keyStore.setKey(networkId: "networkId",
108 | // accountId: "pending_key" + keyPair.getPublicKey().toString(),
109 | // keyPair: keyPair))
110 | // let public_key = keyPair.getPublicKey().toString()
111 | // let url = URL(string: "customscheme://success?account_id=near.account&public_key=\(public_key)")!
112 | // try await(self.walletAccount.completeSignIn(UIApplication.shared, open: url))
113 | // let testKeyPair = try await(self.keyStore.getKey(networkId: "networkId",
114 | // accountId: "near.account"))
115 | // expect(testKeyPair as? KeyPairEd25519).to(equal(keyPair))
116 | // expect(self.walletAccount.isSignedIn()).to(beTrue())
117 | // expect(self.walletAccount.getAccountId()).to(equal("near.account"))
118 | // } catch let error {
119 | // fail("\(error)")
120 | // }
121 | // }
122 | // }
123 | // }
124 | //}
125 |
--------------------------------------------------------------------------------
/Example/Tests/Wasm.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Wasm.swift
3 | // nearclientios_Tests
4 | //
5 | // Created by Dmytro Kurochka on 04.12.2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal class Wasm {
12 | lazy var data: Data = {
13 | let testBundle = Bundle(for: type(of: self))
14 | guard let fileURL = testBundle.url(forResource: "main", withExtension: "wasm") else { fatalError() }
15 | return try! Data(contentsOf: fileURL)
16 | }()
17 | }
18 |
--------------------------------------------------------------------------------
/Example/Tests/main.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/near/near-api-swift/95a9c118065f072aba2fa92c476a8cb53b1559a6/Example/Tests/main.wasm
--------------------------------------------------------------------------------
/Example/nearclientios.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/nearclientios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/nearclientios.xcodeproj/xcshareddata/xcschemes/nearclientios-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
76 |
78 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/Example/nearclientios.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/nearclientios.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/nearclientios.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/nearclientios/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // nearclientios
4 | //
5 | // Created by dmitrykurochka on 10/28/2019.
6 | // Copyright (c) 2019 dmitrykurochka. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | func applicationWillEnterForeground(_ application: UIApplication) {
32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
33 | }
34 |
35 | func applicationDidBecomeActive(_ application: UIApplication) {
36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
37 | }
38 |
39 | func applicationWillTerminate(_ application: UIApplication) {
40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
41 | }
42 | }
43 |
44 | extension UIApplication {
45 | static var name: String? {
46 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/Example/nearclientios/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Example/nearclientios/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/Example/nearclientios/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Example/nearclientios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSAllowsArbitraryLoads
28 |
29 |
30 | UILaunchStoryboardName
31 | LaunchScreen
32 | UIMainStoryboardFile
33 | Main
34 | UIRequiredDeviceCapabilities
35 |
36 | armv7
37 |
38 | UISupportedInterfaceOrientations
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationLandscapeLeft
42 |
43 | NSFaceIDUsageDescription
44 | NEAR uses FaceID to protect your private keys.
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Example/nearclientios/WelcomeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // nearclientios
4 | //
5 | // Created by dmitrykurochka on 10/28/2019.
6 | // Copyright (c) 2019 dmitrykurochka. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import nearclientios
11 | import LocalAuthentication
12 |
13 | class WelcomeViewController: UIViewController, WalletSignInDelegate {
14 |
15 | private var walletAccount: WalletAccount?
16 | private var near: Near?
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | // Do any additional setup after loading the view, typically from a nib.
21 | }
22 |
23 | override func viewDidAppear(_ animated: Bool) {
24 | super.viewDidAppear(animated)
25 | Task {
26 | walletAccount = await setupWallet()
27 | await setupUI(with: walletAccount!)
28 | }
29 | }
30 |
31 | override func didReceiveMemoryWarning() {
32 | super.didReceiveMemoryWarning()
33 | // Dispose of any resources that can be recreated.
34 | }
35 |
36 | private func setupWallet() async -> WalletAccount {
37 | let keyStore = SecureEnclaveKeyStore(keychain: .init(service: "example.keystore"))
38 | keyStore.context = LAContext()
39 | let config = NearConfig(
40 | networkId: "testnet", // "default" for mainnet
41 | nodeUrl: URL(string: "https://rpc.testnet.near.org")!, // "https://rpc.mainnet.near.org" for mainnet
42 | masterAccount: nil,
43 | keyPath: nil,
44 | helperUrl: nil,
45 | initialBalance: nil,
46 | providerType: .jsonRPC(URL(string: "https://rpc.testnet.near.org")!), // "https://rpc.mainnet.near.org" for mainnet
47 | signerType: .inMemory(keyStore),
48 | keyStore: keyStore,
49 | contractName: nil,
50 | walletUrl: "https://wallet.testnet.near.org" // "https://wallet.near.org" for mainnet
51 | )
52 | near = Near(config: config)
53 | return try! WalletAccount(near: near!, authService: DefaultAuthService.shared) // a failed try here represents a configuration error, not a runtime error. It's safe to store a `WalletAccount!`.
54 | }
55 |
56 | private func setupUI(with wallet: WalletAccount) async {
57 | if await wallet.isSignedIn() {
58 | await MainActor.run {
59 | showAccountState(with: wallet)
60 | }
61 | } else {
62 | //Hide preloader
63 | }
64 | }
65 |
66 | private func showAccountState(with wallet: WalletAccount) {
67 | guard let accountVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AccountViewController") as? AccountViewController else {
68 | return
69 | }
70 | accountVC.setup(near: near!, wallet: wallet)
71 | navigationController?.pushViewController(accountVC, animated: true)
72 | }
73 |
74 | @IBAction func tapShowAuthForm(_ sender: UIButton) {
75 | Task {
76 | let appName = UIApplication.name ?? "signInTitle"
77 | DefaultAuthService.shared.walletSignIn = self
78 | try! await walletAccount!.requestSignIn(contractId: nil, title: appName, presentingViewController: self)
79 | }
80 | }
81 |
82 | func completeSignIn(url: URL) async {
83 | do {
84 | try await walletAccount?.completeSignIn(url: url)
85 | } catch {
86 | await MainActor.run {
87 | let alert = UIAlertController(title: "Error", message: "\(error)", preferredStyle: .alert)
88 | alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: { [weak self] _ in
89 | self?.dismiss(animated: true, completion: nil)
90 | }))
91 | present(alert, animated: true, completion: nil)
92 | }
93 | }
94 | await setupUI(with: walletAccount!)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 NEAR Inc
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "AnyCodable",
6 | "repositoryURL": "https://github.com/Flight-School/AnyCodable.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "876d162385e9862ae8b3c8d65dc301312b040005",
10 | "version": "0.6.0"
11 | }
12 | },
13 | {
14 | "package": "Base58Swift",
15 | "repositoryURL": "https://github.com/keefertaylor/Base58Swift.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "1c13ea6b07f1584660526f8bde3d4cc150e91acd",
19 | "version": "2.1.14"
20 | }
21 | },
22 | {
23 | "package": "BigInt",
24 | "repositoryURL": "https://github.com/attaswift/BigInt.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6",
28 | "version": "5.3.0"
29 | }
30 | },
31 | {
32 | "package": "KeychainAccess",
33 | "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
37 | "version": "4.2.2"
38 | }
39 | },
40 | {
41 | "package": "secp256k1",
42 | "repositoryURL": "https://github.com/GigaBitcoin/secp256k1.swift.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "1a796f738bdcd84b41d05f92593188b23163e60b",
46 | "version": "0.7.0"
47 | }
48 | },
49 | {
50 | "package": "TweetNacl",
51 | "repositoryURL": "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "f8fd111642bf2336b11ef9ea828510693106e954",
55 | "version": "1.1.0"
56 | }
57 | }
58 | ]
59 | },
60 | "version": 1
61 | }
62 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 | let package = Package(
6 | name: "nearclientios",
7 | platforms: [
8 | .iOS(.v13),
9 | .macOS(.v12)
10 | ],
11 | products: [
12 | // Products define the executables and libraries a package produces, and make them visible to other packages.
13 | .library(
14 | name: "nearclientios",
15 | targets: ["nearclientios"]
16 | ),
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .exact("4.2.2")),
21 | .package(url: "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git", .exact("1.1.0")),
22 | .package(url: "https://github.com/Flight-School/AnyCodable.git", .exact("0.6.0")),
23 | .package(url: "https://github.com/keefertaylor/Base58Swift.git", from: "2.1.0"),
24 | .package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", .exact("0.7.0"))
25 | ],
26 | targets: [
27 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
28 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
29 | .target(
30 | name: "nearclientios",
31 | dependencies: [
32 | "KeychainAccess",
33 | .product(name: "TweetNacl", package: "tweetnacl-swiftwrap"),
34 | "AnyCodable",
35 | "Base58Swift",
36 | .product(name: "secp256k1", package: "secp256k1.swift"),
37 | ],
38 | path: "./nearclientios/Sources"
39 | ),
40 | ]
41 | )
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nearclientios
2 |
3 | [](https://travis-ci.com/nearprotocol/near-client-ios)
4 | [](https://cocoapods.org/pods/nearclientios)
5 | [](https://github.com/nearprotocol/near-client-ios/blob/master/LICENSE)
6 | [](https://cocoapods.org/pods/nearclientios)
7 |
8 | ## Example
9 |
10 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
11 |
12 | # Usage
13 |
14 | ```swift
15 | import nearclientios
16 | class ViewController: UIViewController, WalletSignInDelegate {
17 | private var walletAccount: WalletAccount?
18 | private var near: Near?
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | let keyStore = KeychainKeyStore(keychain: .init(service: "example.keystore"))
23 | let config = NearConfig(
24 | networkId: "testnet", // "default" for mainnet
25 | nodeUrl: URL(string: "https://rpc.testnet.near.org")!, // "https://rpc.mainnet.near.org" for mainnet
26 | masterAccount: nil,
27 | keyPath: nil,
28 | helperUrl: nil,
29 | initialBalance: nil,
30 | providerType: .jsonRPC(URL(string: "https://rpc.testnet.near.org")!), // "https://rpc.mainnet.near.org" for mainnet
31 | signerType: .inMemory(keyStore),
32 | keyStore: keyStore,
33 | contractName: nil,
34 | walletUrl: "https://wallet.testnet.near.org" // "https://wallet.near.org" for mainnet
35 | )
36 | near = try Near(config: config)
37 | walletAccount = try! WalletAccount(near: near!, authService: DefaultAuthService.shared) // a failed try here represents a configuration error, not a runtime error. It's safe to store a `WalletAccount!`.
38 | let appName = UIApplication.name ?? "signInTitle"
39 | DefaultAuthService.shared.walletSignIn = self
40 | try! await walletAccount!.requestSignIn(contractId: nil, title: appName, presentingViewController: self)
41 | }
42 | func completeSignIn(url: URL) async {
43 | try! await walletAccount?.completeSignIn(url: url)
44 | MainActor.run {
45 | //do any additional UI work on the main thread after sign in is complete
46 | }
47 | }
48 | }
49 | ```
50 |
51 | ## Requirements
52 |
53 | nearclientios makes use of Swift's async/await and thus requires iOS 13.
54 |
55 | ## Installation
56 |
57 | ### CocoaPods
58 | nearclientios is available through [CocoaPods](https://cocoapods.org). To install
59 | it, simply add the following line to your Podfile:
60 |
61 | ```ruby
62 | pod 'nearclientios'
63 | ```
64 |
65 | ### Swift Package Manager
66 | Once you have your Swift package set up, you can add nearclientios as a dependency in Package.swift.
67 |
68 | ```swift
69 | dependencies: [
70 | .package(url: "https://github.com/near/near-api-swift", .upToNextMajor(from: "1.0.29"))
71 | ]
72 |
73 | ```
74 | ## Examples
75 |
76 | * [Basic nearclientios function calls](https://github.com/LyubomyrBurday/near_basic)
77 |
78 | ## Author
79 |
80 | NEAR Inc
81 |
82 | ## License
83 |
84 | nearclientios is available under the MIT license. See the LICENSE file for more info.
85 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/nearclientios.h:
--------------------------------------------------------------------------------
1 | //
2 | // nearclientios.h
3 | // nearclientios
4 | //
5 | // Created by Sam Ingle on 1/14/22.
6 | //
7 |
8 | #import
9 |
10 | //! Project version number for nearclientios.
11 | FOUNDATION_EXPORT double nearclientiosVersionNumber;
12 |
13 | //! Project version string for nearclientios.
14 | FOUNDATION_EXPORT const unsigned char nearclientiosVersionString[];
15 |
16 | // In this header, you should import all the public headers of your framework using statements like #import
17 |
18 |
19 |
--------------------------------------------------------------------------------
/nearclientios.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'nearclientios'
3 | s.version = '1.0.29'
4 | s.summary = 'Swift SDK to interact with NEAR Protocol'
5 |
6 | s.description = <<-DESC
7 | near-client-ios is a SWIFT library for development of DApps on NEAR platform.
8 | DESC
9 | s.homepage = 'https://github.com/nearprotocol/near-client-ios'
10 | s.license = { :type => 'MIT', :file => 'LICENSE' }
11 | s.author = { 'dmitrykurochka' => 'v.i.p.dimak@gmail.com' }
12 | s.source = { :git => 'https://github.com/nearprotocol/near-client-ios.git', :tag => s.version.to_s }
13 | s.social_media_url = 'https://twitter.com/nearprotocol'
14 |
15 | s.ios.deployment_target = '13.0'
16 |
17 | s.source_files = 'nearclientios/Sources/**/*'
18 | s.swift_versions = ["5.0"]
19 |
20 | s.dependency 'TweetNacl', '~> 1.0'
21 | s.dependency 'KeychainAccess', '~> 4.2.2'
22 | s.dependency 'Base58Swift', '~> 2.1.10'
23 | s.dependency 'secp256k1.swift'
24 | s.dependency 'AnyCodable-FlightSchool', '~> 0.6.0'
25 | end
26 |
--------------------------------------------------------------------------------
/nearclientios.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/nearclientios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/nearclientios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/near/near-api-swift/95a9c118065f072aba2fa92c476a8cb53b1559a6/nearclientios/Assets/.gitkeep
--------------------------------------------------------------------------------
/nearclientios/Sources/AccountCreator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountCreator.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol AccountCreator {
12 | func createAccount(newAccountId: String, publicKey: PublicKey) async throws -> Void
13 | }
14 |
15 | public struct LocalAccountCreator {
16 | let masterAccount: Account
17 | let initialBalance: UInt128
18 | }
19 |
20 | extension LocalAccountCreator: AccountCreator {
21 | public func createAccount(newAccountId: String, publicKey: PublicKey) async throws -> Void {
22 | let _ = try await masterAccount.createAccount(newAccountId: newAccountId, publicKey: publicKey, amount: initialBalance)
23 | }
24 | }
25 |
26 | public struct UrlAccountCreator {
27 | let connection: Connection
28 | let helperConnection: ConnectionInfo
29 | }
30 |
31 | extension UrlAccountCreator {
32 | init(connection: Connection, helperUrl: URL) {
33 | self.init(connection: connection, helperConnection: ConnectionInfo(url: helperUrl))
34 | }
35 | }
36 |
37 | extension UrlAccountCreator: AccountCreator {
38 | public func createAccount(newAccountId: String, publicKey: PublicKey) async throws -> Void {
39 | // no-op
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Connection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol ConnectionConfigProtocol {
12 | var networkId: String {get}
13 | var providerType: ProviderType {get}
14 | var signerType: SignerType {get}
15 | }
16 |
17 | public struct ConnectionConfig: ConnectionConfigProtocol {
18 | public let networkId: String
19 | public let providerType: ProviderType
20 | public let signerType: SignerType
21 | }
22 |
23 | public extension ConnectionConfigProtocol {
24 | func provider() -> Provider {
25 | switch providerType {
26 | case .jsonRPC(let url): return JSONRPCProvider(url: url)
27 | }
28 | }
29 | }
30 |
31 | public extension ConnectionConfigProtocol {
32 | func signer() -> Signer {
33 | switch signerType {
34 | case .inMemory(let keyStore): return InMemorySigner(keyStore: keyStore)
35 | }
36 | }
37 | }
38 |
39 | public struct Connection {
40 | public let networkId: String
41 | public let provider: Provider
42 | public let signer: Signer
43 | }
44 |
45 | public extension Connection {
46 | static func fromConfig(config: ConnectionConfigProtocol) -> Connection {
47 | let provider = config.provider()
48 | let signer = config.signer()
49 | return Connection(networkId: config.networkId, provider: provider, signer: signer)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Contract.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Contract.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol ContractOptionsProtocol {
12 | var viewMethods: [ViewMethod] {get}
13 | var changeMethods: [ChangeMethod] {get}
14 | var sender: String? {get}
15 | }
16 |
17 | public typealias MethodName = String
18 | public typealias ViewMethod = MethodName
19 | public typealias ChangeMethod = MethodName
20 |
21 | public extension ViewMethod {
22 | static let getValue = "getValue"
23 | static let getLastResult = "getLastResult"
24 | static let hello = "hello"
25 | static let getAllKeys = "getAllKeys"
26 | static let returnHiWithLogs = "returnHiWithLogs"
27 | }
28 |
29 | public extension ChangeMethod {
30 | static let setValue = "setValue"
31 | static let callPromise = "callPromise"
32 | static let generateLogs = "generateLogs"
33 | static let triggerAssert = "triggerAssert"
34 | static let testSetRemove = "testSetRemove"
35 | }
36 |
37 | public struct ContractOptions: ContractOptionsProtocol {
38 | public let viewMethods: [ViewMethod]
39 | public let changeMethods: [ChangeMethod]
40 | public let sender: String?
41 | }
42 |
43 | public struct Contract {
44 | let account: Account
45 | let contractId: String
46 | let viewMethods: [ViewMethod]
47 | let changeMethods: [ChangeMethod]
48 | let sender: String?
49 | }
50 |
51 | public extension Contract {
52 | init(account: Account, contractId: String, options: ContractOptionsProtocol) {
53 | self.init(account: account, contractId: contractId, viewMethods: options.viewMethods,
54 | changeMethods: options.changeMethods, sender: nil)
55 | }
56 | }
57 |
58 | public extension Contract {
59 | func view(methodName: ChangeMethod, args: [String: Any] = [:]) async throws -> T {
60 | return try await account.viewFunction(contractId: contractId, methodName: methodName, args: args)
61 | }
62 | }
63 |
64 | public extension Contract {
65 | @discardableResult
66 | func change(methodName: ChangeMethod, args: [String: Any] = [:],
67 | gas: UInt64? = nil, amount: UInt128 = 0) async throws -> Any? {
68 | let rawResult = gas == nil ? try await account.functionCall(
69 | contractId: contractId,
70 | methodName: methodName,
71 | args: args,
72 | amount: amount
73 | ) : try await account.functionCall(
74 | contractId: contractId,
75 | methodName: methodName,
76 | args: args,
77 | gas: gas!,
78 | amount: amount
79 | )
80 | return getTransactionLastResult(txResult: rawResult)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/nearclientios/Sources/DefaultAuthService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultAuthService.swift
3 | // nearclientios
4 | //
5 | // Created by Kevin McConnaughay on 2/17/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import WebKit
11 |
12 | public class DefaultAuthService: NSObject, ExternalAuthService {
13 | public static let shared = DefaultAuthService()
14 |
15 | var navController: UINavigationController?
16 | public weak var walletSignIn: WalletSignInDelegate?
17 |
18 | public func openURL(_ url: URL, presentingViewController: UIViewController) -> Bool {
19 | let viewController = UIViewController()
20 | navController = UINavigationController(rootViewController: viewController)
21 | let closeButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.dismiss))
22 | viewController.navigationItem.rightBarButtonItem = closeButton
23 | let webView = WKWebView()
24 | webView.navigationDelegate = self
25 | webView.translatesAutoresizingMaskIntoConstraints = false
26 | viewController.view.addSubview(webView)
27 | webView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor).isActive = true
28 | webView.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor).isActive = true
29 | webView.topAnchor.constraint(equalTo: viewController.view.topAnchor).isActive = true
30 | webView.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor).isActive = true
31 | webView.load(URLRequest(url: url))
32 | presentingViewController.present(navController!, animated: true, completion: nil)
33 | return true
34 | }
35 |
36 | @objc private func dismiss() {
37 | navController?.dismiss(animated: true, completion: { [weak self] in
38 | self?.navController = nil
39 | })
40 | }
41 | }
42 |
43 | extension DefaultAuthService: WKNavigationDelegate {
44 | public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
45 | defer {
46 | decisionHandler(.allow)
47 | }
48 | guard let url = navigationAction.request.url else { return }
49 | guard url.scheme == APP_SCHEME else { return }
50 | Task {
51 | await walletSignIn?.completeSignIn(url: url)
52 | }
53 | dismiss()
54 | }
55 | }
56 |
57 | public protocol WalletSignInDelegate: AnyObject {
58 | func completeSignIn(url: URL) async
59 | }
60 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/FileSystemKeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountInfo.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | // Format of the account stored on disk.
13 | */
14 | public protocol AccountInfoProtocol {
15 | var account_id: String {get}
16 | var private_key: String? {get}
17 | var secret_key: String? {get}
18 | }
19 |
20 | public struct AccountInfo: AccountInfoProtocol, Codable {
21 | public let account_id: String
22 | public let private_key: String?
23 | public let secret_key: String?
24 | }
25 |
26 | public struct UnencryptedFileSystemKeyStore {
27 | let keyDir: String
28 | let manager: FileManager
29 |
30 | public init(keyDir: String, manager: FileManager = .default) {
31 | self.keyDir = keyDir
32 | self.manager = manager
33 | }
34 | }
35 |
36 | public enum UnencryptedFileSystemKeyStoreError: Error {
37 | case noPrivateKey
38 | }
39 |
40 | extension UnencryptedFileSystemKeyStore: KeyStore {
41 | public func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void {
42 | let networkPath = "\(keyDir)/\(networkId)"
43 | let fullNetworkPath = manager.targetDirectory.appendingPathComponent(networkPath).path
44 | try manager.ensureDir(path: fullNetworkPath)
45 | let content = AccountInfo(account_id: accountId, private_key: keyPair.toString(), secret_key: nil)
46 | let encoded = try JSONEncoder().encode(content)
47 | let fileUrl = getKeyFileUrl(networkPath: networkPath, accountId: accountId)
48 | try encoded.write(to: fileUrl, options: [.atomic])
49 | }
50 |
51 | /// Find key / account id.
52 | public func getKey(networkId: String, accountId: String) async throws -> KeyPair? {
53 | let networkPath = "\(keyDir)/\(networkId)"
54 | let path = getKeyFileUrl(networkPath: networkPath, accountId: accountId).path
55 | guard manager.fileExists(atPath: path) else {return nil}
56 | let accountKeyPair = try await UnencryptedFileSystemKeyStore.readKeyFile(path: path)
57 | return accountKeyPair.1
58 | }
59 |
60 | public func removeKey(networkId: String, accountId: String) async throws -> Void {
61 | let networkPath = "\(keyDir)/\(networkId)"
62 | let path = getKeyFileUrl(networkPath: networkPath, accountId: accountId).path
63 | guard manager.fileExists(atPath: path) else {return}
64 | try manager.removeItem(atPath: path)
65 | }
66 |
67 | public func clear() async throws -> Void {
68 | let networksPath = manager.targetDirectory.appendingPathComponent(keyDir).path
69 | try manager.removeItem(atPath: networksPath)
70 | }
71 |
72 | public func getNetworks() async throws -> [String] {
73 | let networksPath = manager.targetDirectory.appendingPathComponent(keyDir).path
74 | let files = try manager.contentsOfDirectory(atPath: networksPath)
75 | return files
76 | }
77 |
78 | public func getAccounts(networkId: String) async throws -> [String] {
79 | let networkPath = "\(keyDir)/\(networkId)"
80 | let fullNetworkPath = manager.targetDirectory.appendingPathComponent(networkPath).path
81 | guard manager.fileExists(atPath: fullNetworkPath) else {return []}
82 | let files = try manager.contentsOfDirectory(atPath: fullNetworkPath)
83 | return files.filter {$0.hasSuffix(".json")}.map {$0.replacingOccurrences(of: ".json", with: "")}
84 | }
85 | }
86 |
87 | extension UnencryptedFileSystemKeyStore {
88 | private func getKeyFileUrl(networkPath: String, accountId: String) -> URL {
89 | return manager.targetDirectory.appendingPathComponent("\(networkPath)/\(accountId).json")
90 | }
91 |
92 | private static func loadJsonFile(path: String) async throws -> AccountInfo {
93 | let content = try Data(contentsOf: URL(fileURLWithPath: path), options: [])
94 | let accountInfo = try JSONDecoder().decode(AccountInfo.self, from: content)
95 | return accountInfo
96 | }
97 |
98 | static func readKeyFile(path: String) async throws -> (String, KeyPair) {
99 | let accountInfo = try await loadJsonFile(path: path)
100 | // The private key might be in private_key or secret_key field.
101 | var privateKey = accountInfo.private_key
102 | if privateKey == nil, accountInfo.secret_key != nil {
103 | privateKey = accountInfo.secret_key
104 | }
105 | guard privateKey != nil else {throw UnencryptedFileSystemKeyStoreError.noPrivateKey}
106 | let keyPair = try keyPairFromString(encodedKey: privateKey!)
107 | return (accountInfo.account_id, keyPair)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/InMemoryKeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InMemoryKeyStore.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * Simple in-memory keystore for testing purposes.
13 | */
14 | public class InMemoryKeyStore {
15 | private var keys: [String: String]
16 |
17 | public init(keys: [String: String] = [:]) {
18 | self.keys = keys
19 | }
20 | }
21 |
22 | extension InMemoryKeyStore: KeyStore {
23 | public func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void {
24 | keys["\(accountId):\(networkId)"] = keyPair.toString()
25 | }
26 |
27 | public func getKey(networkId: String, accountId: String) async throws -> KeyPair? {
28 | guard let value = keys["\(accountId):\(networkId)"] else {return nil}
29 | return try? keyPairFromString(encodedKey: value)
30 | }
31 |
32 | public func removeKey(networkId: String, accountId: String) async throws -> Void {
33 | keys.removeValue(forKey: "\(accountId):\(networkId)")
34 | }
35 |
36 | public func clear() async throws -> Void {
37 | keys = [:]
38 | }
39 |
40 | public func getNetworks() async throws -> [String] {
41 | var result = Set()
42 | keys.keys.forEach {key in
43 | let parts = key.split(separator: ":")
44 | result.insert(String(parts[1]))
45 | }
46 | return Array(result)
47 | }
48 |
49 | public func getAccounts(networkId: String) async throws -> [String] {
50 | var result = [String]()
51 | keys.keys.forEach {key in
52 | let parts = key.split(separator: ":").map {String($0)}
53 | if parts[parts.count - 1] == networkId {
54 | result.append(parts.dropLast().joined(separator: ":"))
55 | }
56 | }
57 | return result
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/KeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyStore.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * Key store interface for `InMemorySigner`.
13 | */
14 | public protocol KeyStore {
15 | func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void
16 | func getKey(networkId: String, accountId: String) async throws -> KeyPair?
17 | func removeKey(networkId: String, accountId: String) async throws -> Void
18 | func clear() async throws -> Void
19 | func getNetworks() async throws -> [String]
20 | func getAccounts(networkId: String) async throws -> [String]
21 | }
22 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/KeychainKeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeychainKeyStore.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 08.11.2019.
6 | //
7 |
8 | import Foundation
9 | import KeychainAccess
10 |
11 | public let NEAR_KEYCHAIN_STORAGE_SERVICE = "near.keystore"
12 |
13 | public struct KeychainKeyStore {
14 | private let keychain: Keychain
15 |
16 | public init(keychain: Keychain = .init(service: NEAR_KEYCHAIN_STORAGE_SERVICE)) {
17 | self.keychain = keychain
18 | }
19 | }
20 |
21 | extension KeychainKeyStore: KeyStore {
22 | public func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void {
23 | keychain[storageKeyForSecretKey(networkId: networkId, accountId: accountId)] = keyPair.toString()
24 | }
25 |
26 | public func setKey(networkId: String, accountId: String, withKeyPairAsData keyPair: Data) async throws -> Void {
27 | keychain[data: storageKeyForSecretKey(networkId: networkId, accountId: accountId)] = keyPair
28 | }
29 |
30 | public func getKey(networkId: String, accountId: String) async throws -> KeyPair? {
31 | guard let value = keychain[storageKeyForSecretKey(networkId: networkId, accountId: accountId)] else {
32 | return nil
33 | }
34 | return try? keyPairFromString(encodedKey: value)
35 | }
36 |
37 | public func getEncryptedKey(networkId: String, accountId: String) async throws -> Data? {
38 | guard let value = keychain[data: storageKeyForSecretKey(networkId: networkId, accountId: accountId)] else {
39 | return nil
40 | }
41 | return value
42 | }
43 |
44 | public func removeKey(networkId: String, accountId: String) async throws -> Void {
45 | keychain[storageKeyForSecretKey(networkId: networkId, accountId: accountId)] = nil
46 | }
47 |
48 | public func clear() async throws -> Void {
49 | try keychain.removeAll()
50 | }
51 |
52 | public func getNetworks() async throws -> [String] {
53 | var result = Set()
54 | for key in storageKeys() {
55 | if let networkId = key.components(separatedBy: ":").last {
56 | result.insert(networkId)
57 | }
58 | }
59 | return Array(result)
60 | }
61 |
62 | public func getAccounts(networkId: String) async throws -> [String] {
63 | var result = [String]()
64 | for key in storageKeys() {
65 | let components = key.components(separatedBy: ":")
66 | let accountId = components[components.count - 2]
67 | if let keychainNetworkId = components.last, keychainNetworkId == networkId, accountId != networkId {
68 | result.append(accountId)
69 | }
70 | }
71 | return result
72 | }
73 | }
74 |
75 | extension KeychainKeyStore {
76 | private func storageKeyForSecretKey(networkId: String, accountId: String) -> String {
77 | return "\(accountId):\(networkId)"
78 | }
79 |
80 | private func storageKeys() -> [String] {
81 | return keychain.allKeys()
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/MergeKeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MergeKeyStore.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * Keystore which can be used to merge multiple key stores into one virtual key store.
13 | */
14 | public struct MergeKeyStore {
15 | /// First keystore gets all write calls, read calls are attempted from start to end of array
16 | private(set) var keyStores: [KeyStore]
17 |
18 | public init(keyStores: [KeyStore] = []) {
19 | self.keyStores = keyStores
20 | }
21 | }
22 |
23 | extension MergeKeyStore: KeyStore {
24 | public func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void {
25 | return try await keyStores[0].setKey(networkId: networkId, accountId: accountId, keyPair: keyPair)
26 | }
27 |
28 | public func getKey(networkId: String, accountId: String) async throws -> KeyPair? {
29 | for keyStore in keyStores {
30 | if let keyPair = try await keyStore.getKey(networkId: networkId, accountId: accountId) {
31 | return keyPair
32 | }
33 | }
34 | return nil
35 | }
36 |
37 | public func removeKey(networkId: String, accountId: String) async throws -> Void {
38 | for keyStore in keyStores {
39 | try await keyStore.removeKey(networkId: networkId, accountId: accountId)
40 | }
41 | }
42 |
43 | public func clear() async throws -> Void {
44 | for keyStore in keyStores {
45 | try await keyStore.clear()
46 | }
47 | }
48 |
49 | public func getNetworks() async throws -> [String] {
50 | var result = Set()
51 | for keyStore in keyStores {
52 | let networks = try await keyStore.getNetworks()
53 | for network in networks {
54 | result.insert(network)
55 | }
56 | }
57 | return Array(result)
58 | }
59 |
60 | public func getAccounts(networkId: String) async throws -> [String] {
61 | var result = Set()
62 | for keyStore in keyStores {
63 | let accounts = try await keyStore.getAccounts(networkId: networkId)
64 | for account in accounts {
65 | result.insert(account)
66 | }
67 | }
68 | return Array(result)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/nearclientios/Sources/KeyStores/SecureEnclaveKeyStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecureEnclaveKeyStore.swift
3 | // nearclientios
4 | //
5 | // Created by Kevin McConnaughay on 4/1/22.
6 | //
7 |
8 | import Foundation
9 | import CryptoKit
10 | import LocalAuthentication
11 | import KeychainAccess
12 |
13 | private let algorithm = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM
14 |
15 | private enum SecureEnclaveKeyStoreError: Error {
16 | case cannotAccessPrivateKey(osStatus: OSStatus)
17 | case cannotAccessPublicKey
18 | case encryptionNotSupported(algorithm: SecKeyAlgorithm)
19 | case decryptionNotSupported(algorithm: SecKeyAlgorithm)
20 | case cannotDeleteKeys(osStatus: OSStatus)
21 | case unexpected(description: String)
22 |
23 | var errorDescription: String {
24 | switch self {
25 | case .cannotAccessPrivateKey(let osStatus):
26 | return "Cannot access private key: \(osStatus)"
27 | case .cannotAccessPublicKey:
28 | return "Cannot access public key"
29 | case .encryptionNotSupported(let algorithm):
30 | return "Encryption not supported: \(algorithm)"
31 | case .decryptionNotSupported(let algorithm):
32 | return "Decryption not supported: \(algorithm)"
33 | case .cannotDeleteKeys(let osStatus):
34 | return "Cannot delete key: \(osStatus)"
35 | case .unexpected(let description):
36 | return description
37 | }
38 | }
39 | }
40 |
41 | public class SecureEnclaveKeyStore {
42 | public let keychain: Keychain
43 | public let keychainKeyStore: KeychainKeyStore
44 | /// Ignored and forced to false on simulators.
45 | let requireUserPresence: Bool
46 | public var context: LAContext?
47 |
48 | public init(keychain: Keychain = .init(service: NEAR_KEYCHAIN_STORAGE_SERVICE), requireUserPresence: Bool = true) {
49 | var userPresence = requireUserPresence
50 | #if targetEnvironment(simulator)
51 | userPresence = false
52 | #endif
53 | self.keychain = keychain
54 | self.keychainKeyStore = .init(keychain: keychain)
55 | self.requireUserPresence = userPresence
56 | }
57 | }
58 |
59 | extension SecureEnclaveKeyStore: KeyStore {
60 | public func setKey(networkId: String, accountId: String, keyPair: KeyPair) async throws -> Void {
61 | let storageKey = storageKeyForSecretKey(networkId: networkId, accountId: accountId)
62 | let keyPairData = keyPair.toString().data(using: .utf8)!
63 | let encrypted = try encrypt(plainTextData: keyPairData, withPublicKeyFromStorageKey: storageKey)
64 | try await keychainKeyStore.setKey(networkId: networkId, accountId: accountId, withKeyPairAsData: encrypted)
65 | }
66 |
67 | public func getKey(networkId: String, accountId: String) async throws -> KeyPair? {
68 | let storageKey = storageKeyForSecretKey(networkId: networkId, accountId: accountId)
69 | guard let encryptedKey = try await keychainKeyStore.getEncryptedKey(networkId: networkId, accountId: accountId) else {
70 | return nil
71 | }
72 |
73 | let decrypted = try decrypt(cipherText: encryptedKey, withPrivateKeyFromStorageKey: storageKey)
74 | return try? keyPairFromString(encodedKey: String(decoding: decrypted, as: UTF8.self))
75 | }
76 |
77 | public func removeKey(networkId: String, accountId: String) async throws -> Void {
78 | let storageKey = storageKeyForSecretKey(networkId: networkId, accountId: accountId)
79 | try delete(storageKey: storageKey)
80 | try await keychainKeyStore.removeKey(networkId: networkId, accountId: accountId)
81 | }
82 |
83 | public func clear() async throws -> Void {
84 | try delete(storageKey: nil)
85 | try await keychainKeyStore.clear()
86 | }
87 |
88 | private func delete(storageKey: String?) throws -> Void {
89 | var params: [String: Any] = [
90 | kSecClass as String: kSecClassKey,
91 | kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
92 | ]
93 | if storageKey != nil {
94 | params[kSecAttrApplicationTag as String] = storageKey! as CFString
95 | }
96 | let status = SecItemDelete(params as CFDictionary)
97 | if status != errSecSuccess && status != errSecItemNotFound {
98 | throw SecureEnclaveKeyStoreError.cannotDeleteKeys(osStatus: status)
99 | }
100 | }
101 |
102 | public func getNetworks() async throws -> [String] {
103 | return try await keychainKeyStore.getNetworks()
104 | }
105 |
106 | public func getAccounts(networkId: String) async throws -> [String] {
107 | return try await keychainKeyStore.getAccounts(networkId: networkId)
108 | }
109 |
110 | private func createPrivateKey(withStorageKey storageKey: String) throws -> SecKey {
111 | let flags: SecAccessControlCreateFlags = requireUserPresence ? [.privateKeyUsage, .userPresence] : .privateKeyUsage
112 |
113 | let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags, nil)!
114 | var attributes: [String: Any] = [
115 | kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
116 | kSecAttrKeySizeInBits as String: 256,
117 | kSecPrivateKeyAttrs as String: [
118 | kSecAttrIsPermanent as String: true,
119 | kSecAttrApplicationTag as String: storageKey as CFString,
120 | kSecAttrAccessControl as String: access
121 | ]
122 | ]
123 |
124 | if SecureEnclave.isAvailable {
125 | attributes[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
126 | }
127 |
128 | var error: Unmanaged?
129 | guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
130 | throw error!.takeRetainedValue() as Swift.Error
131 | }
132 |
133 | return privateKey
134 | }
135 |
136 | private func getPrivateKey(withStorageKey storageKey: String) throws -> SecKey {
137 | var params: [String: Any] = [
138 | kSecClass as String: kSecClassKey,
139 | kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
140 | kSecAttrApplicationTag as String: storageKey as CFString,
141 | kSecReturnRef as String: true,
142 | ]
143 | if context != nil {
144 | params[kSecUseAuthenticationContext as String] = context
145 | }
146 | var raw: CFTypeRef?
147 | let status = SecItemCopyMatching(params as CFDictionary, &raw)
148 | guard status == errSecSuccess, let result = raw else {
149 | throw SecureEnclaveKeyStoreError.cannotAccessPrivateKey(osStatus: status)
150 | }
151 |
152 | return result as! SecKey
153 | }
154 |
155 | private func encrypt(plainTextData: Data, withPublicKey publicKey: SecKey) throws -> Data {
156 | guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, algorithm) else {
157 | throw SecureEnclaveKeyStoreError.encryptionNotSupported(algorithm: algorithm)
158 | }
159 | var error: Unmanaged?
160 | guard let cipherTextData = SecKeyCreateEncryptedData(publicKey, algorithm, plainTextData as CFData, &error) as Data? else {
161 | throw error!.takeRetainedValue() as Swift.Error
162 | }
163 | return cipherTextData
164 | }
165 |
166 | func encrypt(plainTextData: Data, withPublicKeyFromStorageKey storageKey: String) throws -> Data {
167 | let privateKey: SecKey
168 | do {
169 | privateKey = try getPrivateKey(withStorageKey: storageKey)
170 | } catch {
171 | privateKey = try createPrivateKey(withStorageKey: storageKey)
172 | }
173 |
174 | guard let publicKey = SecKeyCopyPublicKey(privateKey) else { throw SecureEnclaveKeyStoreError.cannotAccessPublicKey }
175 | return try encrypt(plainTextData: plainTextData, withPublicKey: publicKey)
176 | }
177 |
178 | private func decrypt(cipherText: Data, privateKey: SecKey) throws -> Data {
179 | guard SecKeyIsAlgorithmSupported(privateKey, .decrypt, algorithm) else {
180 | throw SecureEnclaveKeyStoreError.decryptionNotSupported(algorithm: algorithm)
181 | }
182 |
183 | var error: Unmanaged?
184 | guard let plainTextData = SecKeyCreateDecryptedData(privateKey, algorithm, cipherText as CFData, &error) as Data? else {
185 | throw error!.takeRetainedValue() as Swift.Error
186 | }
187 |
188 | return plainTextData
189 | }
190 |
191 | func decrypt(cipherText: Data, withPrivateKeyFromStorageKey storageKey: String) throws -> Data {
192 | let privateKey = try getPrivateKey(withStorageKey: storageKey)
193 | return try decrypt(cipherText: cipherText, privateKey: privateKey)
194 | }
195 |
196 | private func storageKeyForSecretKey(networkId: String, accountId: String) -> String {
197 | return "enc:\(accountId):\(networkId)"
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Near.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Near.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol NearConfigProtocol: ConnectionConfigProtocol {
12 | var networkId: String {get}
13 | var nodeUrl: URL {get}
14 | var masterAccount: String? {get set}
15 | var keyPath: String? {get}
16 | var helperUrl: URL? {get}
17 | var initialBalance: UInt128? {get}
18 | var keyStore: KeyStore? {get set}
19 | var contractName: String? {get}
20 | var walletUrl: String {get}
21 | }
22 |
23 | public struct NearConfig: NearConfigProtocol {
24 | public let networkId: String
25 | public let nodeUrl: URL
26 | public var masterAccount: String?
27 | public let keyPath: String?
28 | public let helperUrl: URL?
29 | public let initialBalance: UInt128?
30 | public let providerType: ProviderType
31 | public let signerType: SignerType
32 | public var keyStore: KeyStore?
33 | public let contractName: String?
34 | public let walletUrl: String
35 |
36 | public init(networkId: String, nodeUrl: URL, masterAccount: String?, keyPath: String?, helperUrl: URL?, initialBalance: UInt128?, providerType: ProviderType, signerType: SignerType, keyStore: KeyStore?, contractName: String?, walletUrl: String) {
37 | self.networkId = networkId
38 | self.nodeUrl = nodeUrl
39 | self.masterAccount = masterAccount
40 | self.keyPath = keyPath
41 | self.helperUrl = helperUrl
42 | self.initialBalance = initialBalance
43 | self.providerType = providerType
44 | self.signerType = signerType
45 | self.keyStore = keyStore
46 | self.contractName = contractName
47 | self.walletUrl = walletUrl
48 | }
49 | }
50 |
51 | public struct Near {
52 | public let config: NearConfigProtocol
53 | public let connection: Connection
54 | private let accountCreator: AccountCreator?
55 | }
56 |
57 | public enum NearError: Error {
58 | case noAccountCreator(String)
59 | case noAccountId
60 | }
61 |
62 | extension Near {
63 |
64 | public init(config: NearConfigProtocol) {
65 | let connection = Connection.fromConfig(config: config)
66 | var accountCreator: AccountCreator?
67 | if let masterAccount = config.masterAccount {
68 | // TODO: figure out better way of specifiying initial balance.
69 | let initialBalance = config.initialBalance ?? UInt128(1000000000000)
70 | let masterAccount = Account(connection: connection, accountId: masterAccount)
71 | accountCreator = LocalAccountCreator(masterAccount: masterAccount, initialBalance: initialBalance)
72 | } else if let url = config.helperUrl {
73 | accountCreator = UrlAccountCreator(connection: connection, helperUrl: url)
74 | }
75 | self.init(config: config, connection: connection, accountCreator: accountCreator)
76 | }
77 |
78 | // expose global functions as static functions in the Near namespace
79 | public static func keyPairFromString(_ string: String) throws -> KeyPair {
80 | return try nearclientios.keyPairFromString(encodedKey: string)
81 | }
82 |
83 | // expose global functions as static functions in the Near namespace
84 | public static func keyPairFromRandom(curve: KeyType) throws -> KeyPair {
85 | return try nearclientios.keyPairFromRandom(curve: curve)
86 | }
87 |
88 | }
89 |
90 | public extension Near {
91 | func account(accountId: String) async throws -> Account {
92 | let account = Account(connection: connection, accountId: accountId)
93 | try await account.ready()
94 | return account
95 | }
96 |
97 | private func createAccount(accountId: String, publicKey: PublicKey) async throws -> Account {
98 | guard let accountCreator = accountCreator else {
99 | throw NearError.noAccountCreator("Must specify account creator, either via masterAccount or helperUrl configuration settings.")
100 | }
101 | try await accountCreator.createAccount(newAccountId: accountId, publicKey: publicKey)
102 | return Account(connection: connection, accountId: accountId)
103 | }
104 |
105 | /**
106 | - Parameters:
107 | - contractId: contractId
108 | - options: options
109 | - Returns: promise with contract.
110 | */
111 | @available(*, deprecated, renamed: "Contract.init", message: "Backwards compatibility method. Use contract constructor instead")
112 | private func loadContract(contractId: String, options: ContractOptionsProtocol) async throws -> Contract {
113 | print("near.loadContract is deprecated. Use `Contract.init` instead.")
114 | guard let accountId = options.sender else { throw NearError.noAccountId }
115 | let account = Account(connection: connection, accountId: accountId)
116 | let contract = Contract(account: account, contractId: contractId, viewMethods: options.viewMethods,
117 | changeMethods: options.changeMethods, sender: accountId)
118 | return contract
119 | }
120 |
121 | /**
122 | - Parameters:
123 | - amount: amount
124 | - originator: originator
125 | - receiver: receiver
126 | */
127 | @available(*, deprecated, renamed: "yourAccount.sendMoney", message: "Backwards compatibility method. Use `yourAccount.sendMoney` instead")
128 | func sendTokens(amount: UInt128, originator: String, receiver: String) async throws -> String {
129 | print("near.sendTokens is deprecated. Use `yourAccount.sendMoney` instead.")
130 | let account = Account(connection: connection, accountId: originator)
131 | let result = try await account.sendMoney(receiverId: receiver, amount: amount)
132 | return result.transactionOutcome.id
133 | }
134 | }
135 |
136 | func connect(config: NearConfigProtocol) async throws -> Near {
137 | // Try to find extra key in `KeyPath` if provided.let
138 | var configuration = config
139 | if let keyPath = configuration.keyPath, let keyStore = configuration.keyStore {
140 | do {
141 | let (accountId, keyPair) = try await UnencryptedFileSystemKeyStore.readKeyFile(path: keyPath)
142 | // TODO: Only load key if network ID matches
143 | let keyPathStore = InMemoryKeyStore()
144 | try await keyPathStore.setKey(networkId: configuration.networkId, accountId: accountId, keyPair: keyPair)
145 | if configuration.masterAccount == nil {
146 | configuration.masterAccount = accountId
147 | }
148 | configuration.keyStore = MergeKeyStore(keyStores: [keyStore, keyPathStore])
149 | print("Loaded master account \(accountId) key from \(keyPath) with public key = \(keyPair.getPublicKey())")
150 | } catch let error {
151 | print("Failed to load master account key from \(keyPath): \(error)")
152 | }
153 | }
154 | let near = Near(config: configuration)
155 | return near
156 | }
157 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Providers/JSONRPCProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONRPCProvider.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AnyCodable
11 |
12 | public enum TypedError: Error {
13 | case error(type: String = "UntypedError", message: String?)
14 | }
15 |
16 | public enum Finality: String, Codable {
17 | case final
18 | case optimistic
19 | }
20 |
21 | public enum SyncCheckpoint: String, Codable {
22 | case genesis = "genesis"
23 | case earliestAvailable = "earliest_available"
24 | }
25 |
26 | public final class JSONRPCProvider {
27 | /// Keep ids unique across all connections
28 | private var _nextId = 123
29 |
30 | private let connection: ConnectionInfo
31 |
32 | init(url: URL, network: Network? = nil) {
33 | self.connection = ConnectionInfo(url: url)
34 | }
35 | }
36 |
37 | extension JSONRPCProvider {
38 | private func getId() -> Int {
39 | _nextId += 1
40 | return _nextId
41 | }
42 |
43 | private func sendJsonRpc(method: String, params: [Any?]) async throws -> T {
44 | let request: [String: Any] = ["method": method,
45 | "params": params,
46 | "id": getId(),
47 | "jsonrpc": "2.0"]
48 | let json = try await fetchJson(connection: connection, json: request)
49 | return try await processJsonRpc(request: request, json: json)
50 | }
51 |
52 | private func sendJsonRpc(method: String, paramsDict: [String: Any]) async throws -> T {
53 | let request: [String: Any] = ["method": method,
54 | "params": paramsDict,
55 | "id": getId(),
56 | "jsonrpc": "2.0"]
57 | let json = try await fetchJson(connection: connection, json: request)
58 | return try await processJsonRpc(request: request, json: json)
59 | }
60 |
61 | func processJsonRpc(request: [String: Any], json: Any) async throws -> T {
62 | let data = try JSONSerialization.data(withJSONObject: json, options: [])
63 | do {
64 | let decoder = JSONDecoder()
65 | decoder.keyDecodingStrategy = .convertFromSnakeCase
66 | let decoded = try decoder.decode(T.self, from: data)
67 | return decoded
68 | } catch let originalError {
69 | // see if this error can be parsed into a standard RPCError, and return that if possible
70 | do {
71 | let decoder = JSONDecoder()
72 | decoder.keyDecodingStrategy = .convertFromSnakeCase
73 | let decodedError = try decoder.decode(RPCError.self, from: data)
74 | throw decodedError
75 | } catch {
76 | print(originalError)
77 | print(String(decoding: try! JSONSerialization.data(withJSONObject: request, options: []), as: UTF8.self))
78 | print(String(decoding: data, as: UTF8.self))
79 | print(T.self)
80 | throw originalError
81 | }
82 | }
83 | }
84 | }
85 |
86 | extension JSONRPCProvider: Provider {
87 | public func getNetwork() async -> Network {
88 | let result: Network = Network(name: "test", chainId: "test")
89 | return result
90 | }
91 |
92 | public func status() async throws -> NodeStatusResult {
93 | return try await sendJsonRpc(method: "status", params: [])
94 | }
95 |
96 | public func networkInfo() async throws -> NetworkInfoResult {
97 | return try await sendJsonRpc(method: "network_info", params: [])
98 | }
99 |
100 | public func sendTransaction(signedTransaction: SignedTransaction) async throws -> FinalExecutionOutcome {
101 | let data = try BorshEncoder().encode(signedTransaction)
102 | let params = [data.base64EncodedString()]
103 | // debugPrint("params \(params)")
104 | return try await sendJsonRpc(method: "broadcast_tx_commit", params: params)
105 | }
106 |
107 | public func sendTransactionAsync(signedTransaction: SignedTransaction) async throws -> SimpleRPCResult {
108 | let data = try BorshEncoder().encode(signedTransaction)
109 | let params = [data.base64EncodedString()]
110 | // debugPrint("params \(params)")
111 | return try await sendJsonRpc(method: "broadcast_tx_async", params: params)
112 | }
113 |
114 | public func txStatus(txHash: [UInt8], accountId: String) async throws -> FinalExecutionOutcome {
115 | let params = [txHash.baseEncoded, accountId]
116 | return try await sendJsonRpc(method: "tx", params: params)
117 | }
118 |
119 | public func experimentalTxStatusWithReceipts(txHash: [UInt8], accountId: String) async throws -> FinalExecutionOutcome {
120 | let params = [txHash.baseEncoded, accountId]
121 | return try await sendJsonRpc(method: "EXPERIMENTAL_tx_status", params: params)
122 | }
123 |
124 | public func query(params: [String: Any]) async throws -> T {
125 | return try await sendJsonRpc(method: "query", paramsDict: params)
126 | }
127 |
128 | public func block(blockQuery: BlockReference) async throws -> BlockResult {
129 | let params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
130 | return try await sendJsonRpc(method: "block", paramsDict: params)
131 | }
132 |
133 | public func blockChanges(blockQuery: BlockReference) async throws -> BlockChangeResult {
134 | let params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
135 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes_in_block", paramsDict: params)
136 | }
137 |
138 | public func chunk(chunkId: ChunkId) async throws -> ChunkResult {
139 | var params: [String: Any] = [:]
140 | switch chunkId {
141 | case .chunkHash(let chunkHash):
142 | params["chunk_id"] = chunkHash
143 | case .blockShardId(let blockShardId):
144 | params["block_id"] = typeEraseBlockId(blockId: blockShardId.blockId)
145 | params["shard_id"] = blockShardId.shardId
146 | }
147 | return try await sendJsonRpc(method: "chunk", paramsDict: params)
148 | }
149 |
150 | public func gasPrice(blockId: NullableBlockId) async throws -> GasPrice {
151 | let params: Any? = typeEraseNullableBlockId(blockId: blockId)
152 | return try await sendJsonRpc(method: "gas_price", params: [params])
153 | }
154 |
155 | public func experimentalGenesisConfig() async throws -> ExperimentalNearProtocolConfig {
156 | return try await sendJsonRpc(method: "EXPERIMENTAL_genesis_config", params: [])
157 | }
158 |
159 | public func experimentalProtocolConfig(blockQuery: BlockReference) async throws -> ExperimentalNearProtocolConfig {
160 | let params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
161 | return try await sendJsonRpc(method: "EXPERIMENTAL_protocol_config", paramsDict: params)
162 | }
163 |
164 | public func validators(blockId: NullableBlockId) async throws -> EpochValidatorInfo {
165 | let params: Any? = typeEraseNullableBlockId(blockId: blockId)
166 | return try await sendJsonRpc(method: "validators", params: [params])
167 | }
168 |
169 | public func accessKeyChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult {
170 | var params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
171 | params["changes_type"] = "all_access_key_changes"
172 | params["account_ids"] = accountIdArray
173 |
174 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes", paramsDict: params)
175 | }
176 |
177 | public func singleAccessKeyChanges(accessKeyArray: [AccessKeyWithPublicKey], blockQuery: BlockReference) async throws -> ChangeResult {
178 | var params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
179 | params["changes_type"] = "single_access_key_changes"
180 | params["keys"] = accessKeyArray.map { value in
181 | return [
182 | "account_id": value.accountId,
183 | "public_key": value.publicKey
184 | ]
185 | }
186 |
187 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes", paramsDict: params)
188 | }
189 | public func accountChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult {
190 | var params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
191 | params["changes_type"] = "account_changes"
192 | params["account_ids"] = accountIdArray
193 |
194 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes", paramsDict: params)
195 | }
196 |
197 | public func contractStateChanges(accountIdArray: [String], blockQuery: BlockReference, keyPrefix: String?) async throws -> ChangeResult {
198 | var params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
199 | params["changes_type"] = "data_changes"
200 | params["account_ids"] = accountIdArray
201 | params["key_prefix_base64"] = keyPrefix ?? ""
202 |
203 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes", paramsDict: params)
204 | }
205 |
206 | public func contractCodeChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult {
207 | var params: [String: Any] = typeEraseBlockReferenceParams(blockQuery: blockQuery)
208 | params["changes_type"] = "contract_code_changes"
209 | params["account_ids"] = accountIdArray
210 |
211 | return try await sendJsonRpc(method: "EXPERIMENTAL_changes", paramsDict: params)
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Providers/Provider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Provider.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AnyCodable
11 |
12 | public struct SyncInfo: Codable {
13 | let latestBlockHash: String
14 | let latestBlockHeight: Int
15 | let latestBlockTime: String
16 | let latestStateRoot: String
17 | let syncing: Bool
18 | }
19 |
20 | public struct Validator: Codable {}
21 |
22 | public struct NodeStatusResult: Codable {
23 | let chainId: String
24 | let rpcAddr: String
25 | let syncInfo: SyncInfo
26 | let validators: [Validator]
27 | }
28 |
29 | public struct NetworkInfoResult: Decodable {
30 | let peerMaxCount: Int
31 | }
32 |
33 | public struct SimpleRPCResult: Decodable {
34 | public let id: String
35 | public let jsonrpc: String
36 | private let result: String
37 |
38 | public var hash: String {
39 | return result
40 | }
41 |
42 | }
43 |
44 | public typealias BlockHash = String
45 | public typealias BlockHeight = Int
46 | public enum BlockId {
47 | case blockHash(String)
48 | case blockHeight(Int)
49 | }
50 | public enum NullableBlockId {
51 | case blockHash(String)
52 | case blockHeight(Int)
53 | case null
54 | }
55 |
56 | public func typeEraseNullableBlockId(blockId: NullableBlockId) -> Any? {
57 | switch blockId {
58 | case .blockHeight(let height):
59 | return height
60 | case .blockHash(let hash):
61 | return hash
62 | case .null:
63 | return nil
64 | }
65 | }
66 |
67 | public enum BlockReference {
68 | case blockId(BlockId)
69 | case finality(Finality)
70 | }
71 |
72 | public func typeEraseBlockId(blockId: BlockId) -> Any {
73 | switch blockId {
74 | case .blockHeight(let height):
75 | return height
76 | case .blockHash(let hash):
77 | return hash
78 | }
79 | }
80 |
81 | public func typeEraseBlockReferenceParams(blockQuery: BlockReference) -> [String: Any] {
82 | var params: [String: Any] = [:]
83 | switch blockQuery {
84 | case .blockId(let blockId):
85 | params["block_id"] = typeEraseBlockId(blockId: blockId)
86 | case .finality(let finality):
87 | params["finality"] = finality.rawValue
88 | }
89 |
90 | return params
91 | }
92 |
93 | public struct AccessKeyWithPublicKey: Codable {
94 | let accountId: String
95 | let publicKey: String
96 | }
97 |
98 | public enum ExecutionStatusBasic: String, Decodable {
99 | case unknown = "Unknown"
100 | case pending = "Pending"
101 | case failure = "Failure"
102 | }
103 |
104 | public enum ExecutionStatus: Decodable, Equatable {
105 | case successValue(String)
106 | case basic(ExecutionStatusBasic)
107 | case successReceiptId(String)
108 | case failure(ExecutionError)
109 |
110 | private enum CodingKeys: String, CodingKey {
111 | case successValue = "SuccessValue"
112 | case failure = "Failure"
113 | case successReceiptId = "SuccessReceiptId"
114 | }
115 |
116 | public init(from decoder: Decoder) throws {
117 | if let container = try? decoder.singleValueContainer(), let status = try? container.decode(ExecutionStatusBasic.self) {
118 | self = .basic(status)
119 | return
120 | }
121 | let container = try? decoder.container(keyedBy: CodingKeys.self)
122 | if let value = try? container?.decode(String.self, forKey: .successValue) {
123 | self = .successValue(value)
124 | return
125 | }
126 | if let value = try? container?.decode(String.self, forKey: .successReceiptId) {
127 | self = .successReceiptId(value)
128 | return
129 | }
130 | if let value = try? container?.decode(ExecutionError.self, forKey: .failure) {
131 | self = .failure(value)
132 | return
133 | }
134 | throw NEARDecodingError.notExpected
135 | }
136 | }
137 |
138 | public enum FinalExecutionStatusBasic: String, Codable {
139 | case notStarted = "NotStarted"
140 | case started = "Started"
141 | case failure = "Failure"
142 | }
143 |
144 | public struct ExecutionError: Codable, Equatable{
145 | let errorMessage: String?
146 | let errorType: String?
147 |
148 | init(errorMessage: String? = nil, errorType: String? = nil) {
149 | self.errorMessage = errorMessage
150 | self.errorType = errorType
151 | }
152 | }
153 |
154 | public enum FinalExecutionStatus: Decodable, Equatable {
155 | case successValue(String)
156 | case basic(ExecutionStatusBasic)
157 | case failure(ExecutionError)
158 |
159 | private enum CodingKeys: String, CodingKey {
160 | case successValue = "SuccessValue"
161 | case failure = "Failure"
162 | }
163 |
164 | public init(from decoder: Decoder) throws {
165 | if let container = try? decoder.singleValueContainer(), let status = try? container.decode(ExecutionStatusBasic.self) {
166 | self = .basic(status)
167 | return
168 | }
169 | let container = try? decoder.container(keyedBy: CodingKeys.self)
170 | if let value = try? container?.decode(String.self, forKey: .successValue) {
171 | self = .successValue(value)
172 | return
173 | }
174 | if let value = try? container?.decode(ExecutionError.self, forKey: .failure) {
175 | self = .failure(value)
176 | return
177 | }
178 | throw NEARDecodingError.notExpected
179 | }
180 | }
181 |
182 | public struct ExecutionOutcomeWithId: Decodable, Equatable {
183 | public let id: String
184 | public let outcome: ExecutionOutcome
185 | }
186 |
187 | public struct ExecutionOutcome: Decodable, Equatable {
188 | public let status: ExecutionStatus
189 | public let logs: [String]
190 | public let receiptIds: [String]
191 | public let gasBurnt: Int
192 | }
193 |
194 | public struct FinalExecutionOutcome: Decodable, Equatable {
195 | public let transaction: Transaction
196 | public let status: FinalExecutionStatus
197 | public let transactionOutcome: ExecutionOutcomeWithId
198 | public let receiptsOutcome: [ExecutionOutcomeWithId]
199 | public let receipts: AnyDecodable?
200 | }
201 |
202 | public struct TotalWeight: Codable {
203 | let num: Int
204 | }
205 |
206 | public struct BlockHeader: Codable {
207 | let height: Int
208 | let epochId: String
209 | let nextEpochId: String
210 | let hash: String
211 | let prevHash: String
212 | let prevStateRoot: String
213 | let chunkReceiptsRoot: String
214 | let chunkHeadersRoot: String
215 | let chunkTxRoot: String
216 | let outcomeRoot: String
217 | let chunksIncluded: Int
218 | let challengesRoot: String
219 | let timestamp: Int
220 | let timestampNanosec: String
221 | let randomValue: String
222 | let validatorProposals: [ValidatorProposal]
223 | let chunkMask: [Bool]
224 | let gasPrice: String
225 | let rentPaid: String
226 | let validatorReward: String
227 | let totalSupply: String
228 | //let challenges_result: [Any]
229 | let lastFinalBlock: String
230 | let lastDsFinalBlock: String
231 | let nextBpHash: String
232 | let blockMerkleRoot: String
233 | }
234 |
235 | public typealias ChunkHash = String
236 | public typealias ShardId = Int
237 | public struct BlockShardId {
238 | let blockId: BlockId
239 | let shardId: ShardId
240 | }
241 |
242 | public enum ChunkId {
243 | case chunkHash(ChunkHash)
244 | case blockShardId(BlockShardId)
245 | }
246 |
247 | public struct ValidatorProposal: Codable {}
248 |
249 | public struct ChunkHeader: Codable {
250 | let chunkHash: ChunkHash
251 | let prevBlockHash: String
252 | let outcomeRoot: String
253 | let prevStateRoot: String
254 | let encodedMerkleRoot: String
255 | let encodedLength: Int
256 | let heightCreated: Int
257 | let heightIncluded: Int
258 | let shardId: ShardId
259 | let gasUsed: Int
260 | let gasLimit: Int
261 | let rentPaid: String
262 | let validatorReward: String
263 | let balanceBurnt: String
264 | let outgoingReceiptsRoot: String
265 | let txRoot: String
266 | let validatorProposals: [ValidatorProposal]
267 | let signature: String
268 | }
269 |
270 | public struct Receipt: Codable {}
271 |
272 | public struct ChunkResult: Codable {
273 | let header: ChunkHeader
274 | let receipts: [Receipt]
275 | let transactions: [Transaction]
276 | }
277 |
278 | public struct Transaction: Codable, Equatable {
279 | public let hash: String
280 | public let publicKey: String
281 | public let signature: String
282 | }
283 |
284 | public struct BlockResult: Codable {
285 | let header: BlockHeader
286 | let transactions: [Transaction]?
287 | }
288 |
289 | public struct BlockChange: Codable {
290 | let type: String
291 | let accountId: String
292 | }
293 |
294 | public struct BlockChangeResult: Codable {
295 | let blockHash: String
296 | let changes: [BlockChange]
297 | }
298 |
299 | public struct ChangeResult: Decodable {
300 | let blockHash: String
301 | let changes: [AnyDecodable]
302 | }
303 |
304 | public struct ExperimentalNearProtocolConfig: Decodable {
305 | let chainId: String
306 | let genesisHeight: Int
307 | let runtimeConfig: ExperimentalNearProtocolRuntimeConfig?
308 | }
309 |
310 | public struct ExperimentalNearProtocolRuntimeConfig: Decodable {
311 | let storageAmountPerByte: String
312 | }
313 |
314 | public struct GasPrice: Codable {
315 | let gasPrice: String
316 | }
317 |
318 | public struct EpochValidatorInfo: Decodable {
319 | // Validators for the current epoch.
320 | let nextValidators: [NextEpochValidatorInfo]
321 | // Validators for the next epoch.
322 | let currentValidators: [CurrentEpochValidatorInfo]
323 | // Fishermen for the current epoch.
324 | let nextFishermen: [ValidatorStakeView]
325 | // Fishermen for the next epoch.
326 | let currentFishermen: [ValidatorStakeView]
327 | // Proposals in the current epoch.
328 | let currentProposals: [ValidatorStakeView]
329 | // Kickout in the previous epoch.
330 | let prevEpochKickout: [ValidatorStakeView]
331 | // Epoch start height.
332 | let epochStartHeight: Int
333 | }
334 |
335 | public struct CurrentEpochValidatorInfo: Decodable {
336 | let accountId: String
337 | let publicKey: String
338 | let isSlashed: Bool
339 | let stake: String
340 | let shards: [Int]
341 | let numProducedBlocks: Int
342 | let numExpectedBlocks: Int
343 | }
344 |
345 | public struct NextEpochValidatorInfo: Decodable {
346 | let accountId: String
347 | let publicKey: String
348 | let stake: String
349 | let shards: [Int]
350 | }
351 |
352 | public struct ValidatorStakeView: Decodable {
353 | let accountId: String
354 | let publicKey: String
355 | let stake: String
356 | }
357 |
358 | public enum ProviderType {
359 | case jsonRPC(URL)
360 | }
361 |
362 | public protocol Provider {
363 | func getNetwork() async throws -> Network
364 | func status() async throws -> NodeStatusResult
365 | func networkInfo() async throws -> NetworkInfoResult
366 | func sendTransaction(signedTransaction: SignedTransaction) async throws -> FinalExecutionOutcome
367 | func sendTransactionAsync(signedTransaction: SignedTransaction) async throws -> SimpleRPCResult
368 | func txStatus(txHash: [UInt8], accountId: String) async throws -> FinalExecutionOutcome
369 | func experimentalTxStatusWithReceipts(txHash: [UInt8], accountId: String) async throws -> FinalExecutionOutcome
370 | func query(params: [String: Any]) async throws -> T
371 | func block(blockQuery: BlockReference) async throws -> BlockResult
372 | func blockChanges(blockQuery: BlockReference) async throws -> BlockChangeResult
373 | func chunk(chunkId: ChunkId) async throws -> ChunkResult
374 | func gasPrice(blockId: NullableBlockId) async throws -> GasPrice
375 | func experimentalGenesisConfig() async throws -> ExperimentalNearProtocolConfig
376 | func experimentalProtocolConfig(blockQuery: BlockReference) async throws -> ExperimentalNearProtocolConfig
377 | func validators(blockId: NullableBlockId) async throws -> EpochValidatorInfo
378 | func accessKeyChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult
379 | func singleAccessKeyChanges(accessKeyArray: [AccessKeyWithPublicKey], blockQuery: BlockReference) async throws -> ChangeResult
380 | func accountChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult
381 | func contractStateChanges(accountIdArray: [String], blockQuery: BlockReference, keyPrefix: String?) async throws -> ChangeResult
382 | func contractCodeChanges(accountIdArray: [String], blockQuery: BlockReference) async throws -> ChangeResult
383 | }
384 |
385 | public func getTransactionLastResult(txResult: FinalExecutionOutcome) -> Any? {
386 | if case .successValue(let value) = txResult.status, let data = Data(base64Encoded: value) {
387 | do {
388 | return try JSONSerialization.jsonObject(with: data, options: [])
389 | } catch {
390 | return String(data: data,
391 | encoding: .utf8)?.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
392 | }
393 | }
394 | return nil
395 | }
396 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Signer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Signer.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum SignerType {
12 | case inMemory(KeyStore)
13 | }
14 |
15 | /**
16 | General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc.
17 | */
18 | public protocol Signer {
19 |
20 | /**
21 | Creates new key and returns public key.
22 | - Parameters:
23 | - accountId: accountId to retrieve from.
24 | - networkId: network for this accountId.
25 | */
26 | func createKey(accountId: String, networkId: String, curve: KeyType) async throws -> PublicKey
27 |
28 | /**
29 | - Parameters:
30 | - accountId: accountId to retrieve from.
31 | - networkId: network for this accountId.
32 | - Returns: public key for given account / network.
33 | */
34 | func getPublicKey(accountId: String, networkId: String) async throws -> PublicKey?
35 |
36 | /**
37 | Signs given hash.
38 | - Parameters:
39 | - hash: hash to sign.
40 | - accountId: accountId to use for signing.
41 | - networkId: network for this accontId.
42 | */
43 | func signHash(hash: [UInt8], accountId: String, networkId: String) async throws -> SignatureProtocol
44 |
45 | /**
46 | Signs given message, by first hashing with sha256.
47 | - Parameters:
48 | - message: message to sign.
49 | - accountId: accountId to use for signing.
50 | - networkId: network for this accontId.
51 | */
52 | func signMessage(message: [UInt8], accountId: String, networkId: String) async throws -> SignatureProtocol
53 | }
54 |
55 | extension Signer {
56 | public func signMessage(message: [UInt8], accountId: String, networkId: String) async throws -> SignatureProtocol {
57 | return try await signHash(hash: message.digest, accountId: accountId, networkId: networkId)
58 | }
59 | }
60 |
61 | /**
62 | * Signs using in memory key store.
63 | */
64 | public struct InMemorySigner {
65 | let keyStore: KeyStore
66 |
67 | public init(keyStore: KeyStore) {
68 | self.keyStore = keyStore
69 | }
70 | }
71 |
72 | public enum InMemorySignerError: Error {
73 | case notFound(String)
74 | }
75 |
76 | extension InMemorySigner: Signer {
77 | public func createKey(accountId: String, networkId: String, curve: KeyType = .ED25519) async throws -> PublicKey {
78 | let keyPair = try keyPairFromRandom(curve: curve)
79 | try await keyStore.setKey(networkId: networkId, accountId: accountId, keyPair: keyPair)
80 | return keyPair.getPublicKey()
81 | }
82 |
83 | public func getPublicKey(accountId: String, networkId: String) async throws -> PublicKey? {
84 | let keyPair = try await keyStore.getKey(networkId: networkId, accountId: accountId)
85 | return keyPair?.getPublicKey()
86 | }
87 |
88 | public func signHash(hash: [UInt8], accountId: String, networkId: String) async throws -> SignatureProtocol {
89 | guard let keyPair = try await keyStore.getKey(networkId: networkId, accountId: accountId) else {
90 | throw InMemorySignerError.notFound("Key for \(accountId) not found in \(networkId)")
91 | }
92 | let signature = try keyPair.sign(message: hash)
93 | return signature
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/AppInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppInfo.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 10.12.2019.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension UIApplication {
12 | static var urlSchemes: [String]? {
13 | return (Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]])?.first?["CFBundleURLSchemes"] as? [String]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Borsh/BinaryReader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BinaryReader.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 22.11.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct BinaryReader {
11 | private var cursor: Int
12 | private let bytes: [UInt8]
13 |
14 | init(bytes: [UInt8]) {
15 | self.cursor = 0
16 | self.bytes = bytes
17 | }
18 | }
19 |
20 | extension BinaryReader {
21 | mutating func read(count: UInt32) -> [UInt8] {
22 | let newPosition = cursor + Int(count)
23 | let result = bytes[cursor..(_ type: T.Type, from data: Data) throws -> T where T : BorshDeserializable {
13 | var reader = BinaryReader(bytes: [UInt8](data))
14 | return try T.init(from: &reader)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Borsh/BorshDeserialize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BorshDeserialize.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 21.11.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol BorshDeserializable {
11 | init(from reader: inout BinaryReader) throws
12 | }
13 |
14 | enum DeserializationError: Error {
15 | case noData
16 | }
17 |
18 | public extension FixedWidthInteger {
19 | init(from reader: inout BinaryReader) throws {
20 | var value: Self = .zero
21 | let bytes = reader.read(count: UInt32(MemoryLayout.size))
22 | let size = withUnsafeMutableBytes(of: &value, { bytes.copyBytes(to: $0) } )
23 | assert(size == MemoryLayout.size)
24 | self = Self(littleEndian: value)
25 | }
26 | }
27 |
28 | extension UInt8: BorshDeserializable {}
29 | extension UInt16: BorshDeserializable {}
30 | extension UInt32: BorshDeserializable {}
31 | extension UInt64: BorshDeserializable {}
32 | extension UInt128: BorshDeserializable {}
33 | extension Int8: BorshDeserializable {}
34 | extension Int16: BorshDeserializable {}
35 | extension Int32: BorshDeserializable {}
36 | extension Int64: BorshDeserializable {}
37 | extension Int128: BorshDeserializable {}
38 |
39 | extension Float32: BorshDeserializable {
40 | public init(from reader: inout BinaryReader) throws {
41 | var value: Self = .zero
42 | let bytes = reader.read(count: UInt32(MemoryLayout.size))
43 | let size = withUnsafeMutableBytes(of: &value, { bytes.copyBytes(to: $0) } )
44 | assert(size == MemoryLayout.size)
45 | assert(!value.isNaN, "For portability reasons we do not allow to deserialize NaNs.")
46 | self = value
47 | }
48 | }
49 |
50 | extension Float64: BorshDeserializable {
51 | public init(from reader: inout BinaryReader) throws {
52 | var value: Self = .zero
53 | let bytes = reader.read(count: UInt32(MemoryLayout.size))
54 | let size = withUnsafeMutableBytes(of: &value, { bytes.copyBytes(to: $0) } )
55 | assert(size == MemoryLayout.size)
56 | assert(!value.isNaN, "For portability reasons we do not allow to deserialize NaNs.")
57 | self = value
58 | }
59 | }
60 |
61 | extension Bool: BorshDeserializable {
62 | public init(from reader: inout BinaryReader) throws {
63 | var value: Self = false
64 | let bytes = reader.read(count: UInt32(MemoryLayout.size))
65 | let size = withUnsafeMutableBytes(of: &value, { bytes.copyBytes(to: $0) } )
66 | assert(size == MemoryLayout.size)
67 | self = value
68 | }
69 | }
70 |
71 | extension Optional where Wrapped: BorshDeserializable {
72 | init(from reader: inout BinaryReader) throws {
73 | let isSomeValue: UInt8 = try .init(from: &reader)
74 | switch isSomeValue {
75 | case 1: self = try Wrapped.init(from: &reader)
76 | default: self = .none
77 | }
78 | }
79 | }
80 |
81 | extension String: BorshDeserializable {
82 | public init(from reader: inout BinaryReader) throws {
83 | let count: UInt32 = try .init(from: &reader)
84 | let bytes = reader.read(count: count)
85 | guard let value = String(bytes: bytes, encoding: .utf8) else {throw DeserializationError.noData}
86 | self = value
87 | }
88 | }
89 |
90 | extension Array: BorshDeserializable where Element: BorshDeserializable {
91 | public init(from reader: inout BinaryReader) throws {
92 | let count: UInt32 = try .init(from: &reader)
93 | self = try Array(0...init(from: &reader))
100 | }
101 | }
102 |
103 | extension Dictionary: BorshDeserializable where Key: BorshDeserializable & Equatable, Value: BorshDeserializable {
104 | public init(from reader: inout BinaryReader) throws {
105 | let count: UInt32 = try .init(from: &reader)
106 | let keyValuePairs = try Array(0..(_ value: T) throws -> Data where T : BorshSerializable {
13 | var writer = Data()
14 | try value.serialize(to: &writer)
15 | return writer
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Borsh/BorshSerialize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BorshSerialize.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 21.11.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol BorshSerializable {
11 | func serialize(to writer: inout Data) throws
12 | }
13 |
14 | extension UInt8: BorshSerializable {}
15 | extension UInt16: BorshSerializable {}
16 | extension UInt32: BorshSerializable {}
17 | extension UInt64: BorshSerializable {}
18 | extension UInt128: BorshSerializable {}
19 | extension Int8: BorshSerializable {}
20 | extension Int16: BorshSerializable {}
21 | extension Int32: BorshSerializable {}
22 | extension Int64: BorshSerializable {}
23 | extension Int128: BorshSerializable {}
24 |
25 | public extension FixedWidthInteger {
26 | func serialize(to writer: inout Data) throws {
27 | writer.append(contentsOf: withUnsafeBytes(of: self.littleEndian) { Array($0) })
28 | }
29 | }
30 |
31 | extension Float32: BorshSerializable {
32 | public func serialize(to writer: inout Data) throws {
33 | assert(!self.isNaN, "For portability reasons we do not allow to serialize NaNs.")
34 | var start = bitPattern.littleEndian
35 | writer.append(Data(buffer: UnsafeBufferPointer(start: &start, count: 1)))
36 | }
37 | }
38 |
39 | extension Float64: BorshSerializable {
40 | public func serialize(to writer: inout Data) throws {
41 | assert(!self.isNaN, "For portability reasons we do not allow to serialize NaNs.")
42 | var start = bitPattern.littleEndian
43 | writer.append(Data(buffer: UnsafeBufferPointer(start: &start, count: 1)))
44 | }
45 | }
46 |
47 | extension Bool: BorshSerializable {
48 | public func serialize(to writer: inout Data) throws {
49 | let intRepresentation: UInt8 = self ? 1 : 0
50 | try intRepresentation.serialize(to: &writer)
51 | }
52 | }
53 |
54 | extension Optional where Wrapped: BorshSerializable {
55 | func serialize(to writer: inout Data) throws {
56 | switch self {
57 | case .some(let value):
58 | try UInt8(1).serialize(to: &writer)
59 | try value.serialize(to: &writer)
60 | case .none:
61 | try UInt8(0).serialize(to: &writer)
62 | }
63 | }
64 | }
65 |
66 | extension String: BorshSerializable {
67 | public func serialize(to writer: inout Data) throws {
68 | let data = Data(utf8)
69 | try UInt32(data.count).serialize(to: &writer)
70 | writer.append(data)
71 | }
72 | }
73 |
74 | extension Array: BorshSerializable where Element: BorshSerializable {
75 | public func serialize(to writer: inout Data) throws {
76 | try UInt32(count).serialize(to: &writer)
77 | try forEach { try $0.serialize(to: &writer) }
78 | }
79 | }
80 |
81 | extension Set: BorshSerializable where Element: BorshSerializable & Comparable {
82 | public func serialize(to writer: inout Data) throws {
83 | try sorted().serialize(to: &writer)
84 | }
85 | }
86 |
87 | extension Dictionary: BorshSerializable where Key: BorshSerializable & Comparable, Value: BorshSerializable {
88 | public func serialize(to writer: inout Data) throws {
89 | let sortedByKeys = sorted(by: {$0.key < $1.key})
90 | try UInt32(sortedByKeys.count).serialize(to: &writer)
91 | try sortedByKeys.forEach { key, value in
92 | try key.serialize(to: &writer)
93 | try value.serialize(to: &writer)
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/FileManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManager.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 08.11.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension FileManager {
11 | func ensureDir(path: String) throws -> Void {
12 | try createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
13 | }
14 |
15 | var targetDirectory: URL {
16 | let paths = urls(for: .documentDirectory, in: .userDomainMask)
17 | return paths[0]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/FixedLengthByteArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FixedLengthByteArray.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 25.11.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol FixedLengthByteArray {
11 | static var fixedLength: UInt32 {get}
12 | var bytes: [UInt8] {get}
13 | init(bytes: [UInt8]) throws
14 | }
15 |
16 | extension FixedLengthByteArray {
17 | public func serialize(to writer: inout Data) throws {
18 | writer.append(bytes, count: Int(Self.fixedLength))
19 | }
20 |
21 | public init(from reader: inout BinaryReader) throws {
22 | try self.init(bytes: reader.read(count: Self.fixedLength))
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Format.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Format.swift
3 | // nearclientios
4 | //
5 | // Created by Kevin McConnaughay on 3/17/22.
6 | //
7 |
8 | import Foundation
9 |
10 | // Exponent for calculating how many indivisible units are there in one NEAR.
11 | public let NEAR_NOMINATION_EXP = 24
12 |
13 | extension UInt128 {
14 |
15 | // Pre-calculate offsets used for rounding to different number of digits
16 | private static let roundingOffsets: [UInt128] = {
17 | var offsets: [UInt128] = []
18 | let multiplier = UInt128(10)
19 | var offset = UInt128(5)
20 | for _ in 0.. (String, String) {
29 | var balance = self
30 | if fracDigits != exponent {
31 | // Adjust balance for rounding at given number of digits
32 | let roundingExp = exponent - fracDigits - 1
33 | if roundingExp > 0 {
34 | balance += UInt128.roundingOffsets[roundingExp]
35 | }
36 | }
37 |
38 | let balanceString = balance.toString()
39 | let digitOffset = balanceString.count - exponent
40 |
41 | let wholeStr = String(digitOffset > 0 ? balanceString.prefix(digitOffset) : "0")
42 | var fractionStr = String(balanceString.suffix(digitOffset > 0 ? exponent : balanceString.count))
43 |
44 | fractionStr = String(String(fractionStr.reversed()).padding(toLength: exponent, withPad: "0", startingAt: 0).reversed())
45 | fractionStr = String(fractionStr.prefix(fracDigits))
46 | return (wholeStr, fractionStr)
47 | }
48 |
49 | /// Convert account balance value from internal indivisible units to NEAR.
50 | /// - Parameter fracDigits: number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits.
51 | /// - Returns: Value in Ⓝ
52 | public func toNearAmount(fracDigits: Int = NEAR_NOMINATION_EXP, withExponent exponent: Int = NEAR_NOMINATION_EXP) -> String {
53 | let (wholeStr, fractionStr) = nearComponents(fracDigits: fracDigits, exponent: exponent)
54 | return trimTrailingZeroes(value: "\(formatWithCommas(value: wholeStr)).\(fractionStr)")
55 | }
56 |
57 | /// Convert account balance value from internal indivisible units to NEAR
58 | /// - Parameter fracDigits: number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits.
59 | /// - Returns: Value as double in Ⓝ
60 | public func toNearDouble(fracDigits: Int = NEAR_NOMINATION_EXP, withExponent exponent: Int = NEAR_NOMINATION_EXP) -> Double {
61 | let (wholeStr, fractionStr) = nearComponents(fracDigits: fracDigits, exponent: exponent)
62 | return Double("\(wholeStr).\(fractionStr)")!
63 | }
64 |
65 | }
66 |
67 | struct ConversionError: Error {
68 | let message = "Could not convert string to yoctoNEAR."
69 | }
70 |
71 | extension String {
72 |
73 | /// Convert human readable NEAR amount to internal indivisible units.
74 | /// Custom exponent here could be used for Fungible Tokens where the number of decimals differs from NEAR.
75 | /// - Returns: The parsed yoctoⓃ amount
76 | public func toYoctoNearString(withExponent exponent: Int = NEAR_NOMINATION_EXP) throws -> String {
77 | var parsed = self
78 | // Edge cases not covered by our numberyPattern regex.
79 | if (parsed == "" || parsed == ".") {
80 | throw ConversionError()
81 | }
82 | parsed = parsed.replacingOccurrences(of: ",", with: "").trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
83 | let numberyPattern = "^\\d*\\.?\\d*$"
84 | let numberyRegex = try! NSRegularExpression(pattern: numberyPattern)
85 | if numberyRegex.matches(in: parsed, options: [], range: NSRange(location: 0, length: parsed.utf16.count)).count != 1 {
86 | throw ConversionError()
87 | }
88 | parsed = parsed.prefix(1) == "." ? "0\(parsed)" : parsed
89 | let split = parsed.split(separator: ".")
90 | let wholePart = split[0]
91 | let fractionPart = split.indices.contains(1) ? split[1] : ""
92 | if split.count > 2 || fractionPart.count > exponent {
93 | throw ConversionError()
94 | }
95 |
96 | return trimLeadingZeroes(value: "\(wholePart)\(fractionPart.padding(toLength: exponent, withPad: "0", startingAt: 0))")
97 | }
98 |
99 | /// Convert human readable NEAR amount to internal indivisible units.
100 | /// Custom exponent here could be used for Fungible Tokens where the number of decimals differs from NEAR.
101 | /// - Returns: The parsed yoctoⓃ amount
102 | public func toYoctoNear(withExponent exponent: Int = NEAR_NOMINATION_EXP) throws -> UInt128 {
103 | return UInt128(stringLiteral: try self.toYoctoNearString(withExponent: exponent))
104 | }
105 |
106 | /// Convert account balance value from internal indivisible units to NEAR.
107 | /// - Parameter fracDigits: number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits.
108 | /// - Returns: Value in Ⓝ
109 | public func toNearAmount(fracDigits: Int = NEAR_NOMINATION_EXP) -> String {
110 | return UInt128(stringLiteral: self).toNearAmount(fracDigits: fracDigits)
111 | }
112 |
113 | }
114 |
115 | /// Returns a human-readable value with commas
116 | /// - Parameter value: A value that may not contain commas
117 | /// - Returns: A value with commas
118 | private func formatWithCommas(value: String) -> String {
119 | var formatted = value
120 | let pattern = "(-?\\d+)(\\d{3})"
121 | let regex = try! NSRegularExpression(pattern: pattern)
122 | while regex.matches(in: formatted, options: [], range: NSRange(location: 0, length: formatted.utf16.count)).count > 0 {
123 | formatted = regex.stringByReplacingMatches(in: formatted, options: [], range: NSRange(0.. String {
133 | var formatted = value
134 | let pattern = "^0+"
135 | let regex = try! NSRegularExpression(pattern: pattern)
136 | formatted = regex.stringByReplacingMatches(in: formatted, options: [], range: NSRange(0.. String {
149 | var formatted = value
150 | let pattern = "\\.?0*$"
151 | let regex = try! NSRegularExpression(pattern: pattern)
152 | formatted = regex.stringByReplacingMatches(in: formatted, options: [], range: NSRange(0..: Hashable, Codable {
7 | public typealias IntegerLiteralType = UInt64
8 | public typealias Magnitude = UInt2X
9 | public typealias Words = [Word.Words.Element]
10 | public typealias Stride = Int
11 | public var rawValue:Magnitude = 0
12 | public init(rawValue:Magnitude){ self.rawValue = rawValue }
13 | public init(_ source:Int2X) { self.rawValue = source.rawValue }
14 | public init() {}
15 | }
16 | // Swift bug?
17 | // auto-generated == fatalError()'s
18 | // UInt2X(hi:nonzero, lo:0) == 0
19 | extension Int2X {
20 | public static func == (_ lhs: Int2X, _ rhs: Int2X)->Bool {
21 | return lhs.rawValue == rhs.rawValue
22 | }
23 | }
24 | extension Int2X : ExpressibleByIntegerLiteral {
25 | public static var isSigned: Bool { return true }
26 | public static var bitWidth: Int { return Magnitude.bitWidth }
27 | public static var max:Int2X { return Int2X(rawValue:(Magnitude.max >> 1)) }
28 | public static var min:Int2X { return Int2X(rawValue:(Magnitude.max >> 1) &+ 1) }
29 | public init?(exactly source: T) where T : BinaryInteger {
30 | guard source.bitWidth <= Int2X.bitWidth || source.magnitude <= T(Int2X.max.rawValue) else {
31 | return nil
32 | }
33 | if !T.isSigned && source & (1 << (source.bitWidth - 1)) != 0 {
34 | return nil
35 | }
36 | self.init(source)
37 | }
38 | public init(_ source: T) where T : BinaryInteger {
39 | if !T.isSigned && Word.bitWidth * 2 <= source.bitWidth && source & (1 << (source.bitWidth - 1)) != 0 {
40 | fatalError("Not enough bits to represent a signed value")
41 | }
42 | self.rawValue = Magnitude(source.magnitude)
43 | if T.isSigned && source & (1 << (source.bitWidth - 1)) != 0 {
44 | self.rawValue = -self.rawValue
45 | }
46 | }
47 | public init?(exactly source: T) where T : BinaryFloatingPoint {
48 | guard let rv = Magnitude(exactly: source.sign == .minus ? -source : +source) else { return nil }
49 | self = Int2X(rawValue:rv)
50 | guard !self.isNegative else { return nil }
51 | if source.sign == .minus { self = -self }
52 | }
53 | public init(_ source: T) where T : BinaryFloatingPoint {
54 | guard let result = Int2X(exactly: source) else {
55 | fatalError("Not enough bits to represent a signed value")
56 | }
57 | self = result
58 | }
59 | // alway succeeds
60 | public init(truncatingIfNeeded source: T) {
61 | self.rawValue = Magnitude(truncatingIfNeeded:source.magnitude)
62 | if T.isSigned && source < 0 {
63 | self.rawValue = -self.rawValue
64 | }
65 | }
66 | // alway succeeds
67 | public init(clamping source: T) {
68 | self = Int2X(exactly: source) ?? Int2X.max
69 | }
70 | public init(integerLiteral value: IntegerLiteralType) {
71 | self.init(value)
72 | }
73 | }
74 | extension Int2X : Comparable {
75 | internal var isNegative:Bool {
76 | return Int2X.max.rawValue < self.rawValue
77 | }
78 | public var magnitude:Magnitude {
79 | return isNegative ? rawValue == Int2X.min.rawValue ? rawValue : -rawValue : +rawValue
80 | }
81 | public static func < (lhs: Int2X, rhs: Int2X) -> Bool {
82 | return Int2X.max.rawValue < lhs.rawValue &- rhs.rawValue
83 | }
84 | }
85 | extension Int2X : Numeric {
86 | // unary operators
87 | public static prefix func ~(_ value:Int2X)->Int2X {
88 | return Int2X(rawValue:~(value.rawValue))
89 | }
90 | public static prefix func +(_ value:Int2X)->Int2X {
91 | return value
92 | }
93 | public static prefix func -(_ value:Int2X)->Int2X {
94 | return Int2X(rawValue:-(value.rawValue))
95 | }
96 | // additions
97 | public func addingReportingOverflow(_ other: Int2X) -> (partialValue: Int2X, overflow: Bool) {
98 | let (pv, of) = self.rawValue.addingReportingOverflow(other.rawValue)
99 | // For any given int the only possible case that overflows is I.min - I.min
100 | // in which case overflow is true and partialValue is 0
101 | return (Int2X(rawValue:pv), of && pv == 0)
102 |
103 | }
104 | public static func &+(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
105 | return lhs.addingReportingOverflow(rhs).partialValue
106 | }
107 | public static func +(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
108 | let (pv, of) = lhs.addingReportingOverflow(rhs)
109 | precondition(!of, "\(lhs) + \(rhs): Addition overflow!")
110 | return pv
111 | }
112 | public static func += (lhs: inout Int2X, rhs: Int2X) {
113 | lhs = lhs + rhs
114 | }
115 | // subtraction
116 | public func subtractingReportingOverflow(_ other: Int2X) -> (partialValue: Int2X, overflow: Bool) {
117 | let (pv, of) = self.rawValue.subtractingReportingOverflow(other.rawValue)
118 | return (Int2X(rawValue:pv), of && pv == 0)
119 | }
120 | public static func &-(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
121 | return lhs.subtractingReportingOverflow(rhs).partialValue
122 | }
123 | public static func -(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
124 | let (pv, of) = lhs.subtractingReportingOverflow(rhs)
125 | precondition(!of, "\(lhs) - \(rhs): Subtruction overflow!")
126 | return pv
127 | }
128 | public static func -= (lhs: inout Int2X, rhs: Int2X) {
129 | lhs = lhs - rhs
130 | }
131 | // multiplication
132 | public func multipliedFullWidth(by other: Int2X) -> (high: Int2X, low: Magnitude) {
133 | let (h, l) = self.rawValue.multipliedFullWidth(by:other.rawValue)
134 | return (Int2X(h), l)
135 |
136 | }
137 | public func multipliedReportingOverflow(by other: Int2X) -> (partialValue: Int2X, overflow: Bool) {
138 | let hv = self.magnitude.multipliedFullWidth(by: other.magnitude)
139 | return (self.isNegative != other.isNegative ? -Int2X(rawValue:hv.low) : +Int2X(rawValue:hv.low), 0 < hv.high)
140 | }
141 | public static func &*(lhs: Int2X, rhs: Int2X) -> Int2X {
142 | return lhs.multipliedReportingOverflow(by: rhs).partialValue
143 | }
144 | public static func *(lhs: Int2X, rhs: Int2X) -> Int2X {
145 | let result = lhs.multipliedReportingOverflow(by: rhs)
146 | precondition(!result.overflow, "Multiplication overflow!")
147 | return result.partialValue
148 | }
149 | public static func *= (lhs: inout Int2X, rhs: Int2X) {
150 | lhs = lhs * rhs
151 | }
152 | }
153 | // bitshifts
154 | extension Int2X {
155 | public static func &>>(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
156 | if rhs.isNegative { return lhs &<< -rhs }
157 | if Int2X.bitWidth <= rhs { return 0 }
158 | let rv = lhs.magnitude &>> rhs.magnitude
159 | return lhs.isNegative ? -Int2X(rawValue:rv) : +Int2X(rawValue:rv)
160 | }
161 | public static func &<<(_ lhs:Int2X, _ rhs:Int2X)->Int2X {
162 | if rhs.isNegative { return lhs &>> -rhs }
163 | if Int2X.bitWidth <= rhs { return 0 }
164 | let rv = lhs.magnitude &<< rhs.magnitude
165 | return lhs.isNegative ? -Int2X(rawValue:rv) : +Int2X(rawValue:rv)
166 | }
167 | public static func &>>=(_ lhs:inout Int2X, _ rhs:Int2X) {
168 | return lhs = lhs &>> rhs
169 | }
170 | public static func &<<=(_ lhs:inout Int2X, _ rhs:Int2X) {
171 | return lhs = lhs &<< rhs
172 | }
173 | }
174 | // division
175 | extension Int2X {
176 | public func quotientAndRemainder(dividingBy other: Int2X) -> (quotient: Int2X, remainder: Int2X) {
177 | let qv = self.magnitude.quotientAndRemainder(dividingBy:other.magnitude)
178 | let q = self.isNegative != other.isNegative ? -qv.quotient : +qv.quotient
179 | let r = self.isNegative ? -qv.remainder : +qv.remainder
180 | return (Int2X(rawValue:q), Int2X(rawValue:r))
181 | }
182 | public static func / (_ lhs:Int2X, rhs:Int2X)->Int2X {
183 | return lhs.quotientAndRemainder(dividingBy: rhs).quotient
184 | }
185 | public static func /= (_ lhs:inout Int2X, rhs:Int2X) {
186 | lhs = lhs / rhs
187 | }
188 | public static func % (_ lhs:Int2X, rhs:Int2X)->Int2X {
189 | return lhs.quotientAndRemainder(dividingBy: rhs).remainder
190 | }
191 | public static func %= (_ lhs:inout Int2X, rhs:Int2X) {
192 | lhs = lhs % rhs
193 | }
194 | public func dividedReportingOverflow(by other :Int2X) -> (partialValue: Int2X, overflow:Bool) {
195 | return (self / other, false)
196 | }
197 | public func remainderReportingOverflow(dividingBy other :Int2X) -> (partialValue: Int2X, overflow:Bool) {
198 | return (self % other, false)
199 | }
200 | public func dividingFullWidth(_ dividend: (high: Int2X, low: Magnitude)) -> (quotient: Int2X, remainder: Int2X) {
201 | let qv = self.magnitude.dividingFullWidth((high: dividend.high.magnitude, low: dividend.low))
202 | let q = self.isNegative != dividend.high.isNegative ? -qv.quotient : +qv.quotient
203 | let r = self.isNegative ? -qv.remainder : +qv.remainder
204 | return (Int2X(rawValue:q), Int2X(rawValue:r))
205 | }
206 | }
207 | // UInt2X -> String
208 | extension Int2X : CustomStringConvertible, CustomDebugStringConvertible {
209 | public func toString(radix:Int=10, uppercase:Bool=false) -> String {
210 | return (self.isNegative ? "-" : "") + self.magnitude.toString(radix:radix, uppercase:uppercase)
211 | }
212 | public var description:String {
213 | return toString()
214 | }
215 | public var debugDescription:String {
216 | return (self.isNegative ? "-" : "+") + "0x" + self.magnitude.toString(radix:16)
217 | }
218 | }
219 | extension StringProtocol {
220 | public init?(_ source:Int2X, radix:Int=10, uppercase:Bool=false) {
221 | self.init(source.toString(radix:radix, uppercase:uppercase))
222 | }
223 | }
224 | // String <- UInt2X
225 | extension Int2X : ExpressibleByStringLiteral {
226 | public init(stringLiteral value: StringLiteralType) {
227 | self.init()
228 | if let result = Int2X.fromString(value) {
229 | self = result
230 | }
231 | }
232 | internal static func fromString(_ value: String) -> Int2X? {
233 | var source = value
234 | var sign = "+"
235 | if source.first == "-" || source.first == "+" {
236 | sign = String(source.first!)
237 | source.removeFirst()
238 | }
239 | guard let magnitude = Magnitude.fromString(source) else { return nil }
240 | return sign == "-" ? -Int2X(rawValue: magnitude) : +Int2X(rawValue: magnitude)
241 | }
242 | }
243 | // Int -> Int2X
244 | extension Int {
245 | public init(_ source:Int2X) {
246 | let a = Int(bitPattern: UInt(source.magnitude))
247 | self.init(source.isNegative ? -a : +a)
248 | }
249 | }
250 | // Strideable
251 | extension Int2X: Strideable {
252 | public func distance(to other: Int2X) -> Int {
253 | return Int(other) - Int(self)
254 | }
255 | public func advanced(by n: Int) -> Int2X {
256 | return self + Int2X(n)
257 | }
258 | }
259 | // BinaryInteger
260 | extension Int2X: BinaryInteger {
261 | public var bitWidth: Int {
262 | return rawValue.bitWidth
263 | }
264 | public var words: Words {
265 | return rawValue.words
266 | }
267 | public var trailingZeroBitCount: Int {
268 | return rawValue.trailingZeroBitCount
269 | }
270 | public static func &= (lhs: inout Int2X, rhs: Int2X) {
271 | lhs.rawValue &= rhs.rawValue
272 | }
273 | public static func |= (lhs: inout Int2X, rhs: Int2X) {
274 | lhs.rawValue |= rhs.rawValue
275 | }
276 | public static func ^= (lhs: inout Int2X, rhs: Int2X) {
277 | lhs.rawValue ^= rhs.rawValue
278 | }
279 | public static func <<= (lhs: inout Int2X, rhs: RHS) where RHS : BinaryInteger {
280 | lhs.rawValue <<= rhs
281 | }
282 | public static func >>= (lhs: inout Int2X, rhs: RHS) where RHS : BinaryInteger {
283 | lhs.rawValue >>= rhs
284 | }
285 | }
286 | // FixedWidthInteger
287 | extension Int2X: FixedWidthInteger {
288 | public init(_truncatingBits bits: UInt) {
289 | fatalError()
290 | }
291 | public var nonzeroBitCount: Int {
292 | return self.rawValue.nonzeroBitCount
293 | }
294 | public var leadingZeroBitCount: Int {
295 | return self.rawValue.leadingZeroBitCount
296 | }
297 | public var byteSwapped: Int2X {
298 | return Int2X(rawValue:rawValue.byteSwapped)
299 | }
300 | }
301 | // SignedInteger
302 | extension Int2X: SignedInteger {}
303 |
304 | public typealias Int128 = Int2X
305 | public typealias Int256 = Int2X
306 | public typealias Int512 = Int2X
307 | public typealias Int1024 = Int2X
308 |
309 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Network.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Network.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol NetworkProtocol {
12 | var name: String {get}
13 | var chainId: String {get}
14 | var _defaultProvider: ((_ providers: Any) -> Any)? {get}
15 | }
16 |
17 | public struct Network: NetworkProtocol {
18 | public let name: String
19 | public let chainId: String
20 | public var _defaultProvider: ((_ providers: Any) -> Any)?
21 | }
22 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/SHA256.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SHA256.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CommonCrypto
11 |
12 | public extension Collection where Element == UInt8 {
13 | var digest: [UInt8] {
14 | var result = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
15 | data.withUnsafeBytes {
16 | _ = CC_SHA256($0.baseAddress, CC_LONG(count), &result)
17 | }
18 | return result
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Serialize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Serialize.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Base58Swift
11 |
12 | public extension String {
13 | var baseDecoded: [UInt8] {
14 | return Base58.base58Decode(self) ?? []
15 | }
16 | }
17 |
18 | public extension Data {
19 | var baseEncoded: String {
20 | return Base58.base58Encode(bytes)
21 | }
22 |
23 | var bytes: [UInt8] {
24 | return [UInt8](self)
25 | }
26 | }
27 |
28 | public extension Data {
29 | var hexString: String {
30 | return map { String(format: "%02x", UInt8($0)) }.joined()
31 | }
32 | }
33 |
34 | public extension Data {
35 | var json: [String: Any]? {
36 | return try? toDictionary()
37 | }
38 |
39 | init(json: [String: Any]) {
40 | let data = try? json.toData()
41 | self.init(bytes: data?.bytes ?? [], count: data?.bytes.count ?? 0)
42 | }
43 | }
44 |
45 | public extension Sequence where Element == UInt8 {
46 | var baseEncoded: String {
47 | return data.baseEncoded
48 | }
49 |
50 | var data: Data {
51 | return Data(self)
52 | }
53 | }
54 |
55 | public struct CastingError: Error {
56 | let fromType: Any.Type
57 | let toType: Any.Type
58 | init(fromType: FromType.Type, toType: ToType.Type) {
59 | self.fromType = fromType
60 | self.toType = toType
61 | }
62 | }
63 |
64 | extension CastingError: LocalizedError {
65 | var localizedDescription: String { return "Can not cast from \(fromType) to \(toType)" }
66 | }
67 |
68 | extension CastingError: CustomStringConvertible { public var description: String { return localizedDescription } }
69 |
70 | public extension Data {
71 | func toDictionary(options: JSONSerialization.ReadingOptions = []) throws -> [String: Any] {
72 | return try to(type: [String: Any].self, options: options)
73 | }
74 |
75 | func to(type: T.Type, options: JSONSerialization.ReadingOptions = []) throws -> T {
76 | guard let result = try JSONSerialization.jsonObject(with: self, options: options) as? T else {
77 | throw CastingError(fromType: type, toType: T.self)
78 | }
79 | return result
80 | }
81 | }
82 |
83 | public extension String {
84 | func asJSON(to type: T.Type, using encoding: String.Encoding = .utf8) throws -> T {
85 | guard let data = data(using: encoding) else { throw CastingError(fromType: type, toType: T.self) }
86 | return try data.to(type: T.self)
87 | }
88 |
89 | func asJSONToDictionary(using encoding: String.Encoding = .utf8) throws -> [String: Any] {
90 | return try asJSON(to: [String: Any].self, using: encoding)
91 | }
92 | }
93 |
94 | internal extension Dictionary {
95 | func toData(options: JSONSerialization.WritingOptions = []) throws -> Data {
96 | return try JSONSerialization.data(withJSONObject: self, options: options)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/URL+Params.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL+Params.swift
3 | // nearclientios
4 | //
5 | // Created by Dmytro Kurochka on 10.12.2019.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension URL {
11 | var queryParameters: [String: String]? {
12 | guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
13 | let queryItems = components.queryItems else { return nil }
14 | return queryItems.reduce(into: [String: String]()) { result, item in
15 | result[item.name] = item.value
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/nearclientios/Sources/Utils/Web.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Web.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ConnectionInfo {
12 | let url: URL
13 | let user: String? = nil
14 | let password: String? = nil
15 | let allowInsecure: Bool? = nil
16 | let timeout: TimeInterval? = nil
17 | let headers: [String: Any]? = nil
18 | }
19 |
20 | enum HTTPError: Error {
21 | case unknown
22 | case error(status: Int, message: String?)
23 | }
24 |
25 | private func fetch(url: URL, params: [String: Any]?) async throws-> Any {
26 | let session = URLSession.shared
27 | var request = URLRequest(url: url)
28 | request.httpMethod = params.flatMap {_ in "POST"} ?? "GET"
29 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
30 | request.httpBody = params.flatMap { try? $0.toData() }
31 | return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in
32 | let task = session.dataTask(with: request) { data, response, error in
33 | if let error = error { return continuation.resume(throwing: error) }
34 | let result = data.flatMap {try? $0.toDictionary()}
35 | if let json = result?["result"] {
36 | continuation.resume(returning: json)
37 | } else if let httpResponse = response as? HTTPURLResponse {
38 | do {
39 | let decoder = JSONDecoder()
40 | decoder.keyDecodingStrategy = .convertFromSnakeCase
41 | let decodedError = try decoder.decode(TransactionError.self, from: data ?? Data())
42 | continuation.resume(throwing: decodedError)
43 | } catch {
44 | if let result = result, let json = try? JSONSerialization.data(withJSONObject: result, options: []) {
45 | debugPrint("=====================")
46 | print(String(decoding: json, as: UTF8.self))
47 | debugPrint("=====================")
48 | }
49 | let error = HTTPError.error(status: httpResponse.statusCode,
50 | message: data.flatMap({ String(data: $0, encoding: .utf8) }))
51 | continuation.resume(throwing: error)
52 | }
53 | } else {
54 | continuation.resume(throwing: HTTPError.unknown)
55 | }
56 | }
57 | task.resume()
58 | })
59 | }
60 |
61 | public func fetchJson(connection: ConnectionInfo, json: [String: Any]?) async throws -> Any {
62 | let url = connection.url
63 | return try await fetch(url: url, params: json)
64 | }
65 |
66 | func fetchJson(url: URL, json: [String: Any]?) async throws -> Any {
67 | return try await fetch(url: url, params: json)
68 | }
69 |
--------------------------------------------------------------------------------
/nearclientios/Sources/WalletAccount.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WalletAccount.swift
3 | // nearclientios
4 | //
5 | // Created by Dmitry Kurochka on 10/30/19.
6 | // Copyright © 2019 NEAR Protocol. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import KeychainAccess
12 |
13 | public let APP_SCHEME = "x-nearclientios"
14 |
15 | let LOGIN_WALLET_URL_SUFFIX = "/login/"
16 |
17 | let LOCAL_STORAGE_KEY_SUFFIX = "_wallet_auth_key"
18 |
19 | /// storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet)
20 | let PENDING_ACCESS_KEY_PREFIX = "pending_key"
21 |
22 | public protocol AuthDataProtocol {
23 | var accountId: String? {get}
24 | }
25 |
26 | public struct AuthData: AuthDataProtocol {
27 | public let accountId: String?
28 | }
29 |
30 | public enum WalletAccountError: Error {
31 | case noKeyStore
32 | case noKeyPair
33 | case noRegisteredURLSchemes
34 | case successUrlWrongScheme
35 | case failureUrlWrongScheme
36 | case callbackUrlParamsNotValid
37 | }
38 |
39 | public protocol WalletStorage: AnyObject {
40 | subscript(key: String) -> String? {get set}
41 | }
42 |
43 | public let WALLET_STORAGE_SERVICE = "nearlib.wallet"
44 |
45 | extension Keychain: WalletStorage {}
46 |
47 | public protocol ExternalAuthService {
48 | func openURL(_ url: URL, presentingViewController: UIViewController) -> Bool
49 | }
50 |
51 | public actor WalletAccount {
52 | private let _walletBaseUrl: String
53 | private let _authDataKey: String
54 | private let _keyStore: KeyStore
55 | private var _authData: AuthDataProtocol
56 | private let _networkId: String
57 | private let storage: WalletStorage
58 | private let authService: ExternalAuthService
59 |
60 | public init(near: Near, authService: ExternalAuthService, appKeyPrefix: String? = nil,
61 | storage: WalletStorage = Keychain(service: WALLET_STORAGE_SERVICE)) throws {
62 | let keyPrefix = appKeyPrefix ?? (near.config.contractName ?? "default")
63 | let authDataKey = keyPrefix + LOCAL_STORAGE_KEY_SUFFIX
64 | guard let keyStore = (near.connection.signer as? InMemorySigner)?.keyStore else {throw WalletAccountError.noKeyStore}
65 | let authData = AuthData(accountId: storage[authDataKey])
66 | _walletBaseUrl = near.config.walletUrl
67 | _authDataKey = authDataKey
68 | _keyStore = keyStore
69 | _authData = authData
70 | _networkId = near.config.networkId
71 | self.storage = storage
72 | self.authService = authService
73 | }
74 | }
75 |
76 | extension WalletAccount {
77 | /**
78 | - Example:
79 | walletAccount.isSignedIn()
80 | - Returns: Returns true, if this WalletAccount is authorized with the wallet.
81 | */
82 | public func isSignedIn() -> Bool {
83 | return _authData.accountId != nil
84 | }
85 |
86 | /**
87 | - Example:
88 | walletAccount.getAccountId()
89 | - Returns: Authorized Account ID.
90 | */
91 | public func getAccountId() -> String {
92 | return _authData.accountId ?? ""
93 | }
94 |
95 | /**
96 | Redirects current page to the wallet authentication page.
97 | - Parameters:
98 | - contractId: contractId contract ID of the application
99 | - accountId: title name of the application
100 | - networkId: successUrl url to redirect on success
101 | - failureUrl: failureUrl url to redirect on failure
102 | */
103 | @discardableResult
104 | public func requestSignIn(contractId: String?, title: String, presentingViewController: UIViewController, successUrl: URL = URL(string: APP_SCHEME + "://success")!, failureUrl: URL = URL(string: APP_SCHEME + "://fail")!, appUrl: URL = URL(string: APP_SCHEME + "://")!, curve: KeyType = .ED25519) async throws -> Bool {
105 | guard getAccountId().isEmpty else {return true}
106 | guard try await _keyStore.getKey(networkId: _networkId, accountId: getAccountId()) == nil else {return true}
107 |
108 | var newUrlComponents = URLComponents(string: _walletBaseUrl + LOGIN_WALLET_URL_SUFFIX)
109 | let title = URLQueryItem(name: "referrer", value: title)
110 | let contract_id = URLQueryItem(name: "contract_id", value: contractId)
111 | let success_url = URLQueryItem(name: "success_url", value: successUrl.absoluteString)
112 | let failure_url = URLQueryItem(name: "failure_url", value: failureUrl.absoluteString)
113 | let app_url = URLQueryItem(name: "app_url", value: appUrl.absoluteString)
114 | let accessKey = try keyPairFromRandom(curve: curve)
115 | let public_key = URLQueryItem(name: "public_key", value: accessKey.getPublicKey().toString())
116 |
117 | newUrlComponents?.queryItems = [title, contract_id, success_url, failure_url, app_url, public_key]
118 | let accountId = PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey().toString()
119 | try await _keyStore.setKey(networkId: _networkId, accountId: accountId, keyPair: accessKey)
120 | if let openUrl = newUrlComponents?.url {
121 | return await MainActor.run {
122 | authService.openURL(openUrl, presentingViewController: presentingViewController)
123 | }
124 | }
125 | return false
126 | }
127 |
128 | /**
129 | Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet.
130 | */
131 | public func completeSignIn(url: URL) async throws -> Void {
132 | guard let params = url.queryParameters else {throw WalletAccountError.callbackUrlParamsNotValid}
133 | if let publicKey = params["public_key"], let accountId = params["account_id"] {
134 | _authData = AuthData(accountId: accountId)
135 | storage[_authDataKey] = accountId
136 | try await _moveKeyFromTempToPermanent(accountId: accountId, publicKey: publicKey)
137 | }
138 | }
139 |
140 | public func completeSignIn(withKeyPair keyPair: KeyPair, accountId: String) async throws -> Void {
141 | _authData = AuthData(accountId: accountId)
142 | storage[_authDataKey] = accountId
143 | try await _keyStore.setKey(networkId: _networkId, accountId: accountId, keyPair: keyPair)
144 | }
145 |
146 | private func _moveKeyFromTempToPermanent(accountId: String, publicKey: String) async throws -> Void {
147 | let pendingAccountId = PENDING_ACCESS_KEY_PREFIX + publicKey
148 | guard let keyPair = try await (_keyStore.getKey(networkId: _networkId,
149 | accountId: pendingAccountId)) else {throw WalletAccountError.noKeyPair}
150 | try await _keyStore.setKey(networkId: _networkId, accountId: accountId, keyPair: keyPair)
151 | try await _keyStore.removeKey(networkId: _networkId, accountId: PENDING_ACCESS_KEY_PREFIX + publicKey)
152 | }
153 |
154 | /**
155 | Sign out from the current account
156 | */
157 | public func signOut() {
158 | _authData = AuthData(accountId: nil)
159 | storage[_authDataKey] = nil
160 | }
161 | }
162 |
--------------------------------------------------------------------------------