Tesseract Systems, Inc.
tesseract-one/Substrate.swiftGitHub仓库
Substrate.swift
基于Substrate网络的Swift SDK
包管理器
将以下依赖项添加到您的 Package.swift
.package(url: "https://github.com/tesseract-one/Substrate.swift.git", from: "0.0.1")
并将库依赖项添加到您的目标
// Main and RPC
.product(name: "Substrate", package: "Substrate.swift"),
.product(name: "SubstrateRPC", package: "Substrate.swift"),
// Keychain
.product(name: "SubstrateKeychain", package: "Substrate.swift"),
运行 swift build
并构建您的应用程序。
CocoaPods
将以下内容添加到您的 Podfile
# Main and RPC
pod 'Substrate', '~> 0.0.1'
# RPC
pod 'Substrate-RPC', '~> 0.0.1'
# Keychain
pod 'Substrate-Keychain', '~> 0.0.1'
然后运行 pod install
示例和文档
此存储库包含一个 示例项目,它提供了熟悉 SDK API 的良好起点。
下一个步骤是查看 文档,它有助于了解如何实现自定义配置和静态类型。
以下是一组顶级 API 示例,带有动态配置,可以在大多数基于 Substrate 的网络上使用。
初始化
初始化时,Api
需要客户端和运行时配置。目前只有一个客户端 - JsonRPC。
import Substrate
import SubstrateRPC
let nodeUrl = URL(string: "wss://westend-rpc.polkadot.io")!
// Dynamic Config should work for almost all Substrate based networks.
// It's not most eficient though because uses a lot of dynamic types
let substrate = try await Api(
rpc: JsonRpcClient(.ws(url: nodeUrl)),
config: .dynamicBlake2
)
提交外部数据
import SubstrateKeychain
// Create KeyPair for signing
let mnemonic = "your key 12 words"
let from = try Sr25519KeyPair(parsing: mnemonic + "//Key1") // hard key derivation
// Create recipient address from ss58 string
let to = try substrate.runtime.address(ss58: "recipient s58 address")
// Dynamic Call type with Map parameters.
// any ValueRepresentable type can be used as parameter
let call = AnyCall(name: "transfer",
pallet: "Balances",
params: ["dest": to, "value": 15483812850])
// Create Submittable (transaction) from the call
let tx = try await substrate.tx.new(call)
// We are using direct signer API here
// Or we can set Keychain as signer in Api and provide `account` parameter
// `waitForFinalized()` will wait for block finalization
// `waitForInBlock()` will wait for inBlock status
// `success()` will search for failed event and throw in case of failure
let events = try await tx.signSendAndWatch(signer: from)
.waitForFinalized()
.success()
// `parsed()` will dynamically parse all extrinsic events.
// Check `ExtrinsicEvents` struct for more efficient search methods.
print("Events: \(try events.parsed())")
运行时调用
对于动态运行时调用,节点应支持元数据版本15。
// We can check does node have needed runtime call
guard substrate.call.has(method: "versions", api: "Metadata") else {
fatalError("Node doesn't have needed call")
}
// Array<UInt32> is a return value for the call
// AnyValueRuntimeCall can be used for dynamic return parsing
let call = AnyRuntimeCall<[UInt32]>(api: "Metadata",
method: "versions")
// Will parse vall result to Array<UInt32>
let versions = try await substrate.call.execute(call: call)
print("Supported metadata versions: \(versions)")
常量
// It will throw if constant is not found or type is wrong
let deposit = try substrate.constants.get(UInt128.self, name: "ExistentialDeposit", pallet: "Balances")
// This will parse constant to dynamic Value<RuntimeType.Id>
let dynDeposit = try substrate.constants.dynamic(name: "ExistentialDeposit", pallet: "Balances")
print("Existential deposit: \(deposit), \(dynDeposit)")
存储键
存储API通过StorageEntry
助手函数工作,该函数可以用于提供的StorageKey
类型。
为存储创建StorageEntry
// dynamic storage key wirh typed Value
let entry = try substrate.query.entry(UInt128.self, name: "NominatorSlashInEra", pallet: "Stacking")
// dynamic storage key with dynamic Value
let dynEntry = substrate.query.dynamic(name: "NominatorSlashInEra", pallet: "Stacking")
获取键值
当有条目时,可以获取键值。
// We want values for this account.
let accountId = try substrate.runtime.account(ss58: "EoukLS2Rzh6dZvMQSkqFy4zGvqeo14ron28Ue3yopVc8e3Q")
// NominatorSlashInEra storage is Double Map (EraIndex, AccountId).
// We have to provide 2 keys to get value.
// optional value
let optSlash = try await entry.value([652, accountId])
print("Value is: \(optSlash ?? 0)")
// default value used when nil
let slash = try await entry.valueOrDefault([652, accountId])
print("Value is: \(slash)")
键迭代器
Map键支持迭代。StorageEntry具有此功能的辅助函数。
// We can iterate over Key/Value pairs.
for try await (key, value) in entry.entries() {
print("Key: \(key), value: \(value)")
}
// or only over keys
for try await key in entry.keys() {
print("Key: \(key)")
}
// For maps where N > 1 we can filter iterator by first N-1 keys
// This will set EraIndex value to 652
let filtered = entry.filter(652)
// now we can iterate over filtered Key/Value pairs.
for try await (key, value) in filtered.entries() {
print("Key: \(key), value: \(value)")
}
// or only over keys
for try await key in filtered.keys() {
print("Key: \(key)")
}
订阅变更
如果API客户端支持订阅,我们可以订阅存储变更。
// Some account we want to watch
let ALICE = try substrate.runtime.account(ss58: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
// Dynamic entry for System.Account storage key
let entry = try substrate.query.dynamic(name: "Account", pallet: "System")
// It's a Map parameter so we should pass key to watch
for try await account in entry.watch([ALICE]) {
print("Account updated: \(account)")
}
自定义RPC调用
当前的SDK仅封装了其API所需的RPC调用。对于更多调用,可以使用通用API。
// Simple call
let blockHash: Data = try await substrate.rpc.call(method: "chain_getBlockHash", params: Params(0))
// Subscription
let stream = try await substrate.rpc.subscribe(
method: "chain_subscribeNewHeads",
params: Params(),
unsubscribe: "chain_unsubscribeNewHeads",
DynamicConfig.TBlock.THeader.self
)
Substrate签名者
Substrate SDK提供签名协议,这可以用于外物签名。
签名者可以使用两种不同的方式
- 它可以直接提供给外物签名调用
- 它可以存储到
Api
对象的signer
属性中。在这种情况下,应在签名调用中提供账户的PublicKey
。
第一种方式适用于KeyPair签名。第二种方式更适合完整的Keychain。
签名协议
protocol Signer {
// get account with proper type
func account(type: KeyTypeId, algos: [CryptoTypeId]) async -> Result<any PublicKey, SignerError>
// Sign extrinsic payload
func sign<RC: Config, C: Call>(
payload: SigningPayload<C, RC.TExtrinsicManager>,
with account: any PublicKey,
runtime: ExtendedRuntime<RC>
) async -> Result<RC.TSignature, SignerError>
}
Keychain API
SDK提供具有Secp256k1、Ed25519和Sr25519密钥支持的简单内存Keychain。
密钥对
import SubstrateKeychain
// Initializers
// New with random key (OS secure random is used)
let random = EcdsaKeyPair() // or Ed25519KeyPair / Sr25519KeyPair
// From Bip39 mnemonic
let mnemonic = Sr25519KeyPair(phrase: "your words")
// JS compatible path based parsing
let path = try Ed25519KeyPair(parsing: "//Alice")
// Key derivation (JS compatible)
let derived = try mnemonic.derive(path: [PathComponent(string: "//Alice")])
// Sign and verify
let data = Data(repeating: 1, count: 20)
let signature = derived.sign(message: data)
let isSigned = derived.verify(message: data, signature: signature)
Keychain
为多个密钥对提供内存存储(支持多账户)。
初始化和基础API
import SubstrateKeychain
// Create empty keychain object with default delegate
let keychain = Keychain()
// Will be returned for account request
keychain.add(derived, for: .account)
// Key for ImOnline
keychain.add(random, for: .imOnline)
// Key for all types of requests
keychain.add(path)
// Search for PubKey registered for 'account' requests
let pubKey = keychain.publicKeys(for: .account).first!
// get KeyPair for this PubKey
let keyPair = keychain.keyPair(for: pubKey)!
Api Signer集成
钥匙串可以被设置为 Signer
的 Api
实例,以便进行更简单的账户管理和签名。
// Set substrate signer to created keychain instance
substrate.signer = keychain
// Fetch account from Keychain (it will call Keychain delegate for selection)
// Can be stored and reused as needed (active account)
let from = try await substrate.tx.account()
// Signer will return PublicKey from Keychain
// which should be converted to account or address for extrinsics
print("Account: \(try from.account(in: substrate))")
// We can provide this PublicKey as `account` to calls for signing.
// Signing call will be sent to Keychain through Signer protocol
let events = try await tx.signSendAndWatch(account: from)
.waitForFinalized()
.success()
// print parsed events
print("Events: \(try events.parsed())")
密钥链代理
密钥链有一个代理对象,可以选择用于签名协议的公钥。它可以用来向用户显示带有账户选择器的UI,或者实现自定义逻辑。
有一个默认的 KeychainDelegateFirstFound
实现返回从密钥链中找到的第一个兼容密钥。
协议看起来是这样的
enum KeychainDelegateResponse {
case cancelled
case noAccount
case account(any PublicKey)
}
protocol KeychainDelegate: AnyObject {
func account(in keychain: Keychain,
for type: KeyTypeId,
algorithms algos: [CryptoTypeId]) async -> KeychainDelegateResponse
}
批量任选事务
SDK 支持批处理调用:[如何批量事务](https://polkadot.js.org/docs/api/cookbook/tx#how-can-i-batch-transactions)
// Fetch account from Keychain (should be set in substrate as signer)
let from = try await substrate.tx.account()
// Create recipient addresses from ss58 string
let to1 = try substrate.runtime.address(ss58: "recipient1 s58 address")
let to2 = try substrate.runtime.address(ss58: "recipient2 s58 address")
// Create 2 calls
let call1 = AnyCall(name: "transfer",
pallet: "Balances",
params: ["dest": to1, "value": 15483812850])
let call2 = AnyCall(name: "transfer",
pallet: "Balances",
params: ["dest": to2, "value": 21234567890])
// Create batch transaction from the calls (batch or batchAll methods)
let tx = try await substrate.tx.batchAll([call1, call2])
// Sign, send and watch for finalization
let events = try await tx.signSendAndWatch(account: from)
.waitForFinalized()
.success()
// Parsed events
print("Events: \(try events.parsed())")
作者
- Tesseract Systems, Inc. ([@tesseract_one](https://twitter.com/tesseract_one))
许可
Substrate.swift 在 Apache 2.0 许可下可用。有关更多信息,请参阅 [LICENSE 文件](./LICENSE)。