Skip to content

Commit

Permalink
implement getting permit with OfflineSignerOnlyAminoWalletWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
luca992 committed Oct 31, 2022
1 parent 2a6577a commit a45d069
Show file tree
Hide file tree
Showing 17 changed files with 815 additions and 42 deletions.
443 changes: 419 additions & 24 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions sample/src/commonMain/kotlin/SampleApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import io.eqoty.secretk.client.SigningCosmWasmClient
import io.eqoty.secretk.extensions.accesscontrol.PermitFactory
import io.eqoty.secretk.types.MsgExecuteContract
import io.eqoty.secretk.types.TxOptions
import io.eqoty.secretk.types.extensions.Permission
import io.eqoty.secretk.types.extensions.Permit
import io.eqoty.secretk.wallet.DirectSigningWallet
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

val contractAddress = "secret1lz4m46vpdn8f2aj8yhtnexus40663udv7hhprm"


@Composable
fun SampleApp(client: SigningCosmWasmClient) {
val coroutineScope = rememberCoroutineScope()
var permit: Permit? by remember { mutableStateOf(null) }
var contractInfoQueryResponse: String? by remember { mutableStateOf(null) }
var numTokensQueryWithPermitResponse: String? by remember { mutableStateOf(null) }
var viewingKeyTxResponse: String? by remember { mutableStateOf(null) }
var numTokensQueryResponse: String? by remember { mutableStateOf(null) }
MaterialTheme {
Expand Down Expand Up @@ -51,6 +58,44 @@ fun SampleApp(client: SigningCosmWasmClient) {
Text("query response: $contractInfoQueryResponse")
}
}
if (permit == null) {
coroutineScope.launch {
permit = PermitFactory.newPermit(
client.wallet,
client.senderAddress,
client.getChainId(),
"Test",
listOf(contractAddress),
listOf(Permission.Owner),
)
}
} else {
Row {
Button({
coroutineScope.launch {
val numTokensQuery =
"""
{
"with_permit": {
"permit": ${Json.encodeToString(permit)},
"query": { "num_tokens": {} }
}
}
"""
numTokensQueryWithPermitResponse = try {
client.queryContractSmart(contractAddress, numTokensQuery)
} catch (t: Throwable) {
t.message
}
}
}) {
Text("Get number of tokens with permit")
}
numTokensQueryWithPermitResponse?.let {
Text("query response: $numTokensQueryWithPermitResponse")
}
}
}
Row {
Button({
coroutineScope.launch {
Expand Down
5 changes: 3 additions & 2 deletions sample/src/jsMain/kotlin/main.js.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import jslib.walletconnect.WalletConnectProvider

fun main() {
application {
val wallet = setupMetamaskAndGetWallet()
// val wallet = setupKeplerAndGetWallet()
// val wallet = setupMetamaskAndGetWallet()
val wallet = setupKeplerAndGetWallet()
val grpcGatewayEndpoint = "https://api.pulsar.scrttestnet.com"
// A pen is the most basic tool you can think of for signing.
// This wraps a single keypair and allows for signing.
Expand Down Expand Up @@ -108,6 +108,7 @@ suspend fun setupKeplerAndGetWallet(): OfflineSignerOnlyAminoWalletWrapper {
console.log(suggestion)
val promise: Promise<dynamic> = window.asDynamic().keplr.experimentalSuggestChain(suggestion) as Promise<dynamic>
val enablePromise: Promise<dynamic> = window.asDynamic().keplr.enable(CHAIN_ID) as Promise<dynamic>
enablePromise.await()
val wallet: AminoWallet = window.asDynamic().getOfflineSignerOnlyAmino(CHAIN_ID)
return OfflineSignerOnlyAminoWalletWrapper(wallet)
}
Expand Down
2 changes: 2 additions & 0 deletions secretk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ kotlin {
implementation(npm("libsodium-wrappers-sumo", "^0.7.10"))
implementation(npm("secretjs", "^1.4.3"))
implementation(npm("google-protobuf", "^3.21.0"))
implementation(npm("@cosmjs/amino", "^0.29.3"))
implementation(npm("@keplr-wallet/types", "^0.11.13"))
}
}
val jsTest by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package io.eqoty.secretk.extensions.accesscontrol
import io.eqoty.secretk.types.*
import io.eqoty.secretk.types.extensions.Permission
import io.eqoty.secretk.types.extensions.Permit
import io.eqoty.secretk.wallet.DirectSigningWallet
import io.eqoty.secretk.wallet.Wallet
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

object PermitFactory {

private fun newSignDoc(
internal fun newSignDoc(
chainId: String,
permitName: String,
allowedTokens: List<String>,
Expand Down Expand Up @@ -42,15 +43,17 @@ object PermitFactory {
allowedTokens: List<String>,
permissions: List<Permission>,
): Permit {
val signature = when (wallet) {
is DirectSigningWallet -> {
wallet.signAmino(
owner,
newSignDoc(chainId, permitName, allowedTokens, permissions),
).signature
}

else -> TODO()
var signature = newPermitWithTargetSpecificWallet(
wallet, owner, chainId, permitName, allowedTokens, permissions
)
if (signature == null) {
println(Json.encodeToString(newSignDoc(chainId, permitName, allowedTokens, permissions)))
val aa= wallet.signAmino(
owner,
newSignDoc(chainId, permitName, allowedTokens, permissions),
)
println(aa.signature.toString())
signature = aa.signature
}

return Permit(
Expand All @@ -63,4 +66,13 @@ object PermitFactory {
signature = signature,
)
}
}
}

internal expect suspend fun PermitFactory.newPermitWithTargetSpecificWallet(
wallet: Wallet,
owner: String,
chainId: String,
permitName: String,
allowedTokens: List<String>,
permissions: List<Permission>,
): StdSignature?
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ sealed class PubKey : TypeValue<String>()

@kotlinx.serialization.Serializable
@SerialName("tendermint/PubKeySecp256k1")
class PubKeySecp256k1(
data class PubKeySecp256k1(
// Value field is base64-encoded in all cases
// Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first
override val value: String
) : PubKey()

@kotlinx.serialization.Serializable
@SerialName("tendermint/PubKeyMultisigThreshold")
class PubKeyMultisigThreshold(
data class PubKeyMultisigThreshold(
// Value field is base64-encoded in all cases
// Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first
override val value: String
Expand All @@ -24,15 +24,15 @@ class PubKeyMultisigThreshold(

@kotlinx.serialization.Serializable
@SerialName("tendermint/PubKeyEd25519")
class PubKeyEd25519(
data class PubKeyEd25519(
// Value field is base64-encoded in all cases
// Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first
override val value: String
) : PubKey()

@kotlinx.serialization.Serializable
@SerialName("tendermint/PubKeySr25519")
class PubKeySr25519(
data class PubKeySr25519(
// Value field is base64-encoded in all cases
// Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first
override val value: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.eqoty.secretk.extensions.accesscontrol

import io.eqoty.secretk.types.StdSignature
import io.eqoty.secretk.types.extensions.Permission
import io.eqoty.secretk.wallet.AminoSignResponse
import io.eqoty.secretk.wallet.Wallet
import io.eqoty.wallet.OfflineSignerOnlyAminoWalletWrapper
import jslibs.keplrwallet.types.Keplr
import jslibs.keplrwallet.types.KeplrSignOptionsImpl
import kotlinx.browser.window
import kotlinx.coroutines.await
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.w3c.dom.get

internal actual suspend fun PermitFactory.newPermitWithTargetSpecificWallet(
wallet: Wallet,
owner: String,
chainId: String,
permitName: String,
allowedTokens: List<String>,
permissions: List<Permission>
): StdSignature? {
return if (wallet is OfflineSignerOnlyAminoWalletWrapper) {
val jsAminoSignResponse = (window["keplr"] as Keplr).signAmino(
chainId,
owner,
JSON.parse(Json.encodeToString(newSignDoc(chainId, permitName, allowedTokens, permissions))),
// not sure why I can't just directly use KeplrSignOptionsImpl here 🙃
signOptions = JSON.parse(
Json.encodeToString(
KeplrSignOptionsImpl(
preferNoSetFee = true, // Fee must be 0, so hide it from the user
preferNoSetMemo = true, // Memo must be empty, so hide it from the user
disableBalanceCheck = true
)
)
)
).await()
val aminoSignResponse: AminoSignResponse = Json.decodeFromString(JSON.stringify(jsAminoSignResponse))
aminoSignResponse.signature
} else {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

// todo: Remove secret.js dependency and just implement it ourselves
class MetaMaskWalletWrapper(
val wallet: MetaMaskWallet,
) : Wallet {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS"
)

package jslibs.cosmjs.amino

external interface Coin {
var denom: String
var amount: String
}

external fun coin(amount: Number, denom: String): Coin

external fun coin(amount: String, denom: String): Coin

external fun coins(amount: Number, denom: String): Array<Coin>

external fun coins(amount: String, denom: String): Array<Coin>

external fun parseCoins(input: String): Array<Coin>

external fun addCoins(lhs: Coin, rhs: Coin): Coin
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS"
)

package jslibs.cosmjs.amino

external interface Pubkey {
var type: String
var value: dynamic
}

external interface Ed25519Pubkey : SinglePubkey {
override var type: String /* "tendermint/PubKeyEd25519" */
override var value: String
}

external fun isEd25519Pubkey(pubkey: Pubkey): Boolean

external interface Secp256k1Pubkey : SinglePubkey {
override var type: String /* "tendermint/PubKeySecp256k1" */
override var value: String
}

external fun isSecp256k1Pubkey(pubkey: Pubkey): Boolean

external interface SinglePubkey : Pubkey {
override var type: String
override var value: String
}

external fun isSinglePubkey(pubkey: Pubkey): Boolean

external interface MultisigThresholdPubkeyValue {
var threshold: String
var pubkeys: Array<SinglePubkey>
}

external interface MultisigThresholdPubkey : Pubkey {
override var type: String /* "tendermint/PubKeyMultisigThreshold" */
override var value: MultisigThresholdPubkeyValue
}

external fun isMultisigThresholdPubkey(pubkey: Pubkey): Boolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS"
)

package jslibs.cosmjs.amino

import org.khronos.webgl.Uint8Array

external interface StdSignature {
var pub_key: Pubkey
var signature: String
}

external fun encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature

external interface `T$1` {
var pubkey: Uint8Array
var signature: Uint8Array
}

external fun decodeSignature(signature: StdSignature): `T$1`
Loading

0 comments on commit a45d069

Please sign in to comment.