Integration of Tesseract into your wallet allows any dApp to request your wallet to sign a transaction. Currently Tesseract enables native dApps to integrate with wallets through IPC, which from the user perspective is just a modal screen from the wallet.
Getting Tesseract to work in iOS wallet is different from everywhere else only by the transports set it supports.
Currently we provide IPC transport, which allows the wallets to present their screens on top of iOS applications on request and sign the transactions.
Add new Action Extension target to the your Wallet project. This will be your Wallet interface for the dApps.
- Add Tesseract.swift repository dependency through
File
->Add Package
menu in Xcode. - Add
TesseractService
framework dependency to your Extension target.
- Edit Extension target attributes in its
Info.plist
and add supported ptorocols (in this exampletest
andsubstrate-v1
protocol.)
<?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>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionServiceRoleType</key>
<string>NSExtensionServiceRoleTypeEditor</string>
<key>NSExtensionActivationRule</key>
<string>
SUBQUERY(
extensionItems,
$item,
SUBQUERY(
$item.attachments,
$att,
ANY $att.registeredTypeIdentifiers UTI-CONFORMS-TO "one.tesseract.test"
OR ANY $att.registeredTypeIdentifiers UTI-CONFORMS-TO "one.tesseract.substrate-v1"
).@count == $item.attachments.@count
).@count == 1
</string>
<key>NSExtensionServiceAllowsFinderPreviewItem</key>
<true/>
<key>NSExtensionServiceAllowsTouchBarItem</key>
<true/>
<key>NSExtensionServiceFinderPreviewIconName</key>
<string>NSActionTemplate</string>
<key>NSExtensionServiceTouchBarBezelColorName</key>
<string>TouchBarBezel</string>
<key>NSExtensionServiceTouchBarIconName</key>
<string>NSActionTemplate</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
</dict>
</dict>
</plist>
- Add URL schemes to your
Info.plist
of the Wallet target
<?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>one.tesseract</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tesseract+test</string>
<string>tesseract+substrate-v1</string>
</array>
</dict>
</array>
</dict>
</plist>
Through Tesseract, wallets serve the dApps by providing services accessible from the outside. A service implementation is responsible for understanding requests, providing the user with confirmation UI and replying back.
To make Tesseract work in your wallet, you need to describe, how exactly the wallet wants to react when a dApp needs something.
In Tesseract this is done via services. One service per blockchain protocol. The way the wallet signs transactions i.e. for Subsrate and for Ethereum is very different, thus every service has its own API to implement.
Let's take a look at the TestService
implementation example from Examples.:
import TesseractService
protocol TestSigningServiceDelegate: AnyObject {
func acceptTx(tx: String) async throws -> Bool
}
class TestSigningService: TestService {
var signature: String
weak var delegate: TestSigningServiceDelegate?
init(delegate: TestSigningServiceDelegate, signature: String) {
self.delegate = delegate
self.signature = signature
}
func signTransation(req: String) async throws -> String {
guard let delegate = self.delegate else {
throw TesseractError.null(TestSigningServiceDelegate.self)
}
guard try await delegate.acceptTx(tx: req) else {
throw TesseractError.cancelled
}
return req + signature
}
}
The wallet is responsible to present the user with the relevant UI and reply with a response in case the user agrees to proced. Otherwise just throw TesseractError.cancelled
. Or any other error if, in example, the request data is malformed.
For now Tesseract supports only iOS IPC transport which called IPCTransportIOS
.
Transport should be created and provided to the Tesseract
instance. See example bellow.
Full documentation on Transports development can be found here: Transports How To
Here is a typical Tesseract initialization snippet (the example is taken from Example):
ActionViewController
is a root ViewController provided in Extension Info.plist
file.
import TesseractService
class ActionViewController: UIViewController, TestSigningServiceDelegate {
var tesseract: Tesseract!
override func viewDidLoad() {
super.viewDidLoad()
let data = WalletData()
let service = TestSigningService(delegate: self, signature: data.signature)
self.tesseract = try! Tesseract()
.transport(IPCTransportIOS(self))
.service(service)
// add all supported services through .service() calls
}
@MainActor
func acceptTx(tx: String) async throws -> Bool {
// Show UI
}
}
We tried our best to present an API as easy for the wallet developer as we could and handled all the edge cases we know of inside the library. At least we improved it to the point that it satisfied us while building the dev-wallet.swift.
If you have any suggestions, please, create an issue or submit a PR.
Thanks!