mirror of
https://github.com/zkitter/coinbase-wallet-sdk.git
synced 2026-01-09 13:58:03 -05:00
iOS native SDK and example app (#589)
* first commit * ios example app scaffolding * README * Update README.md * pod * integrate pod * Create .codeflow.yml * remove example on pod * cleanup * cleanup * handshake * handshake request encoder / response handler * ios proof of concept * Update README.md * reorg * host example app * build settings * cleanup * request message renderer * url rendering * isCoinbaseWalletInstalled * refactor * key manager * url codable / task manager pod * make request * reintroduce message renderer * dedup Message with custom coding * response handler * key storage * hmmmmm * key storage / no error throwing during init * add domain to key storage * response handling / error handling * distinguish request/response types * access control * initial request * host example app * host * handle encrypted response * simplify symkey generation flow * rename Update README.md Update README.md * update path * ios 12 support * make RawRepresentableKey public * clean up cryptography * RawRepresentableKey * make initialzier public * ugh * Update README.md * Update README.md * Update README.md * sample host app * reorg * Merge remote-tracking branch 'native/master' # Conflicts: # .gitignore # Conflicts: # native/ios/CoinbaseWalletSDK.swift # native/ios/Message/Message.swift # native/ios/Message/Request/EncryptedRequestContent.swift # native/ios/Message/Request/Request.swift # native/ios/Message/Request/RequestMessage.swift # native/ios/Message/Response/EncryptedResponseContent.swift # native/ios/Message/Response/ResponseMessage.swift # native/ios/Task/TaskManager.swift * Merge branch 'master' of github.com:coinbase/coinbase-wallet-sdk # Conflicts: # .github/workflows/codeql-analysis.yml # .github/workflows/main.yml # .github/workflows/versioning.yml # .gitignore # README.md # host/ios/LICENSE # packages/wallet-sdk/.eslintignore # packages/wallet-sdk/.eslintrc.js # packages/wallet-sdk/.prettierignore # packages/wallet-sdk/.prettierrc.yml # packages/wallet-sdk/LICENSE # packages/wallet-sdk/__tests__/addressStorage.test.js # packages/wallet-sdk/__tests__/encryptDecrypt.test.js # packages/wallet-sdk/assets/coinbase_wallet_logo_kit.zip # packages/wallet-sdk/babel.config.js # packages/wallet-sdk/compile-assets.js # packages/wallet-sdk/jest.config.ts # packages/wallet-sdk/jest.setup.ts # packages/wallet-sdk/karma.conf.js # packages/wallet-sdk/package.json # packages/wallet-sdk/scripts/release.sh # packages/wallet-sdk/src/CoinbaseWalletSDK.test.ts # packages/wallet-sdk/src/CoinbaseWalletSDK.ts # packages/wallet-sdk/src/__mocks__/provider.ts # packages/wallet-sdk/src/__mocks__/relay.ts # packages/wallet-sdk/src/assets/wallet-logo.ts # packages/wallet-sdk/src/components/LinkFlow.test.tsx # packages/wallet-sdk/src/components/LinkFlow.tsx # packages/wallet-sdk/src/components/QRCode.tsx # packages/wallet-sdk/src/components/Snackbar.scss # packages/wallet-sdk/src/components/Snackbar.test.tsx # packages/wallet-sdk/src/components/Snackbar.tsx # packages/wallet-sdk/src/components/SnackbarContainer.test.tsx # packages/wallet-sdk/src/components/Spinner.scss # packages/wallet-sdk/src/components/Spinner.test.tsx # packages/wallet-sdk/src/components/Spinner.tsx # packages/wallet-sdk/src/components/TryExtensionLinkDialog.scss # packages/wallet-sdk/src/components/TryExtensionLinkDialog.test.tsx # packages/wallet-sdk/src/components/TryExtensionLinkDialog.tsx # packages/wallet-sdk/src/components/icons/QRLogo.ts # packages/wallet-sdk/src/components/icons/globe-icon.svg # packages/wallet-sdk/src/components/icons/link-icon.svg # packages/wallet-sdk/src/components/icons/lock-icon.svg # packages/wallet-sdk/src/connection/ClientMessage.ts # packages/wallet-sdk/src/connection/DiagnosticLogger.test.ts # packages/wallet-sdk/src/connection/DiagnosticLogger.ts # packages/wallet-sdk/src/connection/EventListener.ts # packages/wallet-sdk/src/connection/RxWebSocket.test.ts # packages/wallet-sdk/src/connection/RxWebSocket.ts # packages/wallet-sdk/src/connection/ServerMessage.ts # packages/wallet-sdk/src/connection/SessionConfig.ts # packages/wallet-sdk/src/connection/WalletSDKConnection.ts # packages/wallet-sdk/src/fixtures/provider.ts # packages/wallet-sdk/src/index.ts # packages/wallet-sdk/src/lib/ScopedLocalStorage.test.ts # packages/wallet-sdk/src/lib/ScopedLocalStorage.ts # packages/wallet-sdk/src/lib/cssReset.scss # packages/wallet-sdk/src/lib/cssReset.ts # packages/wallet-sdk/src/provider/CoinbaseWalletProvider.test.ts # packages/wallet-sdk/src/provider/CoinbaseWalletProvider.ts # packages/wallet-sdk/src/provider/FilterPolyfill.ts # packages/wallet-sdk/src/provider/JSONRPC.ts # packages/wallet-sdk/src/provider/SubscriptionManager.ts # packages/wallet-sdk/src/provider/WalletSDKUI.test.ts # packages/wallet-sdk/src/provider/WalletSDKUI.ts # packages/wallet-sdk/src/provider/WalletUI.ts # packages/wallet-sdk/src/provider/WalletUIError.ts # packages/wallet-sdk/src/provider/Web3Provider.ts # packages/wallet-sdk/src/relay/EthereumTransactionParams.ts # packages/wallet-sdk/src/relay/RelayMessage.ts # packages/wallet-sdk/src/relay/Session.ts # packages/wallet-sdk/src/relay/WalletSDKRelay.ts # packages/wallet-sdk/src/relay/WalletSDKRelayAbstract.ts # packages/wallet-sdk/src/relay/WalletSDKRelayEventManager.test.ts # packages/wallet-sdk/src/relay/WalletSDKRelayEventManager.ts # packages/wallet-sdk/src/relay/Web3Method.ts # packages/wallet-sdk/src/relay/Web3Request.ts # packages/wallet-sdk/src/relay/Web3RequestCanceledMessage.ts # packages/wallet-sdk/src/relay/Web3RequestMessage.ts # packages/wallet-sdk/src/relay/Web3Response.ts # packages/wallet-sdk/src/relay/Web3ResponseMessage.ts # packages/wallet-sdk/src/relay/aes256gcm.test.ts # packages/wallet-sdk/src/relay/aes256gcm.ts # packages/wallet-sdk/src/types.ts # packages/wallet-sdk/src/util.test.ts # packages/wallet-sdk/src/util.ts # packages/wallet-sdk/src/vendor-js/eth-eip712-util/LICENSE # packages/wallet-sdk/src/vendor-js/eth-eip712-util/abi.js # packages/wallet-sdk/src/vendor-js/eth-eip712-util/index.d.ts # packages/wallet-sdk/src/vendor-js/eth-eip712-util/index.js # packages/wallet-sdk/src/vendor-js/eth-eip712-util/util.js # packages/wallet-sdk/src/vendor-js/qrcode-svg/LICENSE # packages/wallet-sdk/src/vendor-js/qrcode-svg/index.d.ts # packages/wallet-sdk/src/vendor-js/qrcode-svg/index.js # packages/wallet-sdk/src/version.ts # packages/wallet-sdk/tsconfig.build.json # packages/wallet-sdk/tsconfig.json # packages/wallet-sdk/webpack.config.js # packages/wallet-sdk/yarn.lock # src/vendor-js/qrcode-svg/LICENSE * move under packages * readme * sample ios client app * pod settings * add `optional` filed to `Action` * update example app * Merge branch 'walletsegue' of github.com:coinbase/coinbase-wallet-sdk into walletsegue * define ReturnType * Update README.md Update README.md * introduce Web3JSONRPC helper * nested params * access control * # This is a combination of 2 commits. # This is the 1st commit message: access control # This is the commit message #2: Revert "access control" This reverts commit c8ea3dac36749ce9632b990708e960e96e230c6e. * remove public initializers access control # This is the commit message #2: Revert "access control" This reverts commit c8ea3dac36749ce9632b990708e960e96e230c6e. # This is the commit message #3: remove public initializers * use JSONSerialization to simplify Web3JSONRPC encoding * singleton * clean up error type * host example * ios example app * example app * add timestamp to message * allow custom scheme / keystorage error message * keystorageerror * sampel ios app * response handler * swizzle * update request on sample app * cleanup keymanager implementation * remove codeflow config * remove old files * gitignore * cleanup * rename * missing changes * sample app name * update podspec * author
This commit is contained in:
125
.gitignore
vendored
125
.gitignore
vendored
@@ -1,2 +1,127 @@
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# Native mobile
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||
build/
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
#
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
*.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
.DS_Store
|
||||
xcshareddata
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
19
CoinbaseWalletSDK.podspec
Normal file
19
CoinbaseWalletSDK.podspec
Normal file
@@ -0,0 +1,19 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'CoinbaseWalletSDK'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'Swift SDK to interact with Coinbase Wallet iOS app'
|
||||
s.description = <<-DESC
|
||||
Swift implementation of WalletSegue protocol for iOS web3 apps to interact with web3 wallets.
|
||||
Inter-app messaging contained within user's device, not requiring a bridge server.
|
||||
Secure communication channel between client app and host wallet.
|
||||
Decentralized verification of participating apps' authenticity without centralized registry.
|
||||
DESC
|
||||
s.homepage = 'https://www.coinbase.com/wallet/developers'
|
||||
s.license = { :type => 'Apache', :file => 'LICENSE' }
|
||||
s.author = { 'Coinbase Wallet' => 'wallet-build-squad@coinbase.com' }
|
||||
s.source = { :git => 'https://github.com/coinbase/coinbase-wallet-sdk.git', :tag => "ios-v#{s.version}" }
|
||||
s.social_media_url = 'https://twitter.com/CoinbaseWallet'
|
||||
s.ios.deployment_target = '12.0'
|
||||
s.swift_version = '5.0'
|
||||
s.source_files = 'packages/wallet-native-sdk/ios/**/*.swift'
|
||||
end
|
||||
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2018-2022 Coinbase, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
6
examples/native-sdk-ios-client/Podfile
Normal file
6
examples/native-sdk-ios-client/Podfile
Normal file
@@ -0,0 +1,6 @@
|
||||
platform :ios, '13.0'
|
||||
use_frameworks!
|
||||
|
||||
target 'NativeWeb3App' do
|
||||
pod 'CoinbaseWalletSDK', path: '../../'
|
||||
end
|
||||
16
examples/native-sdk-ios-client/Podfile.lock
Normal file
16
examples/native-sdk-ios-client/Podfile.lock
Normal file
@@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- CoinbaseWalletSDK (0.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- CoinbaseWalletSDK (from `../../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
CoinbaseWalletSDK:
|
||||
:path: "../../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
CoinbaseWalletSDK: cae36a0585ac804b9a421659092d64476e6e9b85
|
||||
|
||||
PODFILE CHECKSUM: 258b9c0ec0f0d17a701912e5f713afb0de91d709
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
@@ -0,0 +1,433 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5DA9B3CF286A755E0089D6DB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA9B3CE286A755E0089D6DB /* AppDelegate.swift */; };
|
||||
5DA9B3D1286A755E0089D6DB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA9B3D0286A755E0089D6DB /* SceneDelegate.swift */; };
|
||||
5DA9B3D3286A755E0089D6DB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA9B3D2286A755E0089D6DB /* ViewController.swift */; };
|
||||
5DA9B3D6286A755E0089D6DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DA9B3D4286A755E0089D6DB /* Main.storyboard */; };
|
||||
5DA9B3D8286A755F0089D6DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5DA9B3D7286A755F0089D6DB /* Assets.xcassets */; };
|
||||
5DA9B3DB286A755F0089D6DB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DA9B3D9286A755F0089D6DB /* LaunchScreen.storyboard */; };
|
||||
9A156AE0D18D300163D57907 /* Pods_NativeWeb3App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 333C157DDC56839FC68B8E8F /* Pods_NativeWeb3App.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
333C157DDC56839FC68B8E8F /* Pods_NativeWeb3App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeWeb3App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5DA9B3CB286A755E0089D6DB /* NativeWeb3App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeWeb3App.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5DA9B3CE286A755E0089D6DB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5DA9B3D0286A755E0089D6DB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
5DA9B3D2286A755E0089D6DB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
5DA9B3D5286A755E0089D6DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
5DA9B3D7286A755F0089D6DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
5DA9B3DA286A755F0089D6DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
5DA9B3DC286A755F0089D6DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
AD64CFD01D3AB9B726DF335B /* Pods-NativeWeb3App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeWeb3App.release.xcconfig"; path = "Target Support Files/Pods-NativeWeb3App/Pods-NativeWeb3App.release.xcconfig"; sourceTree = "<group>"; };
|
||||
FC120F48CB37E5C1FAFE2CAE /* Pods-NativeWeb3App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeWeb3App.debug.xcconfig"; path = "Target Support Files/Pods-NativeWeb3App/Pods-NativeWeb3App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5DA9B3C8286A755E0089D6DB /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9A156AE0D18D300163D57907 /* Pods_NativeWeb3App.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3E18EF9C1E2A7D3BF660A5CD /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
333C157DDC56839FC68B8E8F /* Pods_NativeWeb3App.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
51AC5D6DCCB61B979D9D716E /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC120F48CB37E5C1FAFE2CAE /* Pods-NativeWeb3App.debug.xcconfig */,
|
||||
AD64CFD01D3AB9B726DF335B /* Pods-NativeWeb3App.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5DA9B3C2286A755E0089D6DB = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5DA9B3CD286A755E0089D6DB /* SampleApp */,
|
||||
5DA9B3CC286A755E0089D6DB /* Products */,
|
||||
51AC5D6DCCB61B979D9D716E /* Pods */,
|
||||
3E18EF9C1E2A7D3BF660A5CD /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5DA9B3CC286A755E0089D6DB /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5DA9B3CB286A755E0089D6DB /* NativeWeb3App.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5DA9B3CD286A755E0089D6DB /* SampleApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5DA9B3CE286A755E0089D6DB /* AppDelegate.swift */,
|
||||
5DA9B3D0286A755E0089D6DB /* SceneDelegate.swift */,
|
||||
5DA9B3D2286A755E0089D6DB /* ViewController.swift */,
|
||||
5DA9B3D4286A755E0089D6DB /* Main.storyboard */,
|
||||
5DA9B3D7286A755F0089D6DB /* Assets.xcassets */,
|
||||
5DA9B3D9286A755F0089D6DB /* LaunchScreen.storyboard */,
|
||||
5DA9B3DC286A755F0089D6DB /* Info.plist */,
|
||||
);
|
||||
path = SampleApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
5DA9B3CA286A755E0089D6DB /* NativeWeb3App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5DA9B3DF286A755F0089D6DB /* Build configuration list for PBXNativeTarget "NativeWeb3App" */;
|
||||
buildPhases = (
|
||||
29FDABA7E59E1339DA4167A7 /* [CP] Check Pods Manifest.lock */,
|
||||
5DA9B3C7286A755E0089D6DB /* Sources */,
|
||||
5DA9B3C8286A755E0089D6DB /* Frameworks */,
|
||||
5DA9B3C9286A755E0089D6DB /* Resources */,
|
||||
4A2E3B84F8F828F77205E15C /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = NativeWeb3App;
|
||||
productName = NativeWeb3App;
|
||||
productReference = 5DA9B3CB286A755E0089D6DB /* NativeWeb3App.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
5DA9B3C3286A755E0089D6DB /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1330;
|
||||
LastUpgradeCheck = 1330;
|
||||
TargetAttributes = {
|
||||
5DA9B3CA286A755E0089D6DB = {
|
||||
CreatedOnToolsVersion = 13.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 5DA9B3C6286A755E0089D6DB /* Build configuration list for PBXProject "SampleApp" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 5DA9B3C2286A755E0089D6DB;
|
||||
productRefGroup = 5DA9B3CC286A755E0089D6DB /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
5DA9B3CA286A755E0089D6DB /* NativeWeb3App */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
5DA9B3C9286A755E0089D6DB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5DA9B3DB286A755F0089D6DB /* LaunchScreen.storyboard in Resources */,
|
||||
5DA9B3D8286A755F0089D6DB /* Assets.xcassets in Resources */,
|
||||
5DA9B3D6286A755E0089D6DB /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
29FDABA7E59E1339DA4167A7 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-NativeWeb3App-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4A2E3B84F8F828F77205E15C /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-NativeWeb3App/Pods-NativeWeb3App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-NativeWeb3App/Pods-NativeWeb3App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NativeWeb3App/Pods-NativeWeb3App-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
5DA9B3C7286A755E0089D6DB /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5DA9B3D3286A755E0089D6DB /* ViewController.swift in Sources */,
|
||||
5DA9B3CF286A755E0089D6DB /* AppDelegate.swift in Sources */,
|
||||
5DA9B3D1286A755E0089D6DB /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
5DA9B3D4286A755E0089D6DB /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
5DA9B3D5286A755E0089D6DB /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5DA9B3D9286A755F0089D6DB /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
5DA9B3DA286A755F0089D6DB /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
5DA9B3DD286A755F0089D6DB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5DA9B3DE286A755F0089D6DB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
5DA9B3E0286A755F0089D6DB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FC120F48CB37E5C1FAFE2CAE /* Pods-NativeWeb3App.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = B7Y3D73M65;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SampleApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.coinbase.NativeWeb3App;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5DA9B3E1286A755F0089D6DB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AD64CFD01D3AB9B726DF335B /* Pods-NativeWeb3App.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = B7Y3D73M65;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SampleApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.coinbase.NativeWeb3App;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
5DA9B3C6286A755E0089D6DB /* Build configuration list for PBXProject "SampleApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5DA9B3DD286A755F0089D6DB /* Debug */,
|
||||
5DA9B3DE286A755F0089D6DB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5DA9B3DF286A755F0089D6DB /* Build configuration list for PBXNativeTarget "NativeWeb3App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5DA9B3E0286A755F0089D6DB /* Debug */,
|
||||
5DA9B3E1286A755F0089D6DB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 5DA9B3C3286A755E0089D6DB /* Project object */;
|
||||
}
|
||||
68
examples/native-sdk-ios-client/SampleApp/AppDelegate.swift
Normal file
68
examples/native-sdk-ios-client/SampleApp/AppDelegate.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// SampleWeb3App
|
||||
//
|
||||
// Created by Jungho Bang on 6/27/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoinbaseWalletSDK
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
UIApplication.swizzleOpenURL()
|
||||
|
||||
CoinbaseWalletSDK.configure(
|
||||
callback: URL(string: "myappxyz://mycallback")!
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
if (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true {
|
||||
return true
|
||||
}
|
||||
// handle other types of deep links
|
||||
return false
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
if let url = userActivity.webpageURL,
|
||||
(try? CoinbaseWalletSDK.shared.handleResponse(url)) == true {
|
||||
return true
|
||||
}
|
||||
// handle other types of deep links
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
static func swizzleOpenURL() {
|
||||
guard
|
||||
let original = class_getInstanceMethod(UIApplication.self, #selector(open(_:options:completionHandler:))),
|
||||
let swizzled = class_getInstanceMethod(UIApplication.self, #selector(swizzledOpen(_:options:completionHandler:)))
|
||||
else { return }
|
||||
method_exchangeImplementations(original, swizzled)
|
||||
}
|
||||
|
||||
@objc func swizzledOpen(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey: Any], completionHandler completion: ((Bool) -> Void)?) {
|
||||
logWalletSegueMessage(url: url)
|
||||
|
||||
// it's not recursive. below is actually the original open(_:) method
|
||||
self.swizzledOpen(url, options: options, completionHandler: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func logWalletSegueMessage(url: URL, function: String = #function) {
|
||||
let keyWindow = UIApplication.shared.connectedScenes
|
||||
.filter({$0.activationState == .foregroundActive})
|
||||
.compactMap({$0 as? UIWindowScene})
|
||||
.first?.windows
|
||||
.filter({$0.isKeyWindow}).first
|
||||
if let vc = keyWindow?.rootViewController as? ViewController {
|
||||
vc.logURL(url, function: function)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="N0H-g8-v1I">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="zug-Ox-8lY">
|
||||
<objects>
|
||||
<tableViewController id="N0H-g8-v1I" customClass="ViewController" customModule="NativeWeb3App" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" estimatedSectionHeaderHeight="-1" sectionFooterHeight="18" estimatedSectionFooterHeight="-1" id="fK4-3E-Da2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<sections>
|
||||
<tableViewSection id="1cD-BP-bqt">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="XMs-AC-dDw" detailTextLabel="crr-DB-0oO" style="IBUITableViewCellStyleValue2" id="TZr-pJ-2rz">
|
||||
<rect key="frame" x="0.0" y="18" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TZr-pJ-2rz" id="gRR-Nr-iUX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="isCBWInstalled" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XMs-AC-dDw">
|
||||
<rect key="frame" x="20" y="15" width="91" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="bool" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="crr-DB-0oO">
|
||||
<rect key="frame" x="117" y="15" width="25" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="bIA-Fa-7iI" detailTextLabel="aQU-OY-KUj" style="IBUITableViewCellStyleValue2" id="oiO-9C-Bhr">
|
||||
<rect key="frame" x="0.0" y="61.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oiO-9C-Bhr" id="vlQ-II-7Nj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="isConnected" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bIA-Fa-7iI">
|
||||
<rect key="frame" x="20" y="15" width="91" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="bool" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="aQU-OY-KUj">
|
||||
<rect key="frame" x="117" y="15" width="25" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="Pah-Wo-mhl" detailTextLabel="kmY-1i-sqZ" style="IBUITableViewCellStyleValue2" id="JIf-L3-HZ0">
|
||||
<rect key="frame" x="0.0" y="105" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JIf-L3-HZ0" id="Yy5-ga-LhL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="ownPubKey" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Pah-Wo-mhl">
|
||||
<rect key="frame" x="20" y="15" width="91" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="string" textAlignment="natural" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="kmY-1i-sqZ">
|
||||
<rect key="frame" x="117" y="15" width="32.5" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="Zi0-rV-vVI" detailTextLabel="Ax0-s5-kcP" style="IBUITableViewCellStyleValue2" id="yFs-wz-EUW">
|
||||
<rect key="frame" x="0.0" y="148.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="yFs-wz-EUW" id="e9K-23-pZe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="peerPubKey" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Zi0-rV-vVI">
|
||||
<rect key="frame" x="20" y="15" width="91" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="string" textAlignment="natural" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Ax0-s5-kcP">
|
||||
<rect key="frame" x="117" y="15" width="32.5" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="MDv-N0-vVu">
|
||||
<rect key="frame" x="0.0" y="192" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="MDv-N0-vVu" id="I60-ow-F4F">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="f0g-3n-F8G">
|
||||
<rect key="frame" x="6" y="6" width="402" height="31.5"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Initiate handshake"/>
|
||||
<connections>
|
||||
<action selector="initiateHandshake" destination="N0H-g8-v1I" eventType="touchUpInside" id="3SX-uc-nCS"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="f0g-3n-F8G" firstAttribute="centerX" secondItem="I60-ow-F4F" secondAttribute="centerX" id="129-dN-ifQ"/>
|
||||
<constraint firstItem="f0g-3n-F8G" firstAttribute="top" secondItem="I60-ow-F4F" secondAttribute="topMargin" constant="-5" id="YYh-Qh-kb4"/>
|
||||
<constraint firstItem="f0g-3n-F8G" firstAttribute="centerY" secondItem="I60-ow-F4F" secondAttribute="centerY" id="o19-K7-jVy"/>
|
||||
<constraint firstItem="f0g-3n-F8G" firstAttribute="leading" secondItem="I60-ow-F4F" secondAttribute="leadingMargin" constant="-14" id="ug1-r0-wzn"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="9Xf-Eq-qch">
|
||||
<rect key="frame" x="0.0" y="235.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9Xf-Eq-qch" id="Nvt-rd-Kzh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UFh-Jt-LEP">
|
||||
<rect key="frame" x="6" y="6" width="402" height="31.5"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Reset connection"/>
|
||||
<connections>
|
||||
<action selector="resetConnection" destination="N0H-g8-v1I" eventType="touchUpInside" id="MrE-Td-fVn"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="UFh-Jt-LEP" firstAttribute="centerY" secondItem="Nvt-rd-Kzh" secondAttribute="centerY" id="7dL-fu-KmW"/>
|
||||
<constraint firstItem="UFh-Jt-LEP" firstAttribute="leading" secondItem="Nvt-rd-Kzh" secondAttribute="leadingMargin" constant="-14" id="Mbb-QD-ih0"/>
|
||||
<constraint firstItem="UFh-Jt-LEP" firstAttribute="centerX" secondItem="Nvt-rd-Kzh" secondAttribute="centerX" id="NOQ-Wx-Y2k"/>
|
||||
<constraint firstItem="UFh-Jt-LEP" firstAttribute="top" secondItem="Nvt-rd-Kzh" secondAttribute="topMargin" constant="-5" id="pJ8-md-cTD"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="TTq-yp-kei">
|
||||
<rect key="frame" x="0.0" y="279" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TTq-yp-kei" id="3Hl-hx-44M">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dpB-W6-PM0">
|
||||
<rect key="frame" x="6" y="6" width="402" height="31.5"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Make request"/>
|
||||
<connections>
|
||||
<action selector="makeRequest" destination="N0H-g8-v1I" eventType="touchUpInside" id="I8M-jS-DMV"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dpB-W6-PM0" firstAttribute="centerX" secondItem="3Hl-hx-44M" secondAttribute="centerX" id="I2F-d0-Dk9"/>
|
||||
<constraint firstItem="dpB-W6-PM0" firstAttribute="top" secondItem="3Hl-hx-44M" secondAttribute="topMargin" constant="-5" id="MZ3-rN-ZWf"/>
|
||||
<constraint firstItem="dpB-W6-PM0" firstAttribute="centerY" secondItem="3Hl-hx-44M" secondAttribute="centerY" id="UXD-Mk-QxF"/>
|
||||
<constraint firstItem="dpB-W6-PM0" firstAttribute="leading" secondItem="3Hl-hx-44M" secondAttribute="leadingMargin" constant="-14" id="sVN-if-X5P"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="log" id="hdE-4l-6RU">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="400" id="7Ix-hF-4f6">
|
||||
<rect key="frame" x="0.0" y="372.5" width="414" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7Ix-hF-4f6" id="hR3-G0-o2o">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="PlN-Ph-shZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="400"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="PlN-Ph-shZ" firstAttribute="top" secondItem="hR3-G0-o2o" secondAttribute="top" id="FYk-FQ-Jq1"/>
|
||||
<constraint firstAttribute="bottom" secondItem="PlN-Ph-shZ" secondAttribute="bottom" id="QKc-h9-7Yn"/>
|
||||
<constraint firstAttribute="trailing" secondItem="PlN-Ph-shZ" secondAttribute="trailing" id="Qxs-hq-uaU"/>
|
||||
<constraint firstItem="PlN-Ph-shZ" firstAttribute="leading" secondItem="hR3-G0-o2o" secondAttribute="leading" id="c6s-Ww-A8k"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="N0H-g8-v1I" id="4Ky-tr-Rnr"/>
|
||||
<outlet property="delegate" destination="N0H-g8-v1I" id="TYV-RP-LAU"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<connections>
|
||||
<outlet property="isCBWalletInstalledLabel" destination="crr-DB-0oO" id="OT3-0e-g8G"/>
|
||||
<outlet property="isConnectedLabel" destination="aQU-OY-KUj" id="iPl-4X-Fjj"/>
|
||||
<outlet property="logTextView" destination="PlN-Ph-shZ" id="BW2-a7-hgy"/>
|
||||
<outlet property="ownPubKeyLabel" destination="kmY-1i-sqZ" id="R7K-YM-JdI"/>
|
||||
<outlet property="peerPubKeyLabel" destination="Ax0-s5-kcP" id="Mlr-FA-CDT"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="wdV-MQ-XPI" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="142.02898550724638" y="132.58928571428572"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="labelColor">
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
42
examples/native-sdk-ios-client/SampleApp/Info.plist
Normal file
42
examples/native-sdk-ios-client/SampleApp/Info.plist
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>xyz.myapp</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>myappxyz</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>cbwallet</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
29
examples/native-sdk-ios-client/SampleApp/SceneDelegate.swift
Normal file
29
examples/native-sdk-ios-client/SampleApp/SceneDelegate.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// SceneDelegate.swift
|
||||
// SampleWeb3App
|
||||
//
|
||||
// Created by Jungho Bang on 6/27/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoinbaseWalletSDK
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
guard let url = URLContexts.first?.url else { return }
|
||||
|
||||
logWalletSegueMessage(url: url)
|
||||
|
||||
do {
|
||||
if try CoinbaseWalletSDK.shared.handleResponse(url) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
106
examples/native-sdk-ios-client/SampleApp/ViewController.swift
Normal file
106
examples/native-sdk-ios-client/SampleApp/ViewController.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// SampleWeb3App
|
||||
//
|
||||
// Created by Jungho Bang on 6/27/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoinbaseWalletSDK
|
||||
|
||||
class ViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var isCBWalletInstalledLabel: UILabel!
|
||||
@IBOutlet weak var isConnectedLabel: UILabel!
|
||||
@IBOutlet weak var ownPubKeyLabel: UILabel!
|
||||
@IBOutlet weak var peerPubKeyLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var logTextView: UITextView!
|
||||
|
||||
private let cbwallet = CoinbaseWalletSDK.shared
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
isCBWalletInstalledLabel.text = "\(CoinbaseWalletSDK.isCoinbaseWalletInstalled())"
|
||||
updateSessionStatus()
|
||||
}
|
||||
|
||||
@IBAction func initiateHandshake() {
|
||||
cbwallet.initiateHandshake(
|
||||
initialActions: [
|
||||
Action(jsonRpc: .eth_requestAccounts)
|
||||
]
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self.logObject(label: "Response:\n", response)
|
||||
case .failure(let error):
|
||||
self.log("\(error)")
|
||||
}
|
||||
self.updateSessionStatus()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func resetConnection() {
|
||||
let result = cbwallet.resetSession()
|
||||
self.log("\(result)")
|
||||
|
||||
updateSessionStatus()
|
||||
}
|
||||
|
||||
@IBAction func makeRequest() {
|
||||
cbwallet.makeRequest(
|
||||
Request(actions: [
|
||||
Action(jsonRpc: .personal_sign(address: "", message: "message".data(using: .utf8)!)),
|
||||
Action(jsonRpc: .eth_signTypedData_v3(address: "", message: Data()))
|
||||
])
|
||||
) { result in
|
||||
self.log("\(result)")
|
||||
}
|
||||
}
|
||||
|
||||
// i should have chosen SwiftUI template...
|
||||
|
||||
private func updateSessionStatus() {
|
||||
DispatchQueue.main.async {
|
||||
let isConnected = self.cbwallet.isConnected()
|
||||
self.isConnectedLabel.textColor = isConnected ? .green : .red
|
||||
self.isConnectedLabel.text = "\(isConnected)"
|
||||
|
||||
self.ownPubKeyLabel.text = self.cbwallet.keyManager.ownPublicKey.rawRepresentation.base64EncodedString()
|
||||
self.peerPubKeyLabel.text = self.cbwallet.keyManager.peerPublicKey?.rawRepresentation.base64EncodedString() ?? "(nil)"
|
||||
}
|
||||
}
|
||||
|
||||
private func logObject<T: Encodable>(label: String = "", _ object: T, function: String = #function) {
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
let data = try encoder.encode(object)
|
||||
let jsonString = String(data: data, encoding: .utf8)!
|
||||
self.log("\(label)\(jsonString)", function: function)
|
||||
} catch {
|
||||
self.log("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func logURL(_ url: URL?, function: String = #function) {
|
||||
guard let url = url else { return }
|
||||
self.log("URL: \(url)", function: function)
|
||||
|
||||
guard
|
||||
!cbwallet.isConnected(),
|
||||
let message: RequestMessage = try? MessageConverter.decode(url, with: nil)
|
||||
else { return }
|
||||
self.logObject(message, function: function)
|
||||
}
|
||||
|
||||
private func log(_ text: String, function: String = #function) {
|
||||
DispatchQueue.main.async {
|
||||
self.logTextView.text = "\(function): \(text)\n\n\(self.logTextView.text ?? "")"
|
||||
// self.logTextView.text += "\(function): \(text)\n\n"
|
||||
// self.logTextView.scrollRangeToVisible(NSMakeRange(self.logTextView.text.count - 1, 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
152
packages/wallet-native-sdk/README.md
Normal file
152
packages/wallet-native-sdk/README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Coinbase Wallet SDK for native mobile apps
|
||||
|
||||
## Example data
|
||||
|
||||
### handshake
|
||||
- URL
|
||||
`https://go.cb-w.com/wsegue?p=eyJ2ZXJzaW9uIjoiMS4yLjMiLCJzZW5kZXIiOiI0UG9QTEFDUjNCb0VxWTJBdSthamRISGRSZStvU0ZaUVwvSkpDU091bHpTYz0iLCJjb250ZW50Ijp7ImhhbmRzaGFrZSI6eyJhcHBJZCI6ImNvbS5teWFwcC5wYWNrYWdlLmlkIiwiY2FsbGJhY2siOiJodHRwczpcL1wvbXlhcHAueHl6XC9uYXRpdmUtc2RrIiwiaW5pdGlhbEFjdGlvbnMiOlt7Im1ldGhvZCI6ImV0aF9zb21lTWV0aG9kIiwicGFyYW1zIjpbInBhcmFtMSIsInBhcmFtMiJdfV19fSwidXVpZCI6IjExMUYzRTI2LTE2N0YtNEJGNS1BQzM4LTZEOTNBNjU2RTMyQiJ9`
|
||||
- JSON (decoded the URL above. handshake messages are not encrypted)
|
||||
```json
|
||||
{
|
||||
"version" : "0.1.0",
|
||||
"sender" : "bwf9U+VbjmvfBr3p3aoJyOEKS6mq7sSrg56V6FDYMBs=",
|
||||
"content" : {
|
||||
"handshake" : {
|
||||
"appId" : "com.coinbase.NativeWeb3App",
|
||||
"callback" : "https:\/\/myapp.xyz\/mycallback",
|
||||
"initialActions" : [
|
||||
{
|
||||
"paramsJson" : "{}",
|
||||
"method" : "eth_requestAccounts",
|
||||
"optional" : false
|
||||
},
|
||||
{
|
||||
"paramsJson" : "{\"fromAddress\":\"\",\"data\":\"bWVzc2FnZQ==\"}",
|
||||
"method" : "personal_sign",
|
||||
"optional" : false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"uuid" : "634A5C15-0316-4FD1-86FB-4818DBD6C12D"
|
||||
}
|
||||
```
|
||||
|
||||
### request
|
||||
- URL
|
||||
`https://go.cb-w.com/wsegue?p=eyJ2ZXJzaW9uIjoiMS4yLjMiLCJzZW5kZXIiOiJmbW5IZVh3OTNlY000QnFzSXpRTk5zb2FvS0ZxK3NOMHZRaWdvaVNQZ2dvPSIsImNvbnRlbnQiOnsicmVxdWVzdCI6eyJfMCI6IkdQYUwwTWJjbVhoVWU5eUprY0p5dnRJWVJUM3RXNEhcL1lBUmNKTXZGZlZWOWh6OXcya1h3YmxIMEJPcFN2UXI0RVhoV0FVUlwvZGFwWUVMZ2lvTkFXY3IxVlwvY1JoNDVUaFN2SFV1cHRlY0lJR0tLeGxKTWdwZ2RiMWNJRWhNWEdpVElBYkZTblJTOVlwOFowOHJcL3pkcjlBVytFQjRXSXBLcnhTTmVqQXRRTDJWTDByRWE2YzhvM0lodG5DQ1U1SzRpaVVZcTJkamd0eGRJZm1FbmhrTUFCQVwvSm5INFpBPT0ifX0sInV1aWQiOiJEMzg3MUYwRS03QkNFLTQxMjYtQjY1Ny05QjlGOEQ1NEU3N0YifQ%3D%3D`
|
||||
- encrypted JSON (decoded from the URL above)
|
||||
```json
|
||||
{
|
||||
"version": "1.2.3",
|
||||
"sender": "oEC2AZndVwTcLs3ixQyxThlHrKBNdBczbWp9OjeglGY=",
|
||||
"content": {
|
||||
"request": {
|
||||
"data": "/LzMCiGCpiYuUHp2vdlQM4V+f7hYygKI2qhX/ZWuFA6/aqZ/bmnWhROK14vtBH3sbrROqfefMXue3rbqLOg1s+xzh6iXoVavhIeCSevugp1ZlERG9q+CSuLXyRR3tou6wdsJ60jOTDjGzLCvcHp2ykglfDUr2qaVRo3i/RXsJRoPrW9CurSM9+TmNZ46aq1Y/K8lBcpq1aFUYSn7+kHHR8xBY+QoPE0yox+dZrvSi7Z16fX3uwZ3NQPmhPqQXpDFEHrZeKEzoIZAPA8NUlrajgY/1mxhbkH9tmM8X5vSG7w="
|
||||
}
|
||||
},
|
||||
"uuid": "3E445386-8CB3-4995-99EC-DCF06A60081C"
|
||||
}
|
||||
```
|
||||
- decrypted JSON (to pass via RN <> native bridge)
|
||||
```json
|
||||
{
|
||||
"version": "1.2.3",
|
||||
"sender": "qSAE/fvQ1cnZvVnKDjiHRyzK6bVQ/qJ7W29DL2aMjns=",
|
||||
"content": {
|
||||
"request": {
|
||||
"actions": [
|
||||
{
|
||||
"paramsJson" : "{}",
|
||||
"method" : "eth_requestAccounts",
|
||||
"optional" : false
|
||||
},
|
||||
{
|
||||
"paramsJson" : "{\"fromAddress\":\"\",\"data\":\"bWVzc2FnZQ==\"}",
|
||||
"method" : "personal_sign",
|
||||
"optional" : false
|
||||
}
|
||||
],
|
||||
"account": {
|
||||
"chain": "eth",
|
||||
"networkId": 17,
|
||||
"address": "0x12345678ABCD"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uuid": "853BFBB5-A6F1-4FBA-B8C6-DC2BE3CCF6DF"
|
||||
}
|
||||
```
|
||||
|
||||
### response
|
||||
- plain response JSON (to pass via RN <> native bridge)
|
||||
```json
|
||||
{
|
||||
"version": "7.13.1",
|
||||
"sender": "u9IB3p8tN8P4U2LvYfaCphd8/bXRN34eTnuQPO4g6zQ=",
|
||||
"content": {
|
||||
"response": {
|
||||
"requestId": "9D34C731-397B-473B-9850-C6F0261BC085",
|
||||
"values": [
|
||||
{
|
||||
"result": {
|
||||
"value": "return value 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"result": {
|
||||
"value": "return value 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": {
|
||||
"message": "error 1",
|
||||
"code": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": {
|
||||
"message": "error 2",
|
||||
"code": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"uuid": "C5CDDCF7-7A31-4185-BB6B-7A3B8F9B19BA"
|
||||
}
|
||||
```
|
||||
- encrypted JSON (encrypted the JSON object above)
|
||||
```json
|
||||
{
|
||||
"version": "7.13.1",
|
||||
"sender": "u9IB3p8tN8P4U2LvYfaCphd8/bXRN34eTnuQPO4g6zQ=",
|
||||
"content": {
|
||||
"response": {
|
||||
"requestId": "9D34C731-397B-473B-9850-C6F0261BC085",
|
||||
"data": "uXqgB78alErYBFXie8gP397igJ18dx+mcQcUV7K44W+96OQxNxJGm3WxEgsGMxeS8Wg1wiiRaXYRghFbwlnJuOLNpIvAiQYMOzJ1IAlFg452hwG7PrCCn6e9/xU5Hc+kzZYio355zSxSuXByo1bItCgNdjp5aocX1kUnZceV7dGjIGv66/z1k93hxGYooH0uCeI2wQ7qaZzJKfUJLgO0NFmj1fbTciv7CoVd8/mQAfHzlgaIhkHB+j4mAw=="
|
||||
}
|
||||
},
|
||||
"uuid": "C5CDDCF7-7A31-4185-BB6B-7A3B8F9B19BA"
|
||||
}
|
||||
```
|
||||
- URL
|
||||
`https://myapp.xyz/native-sdk?p=eyJ2ZXJzaW9uIjoiNy4xMy4xIiwic2VuZGVyIjoidTlJQjNwOHROOFA0VTJMdllmYUNwaGQ4XC9iWFJOMzRlVG51UVBPNGc2elE9IiwiY29udGVudCI6eyJyZXNwb25zZSI6eyJyZXF1ZXN0SWQiOiI5RDM0QzczMS0zOTdCLTQ3M0ItOTg1MC1DNkYwMjYxQkMwODUiLCJkYXRhIjoidVhxZ0I3OGFsRXJZQkZYaWU4Z1AzOTdpZ0oxOGR4K21jUWNVVjdLNDRXKzk2T1F4TnhKR20zV3hFZ3NHTXhlUzhXZzF3aWlSYVhZUmdoRmJ3bG5KdU9MTnBJdkFpUVlNT3pKMUlBbEZnNDUyaHdHN1ByQ0NuNmU5XC94VTVIYytrelpZaW8zNTV6U3hTdVhCeW8xYkl0Q2dOZGpwNWFvY1gxa1VuWmNlVjdkR2pJR3Y2NlwvejFrOTNoeEdZb29IMHVDZUkyd1E3cWFaekpLZlVKTGdPME5GbWoxZmJUY2l2N0NvVmQ4XC9tUUFmSHpsZ2FJaGtIQitqNG1Bdz09In19LCJ1dWlkIjoiQzVDRERDRjctN0EzMS00MTg1LUJCNkItN0EzQjhGOUIxOUJBIn0%3D`
|
||||
|
||||
### error
|
||||
- JSON (error messages are not encrypted)
|
||||
```json
|
||||
{
|
||||
"version": "7.13.1",
|
||||
"sender": "OzXg53x+wwIW1YCEbvP3sya7MmZT5yCQcArK3GbLmDo=",
|
||||
"content": {
|
||||
"failure": {
|
||||
"requestId": "6C7706C2-B17F-4E96-8D52-C6876C09AECB",
|
||||
"description": "error message from host"
|
||||
}
|
||||
},
|
||||
"uuid": "C6900BD5-25EE-46F4-AB01-EBB8FAB1AC76"
|
||||
}
|
||||
```
|
||||
- URL
|
||||
`https://myapp.xyz/native-sdk?p=eyJ2ZXJzaW9uIjoiNy4xMy4xIiwic2VuZGVyIjoiT3pYZzUzeCt3d0lXMVlDRWJ2UDNzeWE3TW1aVDV5Q1FjQXJLM0diTG1Ebz0iLCJjb250ZW50Ijp7ImZhaWx1cmUiOnsicmVxdWVzdElkIjoiNkM3NzA2QzItQjE3Ri00RTk2LThENTItQzY4NzZDMDlBRUNCIiwiZGVzY3JpcHRpb24iOiJlcnJvciBtZXNzYWdlIGZyb20gaG9zdCJ9fSwidXVpZCI6IkM2OTAwQkQ1LTI1RUUtNDZGNC1BQjAxLUVCQjhGQUIxQUM3NiJ9`
|
||||
189
packages/wallet-native-sdk/ios/CoinbaseWalletSDK.swift
Normal file
189
packages/wallet-native-sdk/ios/CoinbaseWalletSDK.swift
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// CoinbaseWalletSDK.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 5/20/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public final class CoinbaseWalletSDK {
|
||||
|
||||
public static func isCoinbaseWalletInstalled() -> Bool {
|
||||
return UIApplication.shared.canOpenURL(URL(string: "cbwallet://")!)
|
||||
}
|
||||
|
||||
// MARK: - Constructor
|
||||
|
||||
static private var host: URL?
|
||||
static private var callback: URL?
|
||||
|
||||
static public func configure(
|
||||
host: URL = URL(string: "https://go.cb-w.com/wsegue")!,
|
||||
callback: URL
|
||||
) {
|
||||
guard self.host == nil && self.callback == nil else {
|
||||
assertionFailure("`CoinbaseWalletSDK.configure` should be called only once.")
|
||||
return
|
||||
}
|
||||
|
||||
self.host = host
|
||||
if callback.pathComponents.count < 2 { // [] or ["/"]
|
||||
self.callback = callback.appendingPathComponent("wsegue")
|
||||
} else {
|
||||
self.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
static public var shared: CoinbaseWalletSDK = {
|
||||
guard let host = CoinbaseWalletSDK.host,
|
||||
let callback = CoinbaseWalletSDK.callback else {
|
||||
preconditionFailure("Missing configuration: call `CoinbaseWalletSDK.configure` before accessing the `shared` instance.")
|
||||
}
|
||||
|
||||
return CoinbaseWalletSDK(host: host, callback: callback)
|
||||
}()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let appId: String
|
||||
private let host: URL
|
||||
private let callback: URL
|
||||
private let version: String
|
||||
|
||||
public lazy var keyManager: KeyManager = {
|
||||
KeyManager(host: self.host)
|
||||
}()
|
||||
private lazy var taskManager: TaskManager = {
|
||||
TaskManager()
|
||||
}()
|
||||
|
||||
private init(
|
||||
host: URL,
|
||||
callback: URL
|
||||
) {
|
||||
self.host = host
|
||||
self.callback = callback
|
||||
|
||||
self.appId = Bundle.main.bundleIdentifier!
|
||||
|
||||
let sdkBundle = Bundle(for: Self.self)
|
||||
self.version = sdkBundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0"
|
||||
}
|
||||
|
||||
// MARK: - Send message
|
||||
|
||||
public func initiateHandshake(initialActions: [Action]? = nil, onResponse: @escaping ResponseHandler) {
|
||||
try? keyManager.resetOwnPrivateKey()
|
||||
let message = RequestMessage(
|
||||
uuid: UUID(),
|
||||
sender: keyManager.ownPublicKey,
|
||||
content: .handshake(
|
||||
appId: appId,
|
||||
callback: callback,
|
||||
initialActions: initialActions
|
||||
),
|
||||
version: version,
|
||||
timestamp: Date()
|
||||
)
|
||||
self.send(message, onResponse)
|
||||
}
|
||||
|
||||
public func makeRequest(_ request: Request, onResponse: @escaping ResponseHandler) {
|
||||
let message = RequestMessage(
|
||||
uuid: UUID(),
|
||||
sender: keyManager.ownPublicKey,
|
||||
content: .request(actions: request.actions, account: request.account),
|
||||
version: version,
|
||||
timestamp: Date()
|
||||
)
|
||||
return self.send(message, onResponse)
|
||||
}
|
||||
|
||||
private func send(_ request: RequestMessage, _ onResponse: @escaping ResponseHandler) {
|
||||
let url: URL
|
||||
do {
|
||||
url = try MessageConverter.encode(request, to: host, with: keyManager.symmetricKey)
|
||||
} catch {
|
||||
onResponse(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
UIApplication.shared.open(
|
||||
url,
|
||||
options: [.universalLinksOnly: url.isHttp]
|
||||
) { result in
|
||||
guard result == true else {
|
||||
onResponse(.failure(Error.openUrlFailed))
|
||||
return
|
||||
}
|
||||
|
||||
self.taskManager.registerResponseHandler(for: request, onResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Receive message
|
||||
|
||||
private func isWalletSegueMessage(_ url: URL) -> Bool {
|
||||
return url.host == callback.host && url.path == callback.path
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func handleResponse(_ url: URL) throws -> Bool {
|
||||
guard isWalletSegueMessage(url) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let response = try decodeResponse(url)
|
||||
taskManager.runResponseHandler(with: response)
|
||||
return true
|
||||
}
|
||||
|
||||
private func decodeResponse(_ url: URL) throws -> ResponseMessage {
|
||||
if let symmetricKey = keyManager.symmetricKey {
|
||||
return try MessageConverter.decode(url, with: symmetricKey)
|
||||
}
|
||||
|
||||
// no symmetric key yet
|
||||
let encryptedResponse: EncryptedResponseMessage = try MessageConverter.decodeWithoutDecryption(url)
|
||||
let request = taskManager.findRequest(for: encryptedResponse)
|
||||
guard case .handshake = request?.content else {
|
||||
throw Error.missingSymmetricKey
|
||||
}
|
||||
|
||||
try handleHandshakeResponse(encryptedResponse)
|
||||
|
||||
return try encryptedResponse.decrypt(with: keyManager.symmetricKey)
|
||||
}
|
||||
|
||||
// MARK: - Session
|
||||
|
||||
public func isConnected() -> Bool {
|
||||
return keyManager.symmetricKey != nil
|
||||
}
|
||||
|
||||
public var sessionPublicKey: PublicKey {
|
||||
return keyManager.ownPublicKey
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func resetSession() -> Result<Void, Swift.Error> {
|
||||
do {
|
||||
taskManager.reset()
|
||||
try keyManager.resetOwnPrivateKey()
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleHandshakeResponse(_ response: EncryptedResponseMessage) throws {
|
||||
if case .failure = response.content {
|
||||
return
|
||||
}
|
||||
|
||||
try keyManager.storePeerPublicKey(response.sender)
|
||||
}
|
||||
}
|
||||
19
packages/wallet-native-sdk/ios/Error.swift
Normal file
19
packages/wallet-native-sdk/ios/Error.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Error.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/16/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension CoinbaseWalletSDK {
|
||||
enum Error: Swift.Error {
|
||||
case encodingFailed
|
||||
case decodingFailed
|
||||
case missingSymmetricKey
|
||||
case openUrlFailed
|
||||
case walletReturnedError(String)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Key+RawRepresentable.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol RawRepresentableKey: Codable {
|
||||
init<D>(rawRepresentation data: D) throws where D: ContiguousBytes
|
||||
var rawRepresentation: Data { get }
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension CoinbaseWalletSDK.PrivateKey: RawRepresentableKey {}
|
||||
@available(iOS 13.0, *)
|
||||
extension CoinbaseWalletSDK.PublicKey: RawRepresentableKey {}
|
||||
|
||||
extension RawRepresentableKey {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let data = try container.decode(Data.self)
|
||||
try self.init(rawRepresentation: data)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.rawRepresentation)
|
||||
}
|
||||
}
|
||||
66
packages/wallet-native-sdk/ios/Key/KeyManager.swift
Normal file
66
packages/wallet-native-sdk/ios/Key/KeyManager.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// KeyManager.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/9/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension CoinbaseWalletSDK {
|
||||
public typealias PrivateKey = Curve25519.KeyAgreement.PrivateKey
|
||||
public typealias PublicKey = Curve25519.KeyAgreement.PublicKey
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public final class KeyManager {
|
||||
private(set) var ownPrivateKey: CoinbaseWalletSDK.PrivateKey
|
||||
public var ownPublicKey: CoinbaseWalletSDK.PublicKey {
|
||||
return ownPrivateKey.publicKey
|
||||
}
|
||||
|
||||
public private(set) var peerPublicKey: CoinbaseWalletSDK.PublicKey?
|
||||
|
||||
private(set) var symmetricKey: SymmetricKey?
|
||||
|
||||
// MARK: - methods
|
||||
|
||||
private let storage: KeyStorage
|
||||
|
||||
init(host: URL) {
|
||||
self.storage = KeyStorage(host: host)
|
||||
|
||||
guard let storedKey = try? storage.read(.ownPrivateKey) else {
|
||||
// generate new private key
|
||||
self.ownPrivateKey = CoinbaseWalletSDK.PrivateKey()
|
||||
try? self.resetOwnPrivateKey(with: ownPrivateKey)
|
||||
return
|
||||
}
|
||||
self.ownPrivateKey = storedKey
|
||||
|
||||
self.peerPublicKey = try? storage.read(.peerPublicKey)
|
||||
|
||||
if let peerPublicKey = self.peerPublicKey {
|
||||
self.symmetricKey = try? Cipher.deriveSymmetricKey(with: ownPrivateKey, peerPublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
func resetOwnPrivateKey(with key: CoinbaseWalletSDK.PrivateKey = CoinbaseWalletSDK.PrivateKey()) throws {
|
||||
self.symmetricKey = nil
|
||||
self.peerPublicKey = nil
|
||||
self.ownPrivateKey = key
|
||||
|
||||
try storage.store(key, at: .ownPrivateKey)
|
||||
|
||||
try storage.delete(.peerPublicKey)
|
||||
}
|
||||
|
||||
func storePeerPublicKey(_ key: CoinbaseWalletSDK.PublicKey) throws {
|
||||
self.peerPublicKey = key
|
||||
self.symmetricKey = try Cipher.deriveSymmetricKey(with: ownPrivateKey, key)
|
||||
|
||||
try storage.store(key, at: .peerPublicKey)
|
||||
}
|
||||
}
|
||||
87
packages/wallet-native-sdk/ios/Key/KeyStorage.swift
Normal file
87
packages/wallet-native-sdk/ios/Key/KeyStorage.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// KeyStorage.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/17/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
final class KeyStorage {
|
||||
private let service: String
|
||||
|
||||
init(host: URL) {
|
||||
service = "wsegue.keystorage.\((host.isHttp ? host.host : host.scheme) ?? host.absoluteString)"
|
||||
}
|
||||
|
||||
func store<K>(_ data: K, at item: KeyStorageItem<K>) throws {
|
||||
try? self.delete(item)
|
||||
|
||||
let query = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrAccount: item.name,
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
kSecUseDataProtectionKeychain: true,
|
||||
kSecValueData: data.rawRepresentation
|
||||
] as [String: Any]
|
||||
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
guard status == errSecSuccess else {
|
||||
throw KeyStorage.Error.storeFailed(status.message)
|
||||
}
|
||||
}
|
||||
|
||||
func read<K>(_ item: KeyStorageItem<K>) throws -> K? {
|
||||
let query = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrAccount: item.name,
|
||||
kSecAttrService: service,
|
||||
kSecUseDataProtectionKeychain: true,
|
||||
kSecReturnData: true
|
||||
] as [String: Any]
|
||||
|
||||
var item: CFTypeRef?
|
||||
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
||||
case errSecSuccess:
|
||||
guard let data = item as? Data else { return nil }
|
||||
return try K(rawRepresentation: data) // Convert back to a key.
|
||||
case errSecItemNotFound:
|
||||
return nil
|
||||
case let status:
|
||||
throw KeyStorage.Error.readFailed(status.message)
|
||||
}
|
||||
}
|
||||
|
||||
func delete<K>(_ item: KeyStorageItem<K>) throws {
|
||||
let query = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecUseDataProtectionKeychain: true,
|
||||
kSecAttrAccount: item.name,
|
||||
kSecAttrService: service
|
||||
] as [String: Any]
|
||||
|
||||
switch SecItemDelete(query as CFDictionary) {
|
||||
case errSecItemNotFound, errSecSuccess:
|
||||
break // Okay to ignore
|
||||
case let status:
|
||||
throw KeyStorage.Error.deleteFailed(status.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension KeyStorage {
|
||||
enum Error: Swift.Error {
|
||||
case storeFailed(String)
|
||||
case readFailed(String)
|
||||
case deleteFailed(String)
|
||||
}
|
||||
}
|
||||
|
||||
extension OSStatus {
|
||||
var message: String {
|
||||
return (SecCopyErrorMessageString(self, nil) as String?) ?? String(self)
|
||||
}
|
||||
}
|
||||
25
packages/wallet-native-sdk/ios/Key/KeyStorageItem.swift
Normal file
25
packages/wallet-native-sdk/ios/Key/KeyStorageItem.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// KeyStorageItem.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/17/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct KeyStorageItem<K: RawRepresentableKey> {
|
||||
let name: String
|
||||
|
||||
init(_ name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
static var ownPrivateKey: KeyStorageItem<CoinbaseWalletSDK.PrivateKey> {
|
||||
return KeyStorageItem<CoinbaseWalletSDK.PrivateKey>("wsegue.ownPrivateKey")
|
||||
}
|
||||
|
||||
static var peerPublicKey: KeyStorageItem<CoinbaseWalletSDK.PublicKey> {
|
||||
return KeyStorageItem<CoinbaseWalletSDK.PublicKey>("wsegue.peerPublicKey")
|
||||
}
|
||||
}
|
||||
45
packages/wallet-native-sdk/ios/Message/Cipher.swift
Normal file
45
packages/wallet-native-sdk/ios/Message/Cipher.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Cryptography.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/17/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public final class Cipher {
|
||||
static func encrypt<C: Encodable>(
|
||||
_ content: C,
|
||||
with symmetricKey: SymmetricKey
|
||||
) throws -> Data {
|
||||
let jsonData = try JSONEncoder().encode(content)
|
||||
let encrypted = try AES.GCM.seal(jsonData, using: symmetricKey).combined!
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
static func decrypt<C: Decodable>(
|
||||
_ data: Data,
|
||||
with symmetricKey: SymmetricKey
|
||||
) throws -> C {
|
||||
let sealedBox = try AES.GCM.SealedBox(combined: data)
|
||||
let decrypted = try AES.GCM.open(sealedBox, using: symmetricKey)
|
||||
|
||||
return try JSONDecoder().decode(C.self, from: decrypted)
|
||||
}
|
||||
|
||||
public static func deriveSymmetricKey(
|
||||
with ownPrivateKey: CoinbaseWalletSDK.PrivateKey,
|
||||
_ peerPublicKey: CoinbaseWalletSDK.PublicKey
|
||||
) throws -> SymmetricKey {
|
||||
let sharedSecret = try ownPrivateKey.sharedSecretFromKeyAgreement(with: peerPublicKey)
|
||||
return sharedSecret.hkdfDerivedSymmetricKey(
|
||||
using: SHA256.self,
|
||||
salt: Data(),
|
||||
sharedInfo: Data(),
|
||||
outputByteCount: 32
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// EncryptedMessage.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/23/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public protocol EncryptedContent: BaseContent {
|
||||
associatedtype Unencrypted: UnencryptedContent where Unencrypted.Encrypted == Self
|
||||
|
||||
func decrypt(with symmetricKey: SymmetricKey?) throws -> Unencrypted
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
typealias EncryptedMessage<C> = BaseMessage<C> where C: EncryptedContent
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension EncryptedMessage {
|
||||
func decrypt(with symmetricKey: SymmetricKey?) throws -> Message<C.Unencrypted> {
|
||||
return Message<C.Unencrypted>.copy(
|
||||
self,
|
||||
replaceContentWith: try self.content.decrypt(with: symmetricKey)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension Message {
|
||||
func encrypt(with symmetricKey: SymmetricKey?) throws -> EncryptedMessage<C.Encrypted> {
|
||||
return EncryptedMessage<C.Encrypted>.copy(
|
||||
self,
|
||||
replaceContentWith: try self.content.encrypt(with: symmetricKey)
|
||||
)
|
||||
}
|
||||
}
|
||||
43
packages/wallet-native-sdk/ios/Message/Message.swift
Normal file
43
packages/wallet-native-sdk/ios/Message/Message.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Message.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias Message<C> = BaseMessage<C> where C: UnencryptedContent
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public protocol UnencryptedContent: BaseContent {
|
||||
associatedtype Encrypted: EncryptedContent where Encrypted.Unencrypted == Self
|
||||
|
||||
func encrypt(with symmetricKey: SymmetricKey?) throws -> Encrypted
|
||||
}
|
||||
|
||||
// MARK: - base types
|
||||
|
||||
public protocol BaseContent: Codable {}
|
||||
extension Array : BaseContent where Element : BaseContent {}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct BaseMessage<C: BaseContent>: Codable {
|
||||
public let uuid: UUID
|
||||
public let sender: CoinbaseWalletSDK.PublicKey
|
||||
public let content: C
|
||||
public let version: String
|
||||
public let timestamp: Date
|
||||
|
||||
static func copy<T>(_ orig: BaseMessage<T>, replaceContentWith content: C) -> BaseMessage<C> {
|
||||
return BaseMessage<C>.init(
|
||||
uuid: orig.uuid,
|
||||
sender: orig.sender,
|
||||
content: content,
|
||||
version: orig.version,
|
||||
timestamp: orig.timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// MessageRenderer.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/14/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public final class MessageConverter {
|
||||
public static func encode<C>(
|
||||
_ message: Message<C>,
|
||||
to recipient: URL,
|
||||
with symmetricKey: SymmetricKey?
|
||||
) throws -> URL {
|
||||
let encrypted = try message.encrypt(with: symmetricKey)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .millisecondsSince1970
|
||||
let data = try encoder.encode(encrypted)
|
||||
let encodedString = data.base64EncodedString()
|
||||
|
||||
var urlComponents = URLComponents(url: recipient, resolvingAgainstBaseURL: true)
|
||||
urlComponents?.queryItems = [URLQueryItem(name: "p", value: encodedString)]
|
||||
|
||||
guard let url = urlComponents?.url else {
|
||||
throw CoinbaseWalletSDK.Error.encodingFailed
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
public static func decode<C>(
|
||||
_ url: URL,
|
||||
with symmetricKey: SymmetricKey?
|
||||
) throws -> Message<C> {
|
||||
let encrypted: EncryptedMessage<C.Encrypted> = try self.decodeWithoutDecryption(url)
|
||||
|
||||
return try encrypted.decrypt(with: symmetricKey)
|
||||
}
|
||||
|
||||
static func decodeWithoutDecryption<C>(
|
||||
_ url: URL
|
||||
) throws -> EncryptedMessage<C> {
|
||||
guard
|
||||
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let queryItem = urlComponents.queryItems?.first(where: { $0.name == "p" }),
|
||||
let encodedString = queryItem.value
|
||||
else {
|
||||
throw CoinbaseWalletSDK.Error.decodingFailed
|
||||
}
|
||||
|
||||
guard let data = Data(base64Encoded: encodedString) else {
|
||||
throw CoinbaseWalletSDK.Error.decodingFailed
|
||||
}
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .millisecondsSince1970
|
||||
return try decoder.decode(EncryptedMessage<C>.self, from: data)
|
||||
}
|
||||
}
|
||||
20
packages/wallet-native-sdk/ios/Message/Request/Account.swift
Normal file
20
packages/wallet-native-sdk/ios/Message/Request/Account.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Account.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Account: Codable {
|
||||
public let chain: String
|
||||
public let networkId: UInt
|
||||
public let address: String
|
||||
|
||||
public init(chain: String, networkId: UInt, address: String) {
|
||||
self.chain = chain
|
||||
self.networkId = networkId
|
||||
self.address = address
|
||||
}
|
||||
}
|
||||
31
packages/wallet-native-sdk/ios/Message/Request/Action.swift
Normal file
31
packages/wallet-native-sdk/ios/Message/Request/Action.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Action.swift
|
||||
// WalletSegueHost
|
||||
//
|
||||
// Created by Jungho Bang on 6/21/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Action: Codable {
|
||||
let method: String
|
||||
let paramsJson: String
|
||||
let optional: Bool
|
||||
|
||||
public init(method: String, params: [String: Any], optional: Bool = false) {
|
||||
self.method = method
|
||||
self.paramsJson = String(data: try! JSONSerialization.data(withJSONObject: params), encoding: .utf8) ?? ""
|
||||
self.optional = optional
|
||||
}
|
||||
}
|
||||
|
||||
extension Action {
|
||||
public init(jsonRpc: Web3JSONRPC, optional: Bool = false) {
|
||||
let (method, params) = jsonRpc.rawValues
|
||||
self.init(
|
||||
method: method,
|
||||
params: params,
|
||||
optional: optional
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// EncryptedRequestContent.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/23/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public enum EncryptedRequestContent: EncryptedContent {
|
||||
case handshake(appId: String, callback: URL, initialActions: [Action]?)
|
||||
case request(data: Data)
|
||||
|
||||
public func decrypt(with symmetricKey: SymmetricKey?) throws -> RequestContent {
|
||||
switch self {
|
||||
case let .handshake(appId, callback, initialActions):
|
||||
return .handshake(appId: appId, callback: callback, initialActions: initialActions)
|
||||
case let .request(data):
|
||||
guard let symmetricKey = symmetricKey else {
|
||||
throw CoinbaseWalletSDK.Error.missingSymmetricKey
|
||||
}
|
||||
let request: Request = try Cipher.decrypt(data, with: symmetricKey)
|
||||
return .request(actions: request.actions, account: request.account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension RequestContent {
|
||||
public func encrypt(with symmetricKey: SymmetricKey?) throws -> EncryptedRequestContent {
|
||||
switch self {
|
||||
case let .handshake(appId, callback, initialActions):
|
||||
return .handshake(appId: appId, callback: callback, initialActions: initialActions)
|
||||
case let .request(actions, account):
|
||||
guard let symmetricKey = symmetricKey else {
|
||||
throw CoinbaseWalletSDK.Error.missingSymmetricKey
|
||||
}
|
||||
let request = Request(actions: actions, account: account)
|
||||
return .request(data: try Cipher.encrypt(request, with: symmetricKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
packages/wallet-native-sdk/ios/Message/Request/Request.swift
Normal file
18
packages/wallet-native-sdk/ios/Message/Request/Request.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Request.swift
|
||||
// WalletSegueHost
|
||||
//
|
||||
// Created by Jungho Bang on 6/21/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Request: Codable {
|
||||
public let actions: [Action]
|
||||
public let account: Account?
|
||||
|
||||
public init(actions: [Action], account: Account? = nil) {
|
||||
self.actions = actions
|
||||
self.account = account
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// RequestMessage.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/9/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public enum RequestContent: UnencryptedContent {
|
||||
case handshake(appId: String, callback: URL, initialActions: [Action]?)
|
||||
case request(actions: [Action], account: Account? = nil)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias RequestMessage = Message<RequestContent>
|
||||
106
packages/wallet-native-sdk/ios/Message/Request/Web3JSONRPC.swift
Normal file
106
packages/wallet-native-sdk/ios/Message/Request/Web3JSONRPC.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Web3JSONRPC.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/28/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public typealias BigInt = String
|
||||
|
||||
public enum Web3JSONRPC: Codable {
|
||||
case eth_requestAccounts
|
||||
|
||||
case personal_sign(
|
||||
address: String,
|
||||
message: Data
|
||||
)
|
||||
|
||||
case eth_signTypedData_v3(
|
||||
address: String,
|
||||
message: Data
|
||||
)
|
||||
|
||||
case eth_signTypedData_v4(
|
||||
address: String,
|
||||
message: Data
|
||||
)
|
||||
|
||||
case eth_signTransaction(
|
||||
fromAddress: String,
|
||||
toAddress: String?,
|
||||
weiValue: BigInt,
|
||||
data: Data,
|
||||
nonce: Int?,
|
||||
gasPriceInWei: BigInt?,
|
||||
maxFeePerGas: BigInt?,
|
||||
maxPriorityFeePerGas: BigInt?,
|
||||
gasLimit: BigInt?,
|
||||
chainId: Int
|
||||
)
|
||||
|
||||
case eth_sendTransaction(
|
||||
fromAddress: String,
|
||||
toAddress: String?,
|
||||
weiValue: BigInt,
|
||||
data: Data,
|
||||
nonce: Int?,
|
||||
gasPriceInWei: BigInt?,
|
||||
maxFeePerGas: BigInt?,
|
||||
maxPriorityFeePerGas: BigInt?,
|
||||
gasLimit: BigInt?,
|
||||
chainId: Int
|
||||
)
|
||||
|
||||
case wallet_switchEthereumChain(chainId: Int)
|
||||
|
||||
case wallet_addEthereumChain(
|
||||
chainId: Int,
|
||||
blockExplorerUrls: [String]?,
|
||||
chainName: String?,
|
||||
iconUrls: [String]?,
|
||||
nativeCurrency: AddChainNativeCurrency?,
|
||||
rpcUrls: [String]
|
||||
)
|
||||
|
||||
case wallet_watchAsset(
|
||||
type: String,
|
||||
options: WatchAssetOptions
|
||||
)
|
||||
|
||||
var rawValues: (method: String, params: [String: Any]) {
|
||||
let json = try! JSONEncoder().encode(self)
|
||||
let dictionary = try! JSONSerialization.jsonObject(with: json) as! [String: [String: Any]]
|
||||
|
||||
let method = dictionary.keys.first!
|
||||
let params = dictionary[method]!
|
||||
return (method, params)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AddChainNativeCurrency: Codable {
|
||||
let name: String
|
||||
let symbol: String
|
||||
let decimals: Int
|
||||
|
||||
public init(name: String, symbol: String, decimals: Int) {
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.decimals = decimals
|
||||
}
|
||||
}
|
||||
|
||||
public struct WatchAssetOptions: Codable {
|
||||
let address: String
|
||||
let symbol: String?
|
||||
let decimals: Int?
|
||||
let image: String?
|
||||
|
||||
public init(address: String, symbol: String?, decimals: Int?, image: String?) {
|
||||
self.address = address
|
||||
self.symbol = symbol
|
||||
self.decimals = decimals
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// EncryptedResponseContent.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/23/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public enum EncryptedResponseContent: EncryptedContent {
|
||||
case response(requestId: UUID, data: Data)
|
||||
case failure(requestId: UUID, description: String)
|
||||
|
||||
public func decrypt(with symmetricKey: SymmetricKey?) throws -> ResponseContent {
|
||||
switch self {
|
||||
case let .response(requestId, encryptedResults):
|
||||
guard let symmetricKey = symmetricKey else {
|
||||
throw CoinbaseWalletSDK.Error.missingSymmetricKey
|
||||
}
|
||||
let values: [ReturnValue] = try Cipher.decrypt(encryptedResults, with: symmetricKey)
|
||||
return .response(requestId: requestId, values: values)
|
||||
case let .failure(requestId, description):
|
||||
return .failure(requestId: requestId, description: description)
|
||||
}
|
||||
}
|
||||
|
||||
var requestId: UUID {
|
||||
switch self {
|
||||
case .response(let requestId, _),
|
||||
.failure(let requestId, _):
|
||||
return requestId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension ResponseContent {
|
||||
public func encrypt(with symmetricKey: SymmetricKey?) throws -> EncryptedResponseContent {
|
||||
switch self {
|
||||
case let .response(requestId, results):
|
||||
guard let symmetricKey = symmetricKey else {
|
||||
throw CoinbaseWalletSDK.Error.missingSymmetricKey
|
||||
}
|
||||
let encrypted = try Cipher.encrypt(results, with: symmetricKey)
|
||||
return .response(requestId: requestId, data: encrypted)
|
||||
case let .failure(requestId, description):
|
||||
return .failure(requestId: requestId, description: description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
typealias EncryptedResponseMessage = EncryptedMessage<EncryptedResponseContent>
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Response.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public enum ResponseContent: UnencryptedContent {
|
||||
case response(requestId: UUID, values: [ReturnValue])
|
||||
case failure(requestId: UUID, description: String)
|
||||
|
||||
var requestId: UUID {
|
||||
switch self {
|
||||
case .response(let requestId, _),
|
||||
.failure(let requestId, _):
|
||||
return requestId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias ResponseMessage = Message<ResponseContent>
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ReturnValue.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/24/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum ReturnValue: BaseContent {
|
||||
case result(value: String)
|
||||
case error(code: Int, message: String)
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias ResponseResult = Result<BaseMessage<[ReturnValue]>, Error>
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias ResponseHandler = (ResponseResult) -> Void
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension ResponseMessage {
|
||||
var result: ResponseResult {
|
||||
switch self.content {
|
||||
case .response(_, let results):
|
||||
return .success(
|
||||
BaseMessage<[ReturnValue]>.copy(self, replaceContentWith: results)
|
||||
)
|
||||
case .failure(_, let description):
|
||||
return .failure(CoinbaseWalletSDK.Error.walletReturnedError(description))
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/wallet-native-sdk/ios/Message/URL+extension.swift
Normal file
14
packages/wallet-native-sdk/ios/Message/URL+extension.swift
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// URL+extension.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/29/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
var isHttp: Bool {
|
||||
return self.scheme?.lowercased().hasPrefix("http") ?? false
|
||||
}
|
||||
}
|
||||
14
packages/wallet-native-sdk/ios/README.md
Normal file
14
packages/wallet-native-sdk/ios/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# CoinbaseWalletSDK
|
||||
|
||||
## Installation
|
||||
|
||||
CoinbaseWalletSDK is available through [CocoaPods](https://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'CoinbaseWalletSDK'
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
jungho.bang@coinbase.com
|
||||
15
packages/wallet-native-sdk/ios/Task/Task.swift
Normal file
15
packages/wallet-native-sdk/ios/Task/Task.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Task.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/14/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct Task {
|
||||
let request: RequestMessage
|
||||
let handler: ResponseHandler
|
||||
let timestamp: Date
|
||||
}
|
||||
50
packages/wallet-native-sdk/ios/Task/TaskManager.swift
Normal file
50
packages/wallet-native-sdk/ios/Task/TaskManager.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// TaskManager.swift
|
||||
// WalletSegue
|
||||
//
|
||||
// Created by Jungho Bang on 6/9/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
class TaskManager {
|
||||
private var tasks = [UUID: Task]()
|
||||
|
||||
func registerResponseHandler(
|
||||
for request: RequestMessage,
|
||||
_ handler: @escaping ResponseHandler
|
||||
) {
|
||||
let uuid = request.uuid
|
||||
tasks[uuid] = Task(
|
||||
request: request,
|
||||
handler: handler,
|
||||
timestamp: Date()
|
||||
)
|
||||
}
|
||||
|
||||
@discardableResult func runResponseHandler(with response: ResponseMessage) -> Bool {
|
||||
let requestId = response.content.requestId
|
||||
|
||||
guard let task = tasks[requestId] else {
|
||||
return false
|
||||
}
|
||||
|
||||
task.handler(response.result)
|
||||
tasks.removeValue(forKey: requestId)
|
||||
return true
|
||||
}
|
||||
|
||||
func findRequest(for response: EncryptedResponseMessage) -> RequestMessage? {
|
||||
guard let task = tasks[response.content.requestId] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return task.request
|
||||
}
|
||||
|
||||
func reset() {
|
||||
tasks.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user