@authon/react-native-wallet 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -2
- package/android/build.gradle +40 -0
- package/android/consumer-rules.pro +10 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +181 -0
- package/android/src/vendor/GENERATED.md +4 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +191 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +82 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +232 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +353 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +58 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +449 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +71 -0
- package/build/authonContext.d.ts +19 -0
- package/build/authonContext.d.ts.map +1 -0
- package/build/authonContext.js +20 -0
- package/build/authonContext.js.map +1 -0
- package/build/client.d.ts +76 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +349 -0
- package/build/client.js.map +1 -0
- package/build/errors.d.ts +18 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +9 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/native.d.ts +39 -0
- package/build/native.d.ts.map +1 -0
- package/build/native.js +12 -0
- package/build/native.js.map +1 -0
- package/build/types.d.ts +121 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/build/useAuthonWallet.d.ts +25 -0
- package/build/useAuthonWallet.d.ts.map +1 -0
- package/build/useAuthonWallet.js +77 -0
- package/build/useAuthonWallet.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AuthonReactNativeWallet.podspec +37 -0
- package/ios/AuthonWalletModule.swift +190 -0
- package/ios/vendor/AuthonWallet/AuthonWallet.swift +212 -0
- package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
- package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
- package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +104 -0
- package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
- package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
- package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
- package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
- package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
- package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
- package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
- package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
- package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
- package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
- package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
- package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
- package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
- package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
- package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +350 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +555 -0
- package/ios/vendor/AuthonWallet/WalletError.swift +54 -0
- package/ios/vendor/CKeccak/LICENSE +22 -0
- package/ios/vendor/CKeccak/README.md +27 -0
- package/ios/vendor/CKeccak/byte_order.h +59 -0
- package/ios/vendor/CKeccak/include/ckeccak.h +9 -0
- package/ios/vendor/CKeccak/memzero.c +75 -0
- package/ios/vendor/CKeccak/memzero.h +8 -0
- package/ios/vendor/CKeccak/options.h +99 -0
- package/ios/vendor/CKeccak/sha3.c +413 -0
- package/ios/vendor/CKeccak/sha3.h +89 -0
- package/ios/vendor/CSecp256k1/COPYING +19 -0
- package/ios/vendor/CSecp256k1/README.md +35 -0
- package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
- package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
- package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
- package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
- package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
- package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
- package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
- package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
- package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
- package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
- package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
- package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
- package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
- package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
- package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
- package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
- package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
- package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
- package/ios/vendor/CSecp256k1/src/field.h +350 -0
- package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
- package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
- package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
- package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
- package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
- package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
- package/ios/vendor/CSecp256k1/src/group.h +212 -0
- package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
- package/ios/vendor/CSecp256k1/src/hash.h +44 -0
- package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
- package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
- package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
- package/ios/vendor/CSecp256k1/src/int128.h +90 -0
- package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
- package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
- package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
- package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
- package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
- package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
- package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
- package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
- package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
- package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
- package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
- package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
- package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
- package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
- package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
- package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
- package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
- package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
- package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
- package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
- package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
- package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
- package/ios/vendor/CSecp256k1/src/util.h +451 -0
- package/ios/vendor/GENERATED.md +4 -0
- package/package.json +65 -3
- package/src/authonContext.ts +30 -0
- package/src/client.ts +375 -0
- package/src/errors.ts +52 -0
- package/src/index.ts +21 -0
- package/src/native.ts +50 -0
- package/src/types.ts +120 -0
- package/src/useAuthonWallet.ts +103 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Wallet/Proof.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Device public key as the API stores it.
|
|
5
|
+
public struct DevicePublicJwk: Codable, Equatable, Sendable {
|
|
6
|
+
public var kty: String
|
|
7
|
+
public var crv: String
|
|
8
|
+
public var x: String
|
|
9
|
+
public var y: String
|
|
10
|
+
|
|
11
|
+
public init(x: String, y: String) {
|
|
12
|
+
kty = "EC"
|
|
13
|
+
crv = "P-256"
|
|
14
|
+
self.x = x
|
|
15
|
+
self.y = y
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// From a 65-byte uncompressed P-256 point.
|
|
19
|
+
init(uncompressed point: [UInt8]) throws {
|
|
20
|
+
guard point.count == 65, point[0] == 4 else { throw WalletError(.internal, "invalid device public key") }
|
|
21
|
+
self.init(x: Bytes.base64url(Array(point[1..<33])), y: Bytes.base64url(Array(point[33..<65])))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var json: JSONValue {
|
|
25
|
+
.object(JSONObject([("kty", .string(kty)), ("crv", .string(crv)), ("x", .string(x)), ("y", .string(y))]))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Server-issued nonce bound into a wallet key proof (`expiresAt` in Unix seconds).
|
|
30
|
+
public struct ProofNonce: Equatable, Sendable {
|
|
31
|
+
public var nonce: String
|
|
32
|
+
public var expiresAt: Int64
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Wallet key proofs (`apps/wallet/src/proof.ts`): EIP-712 under the wallet's own domain.
|
|
36
|
+
enum WalletProof {
|
|
37
|
+
static let domainName = "Authon Wallet Proof"
|
|
38
|
+
static let reservedPrefixes = ["create:", "device:", "restore:"]
|
|
39
|
+
|
|
40
|
+
static func canonicalJwk(_ jwk: DevicePublicJwk) -> String {
|
|
41
|
+
"{\"crv\":\(JSONValue.quote(jwk.crv)),\"kty\":\(JSONValue.quote(jwk.kty)),\"x\":\(JSONValue.quote(jwk.x)),\"y\":\(JSONValue.quote(jwk.y))}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static func devicePublicKeyHash(_ jwk: DevicePublicJwk) -> String {
|
|
45
|
+
Bytes.hex(Bytes.sha256(Array(canonicalJwk(jwk).utf8)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
enum Kind: String {
|
|
49
|
+
case create = "CreateWallet"
|
|
50
|
+
case device = "AddDevice"
|
|
51
|
+
case restore = "RestoreWallet"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static func typedData(_ kind: Kind, _ fields: [(String, JSONValue)], bound: ProofNonce) -> JSONValue {
|
|
55
|
+
func field(_ n: String, _ t: String) -> JSONValue { .object(JSONObject([("name", .string(n)), ("type", .string(t))])) }
|
|
56
|
+
let head: [JSONValue]
|
|
57
|
+
switch kind {
|
|
58
|
+
case .create: head = [field("projectId", "string"), field("userId", "string"), field("deviceKeyHash", "string")]
|
|
59
|
+
case .device, .restore: head = [field("walletId", "string"), field("shareVersion", "uint256"), field("deviceKeyHash", "string")]
|
|
60
|
+
}
|
|
61
|
+
let typeList = head + [field("nonce", "string"), field("expiresAt", "uint256")]
|
|
62
|
+
return .object(JSONObject([
|
|
63
|
+
("domain", .object(JSONObject([("name", .string(domainName)), ("version", .string("1"))]))),
|
|
64
|
+
("types", .object(JSONObject([(kind.rawValue, .array(typeList))]))),
|
|
65
|
+
("primaryType", .string(kind.rawValue)),
|
|
66
|
+
("message", .object(JSONObject(fields + [("nonce", .string(bound.nonce)), ("expiresAt", .number(String(bound.expiresAt)))]))),
|
|
67
|
+
]))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static func sign(_ secret: [UInt8], _ td: JSONValue) throws -> String {
|
|
71
|
+
let account = try WalletShares.account(secret)
|
|
72
|
+
defer { account.wipe() }
|
|
73
|
+
return try account.signTypedData(try TypedData(td))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static func createTypedData(projectId: String, userId: String, deviceKey: DevicePublicJwk, bound: ProofNonce) -> JSONValue {
|
|
77
|
+
typedData(.create, [("projectId", .string(projectId)), ("userId", .string(userId)), ("deviceKeyHash", .string(devicePublicKeyHash(deviceKey)))], bound: bound)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static func deviceTypedData(walletId: String, shareVersion: Int, deviceKey: DevicePublicJwk, bound: ProofNonce) -> JSONValue {
|
|
81
|
+
typedData(.device, [("walletId", .string(walletId)), ("shareVersion", .number(String(shareVersion))), ("deviceKeyHash", .string(devicePublicKeyHash(deviceKey)))], bound: bound)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// The restore proof names the next share version (current + 1).
|
|
85
|
+
static func restoreTypedData(walletId: String, currentShareVersion: Int, deviceKey: DevicePublicJwk, bound: ProofNonce) -> JSONValue {
|
|
86
|
+
typedData(.restore, [("walletId", .string(walletId)), ("shareVersion", .number(String(currentShareVersion + 1))), ("deviceKeyHash", .string(devicePublicKeyHash(deviceKey)))], bound: bound)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static func signCreate(_ secret: [UInt8], projectId: String, userId: String, deviceKey: DevicePublicJwk, bound: ProofNonce) throws -> String {
|
|
90
|
+
try sign(secret, createTypedData(projectId: projectId, userId: userId, deviceKey: deviceKey, bound: bound))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static func signDevice(_ secret: [UInt8], walletId: String, shareVersion: Int, deviceKey: DevicePublicJwk, bound: ProofNonce) throws -> String {
|
|
94
|
+
try sign(secret, deviceTypedData(walletId: walletId, shareVersion: shareVersion, deviceKey: deviceKey, bound: bound))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static func signRestore(_ secret: [UInt8], walletId: String, currentShareVersion: Int, deviceKey: DevicePublicJwk, bound: ProofNonce) throws -> String {
|
|
98
|
+
try sign(secret, restoreTypedData(walletId: walletId, currentShareVersion: currentShareVersion, deviceKey: deviceKey, bound: bound))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// App text that looks like a (legacy) wallet proof is never signed for an app.
|
|
102
|
+
static func isReservedMessageText(_ text: String) -> Bool {
|
|
103
|
+
let scalars = text.unicodeScalars.drop { $0.properties.isWhitespace || $0.value < 0x20 || $0.value == 0xfeff }
|
|
104
|
+
let t = String(String.UnicodeScalarView(scalars)).lowercased()
|
|
105
|
+
return reservedPrefixes.contains { t.hasPrefix($0) }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// `{raw}` hex decoded as strict UTF-8; nil when it is not valid UTF-8.
|
|
109
|
+
static func rawMessageText(_ hex: String) -> String? {
|
|
110
|
+
let body = hex.hasPrefix("0x") ? String(hex.dropFirst(2)) : hex
|
|
111
|
+
guard body.count % 2 == 0, let bytes = Bytes.fromHex(body) else { return nil }
|
|
112
|
+
return String(strictUTF8: bytes)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static func isReservedTypedData(_ td: JSONValue) -> Bool {
|
|
116
|
+
guard case .string(let name) = td["domain"]?["name"] ?? .null else { return false }
|
|
117
|
+
return name.trimmingCharacters(in: jsWhitespace).lowercased() == domainName.lowercased()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// JavaScript String.prototype.trim whitespace.
|
|
121
|
+
static let jsWhitespace: CharacterSet = {
|
|
122
|
+
var set = CharacterSet.whitespacesAndNewlines
|
|
123
|
+
set.insert(charactersIn: "\u{feff}")
|
|
124
|
+
return set
|
|
125
|
+
}()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
extension String {
|
|
129
|
+
/// Strict UTF-8 decoding (TextDecoder with fatal: true).
|
|
130
|
+
init?(strictUTF8 bytes: [UInt8]) {
|
|
131
|
+
var it = bytes.makeIterator()
|
|
132
|
+
var decoder = UTF8()
|
|
133
|
+
var scalars = String.UnicodeScalarView()
|
|
134
|
+
loop: while true {
|
|
135
|
+
switch decoder.decode(&it) {
|
|
136
|
+
case .scalarValue(let s): scalars.append(s)
|
|
137
|
+
case .emptyInput: break loop
|
|
138
|
+
case .error: return nil
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
self = String(scalars)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Wallet/Shares.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// The wallet secret and its shares (`apps/wallet/src/shares.ts`). The secret is BIP39
|
|
5
|
+
/// entropy (16 bytes); a 32-byte secret is a raw key from before HD wallets (account 0 only).
|
|
6
|
+
enum WalletShares {
|
|
7
|
+
static let hdEntropyBytes = 16
|
|
8
|
+
static let maxAccountIndex = 0x7fff_ffff
|
|
9
|
+
|
|
10
|
+
static func evmPath(_ index: Int) -> String { "m/44'/60'/0'/0/\(index)" }
|
|
11
|
+
|
|
12
|
+
static func generateSecret() throws -> [UInt8] { try Bytes.random(hdEntropyBytes) }
|
|
13
|
+
|
|
14
|
+
static func isHdSecret(_ secret: [UInt8]) -> Bool { secret.count == hdEntropyBytes }
|
|
15
|
+
|
|
16
|
+
static func isAccountIndex(_ index: Int) -> Bool { index >= 0 && index <= maxAccountIndex }
|
|
17
|
+
|
|
18
|
+
private static func rootNode(_ secret: [UInt8]) throws -> HDNode {
|
|
19
|
+
var seed = try BIP39.seed(mnemonic: try BIP39.mnemonic(entropy: secret))
|
|
20
|
+
defer { seed.wipe() }
|
|
21
|
+
return try HDNode(seed: seed)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// The EVM private key at account `index`. Callers must wipe the result.
|
|
25
|
+
static func privateKey(_ secret: [UInt8], index: Int = 0) throws -> [UInt8] {
|
|
26
|
+
guard isAccountIndex(index) else { throw WalletError(.internal, "invalid account index") }
|
|
27
|
+
if secret.count == 32 {
|
|
28
|
+
guard index == 0 else { throw WalletError(.internal, "single-key wallet has only account 0") }
|
|
29
|
+
return secret
|
|
30
|
+
}
|
|
31
|
+
guard isHdSecret(secret) else { throw WalletError(.internal, "unsupported wallet secret") }
|
|
32
|
+
var root = try rootNode(secret)
|
|
33
|
+
defer { root.wipe() }
|
|
34
|
+
var node = try root.derive(evmPath(index))
|
|
35
|
+
defer { node.wipe() }
|
|
36
|
+
return node.key
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static func account(_ secret: [UInt8], index: Int = 0) throws -> EvmAccount {
|
|
40
|
+
var key = try privateKey(secret, index: index)
|
|
41
|
+
defer { key.wipe() }
|
|
42
|
+
return try EvmAccount(privateKey: key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static func address(_ secret: [UInt8], index: Int = 0) throws -> String {
|
|
46
|
+
let a = try account(secret, index: index)
|
|
47
|
+
a.wipe()
|
|
48
|
+
return a.address
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Addresses of accounts start..<start+count. The seed and m/44'/60'/0'/0 are derived once.
|
|
52
|
+
static func addresses(_ secret: [UInt8], start: Int, count: Int) throws -> [String] {
|
|
53
|
+
guard isAccountIndex(start), count >= 1, isAccountIndex(start + count - 1) else {
|
|
54
|
+
throw WalletError(.internal, "invalid account range")
|
|
55
|
+
}
|
|
56
|
+
if secret.count == 32 {
|
|
57
|
+
guard start == 0, count == 1 else { throw WalletError(.internal, "single-key wallet has only account 0") }
|
|
58
|
+
return [try address(secret)]
|
|
59
|
+
}
|
|
60
|
+
guard isHdSecret(secret) else { throw WalletError(.internal, "unsupported wallet secret") }
|
|
61
|
+
var root = try rootNode(secret)
|
|
62
|
+
defer { root.wipe() }
|
|
63
|
+
var parent = try root.derive("m/44'/60'/0'/0")
|
|
64
|
+
defer { parent.wipe() }
|
|
65
|
+
var out = [String]()
|
|
66
|
+
out.reserveCapacity(count)
|
|
67
|
+
for i in start..<(start + count) {
|
|
68
|
+
var child = try parent.child(UInt32(i))
|
|
69
|
+
defer { child.wipe() }
|
|
70
|
+
out.append(try EvmAddress.of(privateKey: child.key))
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static func splitUnprotected(_ secret: [UInt8]) throws -> (device: [UInt8], company: [UInt8]) {
|
|
76
|
+
let s = try Shamir.split(secret, shares: 2, threshold: 2)
|
|
77
|
+
return (s[0], s[1])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static func splitProtected(_ secret: [UInt8]) throws -> (device: [UInt8], company: [UInt8], backup: [UInt8]) {
|
|
81
|
+
let s = try Shamir.split(secret, shares: 3, threshold: 2)
|
|
82
|
+
return (s[0], s[1], s[2])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static func assemble(_ a: [UInt8], _ b: [UInt8]) throws -> [UInt8] {
|
|
86
|
+
do {
|
|
87
|
+
return try Shamir.combine([a, b])
|
|
88
|
+
} catch {
|
|
89
|
+
throw WalletError(.internal, "shares cannot be combined")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// One more share on the same line as `a` and `b` at a fresh x (shares.ts deriveShare):
|
|
94
|
+
/// shifting both x coordinates by x3 and interpolating at zero yields f(x3).
|
|
95
|
+
static func deriveShare(_ a: [UInt8], _ b: [UInt8], x: UInt8? = nil) throws -> [UInt8] {
|
|
96
|
+
guard a.count == b.count, a.count >= 2 else { throw WalletError(.internal, "share length mismatch") }
|
|
97
|
+
let xa = a[a.count - 1]
|
|
98
|
+
let xb = b[b.count - 1]
|
|
99
|
+
var x3: UInt8 = x ?? 0
|
|
100
|
+
while x3 == 0 || x3 == xa || x3 == xb {
|
|
101
|
+
guard x == nil else { throw WalletError(.internal, "invalid x") }
|
|
102
|
+
x3 = try Bytes.random(1)[0]
|
|
103
|
+
}
|
|
104
|
+
var shiftedA = a
|
|
105
|
+
var shiftedB = b
|
|
106
|
+
shiftedA[a.count - 1] = xa ^ x3
|
|
107
|
+
shiftedB[b.count - 1] = xb ^ x3
|
|
108
|
+
defer {
|
|
109
|
+
shiftedA.wipe()
|
|
110
|
+
shiftedB.wipe()
|
|
111
|
+
}
|
|
112
|
+
var y = try Shamir.combine([shiftedA, shiftedB])
|
|
113
|
+
defer { y.wipe() }
|
|
114
|
+
return y + [x3]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Wallet/Summary.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Human-readable description of a signing request, built from the exact payload that is
|
|
5
|
+
/// signed (`apps/wallet/src/summary.ts`).
|
|
6
|
+
public struct SignSummary: Equatable, Sendable {
|
|
7
|
+
public enum Kind: String, Sendable { case message, typedData, transaction }
|
|
8
|
+
public enum Risk: String, Sendable { case low, high }
|
|
9
|
+
|
|
10
|
+
public struct Row: Equatable, Sendable {
|
|
11
|
+
public var label: String
|
|
12
|
+
public var value: String
|
|
13
|
+
/// Monospace (addresses, hex).
|
|
14
|
+
public var mono: Bool
|
|
15
|
+
|
|
16
|
+
public init(label: String, value: String, mono: Bool = false) {
|
|
17
|
+
self.label = label
|
|
18
|
+
self.value = value
|
|
19
|
+
self.mono = mono
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public var kind: Kind
|
|
24
|
+
public var title: String
|
|
25
|
+
public var rows: [Row]
|
|
26
|
+
/// The complete signed text (message, or typed data message JSON), never shortened.
|
|
27
|
+
public var body: String?
|
|
28
|
+
/// Any entry marks the request as dangerous.
|
|
29
|
+
public var warnings: [String]
|
|
30
|
+
/// High: moves value or grants spending.
|
|
31
|
+
public var risk: Risk
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
enum Summaries {
|
|
35
|
+
/// Longest text a confirmation shows; longer requests are refused (MESSAGE_TOO_LARGE).
|
|
36
|
+
static let maxSignText = 16_384
|
|
37
|
+
|
|
38
|
+
private static let chains: [Int: (String, String)] = [
|
|
39
|
+
1: ("Ethereum", "ETH"), 10: ("Optimism", "ETH"), 56: ("BNB Smart Chain", "BNB"), 137: ("Polygon", "POL"),
|
|
40
|
+
8453: ("Base", "ETH"), 42161: ("Arbitrum One", "ETH"), 43114: ("Avalanche C-Chain", "AVAX"),
|
|
41
|
+
80002: ("Polygon Amoy (testnet)", "POL"), 84532: ("Base Sepolia (testnet)", "ETH"), 11155111: ("Sepolia (testnet)", "ETH"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
static func chainLabel(_ chainId: Int?) -> String {
|
|
45
|
+
guard let chainId else { return "Unknown network" }
|
|
46
|
+
if let c = chains[chainId] { return "\(c.0) (\(chainId))" }
|
|
47
|
+
return "Chain \(chainId)"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static func nativeSymbol(_ chainId: Int?) -> String { chainId.flatMap { chains[$0]?.1 } ?? "native token" }
|
|
51
|
+
|
|
52
|
+
private static let maxUint256 = BigUInt.power2(256) - BigUInt(1)
|
|
53
|
+
private static let unlimitedThreshold = BigUInt.power2(255)
|
|
54
|
+
private static let permit2 = "0x000000000022d473030f116ddee9f6b43ac78ba3"
|
|
55
|
+
|
|
56
|
+
static func requireShowable(_ text: String) throws -> String {
|
|
57
|
+
let n = text.utf16.count
|
|
58
|
+
if n > maxSignText {
|
|
59
|
+
throw WalletError(.messageTooLarge, "the request has \(n) characters; at most \(maxSignText) can be shown")
|
|
60
|
+
}
|
|
61
|
+
return text
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private static func amountText(_ v: BigUInt, _ unlimited: String = "Unlimited") -> String {
|
|
65
|
+
v >= unlimitedThreshold ? "\(unlimited) (\(v == maxUint256 ? "max uint256" : v.description))" : v.description
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// summary.ts toBigInt: undefined for missing, null, empty or unparsable values.
|
|
69
|
+
private static func toBigInt(_ v: JSONValue?) -> BigInteger? {
|
|
70
|
+
guard let v, v != .null, v != .string("") else { return nil }
|
|
71
|
+
return v.jsBigInt
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private static func toChainId(_ v: JSONValue?) -> Int? {
|
|
75
|
+
guard let b = toBigInt(v), let u = b.magnitude.uint64, u <= UInt64(Int.max) else { return nil }
|
|
76
|
+
return b.negative ? -Int(u) : Int(u)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// viem formatUnits(value, 18).
|
|
80
|
+
static func formatEther(_ v: BigUInt) -> String {
|
|
81
|
+
var digits = v.description
|
|
82
|
+
if digits.count <= 18 { digits = String(repeating: "0", count: 18 - digits.count + 1) + digits }
|
|
83
|
+
let integer = String(digits.dropLast(18))
|
|
84
|
+
var fraction = String(digits.suffix(18))
|
|
85
|
+
while fraction.hasSuffix("0") { fraction.removeLast() }
|
|
86
|
+
return (integer.isEmpty ? "0" : integer) + (fraction.isEmpty ? "" : ".\(fraction)")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Message
|
|
90
|
+
|
|
91
|
+
static func message(_ m: ParsedMessage) throws -> SignSummary {
|
|
92
|
+
switch m {
|
|
93
|
+
case .text(let s):
|
|
94
|
+
return SignSummary(kind: .message, title: "Sign message", rows: [], body: try requireShowable(s), warnings: [], risk: .low)
|
|
95
|
+
case .raw(let hex):
|
|
96
|
+
let text = WalletProof.rawMessageText(hex)
|
|
97
|
+
let printable = text.map { t in !t.unicodeScalars.contains { $0.value <= 0x08 || (0x0e...0x1f).contains($0.value) || $0.value == 0x7f } } ?? false
|
|
98
|
+
return SignSummary(
|
|
99
|
+
kind: .message,
|
|
100
|
+
title: "Sign message",
|
|
101
|
+
rows: printable ? [] : [.init(label: "Format", value: "Raw bytes (not readable text)")],
|
|
102
|
+
body: try requireShowable(printable ? text! : hex),
|
|
103
|
+
warnings: printable ? [] : ["This message is not readable text. Only sign it if you trust this site."],
|
|
104
|
+
risk: .low
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// MARK: - Typed data
|
|
110
|
+
|
|
111
|
+
private static let permitTypes: Set<String> = [
|
|
112
|
+
"Permit", "PermitSingle", "PermitBatch", "PermitTransferFrom", "PermitBatchTransferFrom",
|
|
113
|
+
"PermitWitnessTransferFrom", "PermitBatchWitnessTransferFrom", "PermitForAll",
|
|
114
|
+
]
|
|
115
|
+
private static let spendingTypes: Set<String> = [
|
|
116
|
+
"OrderComponents", "Order", "BulkOrder", "OrderParameters", "ForwardRequest", "MetaTransaction", "SafeTx", "Delegation", "Approval",
|
|
117
|
+
]
|
|
118
|
+
private static let spendingKeys: Set<String> = ["spender", "operator", "delegate", "delegatee", "approved", "allowance", "allowed"]
|
|
119
|
+
|
|
120
|
+
private static func grantsSpending(_ message: JSONValue) -> Bool {
|
|
121
|
+
var budget = 2000
|
|
122
|
+
func walk(_ v: JSONValue, _ depth: Int) -> Bool {
|
|
123
|
+
budget -= 1
|
|
124
|
+
if budget < 0 || depth > 12 { return true }
|
|
125
|
+
if case .array(let a) = v { return a.contains { walk($0, depth + 1) } }
|
|
126
|
+
guard case .object(let o) = v else { return false }
|
|
127
|
+
let entries = o.entries.map { ($0.key.lowercased(), $0.value) }
|
|
128
|
+
let keys = entries.map(\.0)
|
|
129
|
+
if keys.contains(where: spendingKeys.contains) { return true }
|
|
130
|
+
let hasCall = entries.contains { k, x in
|
|
131
|
+
(k == "data" || k == "calldata") && { if case .string(let s) = x { return s != "0x" && s != "" } else { return false } }()
|
|
132
|
+
}
|
|
133
|
+
let hasTarget = keys.contains("to") || keys.contains("recipient")
|
|
134
|
+
let hasValue = keys.contains("value") || keys.contains("amount")
|
|
135
|
+
if hasCall || (hasTarget && hasValue) { return true }
|
|
136
|
+
return o.entries.contains { walk($0.value, depth + 1) }
|
|
137
|
+
}
|
|
138
|
+
return walk(message, 0)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static func typedData(_ td: JSONValue) throws -> SignSummary {
|
|
142
|
+
let domain = td["domain"]?.objectValue ?? JSONObject()
|
|
143
|
+
let primaryType = td["primaryType"].map { $0 == .null ? "" : EvmAddress.jsString($0) } ?? ""
|
|
144
|
+
let message = td["message"].flatMap { $0 == .null ? nil : $0 } ?? .object(JSONObject())
|
|
145
|
+
var rows = [SignSummary.Row]()
|
|
146
|
+
if case .string(let name) = domain["name"] ?? .null { rows.append(.init(label: "App or contract", value: name)) }
|
|
147
|
+
let chainId = toChainId(domain["chainId"])
|
|
148
|
+
if chainId != nil { rows.append(.init(label: "Network", value: chainLabel(chainId))) }
|
|
149
|
+
if case .string = domain["verifyingContract"] ?? .null {
|
|
150
|
+
rows.append(.init(label: "Contract", value: EvmAddress.display(domain["verifyingContract"]), mono: true))
|
|
151
|
+
}
|
|
152
|
+
rows.append(.init(label: "Type", value: primaryType.isEmpty ? "unknown" : primaryType))
|
|
153
|
+
|
|
154
|
+
var warnings = [String]()
|
|
155
|
+
let verifying = domain["verifyingContract"]?.stringValue?.lowercased() ?? ""
|
|
156
|
+
let isPermit2 = domain["name"] == .string("Permit2") || verifying == permit2
|
|
157
|
+
let permit = permitTypes.contains(primaryType) || isPermit2
|
|
158
|
+
if permit {
|
|
159
|
+
warnings.append("This is a token permit. Signing it lets the spender move your tokens without another confirmation.")
|
|
160
|
+
let spender = message["spender"] ?? message["details"]?["spender"]
|
|
161
|
+
if let spender { rows.append(.init(label: "Spender", value: EvmAddress.display(spender), mono: true)) }
|
|
162
|
+
let amount = toBigInt(message["value"]) ?? toBigInt(message["amount"]) ?? toBigInt(message["details"]?["amount"]) ?? toBigInt(message["permitted"]?["amount"])
|
|
163
|
+
if let amount { rows.append(.init(label: "Amount (token units)", value: amount.negative ? amount.description : amountText(amount.magnitude))) }
|
|
164
|
+
if let amount, !amount.negative, amount.magnitude >= unlimitedThreshold { warnings.append("The permitted amount is unlimited.") }
|
|
165
|
+
let deadline = toBigInt(message["deadline"]) ?? toBigInt(message["sigDeadline"]) ?? toBigInt(message["expiry"])
|
|
166
|
+
if let deadline { rows.append(.init(label: "Valid until", value: deadline.description)) }
|
|
167
|
+
} else if message["spender"] != nil || message["operator"] != nil {
|
|
168
|
+
warnings.append("This signature may give another address access to your assets.")
|
|
169
|
+
}
|
|
170
|
+
let high = permit || spendingTypes.contains(primaryType) || grantsSpending(message)
|
|
171
|
+
if high && warnings.isEmpty { warnings.append("This signature may let another address move your assets or act for you.") }
|
|
172
|
+
return SignSummary(kind: .typedData, title: "Sign data", rows: rows, body: try requireShowable(message.pretty()), warnings: warnings, risk: high ? .high : .low)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// MARK: - Transaction
|
|
176
|
+
|
|
177
|
+
private enum AbiType { case address, uint, bool, bytes32 }
|
|
178
|
+
|
|
179
|
+
private struct AbiFunction {
|
|
180
|
+
var name: String
|
|
181
|
+
var signature: String
|
|
182
|
+
var inputs: [AbiType]
|
|
183
|
+
var selector: [UInt8] { Array(Keccak.hash256(Array(signature.utf8))[0..<4]) }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private static let ercAbi: [AbiFunction] = [
|
|
187
|
+
.init(name: "transfer", signature: "transfer(address,uint256)", inputs: [.address, .uint]),
|
|
188
|
+
.init(name: "approve", signature: "approve(address,uint256)", inputs: [.address, .uint]),
|
|
189
|
+
.init(name: "transferFrom", signature: "transferFrom(address,address,uint256)", inputs: [.address, .address, .uint]),
|
|
190
|
+
.init(name: "increaseAllowance", signature: "increaseAllowance(address,uint256)", inputs: [.address, .uint]),
|
|
191
|
+
.init(name: "setApprovalForAll", signature: "setApprovalForAll(address,bool)", inputs: [.address, .bool]),
|
|
192
|
+
.init(name: "permit", signature: "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", inputs: [.address, .address, .uint, .uint, .uint, .bytes32, .bytes32]),
|
|
193
|
+
.init(name: "approve", signature: "approve(address,address,uint160,uint48)", inputs: [.address, .address, .uint, .uint]),
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
private enum AbiValue {
|
|
197
|
+
case address(String)
|
|
198
|
+
case uint(BigUInt)
|
|
199
|
+
case bool(Bool)
|
|
200
|
+
case bytes([UInt8])
|
|
201
|
+
|
|
202
|
+
var text: String {
|
|
203
|
+
switch self {
|
|
204
|
+
case .address(let a): return a
|
|
205
|
+
case .uint(let u): return u.description
|
|
206
|
+
case .bool(let b): return b ? "true" : "false"
|
|
207
|
+
case .bytes(let b): return Bytes.hex(b, prefix: true)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var uint: BigUInt {
|
|
212
|
+
if case .uint(let u) = self { return u }
|
|
213
|
+
return BigUInt()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// viem decodeFunctionData over the ABI above; nil when nothing matches or the data is short or malformed.
|
|
218
|
+
private static func decode(_ data: [UInt8]) -> (name: String, args: [AbiValue])? {
|
|
219
|
+
guard data.count >= 4 else { return nil }
|
|
220
|
+
guard let fn = ercAbi.first(where: { $0.selector == Array(data[0..<4]) }) else { return nil }
|
|
221
|
+
guard data.count >= 4 + 32 * fn.inputs.count else { return nil }
|
|
222
|
+
var args = [AbiValue]()
|
|
223
|
+
for (i, t) in fn.inputs.enumerated() {
|
|
224
|
+
let word = Array(data[(4 + 32 * i)..<(4 + 32 * (i + 1))])
|
|
225
|
+
switch t {
|
|
226
|
+
case .address: args.append(.address(EvmAddress.checksum(Bytes.hex(Array(word[12...])))))
|
|
227
|
+
case .uint: args.append(.uint(BigUInt(bytes: word)))
|
|
228
|
+
case .bool:
|
|
229
|
+
let v = BigUInt(bytes: word)
|
|
230
|
+
guard v <= BigUInt(1) else { return nil }
|
|
231
|
+
args.append(.bool(!v.isZero))
|
|
232
|
+
case .bytes32: args.append(.bytes(word))
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return (fn.name, args)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static func transaction(_ tx: JSONObject) -> SignSummary {
|
|
239
|
+
let chainId = toChainId(tx["chainId"])
|
|
240
|
+
var rows: [SignSummary.Row] = [.init(label: "Network", value: chainLabel(chainId))]
|
|
241
|
+
var warnings = [String]()
|
|
242
|
+
let to: String? = { if case .string(let s) = tx["to"] ?? .null, !s.isEmpty { return s } else { return nil } }()
|
|
243
|
+
if let to {
|
|
244
|
+
rows.append(.init(label: "To", value: EvmAddress.display(.string(to)), mono: true))
|
|
245
|
+
} else {
|
|
246
|
+
rows.append(.init(label: "To", value: "New contract (deployment)"))
|
|
247
|
+
warnings.append("This transaction deploys a contract.")
|
|
248
|
+
}
|
|
249
|
+
let valueInt = toBigInt(tx["value"])
|
|
250
|
+
let value = valueInt.map { $0.negative ? BigUInt() : $0.magnitude } ?? BigUInt()
|
|
251
|
+
rows.append(.init(label: "Amount", value: "\(formatEther(value)) \(nativeSymbol(chainId))"))
|
|
252
|
+
if let gas = toBigInt(tx["gas"]), let fee = toBigInt(tx["maxFeePerGas"]) ?? toBigInt(tx["gasPrice"]), !gas.negative, !fee.negative {
|
|
253
|
+
rows.append(.init(label: "Max network fee", value: "\(formatEther(gas.magnitude * fee.magnitude)) \(nativeSymbol(chainId))"))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let data: String = {
|
|
257
|
+
if case .string(let s) = tx["data"] ?? .null { return s }
|
|
258
|
+
if case .string(let s) = tx["input"] ?? .null { return s }
|
|
259
|
+
return "0x"
|
|
260
|
+
}()
|
|
261
|
+
let hasData = !data.isEmpty && data != "0x"
|
|
262
|
+
if hasData {
|
|
263
|
+
let decoded = Bytes.isHex(data) ? Bytes.fromHex(data).flatMap(decode) : nil
|
|
264
|
+
if let decoded {
|
|
265
|
+
let a = decoded.args
|
|
266
|
+
func address(_ i: Int) -> String { a[i].text }
|
|
267
|
+
switch decoded.name {
|
|
268
|
+
case "transfer":
|
|
269
|
+
rows += [
|
|
270
|
+
.init(label: "Action", value: "Token transfer"),
|
|
271
|
+
.init(label: "Token", value: EvmAddress.display(to.map { .string($0) }), mono: true),
|
|
272
|
+
.init(label: "Recipient", value: address(0), mono: true),
|
|
273
|
+
.init(label: "Amount (token units)", value: a[1].text),
|
|
274
|
+
]
|
|
275
|
+
case "transferFrom":
|
|
276
|
+
rows += [
|
|
277
|
+
.init(label: "Action", value: "Token transfer from another address"),
|
|
278
|
+
.init(label: "From", value: address(0), mono: true),
|
|
279
|
+
.init(label: "Recipient", value: address(1), mono: true),
|
|
280
|
+
.init(label: "Amount (token units)", value: a[2].text),
|
|
281
|
+
]
|
|
282
|
+
case "approve":
|
|
283
|
+
if a.count == 4 {
|
|
284
|
+
rows += [
|
|
285
|
+
.init(label: "Action", value: "Permit2 allowance"),
|
|
286
|
+
.init(label: "Token", value: address(0), mono: true),
|
|
287
|
+
.init(label: "Spender", value: address(1), mono: true),
|
|
288
|
+
.init(label: "Amount (token units)", value: amountText(a[2].uint)),
|
|
289
|
+
]
|
|
290
|
+
} else {
|
|
291
|
+
rows += [
|
|
292
|
+
.init(label: "Action", value: "Token approval"),
|
|
293
|
+
.init(label: "Token", value: EvmAddress.display(to.map { .string($0) }), mono: true),
|
|
294
|
+
.init(label: "Spender", value: address(0), mono: true),
|
|
295
|
+
.init(label: "Amount (token units)", value: amountText(a[1].uint)),
|
|
296
|
+
]
|
|
297
|
+
if a[1].uint >= unlimitedThreshold { warnings.append("The approved amount is unlimited.") }
|
|
298
|
+
}
|
|
299
|
+
warnings.append("This lets the spender move your tokens later without asking again.")
|
|
300
|
+
case "increaseAllowance":
|
|
301
|
+
rows += [
|
|
302
|
+
.init(label: "Action", value: "Increase token allowance"),
|
|
303
|
+
.init(label: "Spender", value: address(0), mono: true),
|
|
304
|
+
.init(label: "Added (token units)", value: amountText(a[1].uint)),
|
|
305
|
+
]
|
|
306
|
+
warnings.append("This lets the spender move more of your tokens without asking again.")
|
|
307
|
+
case "setApprovalForAll":
|
|
308
|
+
let approved: Bool = { if case .bool(let b) = a[1] { return b } else { return false } }()
|
|
309
|
+
rows += [
|
|
310
|
+
.init(label: "Action", value: approved ? "Approve all NFTs in a collection" : "Remove collection approval"),
|
|
311
|
+
.init(label: "Operator", value: address(0), mono: true),
|
|
312
|
+
]
|
|
313
|
+
if approved { warnings.append("The operator could transfer every item you own in this collection.") }
|
|
314
|
+
case "permit":
|
|
315
|
+
rows += [
|
|
316
|
+
.init(label: "Action", value: "Token permit"),
|
|
317
|
+
.init(label: "Token", value: EvmAddress.display(to.map { .string($0) }), mono: true),
|
|
318
|
+
.init(label: "Spender", value: address(1), mono: true),
|
|
319
|
+
.init(label: "Amount (token units)", value: amountText(a[2].uint)),
|
|
320
|
+
]
|
|
321
|
+
warnings.append("This lets the spender move your tokens later without asking again.")
|
|
322
|
+
default:
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
rows.append(.init(label: "Action", value: "Contract call (\(String(data.prefix(10))))", mono: true))
|
|
327
|
+
rows.append(.init(label: "Data", value: "\(max(0, (data.count - 2) / 2)) bytes"))
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if chainId == nil { warnings.append("The network is not specified.") }
|
|
331
|
+
let risk: SignSummary.Risk = !value.isZero || hasData || to == nil ? .high : .low
|
|
332
|
+
return SignSummary(kind: .transaction, title: "Approve transaction", rows: rows, body: nil, warnings: warnings, risk: risk)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// The Account row for a non-zero account (ops.ts withAccountRow).
|
|
336
|
+
static func withAccountRow(_ s: SignSummary, _ account: SignAccount) -> SignSummary {
|
|
337
|
+
guard account.index != 0, let address = account.address else { return s }
|
|
338
|
+
var out = s
|
|
339
|
+
out.rows.insert(.init(label: "Account", value: address, mono: true), at: 0)
|
|
340
|
+
return out
|
|
341
|
+
}
|
|
342
|
+
}
|