updater
This commit is contained in:
parent
27da69f1d2
commit
6c4d382625
@ -47,8 +47,8 @@
|
||||
},
|
||||
"_scale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.805834949016571,
|
||||
"y": 0.805834949016571,
|
||||
"x": 0.9920991955598369,
|
||||
"y": 0.9920991955598369,
|
||||
"z": 1
|
||||
},
|
||||
"_quat": {
|
||||
@ -13797,7 +13797,7 @@
|
||||
"__id__": 411
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_active": true,
|
||||
"_level": 2,
|
||||
"_components": [
|
||||
{
|
||||
|
File diff suppressed because one or more lines are too long
@ -24,34 +24,42 @@
|
||||
****************************************************************************/
|
||||
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.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
import android.net.Uri;
|
||||
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.YouMeCallBackInterface;
|
||||
import com.youme.voiceengine.YouMeConst;
|
||||
import com.youme.voiceengine.api;
|
||||
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.Objects;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterface , Session.Callback {
|
||||
private static AppActivity appActivity = null;
|
||||
private static final String TAG = "AppActivity";
|
||||
private static final String appKey="YOUME1838B3633FF1410BDC9124BBD806F245B9D2E5AC";
|
||||
private static final String appSecret="q6B570yTyj/00Nk4mYZtgDwyew5v05t13V1vo4mxpEuAaWUiinAyVxG41sNu3vsFe8sipOLfKfIVYGhzpQrqzvj5sId3mrBfj/s65a2gp36yDrI/nX5BnUAJB317SEosR6xLoPuhBvHU+/1DWI7nKSKaRNxnQiC46PJKFc2kX50BAAE=";
|
||||
|
||||
@ -182,41 +190,41 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
|
||||
|
||||
switch (eventType) {
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_OK: //YOUME_EVENT_INIT_OK:
|
||||
System.out.println("Talk 初始化成功");
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_FAILED://YOUME_EVENT_INIT_FAILED:
|
||||
System.out.println("Talk 初始化失败");
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_OK://YOUME_EVENT_JOIN_OK:
|
||||
System.out.println("Talk 进入频道成功,频道:" + channelID + " 用户id:" + param);
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_FAILED://YOUME_EVENT_JOIN_FAILED:
|
||||
System.out.println("Talk 进入频道:" + channelID + "失败,code:" + errorCode);
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ONE://YOUME_EVENT_LEAVED_ONE:
|
||||
System.out.println("Talk 离开单个频道:" + channelID);
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ALL://YOUME_EVENT_LEAVED_ALL:
|
||||
System.out.println("Talk 离开所有频道,这个回调channel参数为空字符串");
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_PAUSED://YOUME_EVENT_PAUSED:
|
||||
System.out.println("Talk 暂停");
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_RESUMED://YOUME_EVENT_RESUMED:
|
||||
System.out.println("Talk 恢复");
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_SUCCESS://YOUME_EVENT_SPEAK_SUCCESS:///< 切换对指定频道讲话成功(适用于多频道模式)
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_SUCCESS://YOUME_EVENT_SPEAK_SUCCESS:
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_FAILED://YOUME_EVENT_SPEAK_FAILED:///< 切换对指定频道讲话失败(适用于多频道模式)
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_FAILED://YOUME_EVENT_SPEAK_FAILED://
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTING://YOUME_EVENT_RECONNECTING:///< 断网了,正在重连
|
||||
System.out.println("Talk 正在重连");
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTING://YOUME_EVENT_RECONNECTING:/
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTED://YOUME_EVENT_RECONNECTED:///< 断网重连成功
|
||||
System.out.println("Talk 重连成功");
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTED://YOUME_EVENT_RECONNECTED://
|
||||
|
||||
break;
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_REC_PERMISSION_STATUS://YOUME_EVENT_REC_FAILED:///< 通知录音启动失败(此时不管麦克风mute状态如何,都没有声音输出)
|
||||
System.out.println("录音启动失败,code:" + errorCode);
|
||||
case YouMeConst.YouMeEvent.YOUME_EVENT_REC_PERMISSION_STATUS://YOUME_EVENT_REC_FAILED://
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -249,9 +257,11 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
|
||||
public static void connectwallet(String data){
|
||||
ExampleApplication.Companion.resetSession();
|
||||
ExampleApplication.session.addCallback(appActivity);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(ExampleApplication.config.toWCUri()));
|
||||
System.out.println(ExampleApplication.config.toWCUri());
|
||||
String url = "metamask://wc?uri=" + ExampleApplication.config.toWCUri();
|
||||
// String url = "https://metamask.app.link/wc?uri="+ExampleApplication.config.toWCUri();
|
||||
Uri uri = Uri.parse(url);
|
||||
Log.i(TAG, url);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
try {
|
||||
appActivity.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
@ -264,15 +274,35 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
|
||||
// private boolean hasSign = false;
|
||||
|
||||
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);
|
||||
i.setData(Uri.parse("wc://"));
|
||||
i.setData(Uri.parse("metamask://"));
|
||||
appActivity.startActivity(i);
|
||||
}
|
||||
|
||||
|
||||
public static void changeArea(String area){
|
||||
api.unInit();
|
||||
// api.unInit();
|
||||
System.out.println("change area"+area);
|
||||
switch (area){
|
||||
case "0":
|
||||
@ -298,57 +328,125 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf
|
||||
|
||||
@Override
|
||||
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
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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() })
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
@ -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"}
|
Loading…
x
Reference in New Issue
Block a user