@authon/react-native-wallet 0.0.0-stage → 0.4.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/CHANGELOG.md +20 -0
- package/README.md +251 -2
- package/android/build.gradle +40 -0
- package/android/consumer-rules.pro +10 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +235 -0
- package/android/src/vendor/GENERATED.md +4 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +218 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -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/Encodings.kt +133 -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 +86 -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 +236 -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/Solana.kt +195 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +361 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +551 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +78 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -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 +92 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +431 -0
- package/build/client.js.map +1 -0
- package/build/errors.d.ts +25 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +15 -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 +43 -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 +167 -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 +41 -0
- package/ios/AuthonWalletModule.swift +233 -0
- package/ios/vendor/AuthonWallet/AuthonWallet.swift +232 -0
- package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
- package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
- package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
- package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
- package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
- package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
- package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
- package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
- package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +128 -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 +361 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +654 -0
- package/ios/vendor/AuthonWallet/WalletError.swift +84 -0
- package/ios/vendor/CKeccak/LICENSE +22 -0
- package/ios/vendor/CKeccak/README.md +30 -0
- package/ios/vendor/CKeccak/byte_order.h +59 -0
- package/ios/vendor/CKeccak/include/ckeccak.h +13 -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/ripemd160.c +343 -0
- package/ios/vendor/CKeccak/ripemd160.h +22 -0
- package/ios/vendor/CKeccak/sha3.c +413 -0
- package/ios/vendor/CKeccak/sha3.h +89 -0
- package/ios/vendor/CMonocypher/LICENCE.md +173 -0
- package/ios/vendor/CMonocypher/README.md +27 -0
- package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
- package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
- package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
- package/ios/vendor/CMonocypher/monocypher.c +2956 -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 +66 -3
- package/src/authonContext.ts +30 -0
- package/src/client.ts +459 -0
- package/src/errors.ts +58 -0
- package/src/index.ts +26 -0
- package/src/native.ts +52 -0
- package/src/types.ts +171 -0
- package/src/useAuthonWallet.ts +103 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/Solana.swift. Do not edit: change the source and run the script.
|
|
2
|
+
#if canImport(CMonocypher)
|
|
3
|
+
import CMonocypher
|
|
4
|
+
#endif
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
/// Solana transaction signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/solana.ts`).
|
|
8
|
+
/// The app builds the serialized Message; the wallet parses it only to describe it and signs
|
|
9
|
+
/// the exact bytes.
|
|
10
|
+
enum Solana {
|
|
11
|
+
static let maxMessage = 1232
|
|
12
|
+
|
|
13
|
+
static let system = "11111111111111111111111111111111"
|
|
14
|
+
static let token = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
15
|
+
static let token2022 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
16
|
+
static let associatedToken = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
17
|
+
static let computeBudget = "ComputeBudget111111111111111111111111111111"
|
|
18
|
+
static let memo = "MemoSq4gqABAXKb96qnH8TyNoJ6CDrADxwaj4uuQ"
|
|
19
|
+
|
|
20
|
+
struct Instruction {
|
|
21
|
+
var programId: String
|
|
22
|
+
/// nil for an account that comes from an address lookup table.
|
|
23
|
+
var accounts: [String?]
|
|
24
|
+
var data: [UInt8]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
struct Message {
|
|
28
|
+
var isV0: Bool
|
|
29
|
+
var numRequiredSignatures: Int
|
|
30
|
+
var accountKeys: [String]
|
|
31
|
+
var recentBlockhash: String
|
|
32
|
+
var instructions: [Instruction]
|
|
33
|
+
/// Accounts loaded from address lookup tables (v0 only).
|
|
34
|
+
var lookupAccounts: Int
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static func invalid() -> WalletError { WalletError(.invalidRequest, "not a valid Solana message") }
|
|
38
|
+
|
|
39
|
+
private struct Reader {
|
|
40
|
+
let bytes: [UInt8]
|
|
41
|
+
var pos = 0
|
|
42
|
+
var done: Bool { pos == bytes.count }
|
|
43
|
+
|
|
44
|
+
mutating func u8() throws -> Int {
|
|
45
|
+
guard pos < bytes.count else { throw invalid() }
|
|
46
|
+
pos += 1
|
|
47
|
+
return Int(bytes[pos - 1])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mutating func take(_ n: Int) throws -> [UInt8] {
|
|
51
|
+
guard n >= 0, pos + n <= bytes.count else { throw invalid() }
|
|
52
|
+
pos += n
|
|
53
|
+
return Array(bytes[(pos - n)..<pos])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// compact-u16 (shortvec)
|
|
57
|
+
mutating func len() throws -> Int {
|
|
58
|
+
var value = 0
|
|
59
|
+
for i in 0..<3 {
|
|
60
|
+
let b = try u8()
|
|
61
|
+
value |= (b & 0x7f) << (7 * i)
|
|
62
|
+
if b & 0x80 == 0 { return value }
|
|
63
|
+
}
|
|
64
|
+
throw invalid()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static func parse(_ bytes: [UInt8]) throws -> Message {
|
|
69
|
+
guard !bytes.isEmpty, bytes.count <= maxMessage else { throw invalid() }
|
|
70
|
+
var r = Reader(bytes: bytes)
|
|
71
|
+
var isV0 = false
|
|
72
|
+
var first = try r.u8()
|
|
73
|
+
if first & 0x80 != 0 {
|
|
74
|
+
guard first & 0x7f == 0 else { throw WalletError(.invalidRequest, "unsupported Solana message version") }
|
|
75
|
+
isV0 = true
|
|
76
|
+
first = try r.u8()
|
|
77
|
+
}
|
|
78
|
+
let required = first
|
|
79
|
+
_ = try r.u8() // readonly signed
|
|
80
|
+
_ = try r.u8() // readonly unsigned
|
|
81
|
+
let keyCount = try r.len()
|
|
82
|
+
var keys = [String]()
|
|
83
|
+
for _ in 0..<keyCount { keys.append(Base58.encode(try r.take(32), alphabet: Base58.bitcoin)) }
|
|
84
|
+
let blockhash = Base58.encode(try r.take(32), alphabet: Base58.bitcoin)
|
|
85
|
+
var raw = [(program: Int, accounts: [Int], data: [UInt8])]()
|
|
86
|
+
for _ in 0..<(try r.len()) {
|
|
87
|
+
let program = try r.u8()
|
|
88
|
+
var accounts = [Int]()
|
|
89
|
+
for _ in 0..<(try r.len()) { accounts.append(try r.u8()) }
|
|
90
|
+
let data = try r.take(try r.len())
|
|
91
|
+
raw.append((program, accounts, data))
|
|
92
|
+
}
|
|
93
|
+
var lookups = 0
|
|
94
|
+
if isV0 {
|
|
95
|
+
for _ in 0..<(try r.len()) {
|
|
96
|
+
_ = try r.take(32)
|
|
97
|
+
lookups += try r.take(try r.len()).count
|
|
98
|
+
lookups += try r.take(try r.len()).count
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
guard r.done, required >= 1, required <= keyCount else { throw invalid() }
|
|
102
|
+
let total = keyCount + lookups
|
|
103
|
+
func key(_ i: Int) throws -> String? {
|
|
104
|
+
guard i < total else { throw invalid() }
|
|
105
|
+
return i < keyCount ? keys[i] : nil
|
|
106
|
+
}
|
|
107
|
+
let instructions = try raw.map { ix -> Instruction in
|
|
108
|
+
// Programs must be static keys.
|
|
109
|
+
guard let program = try key(ix.program) else { throw invalid() }
|
|
110
|
+
return Instruction(programId: program, accounts: try ix.accounts.map(key), data: ix.data)
|
|
111
|
+
}
|
|
112
|
+
return Message(isV0: isV0, numRequiredSignatures: required, accountKeys: keys, recentBlockhash: blockhash, instructions: instructions, lookupAccounts: lookups)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private static func u64(_ d: [UInt8], _ at: Int) throws -> UInt64 {
|
|
116
|
+
guard d.count >= at + 8 else { throw invalid() }
|
|
117
|
+
return (0..<8).reduce(UInt64(0)) { $0 | UInt64(d[at + $1]) << (8 * UInt64($1)) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static func u32(_ d: [UInt8], _ at: Int) throws -> UInt32 {
|
|
121
|
+
guard d.count >= at + 4 else { throw invalid() }
|
|
122
|
+
return (0..<4).reduce(UInt32(0)) { $0 | UInt32(d[at + $1]) << (8 * UInt32($1)) }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Integer amount with `decimals` places, trailing zeros removed.
|
|
126
|
+
static func units(_ amount: UInt64, _ decimals: Int) -> String {
|
|
127
|
+
if decimals == 0 { return String(amount) }
|
|
128
|
+
var digits = String(amount)
|
|
129
|
+
if digits.count <= decimals { digits = String(repeating: "0", count: decimals - digits.count + 1) + digits }
|
|
130
|
+
let whole = String(digits.dropLast(decimals))
|
|
131
|
+
var frac = String(digits.suffix(decimals))
|
|
132
|
+
while frac.hasSuffix("0") { frac.removeLast() }
|
|
133
|
+
return whole + (frac.isEmpty ? "" : "." + frac)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private static func addr(_ a: String??) -> String { (a ?? nil) ?? "(address from a lookup table)" }
|
|
137
|
+
|
|
138
|
+
/// The approval summary. The signer must pay the fee (account 0).
|
|
139
|
+
static func summarize(_ msg: Message, signer: String, network: String) throws -> SignSummary {
|
|
140
|
+
guard msg.accountKeys.first == signer else {
|
|
141
|
+
throw WalletError(.addressMismatch, "the fee payer is not the signing account")
|
|
142
|
+
}
|
|
143
|
+
var rows: [SignSummary.Row] = [.init(label: "Network", value: network), .init(label: "Account", value: signer, mono: true)]
|
|
144
|
+
var warnings = [String]()
|
|
145
|
+
if msg.numRequiredSignatures > 1 {
|
|
146
|
+
warnings.append("This transaction needs \(msg.numRequiredSignatures) signatures; you sign as the fee payer.")
|
|
147
|
+
}
|
|
148
|
+
if msg.lookupAccounts > 0 { warnings.append("Some accounts come from address lookup tables and cannot be shown.") }
|
|
149
|
+
for ix in msg.instructions {
|
|
150
|
+
let d = ix.data
|
|
151
|
+
func account(_ i: Int) -> String?? { i < ix.accounts.count ? ix.accounts[i] : .some(nil) }
|
|
152
|
+
let isToken = ix.programId == token || ix.programId == token2022
|
|
153
|
+
if ix.programId == system, d.count >= 12, try u32(d, 0) == 2 {
|
|
154
|
+
rows += [.init(label: "Send", value: units(try u64(d, 4), 9) + " SOL"), .init(label: "To", value: addr(account(1)), mono: true)]
|
|
155
|
+
if (ix.accounts.first ?? nil) != signer { warnings.append("SOL is sent from an account other than yours.") }
|
|
156
|
+
} else if isToken, d.first == 12, d.count >= 10 {
|
|
157
|
+
rows += [
|
|
158
|
+
.init(label: "Send token", value: units(try u64(d, 1), Int(d[9]))),
|
|
159
|
+
.init(label: "Token", value: addr(account(1)), mono: true),
|
|
160
|
+
.init(label: "To token account", value: addr(account(2)), mono: true),
|
|
161
|
+
]
|
|
162
|
+
} else if isToken, d.first == 3, d.count >= 9 {
|
|
163
|
+
rows += [
|
|
164
|
+
.init(label: "Send token (raw units)", value: String(try u64(d, 1))),
|
|
165
|
+
.init(label: "To token account", value: addr(account(1)), mono: true),
|
|
166
|
+
]
|
|
167
|
+
warnings.append("Token amount is shown in raw units (no decimals in this instruction).")
|
|
168
|
+
} else if ix.programId == associatedToken, d.isEmpty || d[0] == 0 || d[0] == 1 {
|
|
169
|
+
rows.append(.init(label: "Create token account", value: addr(account(1)), mono: true))
|
|
170
|
+
} else if ix.programId == computeBudget, d.first == 2, d.count >= 5 {
|
|
171
|
+
rows.append(.init(label: "Compute limit", value: String(try u32(d, 1))))
|
|
172
|
+
} else if ix.programId == computeBudget, d.first == 3, d.count >= 9 {
|
|
173
|
+
rows.append(.init(label: "Priority fee", value: "\(try u64(d, 1)) micro-lamports per unit"))
|
|
174
|
+
} else if ix.programId == memo {
|
|
175
|
+
rows.append(.init(label: "Memo", value: String(decoding: d, as: UTF8.self)))
|
|
176
|
+
} else {
|
|
177
|
+
rows.append(.init(label: "Program call", value: ix.programId, mono: true))
|
|
178
|
+
warnings.append("Calls a program this wallet cannot describe (\(ix.programId)). Approve only if you trust the app.")
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
rows.append(.init(label: "Recent blockhash", value: msg.recentBlockhash, mono: true))
|
|
182
|
+
return SignSummary(kind: .transaction, title: "Solana transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// The SLIP-0010 key of Solana account `index`. Callers wipe it.
|
|
186
|
+
static func privateKey(_ secret: [UInt8], index: Int) throws -> [UInt8] {
|
|
187
|
+
guard WalletShares.isAccountIndex(index) else { throw WalletError(.invalidRequest, "invalid account index") }
|
|
188
|
+
guard WalletShares.isHdSecret(secret) else { throw WalletError(.accountUnavailable, "this wallet has only EVM account 0") }
|
|
189
|
+
var seed = try BIP39.seed(mnemonic: try BIP39.mnemonic(entropy: secret))
|
|
190
|
+
defer { seed.wipe() }
|
|
191
|
+
return try ChainAddresses.ed25519Key(seed: seed, path: WalletChain.solana.path(index))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Deterministic RFC 8032 Ed25519 (Monocypher) over the exact message bytes.
|
|
195
|
+
/// Returns (address, base58 signature).
|
|
196
|
+
static func sign(_ message: [UInt8], privateKey: [UInt8]) -> (address: String, signature: String) {
|
|
197
|
+
var seed = privateKey // crypto_ed25519_key_pair wipes its seed argument
|
|
198
|
+
var secretKey = [UInt8](repeating: 0, count: 64)
|
|
199
|
+
var publicKey = [UInt8](repeating: 0, count: 32)
|
|
200
|
+
var signature = [UInt8](repeating: 0, count: 64)
|
|
201
|
+
crypto_ed25519_key_pair(&secretKey, &publicKey, &seed)
|
|
202
|
+
message.withUnsafeBufferPointer { m in crypto_ed25519_sign(&signature, secretKey, m.baseAddress, m.count) }
|
|
203
|
+
secretKey.wipe()
|
|
204
|
+
seed.wipe()
|
|
205
|
+
return (Base58.encode(publicKey, alphabet: Base58.bitcoin), Base58.encode(signature, alphabet: Base58.bitcoin))
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/Tron.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// TRON transaction signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/tron.ts`). The app
|
|
5
|
+
/// builds raw_data; the wallet decodes it only to describe it and signs SHA-256(raw_data) like
|
|
6
|
+
/// TronWeb (r || s || v with v = recovery + 27).
|
|
7
|
+
enum Tron {
|
|
8
|
+
static let maxRawData = 4096
|
|
9
|
+
|
|
10
|
+
struct Contract {
|
|
11
|
+
var type: UInt64
|
|
12
|
+
/// Decoded fields of TransferContract (1) / TriggerSmartContract (31).
|
|
13
|
+
var owner: String?
|
|
14
|
+
var to: String?
|
|
15
|
+
var amount: UInt64?
|
|
16
|
+
var contractAddress: String?
|
|
17
|
+
var callValue: UInt64?
|
|
18
|
+
var data: [UInt8]?
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct RawData {
|
|
22
|
+
var expiration: UInt64?
|
|
23
|
+
var timestamp: UInt64?
|
|
24
|
+
var feeLimit: UInt64?
|
|
25
|
+
var memo: [UInt8]?
|
|
26
|
+
var contracts: [Contract]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static func invalid() -> WalletError { WalletError(.invalidRequest, "not a valid TRON raw_data") }
|
|
30
|
+
|
|
31
|
+
struct Field {
|
|
32
|
+
var no: UInt64
|
|
33
|
+
var varint: UInt64?
|
|
34
|
+
var bytes: [UInt8]?
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Minimal protobuf reader: varint (0), 64-bit (1), length-delimited (2), 32-bit (5).
|
|
38
|
+
static func fields(_ buf: [UInt8]) throws -> [Field] {
|
|
39
|
+
var out = [Field]()
|
|
40
|
+
var pos = 0
|
|
41
|
+
func varint() throws -> UInt64 {
|
|
42
|
+
var v: UInt64 = 0
|
|
43
|
+
var shift: UInt64 = 0
|
|
44
|
+
while shift < 64 {
|
|
45
|
+
guard pos < buf.count else { throw invalid() }
|
|
46
|
+
let b = buf[pos]
|
|
47
|
+
pos += 1
|
|
48
|
+
if shift == 63 && b > 1 { throw invalid() }
|
|
49
|
+
v |= UInt64(b & 0x7f) << shift
|
|
50
|
+
if b & 0x80 == 0 { return v }
|
|
51
|
+
shift += 7
|
|
52
|
+
}
|
|
53
|
+
throw invalid()
|
|
54
|
+
}
|
|
55
|
+
while pos < buf.count {
|
|
56
|
+
let key = try varint()
|
|
57
|
+
let no = key >> 3
|
|
58
|
+
let wire = key & 7
|
|
59
|
+
guard no != 0 else { throw invalid() }
|
|
60
|
+
switch wire {
|
|
61
|
+
case 0:
|
|
62
|
+
out.append(Field(no: no, varint: try varint()))
|
|
63
|
+
case 2:
|
|
64
|
+
let len = try varint()
|
|
65
|
+
guard len <= UInt64(buf.count - pos) else { throw invalid() }
|
|
66
|
+
out.append(Field(no: no, bytes: Array(buf[pos..<(pos + Int(len))])))
|
|
67
|
+
pos += Int(len)
|
|
68
|
+
case 1, 5:
|
|
69
|
+
let len = wire == 1 ? 8 : 4
|
|
70
|
+
guard pos + len <= buf.count else { throw invalid() }
|
|
71
|
+
pos += len
|
|
72
|
+
out.append(Field(no: no))
|
|
73
|
+
default:
|
|
74
|
+
throw invalid()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static func one(_ fs: [Field], _ no: UInt64) -> Field? { fs.first { $0.no == no } }
|
|
81
|
+
|
|
82
|
+
static func address(_ raw: [UInt8]) throws -> String {
|
|
83
|
+
guard raw.count == 21, raw[0] == 0x41 else { throw invalid() }
|
|
84
|
+
return Base58.encode(raw + ChainAddresses.checksum4(raw), alphabet: Base58.bitcoin)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static func parse(_ raw: [UInt8]) throws -> RawData {
|
|
88
|
+
guard !raw.isEmpty, raw.count <= maxRawData else { throw invalid() }
|
|
89
|
+
let top = try fields(raw)
|
|
90
|
+
let contracts = try top.filter { $0.no == 11 && $0.bytes != nil }.map { f -> Contract in
|
|
91
|
+
let c = try fields(f.bytes!)
|
|
92
|
+
let type = one(c, 1)?.varint ?? 0
|
|
93
|
+
var contract = Contract(type: type)
|
|
94
|
+
let any = one(c, 2)?.bytes
|
|
95
|
+
let value = try any.flatMap { one(try fields($0), 2)?.bytes }
|
|
96
|
+
if let value, type == 1 || type == 31 {
|
|
97
|
+
let v = try fields(value)
|
|
98
|
+
func addr(_ no: UInt64) throws -> String? { try one(v, no)?.bytes.map(address) }
|
|
99
|
+
contract.owner = try addr(1)
|
|
100
|
+
if type == 1 {
|
|
101
|
+
contract.to = try addr(2)
|
|
102
|
+
contract.amount = one(v, 3)?.varint ?? 0
|
|
103
|
+
} else {
|
|
104
|
+
contract.contractAddress = try addr(2)
|
|
105
|
+
contract.callValue = one(v, 3)?.varint ?? 0
|
|
106
|
+
contract.data = one(v, 4)?.bytes ?? []
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return contract
|
|
110
|
+
}
|
|
111
|
+
guard !contracts.isEmpty else { throw invalid() }
|
|
112
|
+
return RawData(
|
|
113
|
+
expiration: one(top, 8)?.varint,
|
|
114
|
+
timestamp: one(top, 14)?.varint,
|
|
115
|
+
feeLimit: one(top, 18)?.varint,
|
|
116
|
+
memo: one(top, 10)?.bytes,
|
|
117
|
+
contracts: contracts
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static func trx(_ sun: UInt64) -> String { Solana.units(sun, 6) + " TRX" }
|
|
122
|
+
|
|
123
|
+
private static func word(_ data: [UInt8], _ i: Int) -> [UInt8] { Array(data[(4 + 32 * i)..<(4 + 32 * (i + 1))]) }
|
|
124
|
+
|
|
125
|
+
/// `new Date(ms).toISOString()`
|
|
126
|
+
static func isoDate(_ ms: UInt64) throws -> String {
|
|
127
|
+
guard ms <= 8_640_000_000_000_000 else { throw invalid() }
|
|
128
|
+
let f = ISO8601DateFormatter()
|
|
129
|
+
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
130
|
+
f.timeZone = TimeZone(identifier: "UTC")
|
|
131
|
+
return f.string(from: Date(timeIntervalSince1970: Double(ms) / 1000))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static func summarize(_ tx: RawData, signer: String) throws -> SignSummary {
|
|
135
|
+
var rows: [SignSummary.Row] = [.init(label: "Network", value: "TRON"), .init(label: "Account", value: signer, mono: true)]
|
|
136
|
+
var warnings = [String]()
|
|
137
|
+
if tx.contracts.count != 1 { warnings.append("This transaction has \(tx.contracts.count) contracts.") }
|
|
138
|
+
for c in tx.contracts {
|
|
139
|
+
if let owner = c.owner, owner != signer {
|
|
140
|
+
throw WalletError(.addressMismatch, "the transaction owner is not the signing account")
|
|
141
|
+
}
|
|
142
|
+
if c.type == 1, let to = c.to, !to.isEmpty {
|
|
143
|
+
rows += [.init(label: "Send", value: trx(c.amount ?? 0)), .init(label: "To", value: to, mono: true)]
|
|
144
|
+
} else if c.type == 31, let data = c.data {
|
|
145
|
+
rows.append(.init(label: "Contract", value: c.contractAddress ?? "?", mono: true))
|
|
146
|
+
if let v = c.callValue, v > 0 { rows.append(.init(label: "Send", value: trx(v))) }
|
|
147
|
+
let selector = Bytes.hex(Array(data.prefix(4)))
|
|
148
|
+
if selector == "a9059cbb" && data.count == 68 {
|
|
149
|
+
rows += [
|
|
150
|
+
.init(label: "Token transfer to", value: try address([0x41] + word(data, 0)[12...]), mono: true),
|
|
151
|
+
.init(label: "Token amount (raw units)", value: BigUInt(bytes: word(data, 1)).description),
|
|
152
|
+
]
|
|
153
|
+
} else if selector == "095ea7b3" && data.count == 68 {
|
|
154
|
+
let amount = BigUInt(bytes: word(data, 1))
|
|
155
|
+
rows += [
|
|
156
|
+
.init(label: "Allow spender", value: try address([0x41] + word(data, 0)[12...]), mono: true),
|
|
157
|
+
.init(label: "Allowance (raw units)", value: amount == BigUInt.power2(256) - BigUInt(1) ? "Unlimited" : amount.description),
|
|
158
|
+
]
|
|
159
|
+
warnings.append("This lets another address spend your tokens.")
|
|
160
|
+
} else {
|
|
161
|
+
rows.append(.init(label: "Call data", value: Bytes.hex(data, prefix: true), mono: true))
|
|
162
|
+
warnings.append("Calls a contract function this wallet cannot describe. Approve only if you trust the app.")
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
rows.append(.init(label: "Contract type", value: String(c.type)))
|
|
166
|
+
warnings.append("TRON contract type \(c.type) cannot be described. Approve only if you trust the app.")
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if let fee = tx.feeLimit { rows.append(.init(label: "Fee limit", value: trx(fee))) }
|
|
170
|
+
if let memo = tx.memo, !memo.isEmpty { rows.append(.init(label: "Memo", value: String(decoding: memo, as: UTF8.self))) }
|
|
171
|
+
if let exp = tx.expiration { rows.append(.init(label: "Expires", value: try isoDate(exp))) }
|
|
172
|
+
return SignSummary(kind: .transaction, title: "TRON transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// txID = SHA-256(raw_data); signature r || s || (recovery + 27) in lowercase hex.
|
|
176
|
+
static func sign(_ raw: [UInt8], privateKey: [UInt8]) throws -> [String: String] {
|
|
177
|
+
let txID = Bytes.sha256(raw)
|
|
178
|
+
let sig = try Secp256k1.sign(hash: txID, key: privateKey)
|
|
179
|
+
return ["txID": Bytes.hex(txID), "signature": Bytes.hex(sig.r + sig.s + [UInt8(27 + sig.recoveryId)])]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/XRP.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import CryptoKit
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/// XRP Ledger signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/xrp.ts`). The app sends
|
|
6
|
+
/// the output of ripple-binary-codec encodeForSigning (prefix STX\0); the wallet decodes it only
|
|
7
|
+
/// to describe it and signs SHA-512Half(blob) with secp256k1, DER, low S.
|
|
8
|
+
enum XRP {
|
|
9
|
+
static let maxBlob = 4096
|
|
10
|
+
static let signingPrefix: [UInt8] = [0x53, 0x54, 0x58, 0x00]
|
|
11
|
+
|
|
12
|
+
struct Tx {
|
|
13
|
+
var transactionType: Int?
|
|
14
|
+
var account: String?
|
|
15
|
+
var destination: String?
|
|
16
|
+
/// Drops for XRP amounts; `.some(nil)` for issued-token amounts.
|
|
17
|
+
var amount: UInt64??
|
|
18
|
+
var fee: UInt64??
|
|
19
|
+
var sequence: Int?
|
|
20
|
+
var destinationTag: Int?
|
|
21
|
+
var lastLedgerSequence: Int?
|
|
22
|
+
var signingPubKey: [UInt8]?
|
|
23
|
+
var memos: [String] = []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private static func invalid(_ why: String = "not a valid XRP signing blob") -> WalletError { WalletError(.invalidRequest, why) }
|
|
27
|
+
|
|
28
|
+
static func address(accountId: [UInt8]) throws -> String {
|
|
29
|
+
guard accountId.count == 20 else { throw invalid() }
|
|
30
|
+
let payload = [0x00] + accountId
|
|
31
|
+
return Base58.encode(payload + ChainAddresses.checksum4(payload), alphabet: Base58.xrp)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static func address(publicKey compressed: [UInt8]) throws -> String {
|
|
35
|
+
try address(accountId: ChainAddresses.hash160(compressed))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private struct Reader {
|
|
39
|
+
let b: [UInt8]
|
|
40
|
+
var pos = 0
|
|
41
|
+
var done: Bool { pos >= b.count }
|
|
42
|
+
|
|
43
|
+
mutating func u8() throws -> Int {
|
|
44
|
+
guard pos < b.count else { throw invalid() }
|
|
45
|
+
pos += 1
|
|
46
|
+
return Int(b[pos - 1])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mutating func take(_ n: Int) throws -> [UInt8] {
|
|
50
|
+
guard pos + n <= b.count else { throw invalid() }
|
|
51
|
+
pos += n
|
|
52
|
+
return Array(b[(pos - n)..<pos])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
mutating func uint(_ n: Int) throws -> Int { try take(n).reduce(0) { $0 * 256 + Int($1) } }
|
|
56
|
+
|
|
57
|
+
/// XRPL variable-length prefix.
|
|
58
|
+
mutating func vl() throws -> [UInt8] {
|
|
59
|
+
let b1 = try u8()
|
|
60
|
+
if b1 <= 192 { return try take(b1) }
|
|
61
|
+
if b1 <= 240 { return try take(193 + (b1 - 193) * 256 + (try u8())) }
|
|
62
|
+
if b1 <= 254 { return try take(12481 + (b1 - 241) * 65536 + (try u8()) * 256 + (try u8())) }
|
|
63
|
+
throw invalid()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
mutating func field() throws -> (type: Int, code: Int) {
|
|
67
|
+
let h = try u8()
|
|
68
|
+
var type = h >> 4
|
|
69
|
+
var code = h & 0x0f
|
|
70
|
+
if type == 0 { type = try u8() }
|
|
71
|
+
if code == 0 { code = try u8() }
|
|
72
|
+
return (type, code)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mutating func amount() throws -> UInt64? {
|
|
76
|
+
guard pos < b.count else { throw invalid() }
|
|
77
|
+
if b[pos] & 0x80 != 0 {
|
|
78
|
+
_ = try take(48) // issued token: 8 value + 20 currency + 20 issuer
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
let v = try take(8).reduce(UInt64(0)) { $0 << 8 | UInt64($1) }
|
|
82
|
+
return v & 0x3fff_ffff_ffff_ffff
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Reads one STObject until its end marker (or the end of the blob at the top level).
|
|
87
|
+
/// `memos` collects MemoData inside the top-level Memos array.
|
|
88
|
+
private static func readObject(_ r: inout Reader, _ tx: inout Tx?, depth: Int, memos: inout [String]?) throws {
|
|
89
|
+
guard depth <= 4 else { throw invalid() }
|
|
90
|
+
while !r.done {
|
|
91
|
+
let (type, code) = try r.field()
|
|
92
|
+
if type == 14 && code == 1 { return } // ObjectEndMarker
|
|
93
|
+
if type == 15 && code == 1 { return } // ArrayEndMarker
|
|
94
|
+
switch type {
|
|
95
|
+
case 1:
|
|
96
|
+
let v = try r.uint(2)
|
|
97
|
+
if code == 2 { tx?.transactionType = v }
|
|
98
|
+
case 2:
|
|
99
|
+
let v = try r.uint(4)
|
|
100
|
+
if code == 4 { tx?.sequence = v } else if code == 14 { tx?.destinationTag = v } else if code == 27 { tx?.lastLedgerSequence = v }
|
|
101
|
+
case 3: _ = try r.take(8)
|
|
102
|
+
case 4: _ = try r.take(16)
|
|
103
|
+
case 5: _ = try r.take(32)
|
|
104
|
+
case 16: _ = try r.take(1)
|
|
105
|
+
case 17: _ = try r.take(20)
|
|
106
|
+
case 6:
|
|
107
|
+
let v = try r.amount()
|
|
108
|
+
if code == 1 { tx?.amount = .some(v) } else if code == 8 { tx?.fee = .some(v) }
|
|
109
|
+
case 7:
|
|
110
|
+
let v = try r.vl()
|
|
111
|
+
if tx != nil, code == 3 {
|
|
112
|
+
tx?.signingPubKey = v
|
|
113
|
+
} else if tx == nil, code == 13 {
|
|
114
|
+
memos?.append(String(decoding: v, as: UTF8.self))
|
|
115
|
+
}
|
|
116
|
+
case 8:
|
|
117
|
+
let v = try r.vl()
|
|
118
|
+
if tx != nil, code == 1 { tx?.account = try address(accountId: v) } else if tx != nil, code == 3 { tx?.destination = try address(accountId: v) }
|
|
119
|
+
case 14:
|
|
120
|
+
var none: Tx? = nil
|
|
121
|
+
try readObject(&r, &none, depth: depth + 1, memos: &memos)
|
|
122
|
+
case 15:
|
|
123
|
+
var none: Tx? = nil
|
|
124
|
+
if tx != nil && code == 9 {
|
|
125
|
+
var collected: [String]? = []
|
|
126
|
+
try readObject(&r, &none, depth: depth + 1, memos: &collected)
|
|
127
|
+
tx?.memos += collected ?? []
|
|
128
|
+
} else {
|
|
129
|
+
try readObject(&r, &none, depth: depth + 1, memos: &memos)
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
throw invalid("XRP field type \(type) is not supported")
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static func parse(_ blob: [UInt8]) throws -> Tx {
|
|
138
|
+
guard blob.count >= 5, blob.count <= maxBlob, Array(blob.prefix(4)) == signingPrefix else { throw invalid() }
|
|
139
|
+
var r = Reader(b: Array(blob.dropFirst(4)))
|
|
140
|
+
var tx: Tx? = Tx()
|
|
141
|
+
var memos: [String]? = nil
|
|
142
|
+
try readObject(&r, &tx, depth: 0, memos: &memos)
|
|
143
|
+
guard let parsed = tx, parsed.transactionType != nil, parsed.account != nil, parsed.signingPubKey != nil else { throw invalid() }
|
|
144
|
+
return parsed
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static func xrp(_ drops: UInt64) -> String { Solana.units(drops, 6) + " XRP" }
|
|
148
|
+
|
|
149
|
+
private static let types: [Int: String] = [0: "Payment", 3: "AccountSet", 7: "OfferCreate", 8: "OfferCancel", 12: "SetRegularKey", 20: "TrustSet"]
|
|
150
|
+
|
|
151
|
+
static func summarize(_ tx: Tx, signer: String, signerPublicKey: [UInt8]) throws -> SignSummary {
|
|
152
|
+
guard tx.account == signer else { throw WalletError(.addressMismatch, "the transaction account is not the signing account") }
|
|
153
|
+
guard tx.signingPubKey == signerPublicKey else {
|
|
154
|
+
throw WalletError(.invalidRequest, "SigningPubKey must be the signing account public key")
|
|
155
|
+
}
|
|
156
|
+
let type = tx.transactionType ?? 0
|
|
157
|
+
var rows: [SignSummary.Row] = [
|
|
158
|
+
.init(label: "Network", value: "XRP Ledger"),
|
|
159
|
+
.init(label: "Account", value: signer, mono: true),
|
|
160
|
+
.init(label: "Type", value: types[type] ?? "Type \(type)"),
|
|
161
|
+
]
|
|
162
|
+
var warnings = [String]()
|
|
163
|
+
if type == 0 {
|
|
164
|
+
switch tx.amount {
|
|
165
|
+
case .some(.none):
|
|
166
|
+
rows.append(.init(label: "Send", value: "Issued token amount"))
|
|
167
|
+
warnings.append("Issued-token payments cannot be described in detail.")
|
|
168
|
+
case .some(.some(let drops)):
|
|
169
|
+
rows.append(.init(label: "Send", value: xrp(drops)))
|
|
170
|
+
case .none:
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
if let d = tx.destination, !d.isEmpty { rows.append(.init(label: "To", value: d, mono: true)) }
|
|
174
|
+
if let tag = tx.destinationTag { rows.append(.init(label: "Destination tag", value: String(tag))) }
|
|
175
|
+
} else {
|
|
176
|
+
warnings.append("\(types[type] ?? "This") transaction cannot be described in detail. Approve only if you trust the app.")
|
|
177
|
+
}
|
|
178
|
+
for m in tx.memos { rows.append(.init(label: "Memo", value: m)) }
|
|
179
|
+
if case .some(.some(let fee)) = tx.fee { rows.append(.init(label: "Network fee", value: xrp(fee))) }
|
|
180
|
+
if let s = tx.sequence { rows.append(.init(label: "Sequence", value: String(s))) }
|
|
181
|
+
if let l = tx.lastLedgerSequence { rows.append(.init(label: "Valid until ledger", value: String(l))) }
|
|
182
|
+
return SignSummary(kind: .transaction, title: "XRP Ledger transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static func sha512Half(_ data: [UInt8]) -> [UInt8] { Array(Array(SHA512.hash(data: data))[0..<32]) }
|
|
186
|
+
|
|
187
|
+
/// DER (low S) over SHA-512Half of the signing blob, uppercase hex like ripple-keypairs.
|
|
188
|
+
static func sign(_ blob: [UInt8], privateKey: [UInt8]) throws -> [String: String] {
|
|
189
|
+
["signature": Bytes.hex(try Secp256k1.signDER(hash: sha512Half(blob), key: privateKey)).uppercased()]
|
|
190
|
+
}
|
|
191
|
+
}
|