@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.
Files changed (159) hide show
  1. package/README.md +221 -2
  2. package/android/build.gradle +40 -0
  3. package/android/consumer-rules.pro +10 -0
  4. package/android/src/main/AndroidManifest.xml +4 -0
  5. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +181 -0
  6. package/android/src/vendor/GENERATED.md +4 -0
  7. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +191 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +82 -0
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +232 -0
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
  21. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
  22. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
  23. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
  24. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +353 -0
  25. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +58 -0
  26. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +449 -0
  27. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +71 -0
  28. package/build/authonContext.d.ts +19 -0
  29. package/build/authonContext.d.ts.map +1 -0
  30. package/build/authonContext.js +20 -0
  31. package/build/authonContext.js.map +1 -0
  32. package/build/client.d.ts +76 -0
  33. package/build/client.d.ts.map +1 -0
  34. package/build/client.js +349 -0
  35. package/build/client.js.map +1 -0
  36. package/build/errors.d.ts +18 -0
  37. package/build/errors.d.ts.map +1 -0
  38. package/build/errors.js +9 -0
  39. package/build/errors.js.map +1 -0
  40. package/build/index.d.ts +7 -0
  41. package/build/index.d.ts.map +1 -0
  42. package/build/index.js +4 -0
  43. package/build/index.js.map +1 -0
  44. package/build/native.d.ts +39 -0
  45. package/build/native.d.ts.map +1 -0
  46. package/build/native.js +12 -0
  47. package/build/native.js.map +1 -0
  48. package/build/types.d.ts +121 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +3 -0
  51. package/build/types.js.map +1 -0
  52. package/build/useAuthonWallet.d.ts +25 -0
  53. package/build/useAuthonWallet.d.ts.map +1 -0
  54. package/build/useAuthonWallet.js +77 -0
  55. package/build/useAuthonWallet.js.map +1 -0
  56. package/expo-module.config.json +9 -0
  57. package/ios/AuthonReactNativeWallet.podspec +37 -0
  58. package/ios/AuthonWalletModule.swift +190 -0
  59. package/ios/vendor/AuthonWallet/AuthonWallet.swift +212 -0
  60. package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
  61. package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
  62. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +104 -0
  63. package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
  64. package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
  65. package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
  66. package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
  67. package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
  68. package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
  69. package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
  70. package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
  71. package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
  72. package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
  73. package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
  74. package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
  75. package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
  76. package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
  77. package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
  78. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +350 -0
  79. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +555 -0
  80. package/ios/vendor/AuthonWallet/WalletError.swift +54 -0
  81. package/ios/vendor/CKeccak/LICENSE +22 -0
  82. package/ios/vendor/CKeccak/README.md +27 -0
  83. package/ios/vendor/CKeccak/byte_order.h +59 -0
  84. package/ios/vendor/CKeccak/include/ckeccak.h +9 -0
  85. package/ios/vendor/CKeccak/memzero.c +75 -0
  86. package/ios/vendor/CKeccak/memzero.h +8 -0
  87. package/ios/vendor/CKeccak/options.h +99 -0
  88. package/ios/vendor/CKeccak/sha3.c +413 -0
  89. package/ios/vendor/CKeccak/sha3.h +89 -0
  90. package/ios/vendor/CSecp256k1/COPYING +19 -0
  91. package/ios/vendor/CSecp256k1/README.md +35 -0
  92. package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
  93. package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
  94. package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
  95. package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
  96. package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
  97. package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
  98. package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
  99. package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
  100. package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
  101. package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
  102. package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
  103. package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
  104. package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
  105. package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
  106. package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
  107. package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
  108. package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
  109. package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
  110. package/ios/vendor/CSecp256k1/src/field.h +350 -0
  111. package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
  112. package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
  113. package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
  114. package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
  115. package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
  116. package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
  117. package/ios/vendor/CSecp256k1/src/group.h +212 -0
  118. package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
  119. package/ios/vendor/CSecp256k1/src/hash.h +44 -0
  120. package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
  121. package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
  122. package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
  123. package/ios/vendor/CSecp256k1/src/int128.h +90 -0
  124. package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
  125. package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
  126. package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
  127. package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
  128. package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
  129. package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
  130. package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
  131. package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
  132. package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
  133. package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
  134. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
  135. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
  136. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
  137. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
  138. package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
  139. package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
  140. package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
  141. package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
  142. package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
  143. package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
  144. package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
  145. package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
  146. package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
  147. package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
  148. package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
  149. package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
  150. package/ios/vendor/CSecp256k1/src/util.h +451 -0
  151. package/ios/vendor/GENERATED.md +4 -0
  152. package/package.json +65 -3
  153. package/src/authonContext.ts +30 -0
  154. package/src/client.ts +375 -0
  155. package/src/errors.ts +52 -0
  156. package/src/index.ts +21 -0
  157. package/src/native.ts +50 -0
  158. package/src/types.ts +120 -0
  159. package/src/useAuthonWallet.ts +103 -0
@@ -0,0 +1,335 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/SignSummary.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.Json
5
+ import kotlinx.serialization.json.JsonArray
6
+ import kotlinx.serialization.json.JsonElement
7
+ import kotlinx.serialization.json.JsonObject
8
+ import kotlinx.serialization.json.JsonPrimitive
9
+ import kotlinx.serialization.json.contentOrNull
10
+ import org.web3j.crypto.Hash
11
+ import org.web3j.crypto.Keys
12
+ import java.math.BigDecimal
13
+ import java.math.BigInteger
14
+
15
+ data class SignRow(val label: String, val value: String, val mono: Boolean = false)
16
+
17
+ enum class SignRisk { LOW, HIGH }
18
+
19
+ /**
20
+ * Human-readable description of a signing request, built by the SDK from the exact payload that
21
+ * will be signed (same rules as `apps/wallet/src/summary.ts`).
22
+ */
23
+ data class SignSummary(
24
+ val kind: Kind,
25
+ val title: String,
26
+ val rows: List<SignRow>,
27
+ /** The complete text that is signed, never shortened. */
28
+ val body: String? = null,
29
+ val warnings: List<String>,
30
+ /** HIGH: moves value or grants spending. */
31
+ val risk: SignRisk,
32
+ ) {
33
+ enum class Kind { MESSAGE, TYPED_DATA, TRANSACTION }
34
+ }
35
+
36
+ internal object Summaries {
37
+ /** Longest text the approval shows; longer requests are refused instead of shown partially. */
38
+ const val MAX_SIGN_TEXT = 16_384
39
+
40
+ private data class Chain(val name: String, val symbol: String)
41
+
42
+ private val CHAINS = mapOf(
43
+ 1L to Chain("Ethereum", "ETH"),
44
+ 10L to Chain("Optimism", "ETH"),
45
+ 56L to Chain("BNB Smart Chain", "BNB"),
46
+ 137L to Chain("Polygon", "POL"),
47
+ 8453L to Chain("Base", "ETH"),
48
+ 42161L to Chain("Arbitrum One", "ETH"),
49
+ 43114L to Chain("Avalanche C-Chain", "AVAX"),
50
+ 80002L to Chain("Polygon Amoy (testnet)", "POL"),
51
+ 84532L to Chain("Base Sepolia (testnet)", "ETH"),
52
+ 11155111L to Chain("Sepolia (testnet)", "ETH"),
53
+ )
54
+
55
+ fun chainLabel(chainId: Long?): String {
56
+ if (chainId == null) return "Unknown network"
57
+ val c = CHAINS[chainId]
58
+ return if (c != null) "${c.name} ($chainId)" else "Chain $chainId"
59
+ }
60
+
61
+ private fun nativeSymbol(chainId: Long?): String = chainId?.let { CHAINS[it]?.symbol } ?: "native token"
62
+
63
+ private val MAX_UINT256: BigInteger = BigInteger.ONE.shiftLeft(256).subtract(BigInteger.ONE)
64
+ private val UNLIMITED_THRESHOLD: BigInteger = BigInteger.ONE.shiftLeft(255)
65
+ private const val PERMIT2 = "0x000000000022d473030f116ddee9f6b43ac78ba3"
66
+ private val ADDRESS = Regex("^0x[0-9a-fA-F]{40}$")
67
+ @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
68
+ private val prettyJson = Json { prettyPrint = true; prettyPrintIndent = " " }
69
+
70
+ private fun requireShowable(text: String): String {
71
+ if (text.length > MAX_SIGN_TEXT) {
72
+ throw walletError(
73
+ WalletErrorCode.MESSAGE_TOO_LARGE,
74
+ "the request has ${text.length} characters; at most $MAX_SIGN_TEXT can be shown",
75
+ )
76
+ }
77
+ return text
78
+ }
79
+
80
+ private fun addr(value: Any?): String {
81
+ val s = when (value) {
82
+ is JsonPrimitive -> value.contentOrNull
83
+ else -> value?.toString()
84
+ } ?: "undefined"
85
+ return if (ADDRESS.matches(s)) Keys.toChecksumAddress(s) else s
86
+ }
87
+
88
+ private fun amountText(value: BigInteger, unlimitedLabel: String = "Unlimited"): String =
89
+ if (value >= UNLIMITED_THRESHOLD) {
90
+ "$unlimitedLabel (${if (value == MAX_UINT256) "max uint256" else value.toString()})"
91
+ } else {
92
+ value.toString()
93
+ }
94
+
95
+ private fun toBigInt(value: JsonElement?): BigInteger? {
96
+ if (value == null || value !is JsonPrimitive) return null
97
+ if (value.isString && value.content.isEmpty()) return null
98
+ return JsValues.toBigIntOrNull(value)
99
+ }
100
+
101
+ private fun toChainId(value: JsonElement?): Long? = toBigInt(value)?.toLong()
102
+
103
+ /** viem formatUnits. */
104
+ fun formatUnits(value: BigInteger, decimals: Int): String {
105
+ val d = BigDecimal(value).movePointLeft(decimals).stripTrailingZeros()
106
+ return if (d.signum() == 0) "0" else d.toPlainString()
107
+ }
108
+
109
+ sealed class Message {
110
+ data class Text(val text: String) : Message()
111
+ data class Raw(val hex: String) : Message()
112
+ }
113
+
114
+ fun message(message: Message): SignSummary = when (message) {
115
+ is Message.Text -> SignSummary(SignSummary.Kind.MESSAGE, "Sign message", emptyList(), requireShowable(message.text), emptyList(), SignRisk.LOW)
116
+ is Message.Raw -> {
117
+ val text = Proof.rawMessageText(message.hex)
118
+ val printable = text != null && !Regex("[\\u0000-\\u0008\\u000e-\\u001f\\u007f]").containsMatchIn(text)
119
+ SignSummary(
120
+ SignSummary.Kind.MESSAGE,
121
+ "Sign message",
122
+ if (printable) emptyList() else listOf(SignRow("Format", "Raw bytes (not readable text)")),
123
+ requireShowable(if (printable) text!! else message.hex),
124
+ if (printable) emptyList() else listOf("This message is not readable text. Only sign it if you trust this site."),
125
+ SignRisk.LOW,
126
+ )
127
+ }
128
+ }
129
+
130
+ private val PERMIT_TYPES = setOf(
131
+ "Permit", "PermitSingle", "PermitBatch", "PermitTransferFrom", "PermitBatchTransferFrom",
132
+ "PermitWitnessTransferFrom", "PermitBatchWitnessTransferFrom", "PermitForAll",
133
+ )
134
+ private val SPENDING_TYPES = setOf(
135
+ "OrderComponents", "Order", "BulkOrder", "OrderParameters", "ForwardRequest", "MetaTransaction", "SafeTx", "Delegation", "Approval",
136
+ )
137
+ private val SPENDING_KEYS = setOf("spender", "operator", "delegate", "delegatee", "approved", "allowance", "allowed")
138
+
139
+ private fun grantsSpending(message: JsonElement): Boolean {
140
+ var budget = 2000
141
+ fun walk(v: JsonElement, depth: Int): Boolean {
142
+ if (--budget < 0 || depth > 12) return true
143
+ if (v is JsonArray) return v.any { walk(it, depth + 1) }
144
+ if (v !is JsonObject) return false
145
+ val entries = v.entries.map { (k, x) -> k.lowercase() to x }
146
+ val keys = entries.map { it.first }
147
+ if (keys.any { it in SPENDING_KEYS }) return true
148
+ val hasCall = entries.any { (k, x) ->
149
+ (k == "data" || k == "calldata") && x is JsonPrimitive && x.isString && x.content != "0x" && x.content != ""
150
+ }
151
+ val hasTarget = "to" in keys || "recipient" in keys
152
+ val hasValue = "value" in keys || "amount" in keys
153
+ if (hasCall || (hasTarget && hasValue)) return true
154
+ return v.values.any { walk(it, depth + 1) }
155
+ }
156
+ return walk(message, 0)
157
+ }
158
+
159
+ private fun JsonElement?.obj(): JsonObject? = this as? JsonObject
160
+
161
+ private fun JsonElement?.str(): String? = (this as? JsonPrimitive)?.takeIf { it.isString }?.content
162
+
163
+ fun typedData(td: JsonObject): SignSummary {
164
+ val domain = td["domain"].obj() ?: JsonObject(emptyMap())
165
+ val primaryType = (td["primaryType"] as? JsonPrimitive)?.contentOrNull ?: ""
166
+ val message = td["message"].obj() ?: JsonObject(emptyMap())
167
+ val rows = mutableListOf<SignRow>()
168
+ domain["name"].str()?.let { rows += SignRow("App or contract", it) }
169
+ toChainId(domain["chainId"])?.let { rows += SignRow("Network", chainLabel(it)) }
170
+ domain["verifyingContract"].str()?.let { rows += SignRow("Contract", addr(it), mono = true) }
171
+ rows += SignRow("Type", primaryType.ifEmpty { "unknown" })
172
+
173
+ val warnings = mutableListOf<String>()
174
+ val verifying = domain["verifyingContract"].str()?.lowercase() ?: ""
175
+ val isPermit2 = domain["name"].str() == "Permit2" || verifying == PERMIT2
176
+ val details = message["details"].obj()
177
+ if (primaryType in PERMIT_TYPES || isPermit2) {
178
+ warnings += "This is a token permit. Signing it lets the spender move your tokens without another confirmation."
179
+ val spender = message["spender"] ?: details?.get("spender")
180
+ if (spender != null) rows += SignRow("Spender", addr(spender), mono = true)
181
+ val amount = toBigInt(message["value"]) ?: toBigInt(message["amount"]) ?: toBigInt(details?.get("amount"))
182
+ ?: toBigInt(message["permitted"].obj()?.get("amount"))
183
+ if (amount != null) rows += SignRow("Amount (token units)", amountText(amount))
184
+ if (amount != null && amount >= UNLIMITED_THRESHOLD) warnings += "The permitted amount is unlimited."
185
+ val deadline = toBigInt(message["deadline"]) ?: toBigInt(message["sigDeadline"]) ?: toBigInt(message["expiry"])
186
+ if (deadline != null) rows += SignRow("Valid until", deadline.toString())
187
+ } else if (message["spender"] != null || message["operator"] != null) {
188
+ warnings += "This signature may give another address access to your assets."
189
+ }
190
+ val permit = primaryType in PERMIT_TYPES || isPermit2
191
+ val high = permit || primaryType in SPENDING_TYPES || grantsSpending(message)
192
+ if (high && warnings.isEmpty()) warnings += "This signature may let another address move your assets or act for you."
193
+ return SignSummary(
194
+ SignSummary.Kind.TYPED_DATA, "Sign data", rows, requireShowable(prettyJson.encodeToString(JsonElement.serializer(), message)),
195
+ warnings, if (high) SignRisk.HIGH else SignRisk.LOW,
196
+ )
197
+ }
198
+
199
+ private class AbiFn(val name: String, val types: List<String>) {
200
+ val selector: String = Bytes.toHex(Hash.sha3(Bytes.utf8("$name(${types.joinToString(",")})")).copyOf(4))
201
+ }
202
+
203
+ private val ERC_FUNCTIONS = listOf(
204
+ AbiFn("transfer", listOf("address", "uint256")),
205
+ AbiFn("approve", listOf("address", "uint256")),
206
+ AbiFn("transferFrom", listOf("address", "address", "uint256")),
207
+ AbiFn("increaseAllowance", listOf("address", "uint256")),
208
+ AbiFn("setApprovalForAll", listOf("address", "bool")),
209
+ AbiFn("permit", listOf("address", "address", "uint256", "uint256", "uint8", "bytes32", "bytes32")),
210
+ AbiFn("approve", listOf("address", "address", "uint160", "uint48")),
211
+ )
212
+
213
+ private sealed class AbiValue {
214
+ data class Address(val value: String) : AbiValue()
215
+ data class UInt(val value: BigInteger) : AbiValue()
216
+ data class Bool(val value: Boolean) : AbiValue()
217
+ data class Word(val hex: String) : AbiValue()
218
+ }
219
+
220
+ /** decodeFunctionData for the static-argument ERC functions above; null when it does not decode. */
221
+ private fun decodeCall(data: ByteArray): Pair<AbiFn, List<AbiValue>>? {
222
+ if (data.size < 4) return null
223
+ val selector = Bytes.toHex(data.copyOf(4))
224
+ val fn = ERC_FUNCTIONS.firstOrNull { it.selector == selector } ?: return null
225
+ if (data.size < 4 + 32 * fn.types.size) return null
226
+ val args = fn.types.mapIndexed { i, type ->
227
+ val word = data.copyOfRange(4 + 32 * i, 4 + 32 * (i + 1))
228
+ val n = BigInteger(1, word)
229
+ when (type) {
230
+ "address" -> AbiValue.Address(Keys.toChecksumAddress("0x" + Bytes.toHex(word.copyOfRange(12, 32))))
231
+ "bool" -> {
232
+ if (n > BigInteger.ONE) return null
233
+ AbiValue.Bool(n == BigInteger.ONE)
234
+ }
235
+ "bytes32" -> AbiValue.Word("0x" + Bytes.toHex(word))
236
+ else -> {
237
+ val bits = type.removePrefix("uint").toInt()
238
+ if (n.bitLength() > bits) return null
239
+ AbiValue.UInt(n)
240
+ }
241
+ }
242
+ }
243
+ return fn to args
244
+ }
245
+
246
+ private fun AbiValue.text(): String = when (this) {
247
+ is AbiValue.Address -> value
248
+ is AbiValue.UInt -> value.toString()
249
+ is AbiValue.Bool -> value.toString()
250
+ is AbiValue.Word -> hex
251
+ }
252
+
253
+ private fun AbiValue.uint(): BigInteger = (this as AbiValue.UInt).value
254
+
255
+ fun transaction(tx: JsonObject): SignSummary {
256
+ val chainId = toChainId(tx["chainId"])
257
+ val rows = mutableListOf(SignRow("Network", chainLabel(chainId)))
258
+ val warnings = mutableListOf<String>()
259
+ val to = tx["to"].str()?.takeIf { it.isNotEmpty() }
260
+ if (to != null) {
261
+ rows += SignRow("To", addr(to), mono = true)
262
+ } else {
263
+ rows += SignRow("To", "New contract (deployment)")
264
+ warnings += "This transaction deploys a contract."
265
+ }
266
+ val value = toBigInt(tx["value"]) ?: BigInteger.ZERO
267
+ rows += SignRow("Amount", "${formatUnits(value, 18)} ${nativeSymbol(chainId)}")
268
+ val gas = toBigInt(tx["gas"])
269
+ val maxFee = toBigInt(tx["maxFeePerGas"]) ?: toBigInt(tx["gasPrice"])
270
+ if (gas != null && maxFee != null) {
271
+ rows += SignRow("Max network fee", "${formatUnits(gas.multiply(maxFee), 18)} ${nativeSymbol(chainId)}")
272
+ }
273
+ val data = tx["data"].str() ?: tx["input"].str() ?: "0x"
274
+ if (data.isNotEmpty() && data != "0x") {
275
+ val decoded = Bytes.fromHexOrNull(data)?.let { decodeCall(it) }
276
+ if (decoded == null) {
277
+ rows += SignRow("Action", "Contract call (${data.take(10)})", mono = true)
278
+ rows += SignRow("Data", "${maxOf(0, (data.length - 2) / 2)} bytes")
279
+ } else {
280
+ val (fn, a) = decoded
281
+ when (fn.name) {
282
+ "transfer" -> {
283
+ rows += SignRow("Action", "Token transfer")
284
+ rows += SignRow("Token", addr(to), mono = true)
285
+ rows += SignRow("Recipient", a[0].text(), mono = true)
286
+ rows += SignRow("Amount (token units)", a[1].text())
287
+ }
288
+ "transferFrom" -> {
289
+ rows += SignRow("Action", "Token transfer from another address")
290
+ rows += SignRow("From", a[0].text(), mono = true)
291
+ rows += SignRow("Recipient", a[1].text(), mono = true)
292
+ rows += SignRow("Amount (token units)", a[2].text())
293
+ }
294
+ "approve" -> {
295
+ if (a.size == 4) {
296
+ rows += SignRow("Action", "Permit2 allowance")
297
+ rows += SignRow("Token", a[0].text(), mono = true)
298
+ rows += SignRow("Spender", a[1].text(), mono = true)
299
+ rows += SignRow("Amount (token units)", amountText(a[2].uint()))
300
+ } else {
301
+ rows += SignRow("Action", "Token approval")
302
+ rows += SignRow("Token", addr(to), mono = true)
303
+ rows += SignRow("Spender", a[0].text(), mono = true)
304
+ rows += SignRow("Amount (token units)", amountText(a[1].uint()))
305
+ if (a[1].uint() >= UNLIMITED_THRESHOLD) warnings += "The approved amount is unlimited."
306
+ }
307
+ warnings += "This lets the spender move your tokens later without asking again."
308
+ }
309
+ "increaseAllowance" -> {
310
+ rows += SignRow("Action", "Increase token allowance")
311
+ rows += SignRow("Spender", a[0].text(), mono = true)
312
+ rows += SignRow("Added (token units)", amountText(a[1].uint()))
313
+ warnings += "This lets the spender move more of your tokens without asking again."
314
+ }
315
+ "setApprovalForAll" -> {
316
+ val approved = (a[1] as AbiValue.Bool).value
317
+ rows += SignRow("Action", if (approved) "Approve all NFTs in a collection" else "Remove collection approval")
318
+ rows += SignRow("Operator", a[0].text(), mono = true)
319
+ if (approved) warnings += "The operator could transfer every item you own in this collection."
320
+ }
321
+ "permit" -> {
322
+ rows += SignRow("Action", "Token permit")
323
+ rows += SignRow("Token", addr(to), mono = true)
324
+ rows += SignRow("Spender", a[1].text(), mono = true)
325
+ rows += SignRow("Amount (token units)", amountText(a[2].uint()))
326
+ warnings += "This lets the spender move your tokens later without asking again."
327
+ }
328
+ }
329
+ }
330
+ }
331
+ if (chainId == null) warnings += "The network is not specified."
332
+ val risk = if (value.signum() > 0 || (data.isNotEmpty() && data != "0x") || to == null) SignRisk.HIGH else SignRisk.LOW
333
+ return SignSummary(SignSummary.Kind.TRANSACTION, "Approve transaction", rows, null, warnings, risk)
334
+ }
335
+ }