add EthereumRPC
This commit is contained in:
parent
a8a6b6c054
commit
200ef38c18
|
@ -3,10 +3,14 @@ package im.status.keycard.connect
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.nfc.NfcAdapter
|
import android.nfc.NfcAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import im.status.keycard.android.NFCCardManager
|
import im.status.keycard.android.NFCCardManager
|
||||||
import im.status.keycard.connect.card.*
|
import im.status.keycard.connect.card.*
|
||||||
import im.status.keycard.connect.data.PINCache
|
import im.status.keycard.connect.data.PINCache
|
||||||
import im.status.keycard.connect.data.PairingManager
|
import im.status.keycard.connect.data.PairingManager
|
||||||
|
import im.status.keycard.connect.data.RPC_ENDPOINT
|
||||||
|
import im.status.keycard.connect.net.EthereumRPC
|
||||||
import im.status.keycard.connect.net.WalletConnect
|
import im.status.keycard.connect.net.WalletConnect
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
@ -30,8 +34,24 @@ object Registry {
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var walletConnect: WalletConnect
|
lateinit var walletConnect: WalletConnect
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var ethereumRPC: EthereumRPC
|
||||||
|
private set
|
||||||
|
|
||||||
|
private fun moshiAddKotlin() {
|
||||||
|
val factories = Moshi::class.java.getDeclaredField("BUILT_IN_FACTORIES")
|
||||||
|
factories.isAccessible = true
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val value = factories.get(null) as java.util.ArrayList<Any>
|
||||||
|
value.add(0, KotlinJsonAdapterFactory())
|
||||||
|
}
|
||||||
|
|
||||||
fun init(activity: Activity, listener: ScriptListener) {
|
fun init(activity: Activity, listener: ScriptListener) {
|
||||||
|
//TODO: remove this hack, it is needed now because KEthereum does not add the KotlinJsonAdapterFactory
|
||||||
|
moshiAddKotlin()
|
||||||
|
|
||||||
this.mainActivity = activity
|
this.mainActivity = activity
|
||||||
|
|
||||||
pairingManager = PairingManager(activity)
|
pairingManager = PairingManager(activity)
|
||||||
|
@ -44,6 +64,8 @@ object Registry {
|
||||||
cardManager.setCardListener(scriptExecutor)
|
cardManager.setCardListener(scriptExecutor)
|
||||||
cardManager.start()
|
cardManager.start()
|
||||||
|
|
||||||
|
//TODO: endpoint should be configurable
|
||||||
|
ethereumRPC = EthereumRPC(RPC_ENDPOINT)
|
||||||
walletConnect = WalletConnect()
|
walletConnect = WalletConnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,3 +17,5 @@ const val REQ_INTERACTIVE_SCRIPT = 0x01
|
||||||
const val REQ_WALLETCONNECT = 0x02
|
const val REQ_WALLETCONNECT = 0x02
|
||||||
|
|
||||||
const val CACHE_VALIDITY = 15 * 60 * 1000
|
const val CACHE_VALIDITY = 15 * 60 * 1000
|
||||||
|
|
||||||
|
const val RPC_ENDPOINT = "http://mainnet.infura.io/v3/27efcb33f94e4bd0866d1aadf8e1a12d"
|
|
@ -0,0 +1,35 @@
|
||||||
|
package im.status.keycard.connect.net
|
||||||
|
|
||||||
|
import org.kethereum.extensions.hexToBigInteger
|
||||||
|
import org.kethereum.rpc.HttpEthereumRPC
|
||||||
|
import org.kethereum.rpc.model.StringResultResponse
|
||||||
|
import java.io.IOException
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
class EthereumRPC(endpointURL: String) {
|
||||||
|
private var endpoint = HttpEthereumRPC(endpointURL)
|
||||||
|
|
||||||
|
fun changeEndpoint(endpointURL: String) {
|
||||||
|
endpoint = HttpEthereumRPC(endpointURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> valueOrThrow(res: StringResultResponse?, body: (String) -> T) : T {
|
||||||
|
if (res != null && res.error == null) {
|
||||||
|
return body(res.result)
|
||||||
|
} else {
|
||||||
|
throw IOException("communication error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ethGetTransactionCount(address: String): BigInteger {
|
||||||
|
return valueOrThrow(endpoint.getTransactionCount(address)) { it.hexToBigInteger() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ethGasPrice(): BigInteger {
|
||||||
|
return valueOrThrow(endpoint.gasPrice()) { it.hexToBigInteger() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ethSendRawTransaction(rawTx: String): String {
|
||||||
|
return valueOrThrow(endpoint.sendRawTransaction(rawTx)) { it }
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,21 +26,21 @@ import org.walleth.khex.hexToByteArray
|
||||||
import org.walleth.khex.toHexString
|
import org.walleth.khex.toHexString
|
||||||
import org.walleth.khex.toNoPrefixHexString
|
import org.walleth.khex.toNoPrefixHexString
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener {
|
class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener, Session.Callback {
|
||||||
//TODO: Provide settings for these two
|
//TODO: Provide settings for these two
|
||||||
private val bip39Path = "m/44'/60'/0'/0"
|
private val bip39Path = "m/44'/60'/0'/0"
|
||||||
private val chainID: Long = 1
|
private val chainID: Long = 1
|
||||||
|
|
||||||
private val scope = MainScope()
|
private val scope = MainScope()
|
||||||
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
private val moshi = Moshi.Builder().build()
|
||||||
private val okHttpClient = OkHttpClient()
|
private val okHttpClient = OkHttpClient()
|
||||||
private val sessionStore = FileWCSessionStore(File(Registry.mainActivity.filesDir, "wcSessions.json").apply { createNewFile() }, moshi)
|
private val sessionStore = FileWCSessionStore(File(Registry.mainActivity.filesDir, "wcSessions.json").apply { createNewFile() }, moshi)
|
||||||
private var session: WCSession? = null
|
private var session: WCSession? = null
|
||||||
private var requestId: Long = 0
|
private var requestId: Long = 0
|
||||||
private var action: (data: Intent?) -> Unit = this::nop
|
private var action: (data: Intent?) -> Unit = this::nop
|
||||||
|
|
||||||
private val sessionCB = object : Session.Callback {
|
|
||||||
override fun onStatus(status: Session.Status) {
|
override fun onStatus(status: Session.Status) {
|
||||||
when (status) {
|
when (status) {
|
||||||
is Session.Status.Error -> println("WalletConnect Error")
|
is Session.Status.Error -> println("WalletConnect Error")
|
||||||
|
@ -62,22 +62,15 @@ class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// would be more elegant with a single inline generic function with reified type, but apparently in Kotlin 1.3.50 code generation fails if I make this function inline.
|
private inline fun <reified T> runOnValidParam(call: Session.MethodCall.Custom, index: Int, body: (T) -> Unit) {
|
||||||
// TODO: check newer version of Kotlin
|
|
||||||
private fun runOnValidParam(call: Session.MethodCall.Custom, body: (String) -> Unit) {
|
|
||||||
val param = call.params?.firstOrNull()
|
|
||||||
if (param is String) {
|
|
||||||
body(param)
|
|
||||||
} else {
|
|
||||||
session?.rejectRequest(call.id, 1L, "Invalid params")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun runOnValidParam(call: Session.MethodCall.Custom, index: Int, body: (Map<*, *>) -> Unit) {
|
|
||||||
val param = call.params?.getOrNull(index)
|
val param = call.params?.getOrNull(index)
|
||||||
|
|
||||||
if (param is Map<*, *>) {
|
if (param is T) {
|
||||||
|
try {
|
||||||
body(param)
|
body(param)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
session?.rejectRequest(call.id, 1L, "Internal error")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
session?.rejectRequest(call.id, 1L, "Invalid params")
|
session?.rejectRequest(call.id, 1L, "Invalid params")
|
||||||
}
|
}
|
||||||
|
@ -85,10 +78,10 @@ class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener {
|
||||||
|
|
||||||
private fun onCustomCall(call: Session.MethodCall.Custom) {
|
private fun onCustomCall(call: Session.MethodCall.Custom) {
|
||||||
when(call.method) {
|
when(call.method) {
|
||||||
"personal_sign" -> runOnValidParam(call) { signText(call.id, it) }
|
"personal_sign" -> runOnValidParam<String>(call, 0) { signText(call.id, it) }
|
||||||
"eth_signTypedData" -> { runOnValidParam(call, 1) { @Suppress("UNCHECKED_CAST") signTypedData(call.id, it as Map<String, String>) } }
|
"eth_signTypedData" -> { runOnValidParam<Map<*, *>>(call, 1) { @Suppress("UNCHECKED_CAST") signTypedData(call.id, it as Map<String, String>) } }
|
||||||
"eth_signTransaction" -> { runOnValidParam(call, 0) { signTransaction(call.id, toTransaction(toSendTransaction(call.id, it)), false)} }
|
"eth_signTransaction" -> { runOnValidParam<Map<*, *>>(call, 0) { signTransaction(call.id, toTransaction(toSendTransaction(call.id, it)), false)} }
|
||||||
"eth_sendRawTransaction" -> { runOnValidParam(call) { relayTX(call.id, it) } }
|
"eth_sendRawTransaction" -> { runOnValidParam<String>(call, 0) { relayTX(call.id, it) } }
|
||||||
else -> session?.rejectRequest(call.id, 1L, "Not implemented")
|
else -> session?.rejectRequest(call.id, 1L, "Not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,11 +100,9 @@ class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener {
|
||||||
private fun toTransaction(tx: Session.MethodCall.SendTransaction): Transaction {
|
private fun toTransaction(tx: Session.MethodCall.SendTransaction): Transaction {
|
||||||
return createEmptyTransaction()
|
return createEmptyTransaction()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun relayTX(id: Long, signedTx: String) {
|
private fun relayTX(id: Long, signedTx: String) {
|
||||||
requestId = id
|
session?.approveRequest(id, Registry.ethereumRPC.ethSendRawTransaction(signedTx))
|
||||||
session?.rejectRequest(id, 1L, "Not implemented yet")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signText(id: Long, message: String) {
|
private fun signText(id: Long, message: String) {
|
||||||
|
@ -166,7 +157,7 @@ class WalletConnect : ExportKeyCommand.Listener, SignCommand.Listener {
|
||||||
Session.PeerMeta(name = "Keycard Connect")
|
Session.PeerMeta(name = "Keycard Connect")
|
||||||
)
|
)
|
||||||
|
|
||||||
session?.addCallback(sessionCB)
|
session?.addCallback(Registry.walletConnect)
|
||||||
session?.init()
|
session?.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue