This commit is contained in:
guoqing.zhu 2022-05-31 18:19:24 +08:00
parent 27da69f1d2
commit 6c4d382625
12 changed files with 886 additions and 89 deletions

View File

@ -47,8 +47,8 @@
}, },
"_scale": { "_scale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.805834949016571, "x": 0.9920991955598369,
"y": 0.805834949016571, "y": 0.9920991955598369,
"z": 1 "z": 1
}, },
"_quat": { "_quat": {
@ -13797,7 +13797,7 @@
"__id__": 411 "__id__": 411
} }
], ],
"_active": false, "_active": true,
"_level": 2, "_level": 2,
"_components": [ "_components": [
{ {

File diff suppressed because one or more lines are too long

View File

@ -24,34 +24,42 @@
****************************************************************************/ ****************************************************************************/
package org.cocos2dx.javascript; package org.cocos2dx.javascript;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;
import org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge;
import org.jetbrains.annotations.NotNull;
import org.walletconnect.Session;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Looper; import android.net.Uri;
import android.widget.Toast; import android.os.Bundle;
import android.util.Log;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.youme.voiceengine.MemberChange; import com.youme.voiceengine.MemberChange;
import com.youme.voiceengine.YouMeCallBackInterface; import com.youme.voiceengine.YouMeCallBackInterface;
import com.youme.voiceengine.YouMeConst; import com.youme.voiceengine.YouMeConst;
import com.youme.voiceengine.api; import com.youme.voiceengine.api;
import com.youme.voiceengine.mgr.YouMeManager; import com.youme.voiceengine.mgr.YouMeManager;
import java.util.Arrays; import org.cocos2dx.javascript.wc.JWCSession;
import org.cocos2dx.javascript.wc.entity.CallResult;
import org.cocos2dx.javascript.wc.entity.ChainObj;
import org.cocos2dx.javascript.wc.entity.ChangeChainObj;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;
import org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge;
import org.jetbrains.annotations.NotNull;
import org.walletconnect.Session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterface , Session.Callback { public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterface , Session.Callback {
private static AppActivity appActivity = null; private static AppActivity appActivity = null;
private static final String TAG = "AppActivity";
private static final String appKey="YOUME1838B3633FF1410BDC9124BBD806F245B9D2E5AC"; private static final String appKey="YOUME1838B3633FF1410BDC9124BBD806F245B9D2E5AC";
private static final String appSecret="q6B570yTyj/00Nk4mYZtgDwyew5v05t13V1vo4mxpEuAaWUiinAyVxG41sNu3vsFe8sipOLfKfIVYGhzpQrqzvj5sId3mrBfj/s65a2gp36yDrI/nX5BnUAJB317SEosR6xLoPuhBvHU+/1DWI7nKSKaRNxnQiC46PJKFc2kX50BAAE="; private static final String appSecret="q6B570yTyj/00Nk4mYZtgDwyew5v05t13V1vo4mxpEuAaWUiinAyVxG41sNu3vsFe8sipOLfKfIVYGhzpQrqzvj5sId3mrBfj/s65a2gp36yDrI/nX5BnUAJB317SEosR6xLoPuhBvHU+/1DWI7nKSKaRNxnQiC46PJKFc2kX50BAAE=";
@ -182,41 +190,41 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
switch (eventType) { switch (eventType) {
case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_OK: //YOUME_EVENT_INIT_OK: case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_OK: //YOUME_EVENT_INIT_OK:
System.out.println("Talk 初始化成功");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_FAILED://YOUME_EVENT_INIT_FAILED: case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_FAILED://YOUME_EVENT_INIT_FAILED:
System.out.println("Talk 初始化失败");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_OK://YOUME_EVENT_JOIN_OK: case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_OK://YOUME_EVENT_JOIN_OK:
System.out.println("Talk 进入频道成功,频道:" + channelID + " 用户id:" + param);
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_FAILED://YOUME_EVENT_JOIN_FAILED: case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_FAILED://YOUME_EVENT_JOIN_FAILED:
System.out.println("Talk 进入频道:" + channelID + "失败,code:" + errorCode);
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ONE://YOUME_EVENT_LEAVED_ONE: case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ONE://YOUME_EVENT_LEAVED_ONE:
System.out.println("Talk 离开单个频道:" + channelID);
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ALL://YOUME_EVENT_LEAVED_ALL: case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ALL://YOUME_EVENT_LEAVED_ALL:
System.out.println("Talk 离开所有频道这个回调channel参数为空字符串");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_PAUSED://YOUME_EVENT_PAUSED: case YouMeConst.YouMeEvent.YOUME_EVENT_PAUSED://YOUME_EVENT_PAUSED:
System.out.println("Talk 暂停");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_RESUMED://YOUME_EVENT_RESUMED: case YouMeConst.YouMeEvent.YOUME_EVENT_RESUMED://YOUME_EVENT_RESUMED:
System.out.println("Talk 恢复");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_SUCCESS://YOUME_EVENT_SPEAK_SUCCESS:///< 切换对指定频道讲话成功适用于多频道模式 case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_SUCCESS://YOUME_EVENT_SPEAK_SUCCESS:
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_FAILED://YOUME_EVENT_SPEAK_FAILED:///< 切换对指定频道讲话失败适用于多频道模式 case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_FAILED://YOUME_EVENT_SPEAK_FAILED://
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTING://YOUME_EVENT_RECONNECTING:///< 断网了正在重连 case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTING://YOUME_EVENT_RECONNECTING:/
System.out.println("Talk 正在重连");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTED://YOUME_EVENT_RECONNECTED:///< 断网重连成功 case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTED://YOUME_EVENT_RECONNECTED://
System.out.println("Talk 重连成功");
break; break;
case YouMeConst.YouMeEvent.YOUME_EVENT_REC_PERMISSION_STATUS://YOUME_EVENT_REC_FAILED:///< 通知录音启动失败此时不管麦克风mute状态如何都没有声音输出 case YouMeConst.YouMeEvent.YOUME_EVENT_REC_PERMISSION_STATUS://YOUME_EVENT_REC_FAILED://
System.out.println("录音启动失败code" + errorCode);
break; break;
default: default:
@ -249,9 +257,11 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
public static void connectwallet(String data){ public static void connectwallet(String data){
ExampleApplication.Companion.resetSession(); ExampleApplication.Companion.resetSession();
ExampleApplication.session.addCallback(appActivity); ExampleApplication.session.addCallback(appActivity);
Intent intent = new Intent(Intent.ACTION_VIEW); String url = "metamask://wc?uri=" + ExampleApplication.config.toWCUri();
intent.setData(Uri.parse(ExampleApplication.config.toWCUri())); // String url = "https://metamask.app.link/wc?uri="+ExampleApplication.config.toWCUri();
System.out.println(ExampleApplication.config.toWCUri()); Uri uri = Uri.parse(url);
Log.i(TAG, url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try { try {
appActivity.startActivity(intent); appActivity.startActivity(intent);
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
@ -264,15 +274,35 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
// private boolean hasSign = false; // private boolean hasSign = false;
public static void signApp(String nonce){ public static void signApp(String nonce){
ExampleApplication.Companion.ethSign(nonce,ExampleApplication.session.approvedAccounts().get(0)); Log.i(TAG, nonce);
List<String> paramList = new ArrayList<>();
paramList.add(ExampleApplication.session.approvedAccounts().get(0));
paramList.add(nonce);
long time = System.currentTimeMillis();
ExampleApplication.session.performMethodCall(new Session.MethodCall.Custom(time, "eth_signTypedData", paramList), new Function1<Session.MethodCall.Response, Unit>() {
@Override
public Unit invoke(Session.MethodCall.Response resp) {
if (resp.getResult() != null) {
String signStr = Objects.requireNonNull(resp.getResult()).toString();
Log.i(TAG, signStr);
signStr = signStr.substring(2);
Cocos2dxJavascriptJavaBridge.evalString("window.signApp(\"" + signStr + "\")");
} else {
Log.i(TAG, "sign is empty");
}
return null;
}
}
);
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("wc://")); i.setData(Uri.parse("metamask://"));
appActivity.startActivity(i); appActivity.startActivity(i);
} }
public static void changeArea(String area){ public static void changeArea(String area){
api.unInit(); // api.unInit();
System.out.println("change area"+area); System.out.println("change area"+area);
switch (area){ switch (area){
case "0": case "0":
@ -298,57 +328,125 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
@Override @Override
public void onMethodCall(@NotNull final Session.MethodCall methodCall) { public void onMethodCall(@NotNull final Session.MethodCall methodCall) {
if(ExampleApplication.session.approvedAccounts()!=null){
if(methodCall.id()==999999999){
//签名
appActivity.runOnGLThread(new Runnable() {
@Override
public void run() {
String tmp = ExampleApplication.Companion.getSignRes().substring(2);
System.out.println("签名无前缀"+tmp);
Cocos2dxJavascriptJavaBridge.evalString("window.signApp(\""+tmp+"\")");
}
});
}else{
//连接钱包
appActivity.runOnGLThread(new Runnable() {
@Override
public void run() {
String result = methodCall.toString();
String[] allRes = result.split(",");
for(int i=0;i<allRes.length;i++){
//删除所有空格
String tt = allRes[i].replaceAll("\\s+", "");
if(tt.startsWith("chainId")){
String[] fi = tt.split("=");
int chainid = Double.valueOf(fi[1]).intValue();
System.out.println("chainid---"+chainid);
if(chainid==321){
// 链正确
String tmp = ExampleApplication.session.approvedAccounts().get(0).substring(2);
Cocos2dxJavascriptJavaBridge.evalString("window.connectOK(\""+tmp+"\")");
}else{
// 链不正确
runOnUiThread(new Runnable() {
public void run() {
final Toast toast = Toast.makeText(appActivity, "Your wallet should support KCC chain" , Toast.LENGTH_SHORT);
toast.show();
}
});
}
}
}
}
});
}
}
} }
@Override @Override
public void onStatus(@NotNull Session.Status status) { public void onStatus(@NotNull Session.Status status) {
if (status.equals(Session.Status.Approved.INSTANCE)) {
sessionApproved();
} else if (status.equals(Session.Status.Closed.INSTANCE)){
Log.i(TAG, "Session.Status.Closed");
} else if (status.equals(Session.Status.Connected.INSTANCE)) {
Log.i(TAG, "Session.Status.Connected");
} else if (status.equals(Session.Status.Disconnected.INSTANCE)) {
Log.i(TAG, "Session.Status.Disconnected");
} else {
// ERROR
Log.e(TAG, "Session.Status.Error: " + status.toString());
}
}
private void showErrMsg(String msg) {
runOnGLThread(new Runnable() {
@Override
public void run() {
// Cocos2dxJavascriptJavaBridge.evalString("window.changeChain()");
}
});
}
private void sessionApproved() {
List<String> accounts = ExampleApplication.session.approvedAccounts();
if (accounts == null || accounts.size() == 0) {
showErrMsg("Login failed, no account");
return;
}
String account = accounts.get(0);
if ("".equals(account) || null == account || account.length() < 2) {
showErrMsg("Login failed, no account");
return;
}
Long chainId = ((JWCSession) ExampleApplication.session).chainId();
if (chainId == null) {
showErrMsg("Login failed, no chainId");
return;
}
if (chainId != 321L) {
appActivity.runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxJavascriptJavaBridge.evalString("window.chainErr()");
}
});
return;
}
final String account2 = account.substring(2);
appActivity.runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxJavascriptJavaBridge.evalString("window.connectOK(\""+account2+"\")");
}
});
} }
/**
*
* @param paramStr json str from bottom:
* * {
* chainId: "0x141",
* chainName: "KCC Mainnet",
* nativeCurrency: {
* name: "kccToken",
* symbol: "KCS",
* decimals: 18
* },
* blockExplorerUrls: ["https://explorer.kcc.io/en"],
* rpcUrls: ["https://rpc-mainnet.kcc.network"]
* }
*/
public static void addChain(String paramStr){
final Moshi moshi = new Moshi.Builder().build();
JsonAdapter<ChainObj> jsonAdapter = moshi.adapter(ChainObj.class);
try {
final ChainObj chainObj = jsonAdapter.fromJson(paramStr);
Log.i(TAG, chainObj.toString());
List paramList = new ArrayList<>();
paramList.add(chainObj);
Log.i(TAG, "addChain");
long time = System.currentTimeMillis();
ExampleApplication.session.performMethodCall(new Session.MethodCall.Custom(time, "eth_signTypedData", paramList), new Function1<Session.MethodCall.Response, Unit>() {
@Override
public Unit invoke(Session.MethodCall.Response resp) {
CallResult result = new CallResult("addChain");
if (resp.getError() != null) {
Log.i(TAG, "change chain with error: " + resp.getError());
result.setErrCode(resp.getError().getCode());
result.setErrMsg(resp.getError().getMessage());
} else if (resp.getResult() != null && resp.getResult().toString().equals("success")) {
Log.i(TAG, "change chain success: " + chainObj.getChainId());
result.setErrCode(0L);
result.setErrMsg("");
result.setData(chainObj.getChainId());
} else {
Log.i(TAG, "change chain fail");
result.setErrCode(9528L);
result.setErrMsg("unknown error");
}
JsonAdapter<CallResult> resultAdapter = moshi.adapter(CallResult.class);
return null;
}
}
);
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("metamask://"));
appActivity.startActivity(i);
} catch(IOException e) {
Log.i(TAG, e.toString());
}
}
} }

View File

@ -0,0 +1,250 @@
package org.cocos2dx.javascript.wc
import com.squareup.moshi.Json
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.engines.AESEngine
import org.bouncycastle.crypto.macs.HMac
import org.bouncycastle.crypto.modes.CBCBlockCipher
import org.bouncycastle.crypto.paddings.PKCS7Padding
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.params.ParametersWithIV
import org.komputing.khex.decode
import org.komputing.khex.extensions.toNoPrefixHexString
import org.walletconnect.Session
import org.walletconnect.types.*
import java.security.SecureRandom
import java.util.concurrent.ConcurrentHashMap
class JMoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
private val payloadAdapter = moshi.adapter(EncryptedPayload::class.java)
private val mapAdapter = moshi.adapter<Map<String, Any?>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
private val requests: MutableMap<Long, Session.MethodCall> = ConcurrentHashMap()
private fun createRandomBytes(i: Int) = ByteArray(i).also { SecureRandom().nextBytes(it) }
override fun parse(payload: String, key: String): Session.MethodCall {
val encryptedPayload = payloadAdapter.fromJson(payload) ?: throw IllegalArgumentException("Invalid json payload!")
// TODO verify hmac
val padding = PKCS7Padding()
val aes = PaddedBufferedBlockCipher(
CBCBlockCipher(AESEngine()),
padding
)
val ivAndKey = ParametersWithIV(
KeyParameter(decode(key)),
decode(encryptedPayload.iv)
)
aes.init(false, ivAndKey)
val encryptedData = decode(encryptedPayload.data)
val minSize = aes.getOutputSize(encryptedData.size)
val outBuf = ByteArray(minSize)
var len = aes.processBytes(encryptedData, 0, encryptedData.size, outBuf, 0)
len += aes.doFinal(outBuf, len)
return outBuf.copyOf(len).toMethodCall()
}
override fun prepare(data: Session.MethodCall, key: String): String {
val bytesData = data.toBytes()
val hexKey = decode(key)
val iv = createRandomBytes(16)
val padding = PKCS7Padding()
val aes = PaddedBufferedBlockCipher(
CBCBlockCipher(AESEngine()),
padding
)
aes.init(true, ParametersWithIV(KeyParameter(hexKey), iv))
val minSize = aes.getOutputSize(bytesData.size)
val outBuf = ByteArray(minSize)
val length1 = aes.processBytes(bytesData, 0, bytesData.size, outBuf, 0)
aes.doFinal(outBuf, length1)
val hmac = HMac(SHA256Digest())
hmac.init(KeyParameter(hexKey))
val hmacResult = ByteArray(hmac.macSize)
hmac.update(outBuf, 0, outBuf.size)
hmac.update(iv, 0, iv.size)
hmac.doFinal(hmacResult, 0)
requests[data.id()] = data
return payloadAdapter.toJson(
EncryptedPayload(
outBuf.toNoPrefixHexString(),
hmac = hmacResult.toNoPrefixHexString(),
iv = iv.toNoPrefixHexString()
)
)
}
/**
* Convert FROM request bytes
*/
private fun ByteArray.toMethodCall(): Session.MethodCall =
String(this).let { json ->
mapAdapter.fromJson(json)?.let {
try {
var method = it["method"]
when (method) {
"wc_sessionRequest" -> it.toSessionRequest()
"wc_sessionUpdate" -> it.toSessionUpdate()
"eth_sendTransaction" -> it.toSendTransaction()
"eth_sign" -> it.toSignMessage()
null -> it.toResponse()
else -> it.toCustom()
}
} catch (e: Exception) {
throw Session.MethodCallException.InvalidRequest(it.getId(), "$json (${e.message ?: "Unknown error"})")
}
} ?: throw IllegalArgumentException("Invalid json")
}
private fun Map<String, *>.toSessionUpdate(): Session.MethodCall.SessionUpdate {
val params = this["params"] as? List<*> ?: throw IllegalArgumentException("params missing")
val data = params.firstOrNull() as? Map<String, *> ?: throw IllegalArgumentException("Invalid params")
return Session.MethodCall.SessionUpdate(
getId(),
data.extractSessionParams()
)
}
private fun Map<String, *>.toSendTransaction(): Session.MethodCall.SendTransaction {
val params = this["params"] as? List<*> ?: throw IllegalArgumentException("params missing")
val data = params.firstOrNull() as? Map<*, *> ?: throw IllegalArgumentException("Invalid params")
val from = data["from"] as? String ?: throw IllegalArgumentException("from key missing")
val to = data["to"] as? String ?: throw IllegalArgumentException("to key missing")
val nonce = data["nonce"] as? String ?: (data["nonce"] as? Double)?.toLong()?.toString()
val gasPrice = data["gasPrice"] as? String
val gasLimit = data["gasLimit"] as? String
val value = data["value"] as? String ?: "0x0"
val txData = data["data"] as? String ?: throw IllegalArgumentException("data key missing")
return Session.MethodCall.SendTransaction(getId(), from, to, nonce, gasPrice, gasLimit, value, txData)
}
private fun Map<String, *>.toSignMessage(): Session.MethodCall.SignMessage {
val params = this["params"] as? List<*> ?: throw IllegalArgumentException("params missing")
val address = params.getOrNull(0) as? String ?: throw IllegalArgumentException("Missing address")
val message = params.getOrNull(1) as? String ?: throw IllegalArgumentException("Missing message")
return Session.MethodCall.SignMessage(getId(), address, message)
}
private fun Map<String, *>.toCustom(): Session.MethodCall.Custom {
val method = this["method"] as? String ?: throw IllegalArgumentException("method missing")
val params = this["params"] as? List<*>
return Session.MethodCall.Custom(getId(), method, params)
}
private fun Map<String, *>.toResponse(): Session.MethodCall.Response {
var result = this["result"]
val error = this["error"] as? Map<*, *>
if (result == null && error == null) {
var hasErr = true
if (requests[getId()] != null) {
when (val localData: Session.MethodCall? = requests[getId()]) {
is Session.MethodCall.Custom -> {
if (localData.method == "wallet_switchEthereumChain" || localData.method == "wallet_addEthereumChain") {
hasErr = false
result = "success"
}
}
}
requests.remove(getId())
}
if (hasErr) {
throw IllegalArgumentException("no result or error")
}
}
return Session.MethodCall.Response(
getId(),
result,
error?.extractError()
)
}
/**
* Convert INTO request bytes
*/
private fun Session.MethodCall.toBytes() =
mapAdapter.toJson(
when (this) {
is Session.MethodCall.SessionRequest -> this.toMap()
is Session.MethodCall.Response -> this.toMap()
is Session.MethodCall.SessionUpdate -> this.toMap()
is Session.MethodCall.SendTransaction -> this.toMap()
is Session.MethodCall.SignMessage -> this.toMap()
is Session.MethodCall.Custom -> this.toMap()
}
).toByteArray()
private fun Session.MethodCall.SessionRequest.toMap() =
jsonRpc(id, "wc_sessionRequest", peer.intoMap())
private fun Session.MethodCall.SessionUpdate.toMap() =
jsonRpc(id, "wc_sessionUpdate", params.intoMap())
private fun Session.MethodCall.SendTransaction.toMap() =
jsonRpc(
id, "eth_sendTransaction", mapOf(
"from" to from,
"to" to to,
"nonce" to nonce,
"gasPrice" to gasPrice,
"gasLimit" to gasLimit,
"value" to value,
"data" to data
)
)
private fun Session.MethodCall.SignMessage.toMap() =
jsonRpc(
id, "eth_sign", address, message
)
private fun Session.MethodCall.Response.toMap() =
mutableMapOf<String, Any>(
"id" to id,
"jsonrpc" to "2.0"
).apply {
result?.let { this["result"] = result!! }
error?.let { this["error"] = error!!.intoMap() }
}
private fun Session.MethodCall.Custom.toMap() =
jsonRpcWithList(
id, method, params ?: emptyList<Any>()
)
private fun jsonRpc(id: Long, method: String, vararg params: Any) =
jsonRpcWithList(id, method, params.asList())
private fun jsonRpcWithList(id: Long, method: String, params: List<*>) =
mapOf(
"id" to id,
"jsonrpc" to "2.0",
"method" to method,
"params" to params
)
// TODO: @JsonClass(generateAdapter = true)
data class EncryptedPayload(
@Json(name = "data") val data: String,
@Json(name = "iv") val iv: String,
@Json(name = "hmac") val hmac: String
)
}

View File

@ -0,0 +1,297 @@
package org.cocos2dx.javascript.wc
import android.util.Log
import org.walletconnect.Session
import org.walletconnect.impls.WCSessionStore
import org.walletconnect.nullOnThrow
import org.walletconnect.types.intoMap
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class JWCSession(
private val config: Session.Config,
private val payloadAdapter: Session.PayloadAdapter,
private val sessionStore: WCSessionStore,
transportBuilder: Session.Transport.Builder,
clientMeta: Session.PeerMeta,
clientId: String? = null
) : Session {
private val keyLock = Any()
// Persisted state
private var currentKey: String
private var approvedAccounts: List<String>? = null
private var chainId: Long? = null
private var handshakeId: Long? = null
private var peerId: String? = null
private var peerMeta: Session.PeerMeta? = null
private val clientData: Session.PeerData
// Getters
private val encryptionKey: String
get() = currentKey
private val decryptionKey: String
get() = currentKey
// Non-persisted state
private val transport = transportBuilder.build(config.bridge, ::handleStatus, ::handleMessage)
private val requests: MutableMap<Long, (Session.MethodCall.Response) -> Unit> = ConcurrentHashMap()
private val sessionCallbacks: MutableSet<Session.Callback> = Collections.newSetFromMap(ConcurrentHashMap<Session.Callback, Boolean>())
init {
currentKey = config.key
clientData = sessionStore.load(config.handshakeTopic)?.let {
currentKey = it.currentKey
approvedAccounts = it.approvedAccounts
chainId = it.chainId
handshakeId = it.handshakeId
peerId = it.peerData?.id
peerMeta = it.peerData?.meta
if (clientId != null && clientId != it.clientData.id)
throw IllegalArgumentException("Provided clientId is different from stored clientId")
it.clientData
} ?: run {
Session.PeerData(clientId ?: UUID.randomUUID().toString(), clientMeta)
}
storeSession()
}
override fun addCallback(cb: Session.Callback) {
sessionCallbacks.add(cb)
}
override fun removeCallback(cb: Session.Callback) {
sessionCallbacks.remove(cb)
}
override fun clearCallbacks() {
sessionCallbacks.clear()
}
private fun propagateToCallbacks(action: Session.Callback.() -> Unit) {
sessionCallbacks.forEach {
try { it.action() }
catch (t: Throwable) {
// If error propagation fails, don't try again
nullOnThrow { it.onStatus(Session.Status.Error(t)) }
}
}
}
override fun peerMeta(): Session.PeerMeta? = peerMeta
override fun approvedAccounts(): List<String>? = approvedAccounts
fun chainId(): Long? = chainId
override fun init() {
if (transport.connect()) {
// Register for all messages for this client
transport.send(
Session.Transport.Message(
config.handshakeTopic, "sub", ""
)
)
}
}
override fun offer() {
if (transport.connect()) {
val requestId = createCallId()
send(Session.MethodCall.SessionRequest(requestId, clientData), topic = config.handshakeTopic, callback = { resp ->
// BUG in extractSessionParams, cast Double to Long error
(resp.result as? Map<String, *>)?.extractSessionParams()?.let { params ->
peerId = params.peerData?.id
peerMeta = params.peerData?.meta
approvedAccounts = params.accounts
chainId = params.chainId
storeSession()
propagateToCallbacks { onStatus(if (params.approved) Session.Status.Approved else Session.Status.Closed) }
}
})
handshakeId = requestId
}
}
override fun approve(accounts: List<String>, chainId: Long) {
val handshakeId = handshakeId ?: return
approvedAccounts = accounts
this.chainId = chainId
// We should not use classes in the Response, since this will not work with proguard
val params = Session.SessionParams(true, chainId, accounts, clientData).intoMap()
send(Session.MethodCall.Response(handshakeId, params))
storeSession()
propagateToCallbacks { onStatus(Session.Status.Approved) }
}
override fun update(accounts: List<String>, chainId: Long) {
val params = Session.SessionParams(true, chainId, accounts, clientData)
send(Session.MethodCall.SessionUpdate(createCallId(), params))
}
override fun reject() {
handshakeId?.let {
// We should not use classes in the Response, since this will not work with proguard
val params = Session.SessionParams(false, null, null, null).intoMap()
send(Session.MethodCall.Response(it, params))
}
endSession()
}
override fun approveRequest(id: Long, response: Any) {
send(Session.MethodCall.Response(id, response))
}
override fun rejectRequest(id: Long, errorCode: Long, errorMsg: String) {
send(
Session.MethodCall.Response(
id,
result = null,
error = Session.Error(errorCode, errorMsg)
)
)
}
override fun performMethodCall(call: Session.MethodCall, callback: ((Session.MethodCall.Response) -> Unit)?) {
send(call, callback = callback)
}
private fun handleStatus(status: Session.Transport.Status) {
when (status) {
Session.Transport.Status.Connected -> {
// Register for all messages for this client
transport.send(
Session.Transport.Message(
clientData.id, "sub", ""
)
)
}
}
propagateToCallbacks {
onStatus(when(status) {
Session.Transport.Status.Connected -> Session.Status.Connected
Session.Transport.Status.Disconnected -> Session.Status.Disconnected
is Session.Transport.Status.Error -> Session.Status.Error(Session.TransportError(status.throwable))
})
}
}
private fun handleMessage(message: Session.Transport.Message) {
if (message.type != "pub") return
val data: Session.MethodCall
synchronized(keyLock) {
try {
data = payloadAdapter.parse(message.payload, decryptionKey)
} catch (e: Exception) {
handlePayloadError(e)
return
}
}
var accountToCheck: String? = null
Log.i("JWCSession", "handle msg: $data")
when (data) {
is Session.MethodCall.SessionRequest -> {
handshakeId = data.id
peerId = data.peer.id
peerMeta = data.peer.meta
storeSession()
}
is Session.MethodCall.SessionUpdate -> {
if (!data.params.approved) {
endSession()
} else {
chainId = data.params.chainId
}
// TODO handle session update -> not important for our usecase
}
is Session.MethodCall.SendTransaction -> {
accountToCheck = data.from
}
is Session.MethodCall.SignMessage -> {
accountToCheck = data.address
}
is Session.MethodCall.Response -> {
val callback = requests[data.id] ?: return
callback(data)
}
}
if (accountToCheck?.let { accountCheck(data.id(), it) } != false) {
propagateToCallbacks { onMethodCall(data) }
}
}
private fun accountCheck(id: Long, address: String): Boolean {
approvedAccounts?.find { it.toLowerCase() == address.toLowerCase() } ?: run {
handlePayloadError(Session.MethodCallException.InvalidAccount(id, address))
return false
}
return true
}
private fun handlePayloadError(e: Exception) {
propagateToCallbacks { Session.Status.Error(e) }
(e as? Session.MethodCallException)?.let {
rejectRequest(it.id, it.code, it.message ?: "Unknown error")
}
}
private fun endSession() {
sessionStore.remove(config.handshakeTopic)
approvedAccounts = null
chainId = null
internalClose()
propagateToCallbacks { onStatus(Session.Status.Closed) }
}
private fun storeSession() {
sessionStore.store(
config.handshakeTopic,
WCSessionStore.State(
config,
clientData,
peerId?.let { Session.PeerData(it, peerMeta) },
handshakeId,
currentKey,
approvedAccounts,
chainId
)
)
}
// Returns true if method call was handed over to transport
private fun send(
msg: Session.MethodCall,
topic: String? = peerId,
callback: ((Session.MethodCall.Response) -> Unit)? = null
): Boolean {
topic ?: return false
Log.i("JWCSession", "sendMsg: $msg.id()")
val payload: String
synchronized(keyLock) {
payload = payloadAdapter.prepare(msg, encryptionKey)
}
callback?.let {
requests[msg.id()] = callback
}
transport.send(Session.Transport.Message(topic, "pub", payload))
return true
}
private fun createCallId() = System.currentTimeMillis() * 1000 + Random().nextInt(999)
private fun internalClose() {
transport.close()
}
override fun kill() {
val params = Session.SessionParams(false, null, null, null)
send(Session.MethodCall.SessionUpdate(createCallId(), params))
endSession()
}
}

View File

@ -0,0 +1,15 @@
package org.cocos2dx.javascript.wc
import org.walletconnect.Session
import org.walletconnect.nullOnThrow
import org.walletconnect.types.extractPeerData
import org.walletconnect.types.toStringList
fun Map<String, *>.extractSessionParams(): Session.SessionParams {
val approved = this["approved"] as? Boolean ?: throw IllegalArgumentException("approved missing")
val chainId = (this["chainId"] as? Double)?.toLong()
val accounts = nullOnThrow { (this["accounts"] as? List<*>)?.toStringList() }
return Session.SessionParams(approved, chainId, accounts, nullOnThrow { this.extractPeerData() })
}

View File

@ -0,0 +1,44 @@
package org.cocos2dx.javascript.wc.entity;
public class CallResult {
private String type;
private long errCode;
private String errMsg;
private String data;
public CallResult(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public long getErrCode() {
return errCode;
}
public void setErrCode(long errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View File

@ -0,0 +1,49 @@
package org.cocos2dx.javascript.wc.entity;
public class ChainObj {
private String chainId;
private String chainName;
private String[] blockExplorerUrls;
private String[] rpcUrls;
private CurrencyObj nativeCurrency;
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getChainName() {
return chainName;
}
public void setChainName(String chainName) {
this.chainName = chainName;
}
public String[] getBlockExplorerUrls() {
return blockExplorerUrls;
}
public void setBlockExplorerUrls(String[] blockExplorerUrls) {
this.blockExplorerUrls = blockExplorerUrls;
}
public String[] getRpcUrls() {
return rpcUrls;
}
public void setRpcUrls(String[] rpcUrls) {
this.rpcUrls = rpcUrls;
}
public CurrencyObj getNativeCurrency() {
return nativeCurrency;
}
public void setNativeCurrency(CurrencyObj nativeCurrency) {
this.nativeCurrency = nativeCurrency;
}
}

View File

@ -0,0 +1,13 @@
package org.cocos2dx.javascript.wc.entity;
public class ChangeChainObj {
private String chainId;
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
}

View File

@ -0,0 +1,31 @@
package org.cocos2dx.javascript.wc.entity;
public class CurrencyObj {
private String name;
private String symbol;
private long decimals;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public long getDecimals() {
return decimals;
}
public void setDecimals(long decimals) {
this.decimals = decimals;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"packageUrl":"https://www.cebg.games/pubgv4/","remoteManifestUrl":"https://www.cebg.games/pubgv4/project.manifest","remoteVersionUrl":"https://www.cebg.games/pubgv4/version.manifest","version":"0.5.0"} {"packageUrl":"https://www.cebg.games/pubgv4/","remoteManifestUrl":"https://www.cebg.games/pubgv4/project.manifest","remoteVersionUrl":"https://www.cebg.games/pubgv4/version.manifest","version":"0.6.0"}