@vultisig/walletcore-native 0.1.1

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.
@@ -0,0 +1,68 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+ apply plugin: 'maven-publish'
4
+
5
+ group = 'expo.modules.walletcore'
6
+ version = '0.1.0'
7
+
8
+ android {
9
+ namespace "expo.modules.walletcore"
10
+
11
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
12
+
13
+ defaultConfig {
14
+ minSdkVersion safeExtGet("minSdkVersion", 23)
15
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
16
+ }
17
+
18
+ publishing {
19
+ singleVariant("release") {
20
+ withSourcesJar()
21
+ }
22
+ }
23
+
24
+ lint {
25
+ abortOnError false
26
+ }
27
+
28
+ compileOptions {
29
+ sourceCompatibility JavaVersion.VERSION_17
30
+ targetCompatibility JavaVersion.VERSION_17
31
+ }
32
+
33
+ kotlinOptions {
34
+ jvmTarget = JavaVersion.VERSION_17.majorVersion
35
+ }
36
+
37
+ sourceSets {
38
+ main.java.srcDirs += 'src/main/java'
39
+ }
40
+ }
41
+
42
+ repositories {
43
+ mavenCentral()
44
+ maven {
45
+ url = uri("https://maven.pkg.github.com/trustwallet/wallet-core")
46
+ credentials {
47
+ username = System.getenv("GITHUB_USER")
48
+ ?: System.getenv("GITHUB_ACTOR")
49
+ ?: "token"
50
+ password = System.getenv("GITHUB_TOKEN")
51
+ ?: System.getenv("GH_TOKEN")
52
+ ?: ""
53
+ }
54
+ content {
55
+ includeGroup("com.trustwallet")
56
+ }
57
+ }
58
+ }
59
+
60
+ dependencies {
61
+ implementation project(':expo-modules-core')
62
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.8.10')}"
63
+ implementation 'com.trustwallet:wallet-core:4.3.22'
64
+ }
65
+
66
+ def safeExtGet(prop, fallback) {
67
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
68
+ }
@@ -0,0 +1,349 @@
1
+ package expo.modules.walletcore
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import wallet.core.jni.*
6
+
7
+ class ExpoWalletCoreModule : Module() {
8
+ // NOTE: nextHandle and the key/wallet maps are accessed from the Expo module
9
+ // dispatch thread. Expo modules run function handlers on a single background
10
+ // serial queue, so concurrent mutation is not expected. If this assumption
11
+ // ever changes (e.g. concurrent async functions are introduced), these maps
12
+ // must be protected with a lock or replaced with thread-safe collections.
13
+ private var nextHandle = 1
14
+ private val publicKeys = mutableMapOf<Int, PublicKey>()
15
+ private val privateKeys = mutableMapOf<Int, PrivateKey>()
16
+ private val hdWallets = mutableMapOf<Int, HDWallet>()
17
+
18
+ init {
19
+ System.loadLibrary("TrustWalletCore")
20
+ }
21
+
22
+ private fun storePublicKey(pk: PublicKey): Int {
23
+ val h = nextHandle++; publicKeys[h] = pk; return h
24
+ }
25
+ private fun storePrivateKey(pk: PrivateKey): Int {
26
+ val h = nextHandle++; privateKeys[h] = pk; return h
27
+ }
28
+ private fun storeHDWallet(w: HDWallet): Int {
29
+ val h = nextHandle++; hdWallets[h] = w; return h
30
+ }
31
+
32
+ override fun definition() = ModuleDefinition {
33
+ Name("ExpoWalletCore")
34
+
35
+ // CoinType — map camelCase names from TS to WalletCore enum values.
36
+ // Uses an explicit mapping (like iOS) instead of valueOf() to avoid
37
+ // casing mismatches (e.g. "bitcoinCash".uppercase() → "BITCOINCASH" ≠ "BITCOIN_CASH").
38
+ Function("coinTypeValue") { name: String ->
39
+ val mapping = mapOf(
40
+ "bitcoin" to CoinType.BITCOIN,
41
+ "litecoin" to CoinType.LITECOIN,
42
+ "dogecoin" to CoinType.DOGECOIN,
43
+ "dash" to CoinType.DASH,
44
+ "ethereum" to CoinType.ETHEREUM,
45
+ "cosmos" to CoinType.COSMOS,
46
+ "zcash" to CoinType.ZCASH,
47
+ "ripple" to CoinType.XRP,
48
+ "xrp" to CoinType.XRP,
49
+ "bitcoinCash" to CoinType.BITCOINCASH,
50
+ "tron" to CoinType.TRON,
51
+ "terra" to CoinType.TERRA,
52
+ "polkadot" to CoinType.POLKADOT,
53
+ "ton" to CoinType.TON,
54
+ "solana" to CoinType.SOLANA,
55
+ "thorchain" to CoinType.THORCHAIN,
56
+ "sui" to CoinType.SUI,
57
+ "cardano" to CoinType.CARDANO,
58
+ "smartChain" to CoinType.SMARTCHAIN,
59
+ "arbitrum" to CoinType.ARBITRUM,
60
+ "avalancheCChain" to CoinType.AVALANCHECCHAIN,
61
+ "base" to CoinType.BASE,
62
+ "polygon" to CoinType.POLYGON,
63
+ "optimism" to CoinType.OPTIMISM,
64
+ "cronosChain" to CoinType.CRONOSCHAIN,
65
+ "blast" to CoinType.BLAST,
66
+ "zksync" to CoinType.ZKSYNC,
67
+ "osmosis" to CoinType.OSMOSIS,
68
+ "terraV2" to CoinType.TERRAV2,
69
+ "noble" to CoinType.NOBLE,
70
+ "kujira" to CoinType.KUJIRA,
71
+ "dydx" to CoinType.DYDX,
72
+ "akash" to CoinType.AKASH,
73
+ "mantle" to CoinType.MANTLE,
74
+ "sei" to CoinType.SEI,
75
+ )
76
+ val ct = mapping[name] ?: throw Exception("Unknown CoinType name: $name")
77
+ ct.value()
78
+ }
79
+
80
+ // CoinTypeExt
81
+ Function("derivationPath") { coinType: Int ->
82
+ CoinType.createFromValue(coinType).derivationPath()
83
+ }
84
+
85
+ Function("deriveAddressFromPublicKey") { coinType: Int, publicKeyHandle: Int ->
86
+ val pk = publicKeys[publicKeyHandle] ?: throw Exception("Invalid PublicKey handle")
87
+ CoinType.createFromValue(coinType).deriveAddressFromPublicKey(pk)
88
+ }
89
+
90
+ Function("chainId") { coinType: Int ->
91
+ CoinType.createFromValue(coinType).chainId()
92
+ }
93
+
94
+ Function("ss58Prefix") { coinType: Int ->
95
+ CoinType.createFromValue(coinType).ss58Prefix()
96
+ }
97
+
98
+ // PublicKey
99
+ Function("publicKeyCreateWithData") { dataBase64: String, typeValue: Int ->
100
+ val data = android.util.Base64.decode(dataBase64, android.util.Base64.NO_WRAP)
101
+ val pkType = PublicKeyType.createFromValue(typeValue)
102
+ val pk = PublicKey(data, pkType)
103
+ storePublicKey(pk)
104
+ }
105
+
106
+ Function("publicKeyData") { handle: Int ->
107
+ val pk = publicKeys[handle] ?: throw Exception("Invalid PublicKey handle")
108
+ android.util.Base64.encodeToString(pk.data(), android.util.Base64.NO_WRAP)
109
+ }
110
+
111
+ Function("publicKeyUncompressed") { handle: Int ->
112
+ val pk = publicKeys[handle] ?: throw Exception("Invalid PublicKey handle")
113
+ storePublicKey(pk.uncompressed())
114
+ }
115
+
116
+ Function("publicKeyCompressed") { handle: Int ->
117
+ val pk = publicKeys[handle] ?: throw Exception("Invalid PublicKey handle")
118
+ storePublicKey(pk.compressed())
119
+ }
120
+
121
+ Function("publicKeyVerify") { handle: Int, signatureBase64: String, messageBase64: String ->
122
+ val pk = publicKeys[handle] ?: return@Function false
123
+ val sig = android.util.Base64.decode(signatureBase64, android.util.Base64.NO_WRAP)
124
+ val msg = android.util.Base64.decode(messageBase64, android.util.Base64.NO_WRAP)
125
+ pk.verify(sig, msg)
126
+ }
127
+
128
+ Function("publicKeyVerifyAsDER") { handle: Int, signatureBase64: String, messageBase64: String ->
129
+ val pk = publicKeys[handle] ?: return@Function false
130
+ val sig = android.util.Base64.decode(signatureBase64, android.util.Base64.NO_WRAP)
131
+ val msg = android.util.Base64.decode(messageBase64, android.util.Base64.NO_WRAP)
132
+ pk.verifyAsDER(sig, msg)
133
+ }
134
+
135
+ Function("freePublicKey") { handle: Int -> publicKeys.remove(handle); Unit }
136
+
137
+ // AnyAddress
138
+ Function("anyAddressIsValid") { address: String, coinType: Int ->
139
+ AnyAddress.isValid(address, CoinType.createFromValue(coinType))
140
+ }
141
+
142
+ Function("anyAddressIsValidBech32") { address: String, coinType: Int, hrp: String ->
143
+ AnyAddress.isValidBech32(address, CoinType.createFromValue(coinType), hrp)
144
+ }
145
+
146
+ // The Trust Wallet Core Android JNI binding does not expose an SS58-prefix
147
+ // overload on AnyAddress. Throw rather than returning a potentially incorrect
148
+ // result, so callers know this code path is not supported yet.
149
+ Function("anyAddressIsValidSS58") { _address: String, _coinType: Int, _ss58Prefix: Int ->
150
+ throw Exception(
151
+ "anyAddressIsValidSS58 is not supported on Android: the JNI binding " +
152
+ "does not expose an SS58-prefix overload. Use anyAddressIsValid as a " +
153
+ "fallback (ignores ss58Prefix) or implement a pure-Kotlin SS58 check."
154
+ )
155
+ }
156
+
157
+ Function("anyAddressCreateWithString") { address: String, coinType: Int ->
158
+ AnyAddress(address, CoinType.createFromValue(coinType)).description()
159
+ }
160
+
161
+ Function("anyAddressCreateBech32WithPublicKey") { publicKeyHandle: Int, coinType: Int, hrp: String ->
162
+ val pk = publicKeys[publicKeyHandle] ?: throw Exception("Invalid PublicKey handle")
163
+ AnyAddress(pk, CoinType.createFromValue(coinType), hrp).description()
164
+ }
165
+
166
+ Function("anyAddressCreateBech32") { address: String, coinType: Int, hrp: String ->
167
+ AnyAddress(address, CoinType.createFromValue(coinType), hrp).description()
168
+ }
169
+
170
+ Function("anyAddressData") { address: String, coinType: Int ->
171
+ val addr = AnyAddress(address, CoinType.createFromValue(coinType))
172
+ android.util.Base64.encodeToString(addr.data(), android.util.Base64.NO_WRAP)
173
+ }
174
+
175
+ // TransactionCompiler
176
+ Function("preImageHashes") { coinType: Int, txInputDataBase64: String ->
177
+ val txData = android.util.Base64.decode(txInputDataBase64, android.util.Base64.NO_WRAP)
178
+ val result = TransactionCompiler.preImageHashes(CoinType.createFromValue(coinType), txData)
179
+ android.util.Base64.encodeToString(result, android.util.Base64.NO_WRAP)
180
+ }
181
+
182
+ Function("compileWithSignatures") { coinType: Int, txInputDataBase64: String, signaturesBase64: List<String>, publicKeysBase64: List<String> ->
183
+ val txData = android.util.Base64.decode(txInputDataBase64, android.util.Base64.NO_WRAP)
184
+ val signatures = DataVector()
185
+ signaturesBase64.forEach { s ->
186
+ signatures.add(android.util.Base64.decode(s, android.util.Base64.NO_WRAP))
187
+ }
188
+ val pubkeys = DataVector()
189
+ publicKeysBase64.forEach { p ->
190
+ pubkeys.add(android.util.Base64.decode(p, android.util.Base64.NO_WRAP))
191
+ }
192
+ val result = TransactionCompiler.compileWithSignatures(
193
+ CoinType.createFromValue(coinType), txData, signatures, pubkeys
194
+ )
195
+ android.util.Base64.encodeToString(result, android.util.Base64.NO_WRAP)
196
+ }
197
+
198
+ // AnySigner
199
+ Function("anySignerPlan") { txInputDataBase64: String, coinType: Int ->
200
+ val txData = android.util.Base64.decode(txInputDataBase64, android.util.Base64.NO_WRAP)
201
+ val result = AnySigner.plan(txData, CoinType.createFromValue(coinType))
202
+ android.util.Base64.encodeToString(result, android.util.Base64.NO_WRAP)
203
+ }
204
+
205
+ // HDWallet
206
+ Function("hdWalletCreate") { mnemonic: String, passphrase: String ->
207
+ storeHDWallet(HDWallet(mnemonic, passphrase))
208
+ }
209
+
210
+ Function("hdWalletGetMasterKey") { handle: Int, curveValue: Int ->
211
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
212
+ storePrivateKey(wallet.getMasterKey(Curve.createFromValue(curveValue)))
213
+ }
214
+
215
+ Function("hdWalletGetKeyForCoin") { handle: Int, coinType: Int ->
216
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
217
+ storePrivateKey(wallet.getKeyForCoin(CoinType.createFromValue(coinType)))
218
+ }
219
+
220
+ Function("hdWalletGetKeyDerivation") { handle: Int, coinType: Int, derivationValue: Int ->
221
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
222
+ storePrivateKey(wallet.getKeyDerivation(CoinType.createFromValue(coinType), Derivation.createFromValue(derivationValue)))
223
+ }
224
+
225
+ Function("hdWalletGetAddressDerivation") { handle: Int, coinType: Int, derivationValue: Int ->
226
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
227
+ wallet.getAddressDerivation(CoinType.createFromValue(coinType), Derivation.createFromValue(derivationValue))
228
+ }
229
+
230
+ Function("hdWalletGetKey") { handle: Int, coinType: Int, derivationPath: String ->
231
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
232
+ storePrivateKey(wallet.getKey(CoinType.createFromValue(coinType), derivationPath))
233
+ }
234
+
235
+ Function("hdWalletGetAddressForCoin") { handle: Int, coinType: Int ->
236
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
237
+ wallet.getAddressForCoin(CoinType.createFromValue(coinType))
238
+ }
239
+
240
+ Function("hdWalletGetExtendedPrivateKey") { handle: Int, purposeValue: Int, coinType: Int, versionValue: Int ->
241
+ val wallet = hdWallets[handle] ?: throw Exception("Invalid HDWallet handle")
242
+ wallet.getExtendedPrivateKey(
243
+ Purpose.createFromValue(purposeValue),
244
+ CoinType.createFromValue(coinType),
245
+ HDVersion.createFromValue(versionValue)
246
+ )
247
+ }
248
+
249
+ Function("freeHDWallet") { handle: Int -> hdWallets.remove(handle); Unit }
250
+
251
+ // PrivateKey
252
+ Function("privateKeyCreate") { -> storePrivateKey(PrivateKey()) }
253
+
254
+ Function("privateKeyData") { handle: Int ->
255
+ val key = privateKeys[handle] ?: throw Exception("Invalid PrivateKey handle")
256
+ android.util.Base64.encodeToString(key.data(), android.util.Base64.NO_WRAP)
257
+ }
258
+
259
+ Function("privateKeyGetPublicKeySecp256k1") { handle: Int, compressed: Boolean ->
260
+ val key = privateKeys[handle] ?: throw Exception("Invalid PrivateKey handle")
261
+ storePublicKey(key.getPublicKeySecp256k1(compressed))
262
+ }
263
+
264
+ Function("privateKeyGetPublicKeyEd25519") { handle: Int ->
265
+ val key = privateKeys[handle] ?: throw Exception("Invalid PrivateKey handle")
266
+ storePublicKey(key.getPublicKeyEd25519())
267
+ }
268
+
269
+ Function("freePrivateKey") { handle: Int -> privateKeys.remove(handle); Unit }
270
+
271
+ // HexCoding
272
+ Function("hexDecode") { hex: String ->
273
+ require(hex.length % 2 == 0) { "Hex string must have even length, got ${hex.length}" }
274
+ require(hex.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) {
275
+ "Hex string contains non-hex characters"
276
+ }
277
+ val bytes = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
278
+ android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
279
+ }
280
+
281
+ Function("hexEncode") { dataBase64: String ->
282
+ val data = android.util.Base64.decode(dataBase64, android.util.Base64.NO_WRAP)
283
+ data.joinToString("") { "%02x".format(it) }
284
+ }
285
+
286
+ // Bech32
287
+ Function("bech32Encode") { hrp: String, dataBase64: String ->
288
+ val data = android.util.Base64.decode(dataBase64, android.util.Base64.NO_WRAP)
289
+ Bech32.encode(hrp, data)
290
+ }
291
+
292
+ // BitcoinScript
293
+ Function("bitcoinScriptBuildPayToWitnessPubkeyHash") { hashBase64: String ->
294
+ val hash = android.util.Base64.decode(hashBase64, android.util.Base64.NO_WRAP)
295
+ val script = BitcoinScript.buildPayToWitnessPubkeyHash(hash)
296
+ android.util.Base64.encodeToString(script.data(), android.util.Base64.NO_WRAP)
297
+ }
298
+
299
+ Function("bitcoinScriptBuildPayToPublicKeyHash") { hashBase64: String ->
300
+ val hash = android.util.Base64.decode(hashBase64, android.util.Base64.NO_WRAP)
301
+ val script = BitcoinScript.buildPayToPublicKeyHash(hash)
302
+ android.util.Base64.encodeToString(script.data(), android.util.Base64.NO_WRAP)
303
+ }
304
+
305
+ Function("bitcoinScriptLockScriptForAddress") { address: String, coinType: Int ->
306
+ val script = BitcoinScript.lockScriptForAddress(address, CoinType.createFromValue(coinType))
307
+ android.util.Base64.encodeToString(script.data(), android.util.Base64.NO_WRAP)
308
+ }
309
+
310
+ Function("bitcoinScriptHashTypeForCoin") { coinType: Int ->
311
+ BitcoinScript.hashTypeForCoin(CoinType.createFromValue(coinType)).toInt()
312
+ }
313
+
314
+ // EthereumAbi
315
+ // WARNING: Only the 4-byte function selector is encoded. Full ABI encoding
316
+ // requires parsing param types/values and calling fn.addParam*(). Throws if
317
+ // the caller passes non-empty params to avoid silent data loss.
318
+ // TODO: Accept a structured param list (e.g. JSON) and call fn.addParam*().
319
+ Function("ethereumAbiEncode") { functionName: String, params: String ->
320
+ require(params.isEmpty()) {
321
+ "ethereumAbiEncode does not yet support params — only the 4-byte selector is encoded"
322
+ }
323
+ val fn = EthereumAbiFunction(functionName)
324
+ val encoded = EthereumAbi.encode(fn)
325
+ android.util.Base64.encodeToString(encoded, android.util.Base64.NO_WRAP)
326
+ }
327
+
328
+ // EthereumAbi — typed encoding
329
+ Function("ethereumAbiEncodeTyped") { messageJson: String ->
330
+ val encoded = EthereumAbi.encodeTyped(messageJson)
331
+ encoded.joinToString("") { "%02x".format(it) }
332
+ }
333
+
334
+ // Mnemonic
335
+ Function("mnemonicIsValid") { mnemonic: String ->
336
+ Mnemonic.isValid(mnemonic)
337
+ }
338
+
339
+ // TONAddressConverter
340
+ Function("tonAddressToUserFriendly") { address: String ->
341
+ TONAddressConverter.toUserFriendly(address)
342
+ }
343
+
344
+ // SolanaAddress
345
+ Function("solanaAddressDefaultTokenAddress") { address: String, tokenMintAddress: String ->
346
+ SolanaAddress(address).defaultTokenAddress(tokenMintAddress)
347
+ }
348
+ }
349
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": { "modules": ["ExpoWalletCoreModule"] },
4
+ "android": { "modules": ["expo.modules.walletcore.ExpoWalletCoreModule"] }
5
+ }
@@ -0,0 +1,17 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'ExpoWalletCore'
3
+ s.version = '0.1.0'
4
+ s.summary = 'Native WalletCore bridge for Vultisig SDK'
5
+ s.description = 'Expo native module wrapping TrustWallet WalletCore for chain operations'
6
+ s.homepage = 'https://github.com/vultisig/vultisig-sdk'
7
+ s.license = 'MIT'
8
+ s.author = 'Vultisig'
9
+ s.source = { git: 'https://github.com/vultisig/vultisig-sdk.git' }
10
+
11
+ s.platform = :ios, '15.1'
12
+ s.swift_version = '5.4'
13
+ s.source_files = '*.swift'
14
+
15
+ s.dependency 'ExpoModulesCore'
16
+ s.dependency 'TrustWalletCore', '4.3.22'
17
+ end