@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,134 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Proof.kt. Do not edit: change the source and run the script.
|
|
2
|
+
package dev.authon.sdk.wallet
|
|
3
|
+
|
|
4
|
+
import kotlinx.serialization.json.JsonArray
|
|
5
|
+
import kotlinx.serialization.json.JsonObject
|
|
6
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
7
|
+
import kotlinx.serialization.json.buildJsonArray
|
|
8
|
+
import kotlinx.serialization.json.buildJsonObject
|
|
9
|
+
import kotlinx.serialization.json.put
|
|
10
|
+
import java.nio.ByteBuffer
|
|
11
|
+
import java.nio.CharBuffer
|
|
12
|
+
import java.nio.charset.CharacterCodingException
|
|
13
|
+
import java.nio.charset.CodingErrorAction
|
|
14
|
+
|
|
15
|
+
/** P-256 device public key as the API stores it (JWK, base64url x/y without padding). */
|
|
16
|
+
data class DevicePublicJwk(val x: String, val y: String) {
|
|
17
|
+
val kty: String get() = "EC"
|
|
18
|
+
val crv: String get() = "P-256"
|
|
19
|
+
|
|
20
|
+
fun toJson(): JsonObject = buildJsonObject {
|
|
21
|
+
put("kty", kty)
|
|
22
|
+
put("crv", crv)
|
|
23
|
+
put("x", x)
|
|
24
|
+
put("y", y)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Server-issued proof nonce (`expiresAt` in Unix seconds). */
|
|
29
|
+
data class ProofNonce(val nonce: String, val expiresAt: Long)
|
|
30
|
+
|
|
31
|
+
/** Wallet key proofs and reserved-message rules of `apps/wallet/src/proof.ts`. */
|
|
32
|
+
internal object Proof {
|
|
33
|
+
const val DOMAIN_NAME = "Authon Wallet Proof"
|
|
34
|
+
const val DOMAIN_VERSION = "1"
|
|
35
|
+
private val RESERVED_PREFIXES = listOf("create:", "device:", "restore:")
|
|
36
|
+
|
|
37
|
+
fun canonicalJwk(jwk: DevicePublicJwk): String =
|
|
38
|
+
"{\"crv\":${json(jwk.crv)},\"kty\":${json(jwk.kty)},\"x\":${json(jwk.x)},\"y\":${json(jwk.y)}}"
|
|
39
|
+
|
|
40
|
+
fun devicePublicKeyHash(jwk: DevicePublicJwk): String = Bytes.toHex(Bytes.sha256(Bytes.utf8(canonicalJwk(jwk))))
|
|
41
|
+
|
|
42
|
+
private fun json(s: String): String = JsonPrimitive(s).toString()
|
|
43
|
+
|
|
44
|
+
private fun fields(vararg pairs: Pair<String, String>): JsonArray = buildJsonArray {
|
|
45
|
+
for ((name, type) in pairs) add(buildJsonObject { put("name", name); put("type", type) })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private val BOUND = arrayOf("nonce" to "string", "expiresAt" to "uint256")
|
|
49
|
+
|
|
50
|
+
private val TYPES: Map<String, JsonArray> = mapOf(
|
|
51
|
+
"CreateWallet" to fields("projectId" to "string", "userId" to "string", "deviceKeyHash" to "string", *BOUND),
|
|
52
|
+
"AddDevice" to fields("walletId" to "string", "shareVersion" to "uint256", "deviceKeyHash" to "string", *BOUND),
|
|
53
|
+
"RestoreWallet" to fields("walletId" to "string", "shareVersion" to "uint256", "deviceKeyHash" to "string", *BOUND),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
fun typedData(primaryType: String, message: List<Pair<String, String>>, bound: ProofNonce): JsonObject = buildJsonObject {
|
|
57
|
+
put("domain", buildJsonObject { put("name", DOMAIN_NAME); put("version", DOMAIN_VERSION) })
|
|
58
|
+
put("types", buildJsonObject { put(primaryType, TYPES.getValue(primaryType)) })
|
|
59
|
+
put("primaryType", primaryType)
|
|
60
|
+
put(
|
|
61
|
+
"message",
|
|
62
|
+
buildJsonObject {
|
|
63
|
+
for ((k, v) in message) put(k, v)
|
|
64
|
+
put("nonce", bound.nonce)
|
|
65
|
+
put("expiresAt", bound.expiresAt.toString())
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun createTypedData(projectId: String, userId: String, deviceKey: DevicePublicJwk, bound: ProofNonce) =
|
|
71
|
+
typedData("CreateWallet", listOf("projectId" to projectId, "userId" to userId, "deviceKeyHash" to devicePublicKeyHash(deviceKey)), bound)
|
|
72
|
+
|
|
73
|
+
fun deviceTypedData(walletId: String, shareVersion: Int, deviceKey: DevicePublicJwk, bound: ProofNonce) =
|
|
74
|
+
typedData(
|
|
75
|
+
"AddDevice",
|
|
76
|
+
listOf("walletId" to walletId, "shareVersion" to shareVersion.toString(), "deviceKeyHash" to devicePublicKeyHash(deviceKey)),
|
|
77
|
+
bound,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
/** shareVersion in the signed message is the current version + 1. */
|
|
81
|
+
fun restoreTypedData(walletId: String, currentShareVersion: Int, newDeviceKey: DevicePublicJwk, bound: ProofNonce) =
|
|
82
|
+
typedData(
|
|
83
|
+
"RestoreWallet",
|
|
84
|
+
listOf(
|
|
85
|
+
"walletId" to walletId,
|
|
86
|
+
"shareVersion" to (currentShareVersion + 1).toString(),
|
|
87
|
+
"deviceKeyHash" to devicePublicKeyHash(newDeviceKey),
|
|
88
|
+
),
|
|
89
|
+
bound,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
/** Proofs are always signed with account 0 of the wallet secret. */
|
|
93
|
+
fun sign(secret: ByteArray, typedData: JsonObject): String = HdWallet.account(secret, 0).signTypedData(typedData)
|
|
94
|
+
|
|
95
|
+
/** True for app text that looks like a (legacy) wallet proof. */
|
|
96
|
+
fun isReservedMessageText(text: String): Boolean {
|
|
97
|
+
var i = 0
|
|
98
|
+
while (i < text.length && (text[i].code <= 0x1f || text[i] == '' || isJsWhitespace(text[i]))) i++
|
|
99
|
+
val t = text.substring(i).lowercase()
|
|
100
|
+
return RESERVED_PREFIXES.any { t.startsWith(it) }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** JavaScript `\s`: WhiteSpace and LineTerminator code points. */
|
|
104
|
+
private fun isJsWhitespace(c: Char): Boolean =
|
|
105
|
+
c == ' ' || c == '\t' || c == '\n' || c == '\u000b' || c == '\u000c' || c == '\r' || c == ' ' || c == ' ' ||
|
|
106
|
+
c in ' '..' ' || c == '
' || c == '
' || c == ' ' || c == ' ' || c == ' ' ||
|
|
107
|
+
c == ''
|
|
108
|
+
|
|
109
|
+
/** Decodes a raw hex message as strict UTF-8; null when it is not valid UTF-8 or not hex. */
|
|
110
|
+
fun rawMessageText(hex: String): String? {
|
|
111
|
+
val bytes = Bytes.fromHexOrNull(hex) ?: return null
|
|
112
|
+
return try {
|
|
113
|
+
Charsets.UTF_8.newDecoder()
|
|
114
|
+
.onMalformedInput(CodingErrorAction.REPORT)
|
|
115
|
+
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
|
116
|
+
.decode(ByteBuffer.wrap(bytes))
|
|
117
|
+
.let { stripBom(it) }
|
|
118
|
+
} catch (_: CharacterCodingException) {
|
|
119
|
+
null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// TextDecoder('utf-8') drops a leading BOM by default.
|
|
124
|
+
private fun stripBom(chars: CharBuffer): String {
|
|
125
|
+
val s = chars.toString()
|
|
126
|
+
return if (s.startsWith("")) s.substring(1) else s
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fun isReservedTypedData(typedData: JsonObject): Boolean {
|
|
130
|
+
val name = ((typedData["domain"] as? JsonObject)?.get("name") as? JsonPrimitive)?.takeIf { it.isString }?.content
|
|
131
|
+
?: return false
|
|
132
|
+
return name.trim().lowercase() == DOMAIN_NAME.lowercase()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Shamir.kt. Do not edit: change the source and run the script.
|
|
2
|
+
package dev.authon.sdk.wallet
|
|
3
|
+
|
|
4
|
+
import java.security.SecureRandom
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Port of the `shamir-secret-sharing` JS library used by the web frame: GF(2^8) with the
|
|
8
|
+
* same tables, a share is `y bytes || x` (x in the last byte). [combine] is deterministic and
|
|
9
|
+
* byte-identical to the library; [split] draws its coefficients and coordinates from SecureRandom.
|
|
10
|
+
*/
|
|
11
|
+
internal object Shamir {
|
|
12
|
+
private val random = SecureRandom()
|
|
13
|
+
|
|
14
|
+
private fun add(a: Int, b: Int): Int = a xor b
|
|
15
|
+
|
|
16
|
+
private fun div(a: Int, b: Int): Int {
|
|
17
|
+
require(b != 0) { "cannot divide by zero" }
|
|
18
|
+
val diff = (GfTables.LOG[a] - GfTables.LOG[b] + 255) % 255
|
|
19
|
+
return if (a == 0) 0 else GfTables.EXP[diff]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private fun mult(a: Int, b: Int): Int {
|
|
23
|
+
val sum = (GfTables.LOG[a] + GfTables.LOG[b]) % 255
|
|
24
|
+
return if (a == 0 || b == 0) 0 else GfTables.EXP[sum]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private fun interpolate(xs: IntArray, ys: IntArray, x: Int): Int {
|
|
28
|
+
var result = 0
|
|
29
|
+
for (i in xs.indices) {
|
|
30
|
+
var basis = 1
|
|
31
|
+
for (j in xs.indices) {
|
|
32
|
+
if (i == j) continue
|
|
33
|
+
basis = mult(basis, div(add(x, xs[j]), add(xs[i], xs[j])))
|
|
34
|
+
}
|
|
35
|
+
result = add(result, mult(ys[i], basis))
|
|
36
|
+
}
|
|
37
|
+
return result
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun evaluate(coefficients: IntArray, x: Int, degree: Int): Int {
|
|
41
|
+
require(x != 0) { "cannot evaluate secret polynomial at zero" }
|
|
42
|
+
var result = coefficients[degree]
|
|
43
|
+
for (i in degree - 1 downTo 0) result = add(mult(result, x), coefficients[i])
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun newCoordinates(): IntArray {
|
|
48
|
+
val coordinates = IntArray(255) { it + 1 }
|
|
49
|
+
val randomIndices = ByteArray(255).also { random.nextBytes(it) }
|
|
50
|
+
for (i in 0 until 255) {
|
|
51
|
+
val j = (randomIndices[i].toInt() and 0xff) % 255
|
|
52
|
+
val tmp = coordinates[i]
|
|
53
|
+
coordinates[i] = coordinates[j]
|
|
54
|
+
coordinates[j] = tmp
|
|
55
|
+
}
|
|
56
|
+
return coordinates
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fun split(secret: ByteArray, shares: Int, threshold: Int): List<ByteArray> {
|
|
60
|
+
require(secret.isNotEmpty()) { "secret cannot be empty" }
|
|
61
|
+
require(shares in 2..255) { "shares must be at least 2 and at most 255" }
|
|
62
|
+
require(threshold in 2..255) { "threshold must be at least 2 and at most 255" }
|
|
63
|
+
require(shares >= threshold) { "shares cannot be less than threshold" }
|
|
64
|
+
val xs = newCoordinates()
|
|
65
|
+
val result = List(shares) { i -> ByteArray(secret.size + 1).also { it[secret.size] = xs[i].toByte() } }
|
|
66
|
+
val degree = threshold - 1
|
|
67
|
+
val coefficients = IntArray(degree + 1)
|
|
68
|
+
val randomCoefficients = ByteArray(degree)
|
|
69
|
+
try {
|
|
70
|
+
for (i in secret.indices) {
|
|
71
|
+
coefficients[0] = secret[i].toInt() and 0xff
|
|
72
|
+
random.nextBytes(randomCoefficients)
|
|
73
|
+
for (k in 0 until degree) coefficients[k + 1] = randomCoefficients[k].toInt() and 0xff
|
|
74
|
+
for (j in 0 until shares) result[j][i] = evaluate(coefficients, xs[j], degree).toByte()
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
coefficients.fill(0)
|
|
78
|
+
randomCoefficients.fill(0)
|
|
79
|
+
}
|
|
80
|
+
return result
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun combine(shares: List<ByteArray>): ByteArray {
|
|
84
|
+
require(shares.size in 2..255) { "shares must have at least 2 and at most 255 elements" }
|
|
85
|
+
val shareLength = shares[0].size
|
|
86
|
+
for (s in shares) {
|
|
87
|
+
require(s.size >= 2) { "each share must be at least 2 bytes" }
|
|
88
|
+
require(s.size == shareLength) { "all shares must have the same byte length" }
|
|
89
|
+
}
|
|
90
|
+
val xs = IntArray(shares.size)
|
|
91
|
+
val seen = HashSet<Int>()
|
|
92
|
+
for (i in shares.indices) {
|
|
93
|
+
val x = shares[i][shareLength - 1].toInt() and 0xff
|
|
94
|
+
require(seen.add(x)) { "shares must contain unique values but a duplicate was found" }
|
|
95
|
+
xs[i] = x
|
|
96
|
+
}
|
|
97
|
+
val secret = ByteArray(shareLength - 1)
|
|
98
|
+
val ys = IntArray(shares.size)
|
|
99
|
+
for (i in secret.indices) {
|
|
100
|
+
for (j in shares.indices) ys[j] = shares[j][i].toInt() and 0xff
|
|
101
|
+
secret[i] = interpolate(xs, ys, 0).toByte()
|
|
102
|
+
}
|
|
103
|
+
ys.fill(0)
|
|
104
|
+
return secret
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -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
|
+
}
|