Skip to content

Commit

Permalink
[Kotlin] JVM support (part 2)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximPestryakov committed Sep 5, 2023
1 parent b809ccc commit 40e01f2
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 12 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/kotlin-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ jobs:
run: |
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON -DTW_COMPILE_JAVA=ON -DTW_COMPILE_KOTLIN=ON -GNinja
# The build artifact will be used in `tools/kotlin-build` in the future.
- name: Build JNI
run: |
ninja -Cbuild
mv build/libTrustWalletCore.dylib kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/
- name: Build Kotlin Multiplatform
run: tools/kotlin-build

- name: Run Kotlin Multiplatform tests
run: tools/kotlin-test
2 changes: 2 additions & 0 deletions kotlin/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ ij_kotlin_imports_layout = *
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_enum_constants_wrap = split_into_lines
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = *
ij_kotlin_wrap_first_method_in_call_chain = true
28 changes: 17 additions & 11 deletions kotlin/wallet-core-kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@file:Suppress("UnstableApiUsage")
@file:Suppress("UnstableApiUsage", "OPT_IN_USAGE")

import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput

Expand All @@ -10,11 +10,19 @@ plugins {
}

kotlin {
targetHierarchy.default()

android {
publishLibraryVariants = listOf("release")
}

jvm()
jvm {
testRuns.named("test") {
executionTask.configure {
useJUnitPlatform()
}
}
}

val nativeTargets =
listOf(
Expand Down Expand Up @@ -50,6 +58,12 @@ kotlin {
}
}

getByName("commonTest") {
dependencies {
implementation(kotlin("test"))
}
}

val androidMain by getting
val jvmMain by getting
create("commonAndroidJvmMain") {
Expand All @@ -60,16 +74,8 @@ kotlin {
jvmMain.dependsOn(this)
}

val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosX64Main by getting
create("iosMain") {
getByName("iosMain") {
kotlin.srcDir(projectDir.resolve("src/iosMain/generated"))

dependsOn(commonMain)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
iosX64Main.dependsOn(this)
}

getByName("jsMain") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.trustwallet.core

actual object LibLoader {
actual fun loadLibrary() {
throw NotImplementedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.trustwallet.core

expect object LibLoader {
fun loadLibrary()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.trustwallet.core.test

import com.trustwallet.core.CoinType
import com.trustwallet.core.CoinType.*
import com.trustwallet.core.HDWallet
import com.trustwallet.core.LibLoader
import kotlin.test.Test
import kotlin.test.assertEquals

class CoinAddressDerivationTests {

init {
LibLoader.loadLibrary()
}

@Test
fun testDeriveAddressesFromPhrase() {
val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "")

CoinType.values().forEach { coin ->
val address = wallet.getAddressForCoin(coin)
val expectedAddress = getExpectedAddress(coin)

assertEquals(expectedAddress, address, "Coin = $coin")
}
}

private fun getExpectedAddress(coin: CoinType): String = when (coin) {
Binance -> "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw"
TBinance -> "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl"
Bitcoin -> "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d"
BitcoinDiamond -> "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn"
BitcoinCash -> "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70"
BitcoinGold -> "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg"
Callisto -> "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04"
Dash -> "XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT"
DigiByte -> "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu"

Ethereum, SmartChain, Polygon, Optimism, Zksync, Arbitrum, ArbitrumNova, ECOChain, AvalancheCChain, XDai,
Fantom, Celo, CronosChain, SmartBitcoinCash, KuCoinCommunityChain, Boba, Metis,
Aurora, Evmos, Moonriver, Moonbeam, KavaEvm, Klaytn, Meter, OKXChain, PolygonzkEVM, Scroll,
ConfluxeSpace, AcalaEVM, OpBNB, Neon, Base, Linea, Greenfield,
-> "0x8f348F300873Fd5DA36950B2aC75a26584584feE"

Ronin -> "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE"
EthereumClassic -> "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c"
GoChain -> "0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2"
Groestlcoin -> "grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j"
ICON -> "hx18b380b53c23dc4ee9f6666bc20d1be02f3fe106"
Litecoin -> "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u"
Ontology -> "AHKTnybvnWo3TeY8uvNXekvYxMrXogUjeT"
POANetwork -> "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9"
XRP -> "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3"
Tezos -> "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX"
ThunderCore -> "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7"
TomoChain -> "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424"
Tron -> "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio"
VeChain -> "0x1a553275dF34195eAf23942CB7328AcF9d48c160"
Wanchain -> "0xD5ca90b928279FE5D06144136a25DeD90127aC15"
Komodo -> "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb"
Zcash -> "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy"
Zen -> "znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX"
Firo -> "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik"
Nimiq -> "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H"
Stellar -> "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P"
Aion -> "0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e"
Nano -> "nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc"
Nebulas -> "n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ"
NEAR -> "0c91f6106ff835c0195d5388565a2d69e25038a7e23d26198f85caf6594117ec"
Theta, ThetaFuel -> "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4"
Cosmos -> "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn"
Decred -> "DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG"
Dogecoin -> "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f"
Kin -> "GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA"
Viacoin -> "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc"
Verge -> "DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2"
Qtum -> "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF"
NULS -> "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en"
EOS -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg"
WAX -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg"
IoTeX -> "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk"
IoTeXEVM -> "0x038B8C633873Ca0f06961100BE5d37676EADDD23"
Zilliqa -> "zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6"
Zelcash -> "t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf"
Ravencoin -> "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS"
Waves -> "3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n"
Aeternity -> "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN"
Terra, TerraV2 -> "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug"
Monacoin -> "M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja"
FIO -> "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3"
Harmony -> "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09"
Solana -> "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m"
Algorand -> "JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ"
Acala -> "25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3"
Kusama -> "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY"
Polkadot -> "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk"
Pivx -> "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm"
Kava -> "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87"
Cardano -> "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2"
NEO -> "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf"
Filecoin -> "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori"
MultiversX -> "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8"
BandChain -> "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r"
SmartChainLegacy -> "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8"
Oasis -> "oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps"
THORChain -> "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65"
IOST -> "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu"
Syscoin -> "sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj"
Stratis -> "strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm"
Bluzelle -> "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund"
CryptoOrg -> "cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8"
Osmosis -> "osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp"
ECash -> "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377"
NativeEvmos -> "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d"
Nervos -> "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3"
Everscale -> "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"
TON -> "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9"
Aptos -> "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"
Nebl -> "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7"
Sui -> "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2"
Hedera -> "0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5"
Secret -> "secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh"
NativeInjective -> "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a"
Agoric -> "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5"
Stargaze -> "stars142j9u5eaduzd7faumygud6ruhdwme98qycayaz"
Juno -> "juno142j9u5eaduzd7faumygud6ruhdwme98qxkfz30"
Stride -> "stride142j9u5eaduzd7faumygud6ruhdwme98qn029zl"
Axelar -> "axelar142j9u5eaduzd7faumygud6ruhdwme98q52u3aj"
Crescent -> "cre142j9u5eaduzd7faumygud6ruhdwme98q5veur7"
Kujira -> "kujira142j9u5eaduzd7faumygud6ruhdwme98qpvgpme"
NativeCanto -> "canto13u6g7vqgw074mgmf2ze2cadzvkz9snlwqua5pd"
Comdex -> "comdex142j9u5eaduzd7faumygud6ruhdwme98qhtgm0y"
Neutron -> "neutron142j9u5eaduzd7faumygud6ruhdwme98q5mrmv5"
Sommelier -> "somm142j9u5eaduzd7faumygud6ruhdwme98quc948e"
FetchAI -> "fetch142j9u5eaduzd7faumygud6ruhdwme98qrera5y"
Mars -> "mars142j9u5eaduzd7faumygud6ruhdwme98qdenqrg"
Umee -> "umee142j9u5eaduzd7faumygud6ruhdwme98qzjhxjp"
Coreum -> "core1rawf376jz2lnchgc4wzf4h9c77neg3zldc7xa8"
Quasar -> "quasar142j9u5eaduzd7faumygud6ruhdwme98q78symk"
Persistence -> "persistence142j9u5eaduzd7faumygud6ruhdwme98q7gv2ch"
Akash -> "akash142j9u5eaduzd7faumygud6ruhdwme98qal870f"
Noble -> "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa"
Rootstock -> "0xA2D7065F94F838a3aB9C04D67B312056846424Df"
Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.trustwallet.core

actual object LibLoader {
actual fun loadLibrary() {
throw NotImplementedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.trustwallet.core

actual object LibLoader {
actual fun loadLibrary() {
throw NotImplementedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.trustwallet.core

import com.trustwallet.core.WalletCoreLibLoader.Linux64Path
import com.trustwallet.core.WalletCoreLibLoader.MacArm64Path
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean

/**
* Simple helper class that extracts proper native library for current OS/arch from .jar to temporary file and loads it.
* You can do the same by yourself, just use path constants: [MacArm64Path], [Linux64Path]
*/
object WalletCoreLibLoader {

const val MacArm64Path = "/jni/macos-arm64/libTrustWalletCore.dylib"
const val Linux64Path = "/jni/linux-x86_64/libTrustWalletCore.so"

private val isLoaded = AtomicBoolean(false)

@JvmStatic
fun loadLibrary() {
if (isLoaded.compareAndSet(false, true)) {
val resLibPath = getLibResourcePath()
val resLibStream = WalletCoreLibLoader::class.java.getResourceAsStream(resLibPath)
?: error("File not found: $resLibPath")

val fileOut = File.createTempFile("libTrustWalletCore", null)
fileOut.deleteOnExit()

resLibStream.copyTo(fileOut.outputStream())

@Suppress("UnsafeDynamicallyLoadedCode")
System.load(fileOut.absolutePath)
}
}

private fun getLibResourcePath(): String {
val osNameOriginal = System.getProperty("os.name").lowercase()
val osName = osNameOriginal.lowercase()
val archOriginal = System.getProperty("os.arch").lowercase()
val arch = archOriginal.lowercase()

return when {
osName.startsWith("mac") -> {
when (arch) {
"aarch64" -> MacArm64Path
else -> error("Arch is not supported: $archOriginal")
}
}

osName.startsWith("linux") -> {
when (arch) {
"amd64", "x86_64" -> Linux64Path
else -> error("Arch is not supported: $archOriginal")
}
}

else -> error("OS is not supported: $osNameOriginal")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libTrustWalletCore.so
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libTrustWalletCore.dylib
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.trustwallet.core

actual object LibLoader {
actual fun loadLibrary() {
WalletCoreLibLoader.loadLibrary()
}
}
7 changes: 7 additions & 0 deletions tools/kotlin-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

set -e

pushd kotlin
./gradlew :wallet-core-kotlin:jvmTest
popd

0 comments on commit 40e01f2

Please sign in to comment.