diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/README.md b/acb-sdk/pluginset/dioxide/offchain-plugin/README.md
new file mode 100644
index 00000000..cc7b34f3
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/README.md
@@ -0,0 +1,59 @@
+
+

+
Dioxide Plugin
+
+
+
+
+
+
+
+
+
+| 说明 | 链接 |
+|-------------------|-----------------|
+| ⭐️ 参考的dioxid_python_sdk | [dioxide_python](https://github.com/1220292040/dioxide_python) |
+| ✅ 测试通过的 dioxide | 2025.11.18更新版本,可联系dioxid_python仓库的Contributors获取 |
+
+# 介绍
+
+在本路径之下,实现了dioxide的异构链接入插件,包括链下插件及链上插件部分
+
+- **offchain-plugin**:链下插件,使用maven管理的Java工程,使用jdk1.21和maven编译即可。基于[dioxide_python](https://github.com/1220292040/dioxide_python)开发,将必要的python_sdk逻辑转换为java_sdk逻辑,在团队提供的2025.11.18版本的链节点上测试通过。
+- **onchain-plugin**:链上插件,使GCL语言开发,实现逻辑参考了ethereum2的链上插件。
+
+# 用法
+
+## 构建
+
+在offchain-plugin下通过`mvn clean package -DskipTests`编译插件Jar包,可以在target下找到`dioxide-acb-plugin-1.0.0-plugin.jar`
+
+## 使用
+
+参考[插件服务](https://github.com/AntChainOpenLab/AntChainBridgePluginServer/blob/main/README.md)(PluginServer, PS)的使用,将Jar包放到指定路径,通过PS加载即可。
+
+### 配置文件
+
+当在AntChainBridge的Relayer服务注册dioxide时,需要指定PS和链类型(dioxide),同时需要提交一个dioxide链的配置。
+
+dioxide链的配置文件`dioxide.json`主要包括节点网络连接信息和用户信息,配置文件大致如下:
+
+```json
+{
+ "rpcUrl": "http://127.0.0.1:62222/api",
+ "wsRpc": "ws://127.0.0.1:62222/api",
+ "privateKey": "WTKi+W99TEEt153Zt8isUznwXqYkA0aVWEbd7edk6AvivGov5hBLJLQbS2hk8bnC3FM8Et6+Axaw1uukce+ZEQ==",
+ "dappName": "ict001",
+ "isPreContractDeployed": false
+}
+```
+
+- rpcUrl:dioxide链的HTTP协议的JSON-RPC接口地址
+- wsPrc:dioxide链的WebSocket协议的JSON-RPC接口地址
+- privateKey:base64格式的用户私钥,样例详见[dioxide_python](https://github.com/1220292040/dioxide_python)给出的account_test.py
+- dappName:dapp是dioxide链上承载智能合约的载体,智能合约只能部署在某一个dapp地址下,一个dapp中可以部署多个智能合约,dapp地址的组成形式为:其中,dapp名字长度限制在4-8内,上述例子中"ict001"就是为长度为6的dapp_name
+- isPreContractDeployed:部署跨链系统合约(AM合约和SDP合约)时,由于两个合约需要依赖一些自编写的库合约作为前置合约先部署上去,所以该字段一般填false
+
+
+# 注意事项
+- 目前仅支持SDPv1消息版本和跨链消息传输的基本功能,基于PTC的跨链消息验证功能有待开发。
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/pom.xml b/acb-sdk/pluginset/dioxide/offchain-plugin/pom.xml
new file mode 100644
index 00000000..d580759b
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/pom.xml
@@ -0,0 +1,141 @@
+
+
+ 4.0.0
+ com.alipay.antchain.bridge
+ dioxide-acb-plugin
+ 1.0.0
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-plugin-lib
+ 1.0.0-SNAPSHOT
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-spi
+ 1.0.0-SNAPSHOT
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ compile
+
+
+ com.alibaba
+ fastjson
+ 1.2.83
+
+
+ cn.hutool
+ hutool-all
+ 5.8.20
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.78.1
+
+
+ black.ninia
+ jep
+ 4.2.2
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.13
+ test
+
+
+ com.google.cloud
+ google-cloud-storage
+ 2.38.0
+
+
+ com.alibaba
+ fastjson
+ 1.2.83_noneautotype
+ compile
+
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ generate-gcl-java
+
+ process-classes
+
+ java
+
+
+
+ com.alipay.antchain.bridge.plugins.dioxide.gcl.GclCodegen
+
+
+
+ true
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+ ${project.artifactId}-${project.version}-plugin
+ false
+ false
+
+
+ true
+ true
+
+
+ com.alipay.antchain.bridge.plugins.lib.pf4j.DefaultPlugin
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCService.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCService.java
new file mode 100644
index 00000000..0d532a23
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCService.java
@@ -0,0 +1,426 @@
+package com.alipay.antchain.bridge.plugins.dioxide;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.AuthMessageContract;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTypeEnum;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCVerifyAnchor;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.core.rcc.ReliableCrossChainMessage;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideConfig;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideTypes;
+import com.alipay.antchain.bridge.plugins.dioxide.core.DioxideClient;
+import com.alipay.antchain.bridge.plugins.dioxide.core.DioxideTransaction;
+import com.alipay.antchain.bridge.plugins.lib.BBCService;
+import com.alipay.antchain.bridge.plugins.spi.bbc.AbstractBBCService;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+
+@BBCService(products = "dioxide", pluginId = "plugin-dioxide")
+@Getter
+public class DioxideBBCService extends AbstractBBCService {
+
+ private DioxideConfig config;
+
+ private AbstractBBCContext bbcContext;
+
+ @Setter
+ private DioxideClient dioxideClient;
+
+ @Override
+ public void startup(AbstractBBCContext abstractBBCContext) {
+ getBBCLogger().info("Dioxide BBCService startup with context: {}",
+ new String(abstractBBCContext.getConfForBlockchainClient()));
+
+ if (ObjectUtil.isNull(abstractBBCContext)) {
+ throw new RuntimeException("null bbc context");
+ }
+ if (ObjectUtil.isEmpty(abstractBBCContext.getConfForBlockchainClient())) {
+ throw new RuntimeException("empty blockchain client conf");
+ }
+
+ // 1. Obtain the configuration information
+ try
+ {
+ config = DioxideConfig.fromJsonString(new String(abstractBBCContext.getConfForBlockchainClient()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (ObjectUtil.isEmpty(config.getPrivateKey())) {
+ throw new RuntimeException("private key is empty");
+ }
+
+ if (StrUtil.isEmpty(config.getRpcUrl()) || StrUtil.isEmpty(config.getWsRpc())) {
+ throw new RuntimeException("dioxide url is empty");
+ }
+
+ // 6. set context
+ this.bbcContext = abstractBBCContext;
+
+ this.dioxideClient = new DioxideClient(config, getBBCLogger());
+
+ // 7. set the pre-deployed contracts into context
+ if (ObjectUtil.isNull(abstractBBCContext.getAuthMessageContract())
+ && StrUtil.isNotEmpty(this.config.getAmContractAddressDeployed())) {
+ AuthMessageContract authMessageContract = new AuthMessageContract();
+ authMessageContract.setContractAddress(this.config.getAmContractAddressDeployed());
+ authMessageContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ this.bbcContext.setAuthMessageContract(authMessageContract);
+ }
+
+ if (ObjectUtil.isNull(abstractBBCContext.getSdpContract())
+ && StrUtil.isNotEmpty(this.config.getSdpContractAddressDeployed())) {
+ SDPContract sdpContract = new SDPContract();
+ sdpContract.setContractAddress(this.config.getSdpContractAddressDeployed());
+ sdpContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ this.bbcContext.setSdpContract(sdpContract);
+ }
+
+ }
+
+ @Override
+ public void shutdown() {
+ getBBCLogger().info("shut down DIOXIDE BBCService!");
+ dioxideClient.shutdown();
+ }
+
+ @Override
+ public AbstractBBCContext getContext() {
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+
+ getBBCLogger().debug("Dioxide BBCService context (amAddr: {}, amStatus: {}, sdpAddr: {}, sdpStatus: {})",
+ this.bbcContext.getAuthMessageContract() != null ? this.bbcContext.getAuthMessageContract().getContractAddress() : "",
+ this.bbcContext.getAuthMessageContract() != null ? this.bbcContext.getAuthMessageContract().getStatus() : "",
+ this.bbcContext.getSdpContract() != null ? this.bbcContext.getSdpContract().getContractAddress() : "",
+ this.bbcContext.getSdpContract() != null ? this.bbcContext.getSdpContract().getStatus() : ""
+ );
+
+ this.bbcContext.setConfForBlockchainClient(this.config.toJsonString().getBytes(StandardCharsets.UTF_8));
+ return this.bbcContext;
+ }
+
+ // NOTICE: for the databases in relayer can only store 32 bytes tx hash, interface [relayAuthMessage] will adopt send ucp in a synchronous way.
+ // In this case, interface [readCrossChainMessageReceipt] will not be used.
+ @Override
+ public CrossChainMessageReceipt readCrossChainMessageReceipt(String txHash) {
+ DioxideTransaction dioxideTransaction = dioxideClient.getTransactionByHash(txHash);
+
+ getBBCLogger().info("[readCrossChainMessageReceipt] tx:\n{}", JSON.toJSONString(dioxideTransaction, SerializerFeature.PrettyFormat));
+
+ // Construct cross-chain message receipt
+ CrossChainMessageReceipt crossChainMessageReceipt = dioxideClient.getCrossChainMessageReceipt(dioxideTransaction);
+ getBBCLogger().info("cross chain message receipt for txhash {} : {}", txHash, JSON.toJSONString(crossChainMessageReceipt));
+
+ return crossChainMessageReceipt;
+ }
+
+ @Override
+ public List readCrossChainMessagesByHeight(long height) {
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getAuthMessageContract())) {
+ throw new RuntimeException("empty am contract in bbc context");
+ }
+
+ try {
+ return dioxideClient.readAuthMessagesFromBlock(height);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to readCrossChainMessagesByHeight (height: %d, contractAddr: %s, topic: %s)",
+ height,
+ "AuthMsg",
+ "SENDAUTHMESSAGE_EVENT"
+ ), e
+ );
+ }
+ }
+
+ @Override
+ public Long queryLatestHeight() {
+ return dioxideClient.queryLatestHeight();
+ }
+
+ @Override
+ public void setupAuthMessageContract() {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNotNull(this.bbcContext.getAuthMessageContract())
+ && StrUtil.isNotEmpty(this.bbcContext.getAuthMessageContract().getContractAddress())) {
+ // If the contract has been pre-deployed and the contract address is configured in the configuration file,
+ // there is no need to redeploy.
+ return;
+ }
+
+ // 2. deploy contract
+ if (ObjectUtil.isNotNull(this.bbcContext.getSdpContract())
+ && StrUtil.isNotEmpty(this.bbcContext.getSdpContract().getContractAddress())) {
+ dioxideClient.getConfig().setIsPreContractDeployed(true);
+ }
+ long amContractCid = dioxideClient.deployAuthMsgContract();
+ AuthMessageContract authMessageContract = new AuthMessageContract();
+ authMessageContract.setContractAddress(String.valueOf(amContractCid));
+ authMessageContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ bbcContext.setAuthMessageContract(authMessageContract);
+
+ getBBCLogger().info("setup am contract successful: contract_name:{}.{}",
+ config.getDappName(), config.getAmContractName());
+ }
+
+ @Override
+ public void setupSDPMessageContract() {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNotNull(this.bbcContext.getSdpContract())
+ && StrUtil.isNotEmpty(this.bbcContext.getSdpContract().getContractAddress())) {
+ // If the contract has been pre-deployed and the contract address is configured in the configuration file,
+ // there is no need to redeploy.
+ return;
+ }
+
+ // 2. deploy contract
+ if (ObjectUtil.isNotNull(this.bbcContext.getAuthMessageContract())
+ && StrUtil.isNotEmpty(this.bbcContext.getAuthMessageContract().getContractAddress())) {
+ dioxideClient.getConfig().setIsPreContractDeployed(true);
+ }
+ long sdpContractCid = dioxideClient.deploySdpContract();
+
+ SDPContract sdpContract = new SDPContract();
+ sdpContract.setContractAddress(String.valueOf(sdpContractCid));
+ sdpContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ bbcContext.setSdpContract(sdpContract);
+ getBBCLogger().info("setup sdp contract successful: contract_name:{}.{}",
+ config.getDappName(), config.getSdpContractName());
+ }
+
+ @Override
+ public long querySDPMessageSeq(String senderDomain, String senderID, String receiverDomain, String receiverID) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getSdpContract())) {
+ throw new RuntimeException("empty sdp contract in bbc context");
+ }
+
+ return dioxideClient.querySdpSeq(senderDomain, senderID, receiverDomain, receiverID);
+ }
+
+ @Override
+ public void setProtocol(String protocolAddress, String protocolType) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getAuthMessageContract())) {
+ throw new RuntimeException("empty am contract in bbc context");
+ }
+
+ // arg protocolAddress: contract cid
+ dioxideClient.setProtocolToAuthMsg(protocolAddress, protocolType);
+
+ // 4. update am contract status
+ try {
+ JSONObject valState = dioxideClient.getContractState(this.config.getDappName(), config.getAmContractName(), DioxideTypes.Scope.Global, "")
+ .getJSONObject("State");
+ if (CollUtil.isNotEmpty(valState.getJSONObject("protocolRoutes"))
+ && StrUtil.isNotEmpty(valState.getJSONObject("protocolRoutes").getString(protocolType))) {
+ this.bbcContext.getAuthMessageContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update am contract status (address: %s)",
+ this.bbcContext.getAuthMessageContract().getContractAddress()
+ ), e);
+ }
+ }
+
+ @Override
+ public void setAmContract(String contractAddress) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getSdpContract())) {
+ throw new RuntimeException("empty sdp contract in bbc context");
+ }
+
+ // arg contractAddress: contract cid
+ dioxideClient.setAmContractToSdp(contractAddress);
+
+ // 4. update sdp contract status
+ try {
+ JSONObject valState = dioxideClient.getContractState(this.config.getDappName(), config.getSdpContractName(), DioxideTypes.Scope.Global, "")
+ .getJSONObject("State");
+ if (StrUtil.isNotEmpty(valState.getString("amAddress"))
+ && StrUtil.isNotEmpty(valState.getString("amContractId"))
+ && CollUtil.isNotEmpty(valState.getJSONArray("localDomain"))) {
+ this.bbcContext.getSdpContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update sdp contract status (address: %s)",
+ this.bbcContext.getSdpContract().getContractAddress()
+ ), e);
+ }
+ }
+
+ @Override
+ public void setLocalDomain(String domain) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (StrUtil.isEmpty(this.bbcContext.getSdpContract().getContractAddress())) {
+ throw new RuntimeException("none sdp contract address");
+ }
+
+ dioxideClient.setLocalDomainToSdp(domain);
+
+ // 4. update sdp contract status
+ try {
+ JSONObject valState = dioxideClient.getContractState(this.config.getDappName(), config.getSdpContractName(), DioxideTypes.Scope.Global, "")
+ .getJSONObject("State");
+ if (StrUtil.isNotEmpty(valState.getString("amAddress"))
+ && StrUtil.isNotEmpty(valState.getString("amContractId"))
+ && CollUtil.isNotEmpty(valState.getJSONArray("localDomain"))) {
+ this.bbcContext.getSdpContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update sdp contract status (address: %s)",
+ this.bbcContext.getSdpContract().getContractAddress()
+ ), e);
+ }
+
+ }
+
+ @Override
+ public CrossChainMessageReceipt relayAuthMessage(byte[] rawMessage) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getAuthMessageContract())) {
+ throw new RuntimeException("empty am contract in bbc context");
+ }
+
+ getBBCLogger().debug("relay AM {} to {} ",
+ HexUtil.encodeHexStr(rawMessage), this.config.getAmContractName());
+
+ return dioxideClient.relayMsgToAuthMsg(rawMessage);
+
+ }
+
+ @Override
+ public ConsensusState readConsensusState(BigInteger height) {
+ return null;
+ }
+
+ @Override
+ public boolean hasTpBta(CrossChainLane tpbtaLane, int tpBtaVersion) {
+ return false;
+ }
+
+ @Override
+ public ThirdPartyBlockchainTrustAnchor getTpBta(CrossChainLane tpbtaLane, int tpBtaVersion) {
+ return null;
+ }
+
+ @Override
+ public Set getSupportedPTCType() {
+ return null;
+ }
+
+ @Override
+ public PTCTrustRoot getPTCTrustRoot(@NonNull ObjectIdentity ptcOwnerOid) {
+ return null;
+ }
+
+ @Override
+ public boolean hasPTCTrustRoot(ObjectIdentity ptcOwnerOid) {
+ return false;
+ }
+
+ @Override
+ public PTCVerifyAnchor getPTCVerifyAnchor(ObjectIdentity ptcOwnerOid, BigInteger version) {
+ return null;
+ }
+
+ @Override
+ public boolean hasPTCVerifyAnchor(ObjectIdentity ptcOwnerOid, BigInteger version) {
+ return false;
+ }
+
+ @Override
+ public void setupPTCContract() {
+
+ }
+
+ @Override
+ public void setPtcContract(String ptcContractAddress) {
+
+ }
+
+ @Override
+ public void addTpBta(ThirdPartyBlockchainTrustAnchor tpbta) {
+
+ }
+
+ @Override
+ public BlockState queryValidatedBlockStateByDomain(CrossChainDomain recvDomain) {
+ return null;
+ }
+
+ @Override
+ public CrossChainMessageReceipt recvOffChainException(String exceptionMsgAuthor, byte[] exceptionMsgPkg) {
+ return null;
+ }
+
+ @Override
+ public CrossChainMessageReceipt reliableRetry(ReliableCrossChainMessage msg) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ private boolean isByteArrayZero(byte[] bytes) {
+ for (byte b : bytes) {
+ if (b != 0x00) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAccountTypeEnum.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAccountTypeEnum.java
new file mode 100644
index 00000000..f1018d7d
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAccountTypeEnum.java
@@ -0,0 +1,18 @@
+package com.alipay.antchain.bridge.plugins.dioxide.conf;
+
+import lombok.Getter;
+
+@Getter
+public enum DioxideAccountTypeEnum {
+
+ ETHEREUM(1),
+ BITCOIN_P2PKH(2),
+ ED25519(3),
+ SM2(4),
+ END(5);
+
+ private final int value;
+
+ DioxideAccountTypeEnum(int value) { this.value = value; }
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAddressTypeEnum.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAddressTypeEnum.java
new file mode 100644
index 00000000..219d04d3
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideAddressTypeEnum.java
@@ -0,0 +1,17 @@
+package com.alipay.antchain.bridge.plugins.dioxide.conf;
+
+import lombok.Getter;
+
+@Getter
+public enum DioxideAddressTypeEnum {
+
+ DEFAULT(0),
+ HASH(8),
+ NAME(9),
+ DAPP(10),
+ TOKEN(11);
+
+ private final int value;
+
+ DioxideAddressTypeEnum(int value) { this.value = value; }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideConfig.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideConfig.java
new file mode 100644
index 00000000..9a9a6073
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideConfig.java
@@ -0,0 +1,59 @@
+package com.alipay.antchain.bridge.plugins.dioxide.conf;
+
+import java.io.IOException;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.annotation.JSONField;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class DioxideConfig {
+
+ /**
+ * 从json字符串反序列化
+ *
+ * @param jsonString raw json
+ */
+ public static DioxideConfig fromJsonString(String jsonString) throws IOException {
+ return JSON.parseObject(jsonString, DioxideConfig.class);
+ }
+
+ // [client]
+ @JSONField
+ private String rpcUrl = "http://127.0.0.1:62222/api";
+
+ @JSONField
+ private String wsRpc = "ws://127.0.0.1:62222/api";
+
+ @JSONField
+ private String privateKey;
+
+ // [address / Id]
+ @JSONField
+ private String amContractAddressDeployed;
+
+ @JSONField
+ private String sdpContractAddressDeployed;
+
+ @JSONField
+ private String dappName = "AcbDapp";
+
+ @JSONField
+ private String amContractName = "AuthMsg";
+
+ @JSONField
+ private String sdpContractName = "SDPMsg";
+
+ @JSONField
+ private Boolean isPreContractDeployed = false;
+
+ /**
+ * json序列化为字符串
+ */
+ public String toJsonString() {
+ return JSON.toJSONString(this);
+ }
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideTypes.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideTypes.java
new file mode 100644
index 00000000..fbc8b4b8
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/DioxideTypes.java
@@ -0,0 +1,202 @@
+package com.alipay.antchain.bridge.plugins.dioxide.conf;
+
+import java.util.List;
+import java.util.Set;
+
+public class DioxideTypes {
+
+ // 全局常量
+ public static final int GLOBAL_IDENTIFIER = 65535;
+
+ // Scope
+ public enum Scope {
+ Global(0),
+ Shard(1),
+ Address(2);
+
+ private final int value;
+
+ Scope(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+
+ // EngineID
+ public enum EngineID {
+ Core(1),
+ Native(2),
+ GCL_NATIVE(3),
+ GCL_WASM(4),
+ SOLIDITY_EVM(5);
+
+ private final int value;
+
+ EngineID(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // BlockState
+ public enum BlockState {
+ DUS_RECEIVED(0),
+ DUS_INVALID(1),
+ DUS_EXCUTED(2),
+ DUS_FORKED(3),
+ DUS_FINALIZED(4),
+ DUS_ARCHIVED(5),
+ DUS_ARCHIVED_UNCLE(6);
+
+ private final int value;
+
+ BlockState(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // BlkCommitState
+ public enum BlkCommitState {
+ BLKCS_INIT(0),
+ BLKCS_PENDING_BLOCKID(1),
+ BLKCS_PENDING_COMMIT(2),
+ BLKCS_PENDING_SHARD_COMMIT(3),
+ BLKCS_CONFIRMED(4),
+ BLKCS_IGNORED(5),
+ BLKCS_COMMIT_ERROR(6),
+ BLKCS_REBASING(7);
+
+ private final int value;
+
+ BlkCommitState(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // BlkFinalityState
+ public enum BlkFinalityState {
+ BLKFS_NONE(0),
+ BLKFS_FINALIZED(1),
+ BLKFS_UNCLED(2),
+ BLKFS_ORPHANED(3);
+
+ private final int value;
+
+ BlkFinalityState(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // BlkArchiveState
+ public enum BlkArchiveState {
+ BLKAS_NONE(0),
+ BLKAS_DISCARD(1),
+ BLKAS_ARCHIVING(2),
+ BLKAS_ARCHIVED(3);
+
+ private final int value;
+
+ BlkArchiveState(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // TxnConfirmState
+ public enum TxnConfirmState {
+ TXN_RELAY_INVALIDED(-3),
+ TXN_READY(0),
+ TXN_CONFIRMED(1),
+ TXN_FINALIZED(2),
+ TXN_ABORTED(3),
+ TXN_EXPIRED(4),
+ TXN_ARCHIVED(5);
+
+ private final int value;
+
+ TxnConfirmState(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ // Txn 状态集合
+ public static final Set TXN_CONFIRMED_STATUS = Set.of(
+ TxnConfirmState.TXN_ARCHIVED.name(),
+ TxnConfirmState.TXN_CONFIRMED.name(),
+ TxnConfirmState.TXN_FINALIZED.name()
+ );
+
+ public static final Set TXN_FINALIZED_STATUS = Set.of(
+ TxnConfirmState.TXN_ARCHIVED.name(),
+ TxnConfirmState.TXN_FINALIZED.name()
+ );
+
+ public static final Set TXN_ARCHIVED_STATUS = Set.of(
+ TxnConfirmState.TXN_ARCHIVED.name()
+ );
+
+ // SubscribeTopic
+ public enum SubscribeTopic {
+ CONSENSUS_HEADER,
+ TRANSACTION_BLOCK,
+ TRANSACTION,
+ STATE,
+ RELAYS,
+ FINALIZED_BLOCK_AND_TRANSACTION,
+ MEMPOOL
+ }
+
+ // 各种 KeyName 列表
+ public static final List ScatterMapStateMsgKeyName = List.of(
+ "GlobalScatteredMaps",
+ "ScatteredMapOnShard_CloneScale",
+ "ScatteredMapOnShard_SplitScale"
+ );
+
+ public static final List KeyedStateMsgKeyName = List.of(
+ "KeyedScopeStates",
+ "GlobalScatteredMaps",
+ "ScatteredMapOnShard_CloneScale",
+ "ScatteredMapOnShard_SplitScale"
+ );
+
+ public static final List NonKeyedStateMsgKeyName = List.of(
+ "GlobalStates",
+ "ShardStates"
+ );
+
+ public static final List AllStateMsgKeyName = List.of(
+ "KeyedScopeStates",
+ "GlobalScatteredMaps",
+ "ScatteredMapOnShard_CloneScale",
+ "ScatteredMapOnShard_SplitScale",
+ "GlobalStates",
+ "ShardStates"
+ );
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/SecSuiteParamEnum.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/SecSuiteParamEnum.java
new file mode 100644
index 00000000..ed283bf8
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/conf/SecSuiteParamEnum.java
@@ -0,0 +1,21 @@
+package com.alipay.antchain.bridge.plugins.dioxide.conf;
+
+import lombok.Getter;
+
+@Getter
+public enum SecSuiteParamEnum {
+
+ DELEGATED_HASH_SIZE(32),
+ DELEGATED_NAME_SIZEMIN(3),
+ DELEGATED_NAME_SIZEMAX(32),
+ DELEGATED_DAPP_SIZEMIN(4),
+ DELEGATED_DAPP_SIZEMAX(8),
+ DELEGATED_TOKEN_SIZEMIN(3),
+ DELEGATED_TOKEN_SIZEMAX(8);
+
+ private final int value;
+
+ SecSuiteParamEnum(int value) {
+ this.value = value;
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAccount.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAccount.java
new file mode 100644
index 00000000..0e342957
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAccount.java
@@ -0,0 +1,85 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideAccountTypeEnum;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Base64;
+
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideAddressTypeEnum;
+import com.alipay.antchain.bridge.plugins.dioxide.utils.Crc32cPure;
+
+import org.bouncycastle.math.ec.rfc8032.Ed25519;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class DioxideAccount {
+
+ private byte[] privateKeyBytes;
+ private byte[] publicKeyBytes;
+ private byte[] addressBytes;
+ private DioxideAccountTypeEnum accountType;
+
+ public DioxideAccount(byte[] sk, byte[] vk, byte[] addr, DioxideAccountTypeEnum type) {
+ this.privateKeyBytes = sk;
+ this.publicKeyBytes = vk;
+ this.addressBytes = addr;
+ this.accountType = type;
+ }
+
+ public static DioxideAccount fromKey(String privateKey) {
+ try {
+ // 1. Base64 decode
+ byte[] skBytes = Base64.getDecoder().decode(privateKey);
+ byte[] skBytes32 = Arrays.copyOf(skBytes, 32); // 截取前32字节
+
+ // 2. 生成公钥
+ byte[] vkBytes = new byte[Ed25519.PUBLIC_KEY_SIZE];
+ Ed25519.generatePublicKey(skBytes32, 0, vkBytes, 0);
+
+ // 3. CRC32C 计算
+ int crcValue = Crc32cPure.crc32c(vkBytes, 3);
+// System.out.println("crcValue: " + crcValue);
+ long crc = DioxideAccountTypeEnum.ED25519.getValue() | (0xfffffff0L & crcValue);
+// System.out.println("crc: " + crc);
+
+ // 4. 拼接公钥 + 4字节CRC(小端序)
+ ByteBuffer buf = ByteBuffer.allocate(vkBytes.length + 4);
+ buf.put(vkBytes);
+ buf.order(ByteOrder.LITTLE_ENDIAN).putInt((int) crc);
+ byte[] address = buf.array();
+
+ return new DioxideAccount(skBytes, vkBytes, address, DioxideAccountTypeEnum.ED25519);
+
+ } catch (Exception e){
+ throw new RuntimeException("fail to init dioxideAccount from privateKey", e);
+ }
+ }
+
+ public String getAddressInString() {
+ return new DioxideAddress(this.addressBytes, DioxideAddressTypeEnum.DEFAULT).getAddressInString();
+ }
+
+ public String getPrivateKeyInString() {
+ return Base64.getEncoder().encodeToString(this.privateKeyBytes);
+ }
+
+ public String toString() {
+ return String.format(
+ "{\n \"PrivateKey\": \"%s\",\n \"PublicKey\": \"%s\",\n \"Address\": \"%s\"\n}",
+ Base64.getEncoder().encodeToString(this.privateKeyBytes), Base64.getEncoder().encodeToString(this.publicKeyBytes),
+ Base64.getEncoder().encodeToString(this.addressBytes)
+ );
+ }
+
+// public boolean isValid() {
+// return this.privateKeyBytes != null && this.privateKeyBytes.length == 64 &&
+// this.publicKeyBytes != null && this.publicKeyBytes.length == 32 &&
+// this.addressBytes != null && this.addressBytes.length == 36 &&
+// this.accountType.getValue() < DioxideAccountTypeEnum.END.getValue();
+// }
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAddress.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAddress.java
new file mode 100644
index 00000000..449d624d
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideAddress.java
@@ -0,0 +1,111 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideAddressTypeEnum;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.SecSuiteParamEnum;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+import com.alipay.antchain.bridge.plugins.dioxide.utils.Crc32cPure;
+import com.alipay.antchain.bridge.plugins.dioxide.utils.Krock32Decoder;
+import com.alipay.antchain.bridge.plugins.dioxide.utils.Krock32Encoder;
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Getter
+@Setter
+public class DioxideAddress {
+
+ private byte[] addressBytes;
+ private DioxideAddressTypeEnum type;
+
+ public DioxideAddress(byte[] addressBytes, DioxideAddressTypeEnum type) {
+ this.addressBytes = addressBytes;
+ this.type = type;
+ }
+
+ public DioxideAddress(String addressString, DioxideAddressTypeEnum type) {
+ this.type = type;
+ setDelegateeFromString(addressString);
+ }
+
+ public void setDelegateeFromString(String addressString) {
+ if (!isDelegateeNameValid(addressString)) {
+ this.addressBytes = null;
+ throw new IllegalArgumentException("Invalid address string: " + addressString);
+ }
+ // 填充到 32 字节(右填充 \x00)
+ byte[] raw = addressString.getBytes(StandardCharsets.UTF_8);
+ byte[] addr = new byte[32];
+ Arrays.fill(addr, (byte) 0);
+ System.arraycopy(raw, 0, addr, 0, Math.min(raw.length, 32));
+
+ // sid + CRC32C
+ int sid = type.getValue();
+ int crcValue = Crc32cPure.crc32c(addr, sid);
+ long crc = sid | (0xfffffff0L & crcValue);
+
+ ByteBuffer buf = ByteBuffer.allocate(addr.length + 4);
+ buf.put(addr);
+ buf.order(ByteOrder.LITTLE_ENDIAN).putInt((int) crc);
+ this.addressBytes = buf.array();
+ }
+
+ public String getAddressInString() {
+ if (type == DioxideAddressTypeEnum.DEFAULT) {
+ Krock32Encoder krock32Eecoder = new Krock32Encoder();
+ krock32Eecoder.update(this.addressBytes);
+ return krock32Eecoder.finalizeEncode().toLowerCase();
+ } else {
+ // 非 DEFAULT 类型用 Java 等价实现
+ int valid = 0;
+ while (valid < 32) {
+ if (this.addressBytes[valid] == 0) break;
+ valid++;
+ }
+ byte[] addr = new byte[valid];
+ System.arraycopy(this.addressBytes, 0, addr, 0, valid);
+ String addrStr = new String(addr, StandardCharsets.UTF_8);
+ return addrStr + ":" + this.type.name().toLowerCase();
+ }
+ }
+
+ public boolean isDelegateeNameValid(String name) {
+ int minLen = 0, maxLen = 0;
+ String regex = "";
+
+ switch (this.type) {
+ case DioxideAddressTypeEnum.DAPP -> {
+ minLen = SecSuiteParamEnum.DELEGATED_DAPP_SIZEMIN.getValue();
+ maxLen = SecSuiteParamEnum.DELEGATED_DAPP_SIZEMAX.getValue();
+ regex = "[a-zA-Z0-9_]+"; // Python r'[a-z|A-Z|\d|_]+' → 简化为 a-zA-Z0-9_
+ }
+ case DioxideAddressTypeEnum.TOKEN -> {
+ minLen = SecSuiteParamEnum.DELEGATED_TOKEN_SIZEMIN.getValue();
+ maxLen = SecSuiteParamEnum.DELEGATED_TOKEN_SIZEMAX.getValue();
+ regex = "[A-Z0-9#-]+"; // Python r'[A-Z|\d|-|#]+'
+ }
+ case DioxideAddressTypeEnum.NAME -> {
+ minLen = SecSuiteParamEnum.DELEGATED_NAME_SIZEMIN.getValue();
+ maxLen = SecSuiteParamEnum.DELEGATED_NAME_SIZEMAX.getValue();
+ regex = "[\\w\\d_\\-!#$@&^*()\\[\\]{}<>,;?~]+";
+ // Python r'[\w\d|_|-|!|#|$|@|&|^|*|(|)|\[|\]|{|}|<|>|,|;|?|~]+'
+ // Java 正则无需 `|`
+ }
+ default -> {
+ return false;
+ }
+ }
+
+ if (name.length() < minLen || name.length() > maxLen) {
+ return false;
+ }
+
+ return Pattern.matches(regex, name);
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideClient.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideClient.java
new file mode 100644
index 00000000..3ac132c5
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideClient.java
@@ -0,0 +1,880 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.http.*;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import org.apache.commons.collections.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.helpers.NOPLogger;
+
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideAddressTypeEnum;
+import com.alipay.antchain.bridge.plugins.dioxide.gcl.*;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideConfig;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideTypes;
+
+@Getter
+public class DioxideClient {
+
+ private final Logger bbcLogger;
+
+ private final DioxideConfig config;
+
+ private final DioxideAccount dioxideAccount;
+
+ private final DioxideAddress dappAddress;
+
+ private final HttpClient httpClient;
+
+ private final ExecutorService executor;
+
+ private static final int DEFAULT_TIMEOUT = 20 * 1000;
+
+ private static final List PRE_CONTRACT_ORDER = List.of(
+ Interfaces.IAUTHMESSAGE,
+ Interfaces.ICONTRACTUSINGSDP,
+ Interfaces.ISDPMESSAGE,
+ Interfaces.ISUBPROTOCOL,
+ Libs.UTILS,
+ Libs.SIZEOF,
+ Libs.BYTESTOTYPES,
+ Libs.TYPESTOBYTES,
+ Libs.TLVUTILS,
+ Libs.SDPLIB,
+ Libs.AMLIB
+ );
+
+ private static final String SEND_AUTH_MESSAGE_EVENT_NAME = "SendAuthMessage:name";
+
+ private static final String RECV_MESSAGE_IN_PROTOCOL = "recvMessageInProtocol:name";
+
+ public DioxideClient(DioxideConfig config, Logger bbcLogger) {
+ this.bbcLogger = bbcLogger;
+ this.config = config;
+
+ this.executor = Executors.newVirtualThreadPerTaskExecutor();
+ httpClient = HttpClient.newBuilder()
+ .executor(executor)
+ .connectTimeout(Duration.ofSeconds(5))
+ .build();
+
+ // test if rpcUrl is true
+ try {
+ String rawResp = makeRequest("dx.overview", "");
+ checkIfErrorResponse(rawResp);
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("failed to connect dioxide (url: %s) or other error (exception: %s)", config.getRpcUrl(), e.getMessage()), e);
+ }
+
+ this.dioxideAccount = DioxideAccount.fromKey(this.config.getPrivateKey());
+ dappAddress = new DioxideAddress(this.config.getDappName(), DioxideAddressTypeEnum.DAPP);
+ }
+
+ public void shutdown() {
+ this.executor.shutdown();
+ this.httpClient.close();
+ }
+
+ @SneakyThrows
+ public Long queryLatestHeight() {
+ String rawResp = makeRequest("dx.committed_head_height", "");
+ JSONObject resp = checkIfErrorResponse(rawResp);
+ return resp.getLong("HeadHeight");
+ }
+
+ public DioxideTransaction getTransactionByHash(String txHash) {
+ String rawResp = makeRequest("dx.transaction", JSON.toJSONString(orderedMap("hash", txHash)));
+ JSONObject ret = checkIfErrorResponse(rawResp);
+ try {
+ return JSON.toJavaObject(ret, DioxideTransaction.class);
+ } catch (Exception e) {
+ getBbcLogger().error("[getTransactionByHash] error: {}", e.getMessage());
+ getBbcLogger().info("\n{}", JSON.toJSONString(ret, SerializerFeature.PrettyFormat));
+ return null;
+ }
+ }
+
+ public JSONObject getTransactionJonObjectByHash(String txHash) {
+ String rawResp = makeRequest("dx.transaction", JSON.toJSONString(orderedMap("hash", txHash)));
+ return checkIfErrorResponse(rawResp);
+ }
+
+ public DioxideTransactionBlock getTransactionBlockInGlobalShardByHeight(long height) {
+ String rawResp = makeRequest("dx.transaction_block", JSON.toJSONString(orderedMap(
+ "query_type", 0,
+ "shard_index", DioxideTypes.GLOBAL_IDENTIFIER,
+ "height", height
+ )));
+ JSONObject ret = checkIfErrorResponse(rawResp);
+ try {
+ return JSON.toJavaObject(ret, DioxideTransactionBlock.class);
+ } catch (Exception e) {
+ getBbcLogger().error(e.getMessage(), e);
+ getBbcLogger().info("\n{}", JSON.toJSONString(ret, SerializerFeature.PrettyFormat));
+ return null;
+ }
+ }
+
+ public JSONObject getConsensusHeaderByHeight(long height) {
+ String rawResp = makeRequest("dx.consensus_header", JSON.toJSONString(orderedMap(
+ "query_type", 0,
+ "height", height
+ )));
+ return checkIfErrorResponse(rawResp);
+ }
+
+ public CrossChainMessageReceipt getCrossChainMessageReceipt(DioxideTransaction dioxideTransaction) {
+ CrossChainMessageReceipt crossChainMessageReceipt = new CrossChainMessageReceipt();
+ // check if tx0 is null
+ if (dioxideTransaction == null) {
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setSuccessful(false);
+ crossChainMessageReceipt.setTxhash("");
+ crossChainMessageReceipt.setErrorMsg("tx0 is null");
+ return crossChainMessageReceipt;
+ }
+
+ // check if tx0 is confirmed
+ Long currHeight = queryLatestHeight();
+ if (dioxideTransaction.getHeight() == null || dioxideTransaction.getHeight().compareTo(currHeight) > 0) {
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setSuccessful(true);
+ crossChainMessageReceipt.setTxhash(dioxideTransaction.getTxHash());
+ crossChainMessageReceipt.setErrorMsg("tx0 is not confirmed");
+ return crossChainMessageReceipt;
+ }
+
+ // check if tx1(inbound relay) in tx0 is confirmed
+ if (dioxideTransaction.getInvocation() == null || CollUtil.isEmpty(dioxideTransaction.getInvocation().getRelays())) {
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setSuccessful(true);
+ crossChainMessageReceipt.setTxhash(dioxideTransaction.getTxHash());
+ crossChainMessageReceipt.setErrorMsg("tx1(inbound relay) in tx0 is not confirmed");
+ return crossChainMessageReceipt;
+ }
+
+ // check if tx2(contain recvMessageInProtocol event txHash) in tx1 is confirmed and successful
+ List tx2Hashes = dioxideTransaction.getInvocation().getRelays().stream()
+ .filter(Objects::nonNull)
+ .map(s -> {
+ int idx = s.indexOf(':');
+ return idx >= 0 ? s.substring(0, idx) : s;
+ })
+ .distinct()
+ .toList();
+ for (String tx2Hash : tx2Hashes) {
+ JSONObject tx2 = getTransactionJonObjectByHash(tx2Hash);
+ // check if tx3 (is recvMessageInProtocol event tx) in tx2 is confirmed and successful
+ JSONArray eventRelays = tx2.getJSONArray("Relays");
+ if (CollUtil.isEmpty(eventRelays)) {
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setSuccessful(true);
+ crossChainMessageReceipt.setTxhash(dioxideTransaction.getTxHash());
+ crossChainMessageReceipt.setErrorMsg("recvMessageInProtocol event in protocol contract is not confirmed");
+ return crossChainMessageReceipt;
+ }
+ List eventTxs = eventRelays.toJavaList(DioxideTransaction.class);
+ for (DioxideTransaction eventTx : eventTxs) {
+ if (eventTx.getInvocation() != null && CollUtil.isNotEmpty(eventTx.getInvocation().getRelays())) {
+ // find recvMessageInProtocol event
+ List tx3Hashes = eventTx.getInvocation().getRelays();
+ for (String tx3Hash : tx3Hashes) {
+ DioxideTransaction dtx = getTransactionByHash(tx3Hash);
+ if (dtx != null && StrUtil.isNotEmpty(dtx.getTarget()) && dtx.getTarget().equals(DioxideClient.RECV_MESSAGE_IN_PROTOCOL)) {
+ crossChainMessageReceipt.setConfirmed(true);
+ crossChainMessageReceipt.setSuccessful(true);
+ crossChainMessageReceipt.setTxhash(dioxideTransaction.getTxHash());
+ crossChainMessageReceipt.setErrorMsg("");
+ return crossChainMessageReceipt;
+ }
+ }
+ }
+ }
+ }
+
+ // not find recvMessageInProtocol event: tx is confirmed but failed?
+ crossChainMessageReceipt.setConfirmed(true);
+ crossChainMessageReceipt.setSuccessful(false);
+ crossChainMessageReceipt.setTxhash(dioxideTransaction.getTxHash());
+ crossChainMessageReceipt.setErrorMsg("no recvMessageInProtocol event is protocol contract");
+
+ return crossChainMessageReceipt;
+ }
+
+
+ public List readAuthMessagesFromBlock(long height) {
+ List messageList = ListUtil.toList();
+ // 1. get tx block on shard where AM is located
+ DioxideTransactionBlock block = getTransactionBlockInGlobalShardByHeight(height);
+ // 2. get tx from tx block
+ List dispatchedAndOutboundRelays = ListUtil.toList();
+ if (block.getDispatchedRelayTxnCount() != 0) {
+ dispatchedAndOutboundRelays.addAll(block.getTransactions().getDispatchedRelays());
+ }
+ if (block.getOutboundRelayTxnCount() != 0) {
+ dispatchedAndOutboundRelays.addAll(block.getTransactions().getOutboundRelays());
+ }
+ if (CollectionUtils.isNotEmpty(dispatchedAndOutboundRelays)) {
+ dispatchedAndOutboundRelays.forEach(txn -> {
+ DioxideTransaction dtx = getTransactionByHash(txn);
+ if (StrUtil.isNotEmpty(dtx.getTarget())) {
+ if (dtx.getTarget().equals(SEND_AUTH_MESSAGE_EVENT_NAME)) {
+ getBbcLogger().info("send am event found in global shard, block: {}, block hash: {}, contract name: {}",
+ block.getHeight(), block.getHash(), String.format("%s:%s", config.getDappName(), config.getAmContractName()));
+ messageList.add(CrossChainMessage.createCrossChainMessage(
+ CrossChainMessage.CrossChainMessageType.AUTH_MSG,
+ dtx.getHeight(),
+ getConsensusHeaderByHeight(height).getLongValue("Timestamp"),
+ // NOTICE: use sha256 to ensure this blockHash have 32 bytes
+ DigestUtil.sha256(block.getHash()),
+ // todo: parse the am msg
+ deserializedSendMsgEventArgs(dtx.getInputAsString()),
+ // todo: put ledger data, for SPV or other attestations
+ // this time we need no verify. it's ok to set it with empty bytes
+ "".getBytes(),
+ // todo: put proof data
+ // this time we need no proof data. it's ok to set it with empty bytes
+ "".getBytes(),
+ // NOTICE: use sha256 to ensure this txHash have 32 bytes
+ DigestUtil.sha256(dtx.getTxHash())
+// dtx.getTxHash().getBytes(StandardCharsets.UTF_8)
+
+ ));
+ }
+ }
+ });
+ return messageList;
+ }
+
+ if (!messageList.isEmpty()) {
+ getBbcLogger().info("read cross chain messages (blockNumber: {}, msg_size: {})", height, messageList.size());
+ getBbcLogger().debug("read cross chain messages (blockNumber: {}, msgs: {})",
+ height,
+ messageList.stream().map(JSON::toJSONString).collect(Collectors.joining(","))
+ );
+ }
+
+ return ListUtil.empty();
+ }
+
+ private byte[] deserializedSendMsgEventArgs(String input) {
+ // 1. hex 转 byte[]
+ byte[] inputBytes = HexUtil.decodeHex(input);
+
+ // 2. 检查长度是否至少包含 4 字节
+ if (inputBytes.length < 4) {
+ throw new IllegalArgumentException("illegal input bytes length: " + inputBytes.length);
+ }
+
+ // 3. 解析前 4 字节(小端序)作为长度字段
+ int len = ((inputBytes[3] & 0xFF) << 24) |
+ ((inputBytes[2] & 0xFF) << 16) |
+ ((inputBytes[1] & 0xFF) << 8) |
+ ((inputBytes[0] & 0xFF));
+
+ // 4. 校验后续字节长度是否与长度字段一致
+ if (inputBytes.length != 4 + len) {
+ throw new IllegalArgumentException("Length field mismatch: expected " + len +
+ " bytes, but got " + (inputBytes.length - 4));
+ }
+
+ // 5. 拷贝数据字段(丢弃前4字节)
+ byte[] evnetData = new byte[len];
+ System.arraycopy(inputBytes, 4, evnetData, 0, len);
+
+ return evnetData;
+ }
+
+ public long deployAuthMsgContract() {
+ try {
+ deployPreContract();
+
+ List codes = new ArrayList<>();
+ List cargs = new ArrayList<>();
+
+ String amContractSource =
+ new String(
+ Base64.getDecoder().decode(Contracts.AUTHMSG),
+ StandardCharsets.UTF_8
+ );
+
+ codes.add(amContractSource);
+ cargs.add(JSON.toJSONString(orderedMap(
+ "_owner", dioxideAccount.getAddressInString(),
+ "_relayer", dioxideAccount.getAddressInString()
+ )));
+
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "delegatee", dappAddress.getAddressInString(),
+ "function", "core.delegation.deploy_contracts",
+ "args", orderedMap("code", codes, "cargs", cargs))),
+ true
+ );
+ waitForContractDeployed(txHash);
+ // test if the contract been deployed successfully, and get the contract name
+ return getContractCid(config.getAmContractName());
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("failed to deploy contract: %s", config.getAmContractName()) ,e);
+ }
+ }
+
+ public long deploySdpContract() {
+ try {
+ deployPreContract();
+
+ List codes = new ArrayList<>();
+ List cargs = new ArrayList<>();
+
+ String sdpContractSource =
+ new String(
+ Base64.getDecoder().decode(Contracts.SDPMSG),
+ StandardCharsets.UTF_8
+ );
+
+ codes.add(sdpContractSource);
+ cargs.add(JSON.toJSONString(orderedMap(
+ "_owner", dioxideAccount.getAddressInString()
+ )));
+
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "delegatee", dappAddress.getAddressInString(),
+ "function", "core.delegation.deploy_contracts",
+ "args", orderedMap("code", codes, "cargs", cargs))),
+ true
+ );
+ waitForContractDeployed(txHash);
+ // test if the contract been deployed successfully, and get the contract name
+ return getContractCid(config.getSdpContractName());
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("failed to deploy contract: %s", config.getSdpContractName()) ,e);
+ }
+ }
+
+ public long deployContract(String contractName, String contractSource, String cargsString) {
+ try {
+ deployPreContract();
+
+ List codes = new ArrayList<>();
+ List cargs = new ArrayList<>();
+
+ codes.add(contractSource);
+ cargs.add(cargsString);
+
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "delegatee", dappAddress.getAddressInString(),
+ "function", "core.delegation.deploy_contracts",
+ "args", orderedMap("code", codes, "cargs", cargs))),
+ true
+ );
+ waitForContractDeployed(txHash);
+ // test if the contract been deployed successfully, and get the contract name
+ return getContractCid(contractName);
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("failed to deploy contract: %s", contractName) ,e);
+ }
+ }
+
+ private void deployPreContract() {
+ if (config.getIsPreContractDeployed()) {
+ getBbcLogger().info("[deployPreContract] is already deployed");
+ return;
+ }
+
+ // mint some tokens for client
+ try {
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", "core.coin.mint",
+ "args", orderedMap(
+ "Amount", String.format("%d", (long) Math.pow(10, 18))
+ ))),
+ true
+ );
+ getBbcLogger().info("success to mint some tokens");
+ } catch (Exception e) {
+ throw new RuntimeException("failed to mint some tokens", e);
+ }
+
+ // deploy dapp
+ try {
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", "core.delegation.create",
+ "args", orderedMap(
+ "Type", 10,
+ "Name", config.getDappName(),
+ "Deposit", String.format("%d", (long) Math.pow(10, 11))
+ ))),
+ true
+ );
+ getBbcLogger().info("wait for deploying dapp: [txhash]{}", txHash);
+ if (!waitForDappDeployed(txHash, DEFAULT_TIMEOUT)) {
+ throw new RuntimeException("waitForDappDeployed failed");
+ }
+ getBbcLogger().info("success to deploy dapp: [txhash]{}", dappAddress.getAddressInString());
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy dapp", e);
+ }
+
+ // deploy lib utils etc.
+ try {
+ List codes = new ArrayList<>();
+ List cargs = new ArrayList<>();
+
+ // 按 FILE_ORDER 顺序填充 codes 和 cargs
+ for (String contractSource : PRE_CONTRACT_ORDER) {
+ codes.add(new String(
+ Base64.getDecoder().decode(contractSource),
+ StandardCharsets.UTF_8
+ ));
+ cargs.add("");
+ }
+
+ getBbcLogger().info("dapp name to use: {}", dappAddress.getAddressInString());
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "delegatee", dappAddress.getAddressInString(),
+ "function", "core.delegation.deploy_contracts",
+ "args", orderedMap("code", codes, "cargs", cargs))),
+ true
+ );
+ waitForContractDeployed(txHash);
+ getBbcLogger().info("deploy pre contract from onchain file: {}", txHash);
+ config.setIsPreContractDeployed(true);
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy pre contract", e);
+ }
+ }
+
+ private long getContractCid(String contractName) {
+ String rawResp = makeRequest("dx.contract_info", JSON.toJSONString(orderedMap(
+ "contract", String.format("%s.%s", config.getDappName(), contractName)
+ )));
+ JSONObject resp = checkIfErrorResponse(rawResp);
+ return resp.getLongValue("ContractVersionID");
+ }
+
+ public long querySdpSeq(String senderDomain, String senderID, String receiverDomain, String receiverID) {
+ try {
+ int[] senderDomainArray = toIntArray(senderDomain.getBytes(StandardCharsets.UTF_8));
+ int[] senderIDArray = toIntArray(HexUtil.decodeHex(senderID));
+ int[] receiverDomainArray = toIntArray(receiverDomain.getBytes(StandardCharsets.UTF_8));
+ int[] receiverIDArray = toIntArray(HexUtil.decodeHex(receiverID));
+ long seq;
+
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", String.format("%s.%s.%s",config.getDappName(), config.getSdpContractName(), "querySDPMessageSeq"),
+ "args", orderedMap(
+ "senderDomain", senderDomainArray,
+ "senderID", senderIDArray,
+ "receiverDomain", receiverDomainArray,
+ "receiverID", receiverIDArray
+ ))),
+ true
+ );
+
+ if (StrUtil.isEmpty(txHash)) {
+ throw new RuntimeException("tx hash is empty");
+ }
+ JSONObject resp = getContractState(config.getDappName(), config.getSdpContractName(), DioxideTypes.Scope.Address, dioxideAccount.getAddressInString());
+ JSONObject state = resp.getJSONObject("State");
+ seq = state.getLongValue("resultInQuerySDPMessageSeq");
+
+ getBbcLogger().info("result of sdp contract state [address]: \n{}", JSON.toJSONString(resp, SerializerFeature.PrettyFormat));
+
+ getBbcLogger().info("sdpMsg seq: {} (senderDomain: {}, senderID: {}, receiverDomain: {}, receiverID: {})",
+ seq, senderDomain, senderID, receiverDomain, receiverID);
+ return seq;
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to query sdpMsg seq (senderDomain: %s, senderID: %s, receiverDomain: %s, receiverID: %s)",
+ senderDomain, senderID, receiverDomain, receiverID), e
+ );
+ }
+ }
+
+ public void setProtocolToAuthMsg(String protocolCidInString, String protocolType) {
+ long protocolCid = Long.parseLong(protocolCidInString);
+ String protocolAddress = String.format("0x%016X:contract", protocolCid);
+
+ try {
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", String.format("%s.%s.%s",config.getDappName(), config.getAmContractName(), "setProtocol"),
+ "args", orderedMap(
+ "protocolID", protocolCid,
+ "protocolAddress", protocolAddress,
+ "protocolType", Integer.parseInt(protocolType)
+ ))),
+ true
+ );
+
+ if (StrUtil.isEmpty(txHash)) {
+ throw new RuntimeException("tx hash is empty");
+ }
+ getBbcLogger().info("set protocol (cid: {}, address: {}, type: {}) to AM {} by tx {} ",
+ protocolCid, protocolAddress, protocolType, config.getAmContractName(), txHash
+ );
+ JSONObject resp = getContractState(config.getDappName(), config.getAmContractName(), DioxideTypes.Scope.Global, "");
+ getBbcLogger().info("result of am contract state: \n{}", JSON.toJSONString(resp, SerializerFeature.PrettyFormat));
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to set protocol (cid: %s, address: %s, type: %s) to AM %s",
+ protocolCid, protocolAddress, protocolType, config.getAmContractName()
+ ), e
+ );
+ }
+ }
+
+ // NOTICE: 1.sync send tx, 2.truncate txHash to 32 bytes(temporary solution)
+ public CrossChainMessageReceipt relayMsgToAuthMsg(byte[] rawMessage) {
+ try {
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", String.format("%s.%s.%s",config.getDappName(), config.getAmContractName(), "recvPkgFromRelayer"),
+ "args", orderedMap(
+ "pkg", toIntArray(rawMessage)
+ ))),
+ true
+ );
+
+ if (StrUtil.isEmpty(txHash)) {
+ throw new RuntimeException("tx hash is empty");
+ }
+
+ CrossChainMessageReceipt crossChainMessageReceipt = new CrossChainMessageReceipt();
+ crossChainMessageReceipt.setConfirmed(true);
+ crossChainMessageReceipt.setSuccessful(true);
+ // NOTICE: use sha256 to ensure txHash have 32 bytes
+ crossChainMessageReceipt.setTxhash(DigestUtil.sha256Hex(txHash));
+ crossChainMessageReceipt.setErrorMsg("");
+ getBbcLogger().info("relay am msg by tx {}", txHash);
+
+ return crossChainMessageReceipt;
+
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to relay AM %s to %s",
+ HexUtil.encodeHexStr(rawMessage), config.getAmContractName()
+ ), e
+ );
+ }
+ }
+
+ public void setAmContractToSdp(String amContractCidInString) {
+ long amContractCid = Long.parseLong(amContractCidInString);
+ String amContractAddress = String.format("0x%016X:contract", amContractCid);
+
+ try {
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", String.format("%s.%s.%s",config.getDappName(), config.getSdpContractName(), "setAmContract"),
+ "args", orderedMap(
+ "_amContractId", amContractCid,
+ "_amAddress", amContractAddress
+ ))),
+ true
+ );
+
+ if (StrUtil.isEmpty(txHash)) {
+ throw new RuntimeException("tx hash is empty");
+ }
+ getBbcLogger().info("set am contract (cid: {}, address: {}) to SDP {} by tx {}",
+ amContractCid, amContractAddress, config.getSdpContractName(), txHash);
+ JSONObject resp = getContractState(config.getDappName(), config.getSdpContractName(), DioxideTypes.Scope.Global, "");
+ getBbcLogger().info("result of sdp contract state: \n{}", JSON.toJSONString(resp, SerializerFeature.PrettyFormat));
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to set am contract (cid: %s, address: %s) to SDP %s",
+ amContractCid, amContractAddress, config.getSdpContractName()
+ ), e
+ );
+ }
+ }
+
+ public void setLocalDomainToSdp(String localDomain) {
+ try {
+ int[] domainArray = toIntArray(localDomain.getBytes(StandardCharsets.UTF_8));
+ String txHash = sendTransaction(
+ JSON.toJSONString(orderedMap(
+ "sender", dioxideAccount.getAddressInString(),
+ "function", String.format("%s.%s.setLocalDomain", config.getDappName(), config.getSdpContractName()),
+ "args", orderedMap(
+ "domain", domainArray)
+ )),
+ true
+ );
+
+ if (StrUtil.isEmpty(txHash)) {
+ throw new RuntimeException("tx hash is empty");
+ }
+ getBbcLogger().info("set domain ({}) to SDP {} by tx {}", localDomain, config.getSdpContractName(), txHash);
+ JSONObject resp = getContractState(config.getDappName(), config.getSdpContractName(), DioxideTypes.Scope.Global, "");
+ getBbcLogger().info("result of sdp contract state: \n{}", JSON.toJSONString(resp, SerializerFeature.PrettyFormat));
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to set domain (%s) to SDP %s", localDomain, config.getSdpContractName()
+ ), e
+ );
+ }
+ }
+
+ private int[] toIntArray(byte[] bytes) {
+ int[] arr = new int[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ arr[i] = bytes[i] & 0xFF; // 转无符号 uint8
+ }
+ return arr;
+ }
+
+ private void waitForContractDeployed(String txHash) {
+ JSONObject state = getContractState("core","contracts", DioxideTypes.Scope.Global, "").getJSONObject("State");
+ long targetHeight = -1;
+ if (state != null && !state.isEmpty()) {
+ JSONArray scheduledList = state.getJSONArray("Scheduled");
+ if (scheduledList != null) {
+ for (int i = 0; i < scheduledList.size(); i++) {
+ JSONObject s = scheduledList.getJSONObject(i);
+ if (txHash.equals(s.getString("BuildKey"))) {
+ targetHeight = s.getLongValue("TargetHeight");
+ break;
+ }
+ }
+ }
+ }
+ long curHeight = queryLatestHeight();
+ while (curHeight <= targetHeight) {
+ curHeight = queryLatestHeight();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @SneakyThrows
+ public JSONObject getContractState(String dappName, String contractName, DioxideTypes.Scope scopeType, String key) {
+ LinkedHashMap params = orderedMap(
+ "contract_with_scope", String.format("%s.%s.%s", dappName, contractName, scopeType.toString().toLowerCase())
+ );
+ if (scopeType != DioxideTypes.Scope.Global) {
+ params.put("scope_key", key);
+ }
+
+ String rawResp = makeRequest("dx.contract_state", JSON.toJSONString(params));
+ return checkIfErrorResponse(rawResp);
+ }
+
+ private boolean waitForDappDeployed(String txHash, int timeOut) {
+ if (waitForTransactionConfirmed(txHash, timeOut)) {
+ List relays = getAllRelayTransactions(getTransactionByHash(txHash) ,true);
+ for (JSONObject relay : relays) {
+ if (relay.getString("Function").equals("core.coin.address.deposit")) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private List getAllRelayTransactions(DioxideTransaction tx, boolean detail) {
+ List res = new ArrayList<>();
+
+ if (!isTxConfirmedWithRelays(tx)) {
+ return res;
+ }
+
+ Queue queue = new LinkedList<>();
+ queue.add(tx);
+ while (!queue.isEmpty()) {
+ DioxideTransaction curTx = queue.poll();
+ if (curTx.getInvocation() != null && curTx.getInvocation().getRelays() != null) {
+ curTx.getInvocation().getRelays().forEach(relayHash -> {
+ DioxideTransaction relayTx = getTransactionByHash(relayHash.split(":")[0]);
+ if (detail) {
+ res.add(JSON.parseObject(JSON.toJSONString(relayTx)));
+ } else {
+ res.add(JSON.parseObject(relayHash));
+ }
+ queue.add(relayTx);
+ });
+ }
+ }
+ return res;
+ }
+
+ private boolean waitForTransactionConfirmed(String txHash, int timeOut) {
+ long start = System.currentTimeMillis();
+ DioxideTransaction dioxideTransaction = getTransactionByHash(txHash);
+
+ while (!isTxConfirmedWithRelays(dioxideTransaction)) {
+ if (System.currentTimeMillis() - start > timeOut) {
+ return false;
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isTxConfirmedWithRelays(DioxideTransaction dioxideTransaction) {
+ Queue queue = new ArrayDeque<>();
+ queue.add(dioxideTransaction.getTxHash());
+ while (!queue.isEmpty()) {
+ String txHash = queue.poll();
+ DioxideTransaction curTx = getTransactionByHash(txHash);
+ if (!isTxConfirmed(curTx)) {
+ return false;
+ }
+ if (curTx.getInvocation() != null && curTx.getInvocation().getRelays() != null) {
+ queue.addAll(
+ curTx.getInvocation().getRelays()
+ .stream()
+ .map(s -> s.split(":")[0])
+ .toList()
+ );
+ }
+ }
+ return true;
+ }
+
+ public boolean isTxConfirmed(DioxideTransaction tx) {
+ if (tx == null || tx.getConfirmState() == null) {
+ return false;
+ }
+ return DioxideTypes.TXN_CONFIRMED_STATUS.contains(tx.getConfirmState());
+ }
+
+ @SneakyThrows
+ public String sendTransaction(String params, boolean sync) {
+// getBbcLogger().info("params of compose: \n{}", JSON.toJSONString(JSON.parseObject(params), SerializerFeature.PrettyFormat));
+ String unsigned_txn = composeTransaction(params);
+ String signed_txn = signTransaction(unsigned_txn);
+ return sendRawTransaction(signed_txn, sync); // return txHash
+ }
+
+ @SneakyThrows
+ private String composeTransaction(String params) {
+ String rawResp = makeRequest("tx.compose", params);
+ JSONObject resp = checkIfErrorResponse(rawResp);
+ return resp.getString("TxData");
+ }
+
+ @SneakyThrows
+ private String signTransaction(String unsigned_txn) {
+ String rawResp = makeRequest("tx.sign", JSON.toJSONString(orderedMap(
+ "sk", List.of(dioxideAccount.getPrivateKeyInString()),
+ "txdata", unsigned_txn
+ )));
+ JSONObject resp = checkIfErrorResponse(rawResp);
+ return resp.getString("TxData");
+ }
+
+ @SneakyThrows
+ private String sendRawTransaction(String signed_txn, boolean sync) {
+ String rawResp = makeRequest("tx.send", JSON.toJSONString(orderedMap("txdata", signed_txn)));
+ JSONObject resp = checkIfErrorResponse(rawResp);
+ String txHash = resp.getString("Hash");
+ if (sync) {
+ waitForTransactionConfirmed(txHash, DEFAULT_TIMEOUT);
+ }
+ return txHash;
+ }
+
+ private String makeRequest(String method, String params) {
+ String uri = config.getRpcUrl() + "?req=" + method;
+ HttpRequest req = HttpRequest.newBuilder()
+ .uri(URI.create(uri))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(params))
+ .timeout(Duration.ofSeconds(10))
+ .build();
+
+ try {
+ return httpClient.send(req, HttpResponse.BodyHandlers.ofString()).body();
+ } catch (IOException | InterruptedException e) {
+ String msg = "[makeRequest] unexpected error in method: " + method;
+ getBbcLogger().error(msg, e);
+ throw new RuntimeException(msg, e);
+ }
+ }
+
+ private JSONObject checkIfErrorResponse(String rawResp) {
+ if(Objects.isNull(rawResp)) {
+ getBbcLogger().error("[checkIfErrorResponse] get null response from dioxide");
+ throw new RuntimeException(StrUtil.format("[checkIfErrorResponse] get null response from dioxide"));
+ }
+ RpcResponse resp = JSON.parseObject(rawResp, RpcResponse.class);
+ if (!resp.isSuccess()) {
+ getBbcLogger().error("[checkIfErrorResponse] get ERROR response from dioxide: {}", JSON.toJSONString(resp));
+ throw new RuntimeException(StrUtil.format("[checkIfErrorResponse] get ERROR response from dioxide: {}", JSON.toJSONString(resp)));
+ }
+ return resp.getSuccessResponse();
+ }
+
+ private static LinkedHashMap orderedMap(Object... kvPairs) {
+ if (kvPairs.length % 2 != 0) {
+ throw new IllegalArgumentException("Key-value pairs must be even");
+ }
+ int size = kvPairs.length / 2;
+ LinkedHashMap map = LinkedHashMap.newLinkedHashMap(size);
+
+ for (int i = 0; i < kvPairs.length; i += 2) {
+ @SuppressWarnings("unchecked")
+ K key = (K) kvPairs[i];
+ @SuppressWarnings("unchecked")
+ V value = (V) kvPairs[i + 1];
+ map.put(key, value);
+ }
+ return map;
+ }
+
+ private Logger getBbcLogger() {
+ return ObjectUtil.isNull(this.bbcLogger) ? NOPLogger.NOP_LOGGER : this.bbcLogger;
+ }
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransaction.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransaction.java
new file mode 100644
index 00000000..4d9e75a2
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransaction.java
@@ -0,0 +1,105 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class DioxideTransaction {
+
+ @JSONField(name = "Hash")
+ private String txHash;
+
+ @JSONField(name = "GasOffered")
+ private Integer gasOffered;
+
+ @JSONField(name = "GasPrice")
+ private String gasPrice;
+
+ @JSONField(name = "Grouped")
+ private boolean grouped;
+
+ @JSONField(name = "uTxnSize")
+ private Integer uTxnSize;
+
+ @JSONField(name = "Mode")
+ private String mode;
+
+ @JSONField(name = "Function")
+ private String function;
+
+ // target: only some relay tx has, represents event name
+ @JSONField(name = "Target")
+ private String target;
+
+ //Input字段不嵌套
+ @JSONField(name = "Input")
+ private Object input;
+
+ @JSONField(name = "Invocation")
+ private Invocation invocation;
+
+ @JSONField(name = "ExecStage")
+ private String stage;
+
+ @JSONField(name = "Height")
+ private Long height;
+
+ // Shard 字段是一个数组 [shard, shardOrder]
+ @JSONField(name = "Shard")
+ private List shard;
+
+ @JSONField(name = "ConfirmState")
+ private String confirmState;
+
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Builder
+ public static class Invocation {
+
+ @JSONField(name = "Status")
+ private String status;
+
+ @JSONField(name = "Return")
+ private List returnValues;
+
+ @JSONField(name = "GasFee")
+ private String gasFee;
+
+ @JSONField(name = "CoinDelta")
+ private String coinDelta;
+
+ @JSONField(name = "Relays")
+ private List relays;
+ }
+
+
+ public String getInputAsString() {
+ if (input instanceof String) {
+ return (String) input;
+ }
+ return null;
+ }
+
+ public JSONObject getInputAsJson() {
+ if (input instanceof JSONObject) {
+ return (JSONObject) input;
+ }
+ // 如果 input 是 JSON 字符串,也尝试解析
+ if (input instanceof String && ((String) input).trim().startsWith("{")) {
+ return JSON.parseObject((String) input);
+ }
+ return null;
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransactionBlock.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransactionBlock.java
new file mode 100644
index 00000000..2430d9e2
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/DioxideTransactionBlock.java
@@ -0,0 +1,106 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class DioxideTransactionBlock {
+
+ @JSONField(name = "Size")
+ private Integer size;
+
+ @JSONField(name = "Version")
+ private Integer version;
+
+ @JSONField(name = "Scope")
+ private String scope;
+
+ // [shard_index, shard_order]
+ @JSONField(name = "Shard")
+ private List shard;
+
+ @JSONField(name = "Prev")
+ private String prev;
+
+ @JSONField(name = "ScheduledTxnCount")
+ private Integer scheduledTxnCount;
+
+ @JSONField(name = "UserInitiatedTxnCount")
+ private Integer userInitiatedTxnCount;
+
+ @JSONField(name = "IntraRelayTxnCount")
+ private Integer intraRelayTxnCount;
+
+ @JSONField(name = "InboundRelayTxnCount")
+ private Integer inboundRelayTxnCount;
+
+ @JSONField(name = "OutboundRelayTxnCount")
+ private Integer outboundRelayTxnCount;
+
+ @JSONField(name = "DeferredRelayTxnCount")
+ private Integer deferredRelayTxnCount;
+
+ @JSONField(name = "DispatchedRelayTxnCount")
+ private Integer dispatchedRelayTxnCount;
+
+ @JSONField(name = "ExecutionCount")
+ private Integer executionCount;
+
+ @JSONField(name = "ConsensusHeaderHash")
+ private String consensusHeaderHash;
+
+ @JSONField(name = "ConfirmedTxnMerkle")
+ private String confirmedTxnMerkle;
+
+ @JSONField(name = "ChainStateMerkle")
+ private String chainStateMerkle;
+
+ @JSONField(name = "Hash")
+ private String hash;
+
+ @JSONField(name = "Height")
+ private Long height;
+
+ @JSONField(name = "Timestamp")
+ private Long timestamp;
+
+ @JSONField(name = "Miner")
+ private String miner;
+
+ @JSONField(name = "State")
+ private String state;
+
+ @JSONField(name = "Transactions")
+ private Transactions transactions;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Builder
+ public static class Transactions {
+
+ @JSONField(name = "Scheduled")
+ private List scheduled;
+
+ @JSONField(name = "DispatchedRelays")
+ private List dispatchedRelays;
+
+ @JSONField(name = "OutboundRelays")
+ private List outboundRelays;
+
+ @JSONField(name = "Deferred")
+ private List deferred;
+
+ @JSONField(name = "Confirmed")
+ private List confirmed;
+ }
+}
+
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/RpcResponse.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/RpcResponse.java
new file mode 100644
index 00000000..72651cf1
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/core/RpcResponse.java
@@ -0,0 +1,60 @@
+package com.alipay.antchain.bridge.plugins.dioxide.core;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+@Data
+public class RpcResponse {
+
+ /**
+ * rsp 响应说明,标识是对哪个模块动作的响应
+ */
+ @JSONField(name = "rsp")
+ private String rsp;
+
+ /**
+ * ret 返回结果
+ * - 成功时是 JSON 对象
+ * - 失败时是错误说明字符串
+ */
+ @JSONField(name = "ret")
+ private Object ret;
+
+ /**
+ * err 响应失败时的错误码
+ * 成功响应时为 null
+ */
+ @JSONField(name = "err")
+ private Integer err;
+
+ /**
+ * 判断响应是否成功
+ */
+ @JSONField(serialize = false)
+ public boolean isSuccess() {
+ return err == null;
+ }
+
+ /**
+ * 获取 ret 对象,如果是 JSON,返回 JSONObject
+ */
+ @JSONField(serialize = false)
+ public JSONObject getSuccessResponse() {
+ if (ret instanceof JSONObject) {
+ return (JSONObject) ret;
+ }
+ return null;
+ }
+
+ /**
+ * 获取 ret 字符串,如果失败时 ret 是 string
+ */
+ @JSONField(serialize = false)
+ public String getFailResponse() {
+ if (ret instanceof String) {
+ return (String) ret;
+ }
+ return null;
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Contracts.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Contracts.java
new file mode 100644
index 00000000..5cb2627f
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Contracts.java
@@ -0,0 +1,18 @@
+package com.alipay.antchain.bridge.plugins.dioxide.gcl;
+
+/**
+ * AUTO-GENERATED BY GclCodegen
+ * DO NOT EDIT MANUALLY
+ */
+public final class Contracts {
+
+ /** source: ../onchain-plugin/AppContract.gcl */
+ public static final String APPCONTRACT = "aW1wb3J0IElDb250cmFjdFVzaW5nU0RQOwppbXBvcnQgSVNEUE1lc3NhZ2U7Cgpjb250cmFjdCBBcHBDb250cmFjdCBpbXBsZW1lbnRzIElDb250cmFjdFVzaW5nU0RQLkNvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UgewoKICAgIC8vIOa2iOaBr+WIl+ihqOe7k+aehCAtIOeUqOS6juWtmOWCqOWkmuadoea2iOaBrwogICAgc3RydWN0IE1lc3NhZ2VMaXN0IHsKICAgICAgICBhcnJheTxhcnJheTx1aW50OD4+IG1lc3NhZ2VzOwogICAgfQoKICAgIEBnbG9iYWwgYWRkcmVzcyBvd25lcjsKICAgIEBnbG9iYWwgdWludDY0IHNkcENvbnRyYWN0SWQ7CiAgICBAZ2xvYmFsIGFkZHJlc3Mgc2RwQWRkcmVzczsKCiAgICBAZ2xvYmFsIGFycmF5PHVpbnQ4PiBsYXN0X3VvX21zZzsKICAgIEBnbG9iYWwgYXJyYXk8dWludDg+IGxhc3RfbXNnOwoKICAgIC8vIOS9v+eUqCBoYXNoIOS9nOS4uumUru+8iOWvueW6lCBTb2xpZGl0eSDnmoQgYnl0ZXMzMu+8iQogICAgLy8g6YCa6L+HIHN0cnVjdCDlsIHoo4XmnaXlrZjlgqjmtojmga/mlbDnu4TliJfooagKICAgIEBnbG9iYWwgbWFwPGhhc2gsIE1lc3NhZ2VMaXN0PiByZWN2TXNnOyAgLy8gaGFzaChhdXRob3IpIC0+IE1lc3NhZ2VMaXN0CiAgICBAZ2xvYmFsIG1hcDxoYXNoLCBNZXNzYWdlTGlzdD4gc2VuZE1zZzsgIC8vIGhhc2gocmVjZWl2ZXIpIC0+IE1lc3NhZ2VMaXN0CgogICAgQGdsb2JhbCBmdW5jdGlvbiBvbl9kZXBsb3koYWRkcmVzcyBfb3duZXIpIHsKICAgICAgICBvd25lciA9IF9vd25lcjsKICAgICAgICBfX2RlYnVnLnByaW50KCJbb25fZGVwbG95XSBBcHBDb250cmFjdCBkZXBsb3llZCAtIGFkZHJlc3M6IiwgX19hZGRyZXNzKCksICIgaWQ6IiwgX19pZCgpKTsKICAgIH0KCiAgICAvLyDovoXliqnlh73mlbDvvJrlsIYgYXJyYXk8dWludDg+IOi9rOaNouS4uiBoYXNo77yI55So5L2cIG1hcCDplK7vvIkKICAgIEBnbG9iYWwgZnVuY3Rpb24gaGFzaCBieXRlc1RvSGFzaChhcnJheTx1aW50OD4gZGF0YSkgY29uc3QgewogICAgICAgIC8vIEdDTCDlj6/ku6Xnm7TmjqXku44gYXJyYXk8dWludDg+IOaehOmAoCBoYXNoCiAgICAgICAgcmV0dXJuIGhhc2goZGF0YSk7CiAgICB9CgogICAgQGFkZHJlc3MgZnVuY3Rpb24gc2V0UHJvdG9jb2wodWludDY0IF9wcm90b2NvbENvbnRyYWN0SWQsIGFkZHJlc3MgX3Byb3RvY29sQWRkcmVzcykgcHVibGljIGV4cG9ydCB7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQoX190cmFuc2FjdGlvbi5nZXRfc2VuZGVyKCkgPT0gb3duZXIpOwogICAgICAgIHJlbGF5QGdsb2JhbCAoXl9wcm90b2NvbENvbnRyYWN0SWQsIF5fcHJvdG9jb2xBZGRyZXNzKSB7CiAgICAgICAgICAgIHNkcENvbnRyYWN0SWQgPSBfcHJvdG9jb2xDb250cmFjdElkOwogICAgICAgICAgICBzZHBBZGRyZXNzID0gX3Byb3RvY29sQWRkcmVzczsKICAgICAgICAgICAgX19kZWJ1Zy5wcmludCgiW3NldFByb3RvY29sXSBjb250cmFjdElkOiAiLCBzZHBDb250cmFjdElkLCAiIGFkZHJlc3M6ICIsIHNkcEFkZHJlc3MpOwogICAgICAgIH0KICAgIH0KCiAgICAvLyDnp4HmnInovoXliqnlh73mlbDvvJrlpITnkIbmjqXmlLbliLDnmoTmtojmga8KICAgIEBnbG9iYWwgZnVuY3Rpb24gX3Byb2Nlc3NSZWNlaXZlZE1lc3NhZ2UoYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbiwgYXJyYXk8dWludDg+IGF1dGhvciwgYXJyYXk8dWludDg+IG1lc3NhZ2UsIGJvb2wgaXNPcmRlcmVkKSB7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQoX190cmFuc2FjdGlvbi5nZXRfc2VuZGVyKCkgPT0gc2RwQWRkcmVzcyk7CgogICAgICAgIC8vIOWtmOWCqOacgOaWsOa2iOaBr+WIsOWvueW6lOeahOWPmOmHjwogICAgICAgIGlmIChpc09yZGVyZWQpIHsKICAgICAgICAgICAgbGFzdF9tc2cuc2V0X2xlbmd0aCgwdTMyKTsKICAgICAgICAgICAgZm9yKHVpbnQzMiBpPTB1MzI7IGkgPCBtZXNzYWdlLmxlbmd0aCgpOyBpKyspIHsKICAgICAgICAgICAgICAgIGxhc3RfbXNnLnB1c2gobWVzc2FnZVtpXSk7CiAgICAgICAgICAgIH0KICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBsYXN0X3VvX21zZy5zZXRfbGVuZ3RoKDB1MzIpOwogICAgICAgICAgICBmb3IodWludDMyIGk9MHUzMjsgaSA8IG1lc3NhZ2UubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICAgICAgbGFzdF91b19tc2cucHVzaChtZXNzYWdlW2ldKTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8g5bCG5raI5oGv5re75Yqg5Yiw5Y6G5Y+y6K6w5b2V77yI5L2/55SoIGhhc2gg5L2c5Li66ZSu77yJCiAgICAgICAgaGFzaCBhdXRob3JIYXNoID0gYnl0ZXNUb0hhc2goYXV0aG9yKTsKICAgICAgICBhcnJheTx1aW50OD4gbXNnQ29weTsKICAgICAgICBmb3IodWludDMyIGk9MHUzMjsgaSA8IG1lc3NhZ2UubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICBtc2dDb3B5LnB1c2gobWVzc2FnZVtpXSk7CiAgICAgICAgfQogICAgICAgIC8vIOehruS/nSBNZXNzYWdlTGlzdCDlrZjlnKjvvIzlpoLmnpzkuI3lrZjlnKjliJnliJvlu7oKICAgICAgICBpZiAoIXJlY3ZNc2cuaGFzKGF1dGhvckhhc2gpKSB7CiAgICAgICAgICAgIE1lc3NhZ2VMaXN0IG5ld0xpc3Q7CiAgICAgICAgICAgIHJlY3ZNc2dbYXV0aG9ySGFzaF0gPSBuZXdMaXN0OwogICAgICAgIH0KICAgICAgICByZWN2TXNnW2F1dGhvckhhc2hdLm1lc3NhZ2VzLnB1c2gobXNnQ29weSk7CgogICAgICAgIC8vIOaJk+WNsOS6i+S7tuaXpeW/lwogICAgICAgIF9fZGVidWcucHJpbnQoIltFdmVudF0gcmVjdkNyb3NzY2hhaW5Nc2cgLSBzZW5kZXJEb21haW46Iiwgc2VuZGVyRG9tYWluLCAiIGF1dGhvcjoiLCBhdXRob3IsICIgbWVzc2FnZToiLCBtZXNzYWdlLCAiIGlzT3JkZXJlZDoiLCBpc09yZGVyZWQpOwogICAgfQoKICAgIEBnbG9iYWwgZnVuY3Rpb24gcmVjdlVub3JkZXJlZE1lc3NhZ2UoYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbiwgYXJyYXk8dWludDg+IGF1dGhvciwgYXJyYXk8dWludDg+IG1lc3NhZ2UpIHB1YmxpYyBleHBvcnQgewogICAgICAgIF9wcm9jZXNzUmVjZWl2ZWRNZXNzYWdlKHNlbmRlckRvbWFpbiwgYXV0aG9yLCBtZXNzYWdlLCBmYWxzZSk7CiAgICB9CgogICAgQGdsb2JhbCBmdW5jdGlvbiByZWN2TWVzc2FnZShhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluLCBhcnJheTx1aW50OD4gYXV0aG9yLCBhcnJheTx1aW50OD4gbWVzc2FnZSkgcHVibGljIGV4cG9ydCB7CiAgICAgICAgX3Byb2Nlc3NSZWNlaXZlZE1lc3NhZ2Uoc2VuZGVyRG9tYWluLCBhdXRob3IsIG1lc3NhZ2UsIHRydWUpOwogICAgfQoKICAgIC8vIOengeaciei+heWKqeWHveaVsO+8muWkhOeQhuWPkemAgea2iOaBrwogICAgQGFkZHJlc3MgZnVuY3Rpb24gX3Byb2Nlc3NTZW5kTWVzc2FnZShhcnJheTx1aW50OD4gcmVjZWl2ZXJEb21haW4sIGFycmF5PHVpbnQ4PiByZWNlaXZlciwgYXJyYXk8dWludDg+IG1lc3NhZ2UsIGJvb2wgaXNPcmRlcmVkKSB7CiAgICAgICAgdWludDY0IHNlbmRlcklkID0gdWludDY0KF9faWQoKSk7CiAgICAgICAgSVNEUE1lc3NhZ2UuU0RQTWVzc2FnZUludGVyZmFjZSBTRFBNc2cgPSBJU0RQTWVzc2FnZS5TRFBNZXNzYWdlSW50ZXJmYWNlKHNkcENvbnRyYWN0SWQpOwoKICAgICAgICAvLyDnm7TmjqXosIPnlKggU0RQTXNnIOeahOWPkemAgeWHveaVsAogICAgICAgIGlmIChpc09yZGVyZWQpIHsKICAgICAgICAgICAgU0RQTXNnLnNlbmRNZXNzYWdlKHJlY2VpdmVyRG9tYWluLCByZWNlaXZlciwgbWVzc2FnZSwgc2VuZGVySWQpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIFNEUE1zZy5zZW5kVW5vcmRlcmVkTWVzc2FnZShyZWNlaXZlckRvbWFpbiwgcmVjZWl2ZXIsIG1lc3NhZ2UsIHNlbmRlcklkKTsKICAgICAgICB9CgogICAgICAgIC8vIOWwhuWPkemAgeeahOa2iOaBr+a3u+WKoOWIsOWOhuWPsuiusOW9lQogICAgICAgIHJlbGF5QGdsb2JhbCAoXnJlY2VpdmVyLCBebWVzc2FnZSkgewogICAgICAgICAgICBoYXNoIHJlY2VpdmVySGFzaCA9IGJ5dGVzVG9IYXNoKHJlY2VpdmVyKTsKICAgICAgICAgICAgYXJyYXk8dWludDg+IG1zZ0NvcHk7CiAgICAgICAgICAgIGZvcih1aW50MzIgaT0wdTMyOyBpIDwgbWVzc2FnZS5sZW5ndGgoKTsgaSsrKSB7CiAgICAgICAgICAgICAgICBtc2dDb3B5LnB1c2gobWVzc2FnZVtpXSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLy8g56Gu5L+dIE1lc3NhZ2VMaXN0IOWtmOWcqO+8jOWmguaenOS4jeWtmOWcqOWImeWIm+W7ugogICAgICAgICAgICBpZiAoIXNlbmRNc2cuaGFzKHJlY2VpdmVySGFzaCkpIHsKICAgICAgICAgICAgICAgIE1lc3NhZ2VMaXN0IG5ld0xpc3Q7CiAgICAgICAgICAgICAgICBzZW5kTXNnW3JlY2VpdmVySGFzaF0gPSBuZXdMaXN0OwogICAgICAgICAgICB9CiAgICAgICAgICAgIHNlbmRNc2dbcmVjZWl2ZXJIYXNoXS5tZXNzYWdlcy5wdXNoKG1zZ0NvcHkpOwogICAgICAgIH0KCiAgICAgICAgLy8g5omT5Y2w5LqL5Lu25pel5b+XCiAgICAgICAgX19kZWJ1Zy5wcmludCgiW0V2ZW50XSBzZW5kQ3Jvc3NjaGFpbk1zZyAtIHJlY2VpdmVyRG9tYWluOiIsIHJlY2VpdmVyRG9tYWluLCAiIHJlY2VpdmVyOiIsIHJlY2VpdmVyLCAiIG1lc3NhZ2U6IiwgbWVzc2FnZSwgIiBpc09yZGVyZWQ6IiwgaXNPcmRlcmVkKTsKICAgIH0KCiAgICBAYWRkcmVzcyBmdW5jdGlvbiBzZW5kVW5vcmRlcmVkTWVzc2FnZShhcnJheTx1aW50OD4gcmVjZWl2ZXJEb21haW4sIGFycmF5PHVpbnQ4PiByZWNlaXZlciwgYXJyYXk8dWludDg+IG1lc3NhZ2UpIHB1YmxpYyBleHBvcnQgewogICAgICAgIF9wcm9jZXNzU2VuZE1lc3NhZ2UocmVjZWl2ZXJEb21haW4sIHJlY2VpdmVyLCBtZXNzYWdlLCBmYWxzZSk7CiAgICB9CgogICAgQGFkZHJlc3MgZnVuY3Rpb24gc2VuZE1lc3NhZ2UoYXJyYXk8dWludDg+IHJlY2VpdmVyRG9tYWluLCBhcnJheTx1aW50OD4gcmVjZWl2ZXIsIGFycmF5PHVpbnQ4PiBtZXNzYWdlKSBwdWJsaWMgZXhwb3J0IHsKICAgICAgICBfcHJvY2Vzc1NlbmRNZXNzYWdlKHJlY2VpdmVyRG9tYWluLCByZWNlaXZlciwgbWVzc2FnZSwgdHJ1ZSk7CiAgICB9CgogICAgLy8gR2V0dGVyIOWHveaVsAogICAgQGdsb2JhbCBmdW5jdGlvbiBhcnJheTx1aW50OD4gZ2V0TGFzdFVvTXNnKCkgZXhwb3J0IGNvbnN0IHsKICAgICAgICBhcnJheTx1aW50OD4gcmVzdWx0OwogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgbGFzdF91b19tc2cubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICByZXN1bHQucHVzaChsYXN0X3VvX21zZ1tpXSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiByZXN1bHQ7CiAgICB9CgogICAgQGdsb2JhbCBmdW5jdGlvbiBhcnJheTx1aW50OD4gZ2V0TGFzdE1zZygpIGV4cG9ydCBjb25zdCB7CiAgICAgICAgYXJyYXk8dWludDg+IHJlc3VsdDsKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IGxhc3RfbXNnLmxlbmd0aCgpOyBpKyspIHsKICAgICAgICAgICAgcmVzdWx0LnB1c2gobGFzdF9tc2dbaV0pOwogICAgICAgIH0KICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgfQoKICAgIC8vIEdldHRlciDlh73mlbDvvJrojrflj5bmjqXmlLbliLDnmoTmtojmga/liJfooajvvIjlr7nlupQgU29saWRpdHkg55qEIHB1YmxpYyBtYXBwaW5n77yJCiAgICBAZ2xvYmFsIGZ1bmN0aW9uIGFycmF5PGFycmF5PHVpbnQ4Pj4gZ2V0UmVjdk1zZyhhcnJheTx1aW50OD4gYXV0aG9yKSBleHBvcnQgY29uc3QgewogICAgICAgIGhhc2ggYXV0aG9ySGFzaCA9IGJ5dGVzVG9IYXNoKGF1dGhvcik7CiAgICAgICAgYXJyYXk8YXJyYXk8dWludDg+PiByZXN1bHQ7CiAgICAgICAgaWYgKCFyZWN2TXNnLmhhcyhhdXRob3JIYXNoKSkgewogICAgICAgICAgICByZXR1cm4gcmVzdWx0OyAgLy8g6L+U5Zue56m65pWw57uECiAgICAgICAgfQogICAgICAgIGNvbnN0IE1lc3NhZ2VMaXN0IG1zZ0xpc3QgPSByZWN2TXNnW2F1dGhvckhhc2hdOwogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgbXNnTGlzdC5tZXNzYWdlcy5sZW5ndGgoKTsgaSsrKSB7CiAgICAgICAgICAgIHJlc3VsdC5wdXNoKG1zZ0xpc3QubWVzc2FnZXNbaV0pOwogICAgICAgIH0KICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgfQoKICAgIC8vIEdldHRlciDlh73mlbDvvJrojrflj5blj5HpgIHnmoTmtojmga/liJfooajvvIjlr7nlupQgU29saWRpdHkg55qEIHB1YmxpYyBtYXBwaW5n77yJCiAgICBAZ2xvYmFsIGZ1bmN0aW9uIGFycmF5PGFycmF5PHVpbnQ4Pj4gZ2V0U2VuZE1zZyhhcnJheTx1aW50OD4gcmVjZWl2ZXIpIGV4cG9ydCBjb25zdCB7CiAgICAgICAgaGFzaCByZWNlaXZlckhhc2ggPSBieXRlc1RvSGFzaChyZWNlaXZlcik7CiAgICAgICAgYXJyYXk8YXJyYXk8dWludDg+PiByZXN1bHQ7CiAgICAgICAgaWYgKCFzZW5kTXNnLmhhcyhyZWNlaXZlckhhc2gpKSB7CiAgICAgICAgICAgIHJldHVybiByZXN1bHQ7ICAvLyDov5Tlm57nqbrmlbDnu4QKICAgICAgICB9CiAgICAgICAgY29uc3QgTWVzc2FnZUxpc3QgbXNnTGlzdCA9IHNlbmRNc2dbcmVjZWl2ZXJIYXNoXTsKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IG1zZ0xpc3QubWVzc2FnZXMubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICByZXN1bHQucHVzaChtc2dMaXN0Lm1lc3NhZ2VzW2ldKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHJlc3VsdDsKICAgIH0KCiAgICAvLyBHZXR0ZXIg5Ye95pWw77ya6I635Y+W5o6l5pS25raI5oGv55qE5pWw6YePCiAgICBAZ2xvYmFsIGZ1bmN0aW9uIHVpbnQzMiBnZXRSZWN2TXNnQ291bnQoYXJyYXk8dWludDg+IGF1dGhvcikgZXhwb3J0IGNvbnN0IHsKICAgICAgICBoYXNoIGF1dGhvckhhc2ggPSBieXRlc1RvSGFzaChhdXRob3IpOwogICAgICAgIGlmICghcmVjdk1zZy5oYXMoYXV0aG9ySGFzaCkpIHsKICAgICAgICAgICAgcmV0dXJuIDB1MzI7CiAgICAgICAgfQogICAgICAgIHJldHVybiByZWN2TXNnW2F1dGhvckhhc2hdLm1lc3NhZ2VzLmxlbmd0aCgpOwogICAgfQoKICAgIC8vIEdldHRlciDlh73mlbDvvJrojrflj5blj5HpgIHmtojmga/nmoTmlbDph48KICAgIEBnbG9iYWwgZnVuY3Rpb24gdWludDMyIGdldFNlbmRNc2dDb3VudChhcnJheTx1aW50OD4gcmVjZWl2ZXIpIGV4cG9ydCBjb25zdCB7CiAgICAgICAgaGFzaCByZWNlaXZlckhhc2ggPSBieXRlc1RvSGFzaChyZWNlaXZlcik7CiAgICAgICAgaWYgKCFzZW5kTXNnLmhhcyhyZWNlaXZlckhhc2gpKSB7CiAgICAgICAgICAgIHJldHVybiAwdTMyOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2VuZE1zZ1tyZWNlaXZlckhhc2hdLm1lc3NhZ2VzLmxlbmd0aCgpOwogICAgfQp9";
+
+ /** source: ../onchain-plugin/AuthMsg.gcl */
+ public static final String AUTHMSG = "aW1wb3J0IElBdXRoTWVzc2FnZTsKaW1wb3J0IElTdWJQcm90b2NvbDsKaW1wb3J0IEFNTGliOwppbXBvcnQgVXRpbHM7Cgpjb250cmFjdCBBdXRoTXNnIGltcGxlbWVudHMgSUF1dGhNZXNzYWdlLkF1dGhNZXNzYWdlSW50ZXJmYWNlIHsKCiAgICBzdHJ1Y3QgU3ViUHJvdG9jb2wgewogICAgICAgIHVpbnQzMiBwcm90b2NvbFR5cGU7CiAgICAgICAgdWludDY0IHByb3RvY29sSUQ7CiAgICAgICAgYWRkcmVzcyBwcm90b2NvbEFkZHJlc3M7CiAgICAgICAgYm9vbCBleGlzdDsKICAgIH0KCiAgICBAZ2xvYmFsIGFkZHJlc3Mgb3duZXI7CiAgICBAZ2xvYmFsIGFkZHJlc3MgcmVsYXllcjsKCiAgICBAZ2xvYmFsIG1hcDxhZGRyZXNzLCBTdWJQcm90b2NvbD4gc3ViUHJvdG9jb2xzOwogICAgQGdsb2JhbCBtYXA8dWludDMyLCBhZGRyZXNzPiBwcm90b2NvbFJvdXRlczsKICAgIEBnbG9iYWwgbWFwPHVpbnQzMiwgdWludDY0PiBwcm90b2NvbElEczsKCiAgICBAZ2xvYmFsIEFNTGliLkF1dGhNZXNzYWdlIGRlY29kZUF1dGhNc2c7CgogICAgQGdsb2JhbCBmdW5jdGlvbiBvbl9kZXBsb3koYWRkcmVzcyBfb3duZXIsIGFkZHJlc3MgX3JlbGF5ZXIpIHsKICAgICAgICBvd25lciA9IF9vd25lcjsKICAgICAgICByZWxheWVyID0gX3JlbGF5ZXI7CgogICAgICAgIF9fZGVidWcucHJpbnQoIltvbl9kZXBsb3ldIEF1dGhNc2cgZGVwbG95ZWQgLSBhZGRyZXNzOiIsIF9fYWRkcmVzcygpLCAiIGlkOiIsIF9faWQoKSk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW29uX2RlcGxveV0gT3duZXI6Iiwgb3duZXIsICIgUmVsYXllcjoiLCByZWxheWVyKTsKICAgIH0KCiAgICBAYWRkcmVzcyBmdW5jdGlvbiBzZXRSZWxheWVyKGFkZHJlc3MgX3JlbGF5ZXIpIHB1YmxpYyBleHBvcnQgewogICAgICAgIF9fZGVidWcuYXNzZXJ0KF9fdHJhbnNhY3Rpb24uZ2V0X3NlbmRlcigpID09IG93bmVyKTsKICAgICAgICByZWxheUBnbG9iYWwgKF5fcmVsYXllcil7CiAgICAgICAgICAgIHJlbGF5ZXIgPSBfcmVsYXllcjsKICAgICAgICB9CiAgICB9CgogICAgQGFkZHJlc3MgZnVuY3Rpb24gc2V0UHJvdG9jb2wodWludDY0IHByb3RvY29sSUQsIGFkZHJlc3MgcHJvdG9jb2xBZGRyZXNzLCB1aW50MzIgcHJvdG9jb2xUeXBlKSBwdWJsaWMgZXhwb3J0IHsKICAgICAgICBfX2RlYnVnLmFzc2VydChfX3RyYW5zYWN0aW9uLmdldF9zZW5kZXIoKSA9PSBvd25lcik7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQoIXN1YlByb3RvY29sc1twcm90b2NvbEFkZHJlc3NdLmV4aXN0KTsKCiAgICAgICAgcmVsYXlAZ2xvYmFsIChecHJvdG9jb2xJRCwgXnByb3RvY29sQWRkcmVzcywgXnByb3RvY29sVHlwZSkgewogICAgICAgICAgICBTdWJQcm90b2NvbCBwOwogICAgICAgICAgICBwLmV4aXN0ID0gdHJ1ZTsKICAgICAgICAgICAgcC5wcm90b2NvbFR5cGUgPSBwcm90b2NvbFR5cGU7CiAgICAgICAgICAgIHAucHJvdG9jb2xJRCA9IHByb3RvY29sSUQ7CiAgICAgICAgICAgIHAucHJvdG9jb2xBZGRyZXNzID0gcHJvdG9jb2xBZGRyZXNzOwogICAgICAgICAgICBzdWJQcm90b2NvbHNbcHJvdG9jb2xBZGRyZXNzXSA9IHA7CiAgICAgICAgICAgIHByb3RvY29sUm91dGVzW3Byb3RvY29sVHlwZV0gPSBwcm90b2NvbEFkZHJlc3M7CiAgICAgICAgICAgIHByb3RvY29sSURzW3Byb3RvY29sVHlwZV0gPSBwcm90b2NvbElEOwoKICAgICAgICAgICAgcmVsYXlAZXh0ZXJuYWwgU3ViUHJvdG9jb2xVcGRhdGUocHJvdG9jb2xUeXBlLCBwcm90b2NvbElELCBwcm90b2NvbEFkZHJlc3MpOwogICAgICAgICAgICBfX2RlYnVnLnByaW50KCJbRXZlbnRdIFN1YlByb3RvY29sVXBkYXRlIC0gdHlwZToiLCBwcm90b2NvbFR5cGUsICIgaWQ6IiwgcHJvdG9jb2xJRCwgIiBhZGRyZXNzOiIsIHByb3RvY29sQWRkcmVzcyk7CiAgICAgICAgfQoKICAgICAgICBhcnJheTx1aW50OD4gdGVzdDsKICAgICAgICB0ZXN0LnB1c2goMTAwdTgpOwogICAgICAgIHRlc3QucHVzaCgxMDF1OCk7CiAgICAgICAgdGVzdC5wdXNoKDEwMnU4KTsKICAgICAgICB0ZXN0LnB1c2goMTAzdTgpOwogICAgICAgIHRlc3QucHVzaCgxMDR1OCk7CiAgICAgICAgdGVzdC5wdXNoKDF1OCk7CiAgICAgICAgdGVzdC5wdXNoKDJ1OCk7CiAgICAgICAgdGVzdC5wdXNoKDMwdTgpOwogICAgICAgIHRlc3QucHVzaCg0MHU4KTsKICAgICAgICByZWxheUBnbG9iYWwgKF50ZXN0KSB7CiAgICAgICAgICAgIHJlbGF5QGV4dGVybmFsIHRlc3RFdmVudCh0ZXN0KTsKICAgICAgICB9CiAgICB9CgogICAgQGdsb2JhbCBmdW5jdGlvbiByZWN2RnJvbVByb3RvY29sKHVpbnQ2NCBzZW5kZXJJRCwgYXJyYXk8dWludDg+IG1lc3NhZ2UpIHB1YmxpYyBleHBvcnQgewogICAgICAgIGFkZHJlc3MgcHJvdG9jb2wgPSBfX3RyYW5zYWN0aW9uLmdldF9zZW5kZXIoKTsKICAgICAgICBfX2RlYnVnLmFzc2VydChzdWJQcm90b2NvbHNbcHJvdG9jb2xdLmV4aXN0KTsKCiAgICAgICAgLy8gdXNlIHZlcnNpb24gMSBmb3Igbm93CiAgICAgICAgQU1MaWIuQXV0aE1lc3NhZ2UgYW1WMTsKICAgICAgICBhbVYxLnZlcnNpb24gPSAxdTMyOwogICAgICAgIGFtVjEuYXV0aG9yID0gVXRpbHMudWludDI1NlRvQnl0ZXMzMih1aW50MjU2KHNlbmRlcklEKSk7CiAgICAgICAgYW1WMS5wcm90b2NvbFR5cGUgPSBzdWJQcm90b2NvbHNbcHJvdG9jb2xdLnByb3RvY29sVHlwZTsKICAgICAgICBhbVYxLmJvZHkgPSBVdGlscy5ieXRlc0NvcHkobWVzc2FnZSk7CgogICAgICAgIGFycmF5PHVpbnQ4PiBBTU1zZyA9IEFNTGliLmVuY29kZUF1dGhNZXNzYWdlKGFtVjEpOwogICAgICAgIF9fZGVidWcucHJpbnQoIltyZWN2RnJvbVByb3RvY29sXSBBdXRoTXNnIGVuY29kZWQ6ICIsIEFNTXNnKTsKICAgICAgICByZWxheUBleHRlcm5hbCBTZW5kQXV0aE1lc3NhZ2UoQU1Nc2cpOwogICAgfQoKICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHJlY3ZQa2dGcm9tUmVsYXllcihhcnJheTx1aW50OD4gcGtnKSBwdWJsaWMgZXhwb3J0IHsKICAgICAgICBfX2RlYnVnLmFzc2VydChfX3RyYW5zYWN0aW9uLmdldF9zZW5kZXIoKSA9PSByZWxheWVyKTsKCiAgICAgICAgX19kZWJ1Zy5wcmludCgiW3JlY3ZQa2dGcm9tUmVsYXllcl0gUmVjZWl2ZWQgVUNQIHBhY2thZ2UsIGxlbmd0aDogIiwgcGtnLmxlbmd0aCgpKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJbcmVjdlBrZ0Zyb21SZWxheWVyXSBGaXJzdCAyMCBieXRlczogIiwgcGtnKTsKCiAgICAgICAgLy8gMS4g6Kej56CBIE1lc3NhZ2VGcm9tUmVsYXllcu+8jOiOt+WPliBzZW5kZXJEb21haW4g5ZKMIHJhd1Jlc3AgKEF1dGhNZXNzYWdlIGJ5dGVzKQogICAgICAgIF9fZGVidWcucHJpbnQoIltyZWN2UGtnRnJvbVJlbGF5ZXJdIEFib3V0IHRvIGNhbGwgQU1MaWIuZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyIik7CiAgICAgICAgQU1MaWIuRGVjb2RlUmVzdWx0IGRyID0gQU1MaWIuZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyKHBrZyk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW3JlY3ZQa2dGcm9tUmVsYXllcl0gQU1MaWIuZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyIGNvbXBsZXRlZCIpOwogICAgICAgIF9fZGVidWcucHJpbnQoIltyZWN2UGtnRnJvbVJlbGF5ZXJdIHNlbmRlckRvbWFpbjogIiwgZHIuc2VuZGVyRG9tYWluKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJbcmVjdlBrZ0Zyb21SZWxheWVyXSByYXdSZXNwIGxlbmd0aDogIiwgZHIucmF3UmVzcC5sZW5ndGgoKSk7CgogICAgICAgIC8vIDIuIOino+eggSBBdXRoTWVzc2FnZQogICAgICAgIEFNTGliLkF1dGhNZXNzYWdlIG1zZyA9IEFNTGliLmRlY29kZUF1dGhNZXNzYWdlKGRyLnJhd1Jlc3ApOwogICAgICAgIF9fZGVidWcucHJpbnQoIltyZWN2UGtnRnJvbVJlbGF5ZXJdIERlY29kZWQgQXV0aE1lc3NhZ2U6ICIsIG1zZyk7CgogICAgICAgIHJlbGF5QGdsb2JhbCAoXm1zZykgewogICAgICAgICAgICBkZWNvZGVBdXRoTXNnID0gbXNnOwogICAgICAgIH0KCiAgICAgICAgLy8gMy4g6aqM6K+B5Y2P6K6u6Lev55Sx5a2Y5ZyoCiAgICAgICAgYWRkcmVzcyB6ZXJvQWRkcmVzczsKICAgICAgICBfX2RlYnVnLmFzc2VydChwcm90b2NvbFJvdXRlc1ttc2cucHJvdG9jb2xUeXBlXSAhPSB6ZXJvQWRkcmVzcyk7CgogICAgICAgIC8vIDQuIOiwg+eUqOS4iuWxguWNj+iuru+8iFNEUE1zZ++8ieeahCByZWN2TWVzc2FnZSwg6Kem5Y+R5o6l5pS25LqL5Lu2CiAgICAgICAgcmVsYXlAZ2xvYmFsIChebXNnLCBeZHIpIHsKICAgICAgICAgICAgdWludDY0IHByb3RvY29sSUQgPSBwcm90b2NvbElEc1ttc2cucHJvdG9jb2xUeXBlXTsKICAgICAgICAgICAgSVN1YlByb3RvY29sLlN1YlByb3RvY29sSW50ZXJmYWNlIHNkcCA9IElTdWJQcm90b2NvbC5TdWJQcm90b2NvbEludGVyZmFjZShwcm90b2NvbElEKTsKICAgICAgICAgICAgc2RwLnJlY3ZNZXNzYWdlKGRyLnNlbmRlckRvbWFpbiwgbXNnLmF1dGhvciwgbXNnLmJvZHkpOwoKICAgICAgICAgICAgcmVsYXlAZXh0ZXJuYWwgcmVjdkF1dGhNZXNzYWdlKGRyLnNlbmRlckRvbWFpbiwgZHIucmF3UmVzcCk7CiAgICAgICAgICAgIF9fZGVidWcucHJpbnQoIltyZWN2UGtnRnJvbVJlbGF5ZXJdIE1lc3NhZ2Ugcm91dGVkIHN1Y2Nlc3NmdWxseSIpOwogICAgICAgIH0KICAgIH0KfQ==";
+
+ /** source: ../onchain-plugin/SDPMsg.gcl */
+ public static final String SDPMSG = "aW1wb3J0IFNEUExpYjsKaW1wb3J0IElBdXRoTWVzc2FnZTsKaW1wb3J0IElDb250cmFjdFVzaW5nU0RQOwppbXBvcnQgSVNEUE1lc3NhZ2U7CmltcG9ydCBJU3ViUHJvdG9jb2w7CmltcG9ydCBVdGlsczsKCmNvbnRyYWN0IFNEUE1zZyBpbXBsZW1lbnRzIElTRFBNZXNzYWdlLlNEUE1lc3NhZ2VJbnRlcmZhY2UsIElTdWJQcm90b2NvbC5TdWJQcm90b2NvbEludGVyZmFjZSB7CgogICAgQGdsb2JhbCBhZGRyZXNzIG93bmVyOwoKICAgIEBnbG9iYWwgdWludDY0IGFtQ29udHJhY3RJZDsKICAgIEBnbG9iYWwgYWRkcmVzcyBhbUFkZHJlc3M7CiAgICBAZ2xvYmFsIGFycmF5PHVpbnQ4PiBsb2NhbERvbWFpbjsKCiAgICBAZ2xvYmFsIG1hcDxoYXNoLCB1aW50MzI+IHNlbmRTZXE7CiAgICBAZ2xvYmFsIG1hcDxoYXNoLCB1aW50MzI+IHJlY3ZTZXE7CgogICAgQGFkZHJlc3MgdWludDMyIHJlc3VsdEluUXVlcnlTRFBNZXNzYWdlU2VxOwoKICAgIEBnbG9iYWwgU0RQTGliLlNEUE1lc3NhZ2UgZGVjb2RlZFNkcE1zZzsKCiAgICBjb25zdCB1aW50MzIgVU5PUkRFUkVEX1NFUVVFTkNFID0gMHhmZmZmZmZmZnUzMjsKCiAgICAvLyBAbm90aWNlIG9ubHkgZm9yIG9yZGVycmVkIG1zZwogICAgY29uc3QgdWludDY0IE1BWF9OT05DRSA9IDB4ZmZmZmZmZmZmZmZmZmZmZnU2NDsKCiAgICBAZ2xvYmFsIGZ1bmN0aW9uIG9uX2RlcGxveShhZGRyZXNzIF9vd25lcikgewogICAgICAgIG93bmVyID0gX293bmVyOwogICAgICAgIF9fZGVidWcucHJpbnQoIltvbl9kZXBsb3ldIFNEUE1zZyBkZXBsb3llZCAtIGFkZHJlc3M6IiwgX19hZGRyZXNzKCksICIgaWQ6IiwgX19pZCgpKTsKICAgIH0KCiAgICBAYWRkcmVzcyBmdW5jdGlvbiBzZXRBbUNvbnRyYWN0KHVpbnQ2NCBfYW1Db250cmFjdElkLCBhZGRyZXNzIF9hbUFkZHJlc3MpIHB1YmxpYyBleHBvcnQgewogICAgICAgIF9fZGVidWcuYXNzZXJ0KF9fdHJhbnNhY3Rpb24uZ2V0X3NlbmRlcigpID09IG93bmVyKTsKICAgICAgICByZWxheUBnbG9iYWwgKF5fYW1Db250cmFjdElkLCBeX2FtQWRkcmVzcykgewogICAgICAgICAgICBhbUNvbnRyYWN0SWQgPSBfYW1Db250cmFjdElkOwogICAgICAgICAgICBhbUFkZHJlc3MgPSBfYW1BZGRyZXNzOwogICAgICAgICAgICBfX2RlYnVnLnByaW50KCJbc2V0QW1Db250cmFjdF0gQU0gQ29udHJhY3QgcmVjb25maWd1cmVkIC0gY29udHJhY3RJZDogIiwgYW1Db250cmFjdElkLCAiIGFkZHJlc3M6ICIsIGFtQWRkcmVzcyk7CiAgICAgICAgfQogICAgfQoKICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHNldExvY2FsRG9tYWluKGFycmF5PHVpbnQ4PiBkb21haW4pIHB1YmxpYyBleHBvcnQgewogICAgICAgIF9fZGVidWcuYXNzZXJ0KF9fdHJhbnNhY3Rpb24uZ2V0X3NlbmRlcigpID09IG93bmVyKTsKICAgICAgICByZWxheUBnbG9iYWwgKF5kb21haW4pIHsKICAgICAgICAgICAgbG9jYWxEb21haW4gPSBkb21haW47CiAgICAgICAgICAgIF9fZGVidWcucHJpbnQoInNldExvY2FsRG9tYWluOiAiLCBsb2NhbERvbWFpbik7CiAgICAgICAgfQogICAgfQoKICAgIC8vIG5vdGljZTogaW4gR0NMLCByZWNlaXZlcklEIGlzIGNvbnZlcnRlZCBmcm9tIHVpbnQ2NCB0byBieXRlczMyCiAgICBAYWRkcmVzcyBmdW5jdGlvbiBzZW5kTWVzc2FnZShhcnJheTx1aW50OD4gcmVjZWl2ZXJEb21haW4sIGFycmF5PHVpbnQ4PiByZWNlaXZlcklELCBhcnJheTx1aW50OD4gbWVzc2FnZSwgdWludDY0IHNlbmRlcklEKSBwdWJsaWMgZXhwb3J0IHsKICAgICAgICBTRFBMaWIuU0RQTWVzc2FnZSBzZHBNZXNzYWdlOwogICAgICAgIHNkcE1lc3NhZ2UucmVjZWl2ZXJEb21haW4gPSBVdGlscy5ieXRlc0NvcHkocmVjZWl2ZXJEb21haW4pOwogICAgICAgIHNkcE1lc3NhZ2UucmVjZWl2ZXIgPSBVdGlscy5ieXRlc0NvcHkocmVjZWl2ZXJJRCk7CiAgICAgICAgc2RwTWVzc2FnZS5tZXNzYWdlID0gVXRpbHMuYnl0ZXNDb3B5KG1lc3NhZ2UpOwogICAgICAgIHNkcE1lc3NhZ2Uuc2VxdWVuY2UgPSBfZ2V0QW5kVXBkYXRlU2VuZFNlcShyZWNlaXZlckRvbWFpbiwgc2VuZGVySUQsIHJlY2VpdmVySUQpOwoKICAgICAgICBhcnJheTx1aW50OD4gcmF3TXNnID0gU0RQTGliLmVuY29kZVNEUE1zZ1YxKHNkcE1lc3NhZ2UpOwogICAgICAgIF9fZGVidWcucHJpbnQoIltzZW5kTWVzc2FnZV0gcmF3TXNnOiAiLCByYXdNc2cpOwoKICAgICAgICByZWxheUBnbG9iYWwgKF5zZW5kZXJJRCwgXnJhd01zZykgewogICAgICAgICAgICBJQXV0aE1lc3NhZ2UuQXV0aE1lc3NhZ2VJbnRlcmZhY2UgYW0gPSBJQXV0aE1lc3NhZ2UuQXV0aE1lc3NhZ2VJbnRlcmZhY2UoYW1Db250cmFjdElkKTsKICAgICAgICAgICAgYW0ucmVjdkZyb21Qcm90b2NvbChzZW5kZXJJRCwgcmF3TXNnKTsKICAgICAgICB9CiAgICB9CgogICAgLy8gbm90aWNlOiBpbiBHQ0wsIHJlY2VpdmVySUQgaXMgY29udmVydGVkIGZyb20gdWludDY0IHRvIGJ5dGVzMzIKICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHNlbmRVbm9yZGVyZWRNZXNzYWdlKGFycmF5PHVpbnQ4PiByZWNlaXZlckRvbWFpbiwgYXJyYXk8dWludDg+IHJlY2VpdmVySUQsIGFycmF5PHVpbnQ4PiBtZXNzYWdlLCB1aW50NjQgc2VuZGVySUQpIHB1YmxpYyBleHBvcnQgewogICAgICAgIFNEUExpYi5TRFBNZXNzYWdlIHNkcE1lc3NhZ2U7CiAgICAgICAgc2RwTWVzc2FnZS5yZWNlaXZlckRvbWFpbiA9IFV0aWxzLmJ5dGVzQ29weShyZWNlaXZlckRvbWFpbik7CiAgICAgICAgc2RwTWVzc2FnZS5yZWNlaXZlciA9IFV0aWxzLmJ5dGVzQ29weShyZWNlaXZlcklEKTsKICAgICAgICBzZHBNZXNzYWdlLm1lc3NhZ2UgPSBVdGlscy5ieXRlc0NvcHkobWVzc2FnZSk7CiAgICAgICAgc2RwTWVzc2FnZS5zZXF1ZW5jZSA9IFVOT1JERVJFRF9TRVFVRU5DRTsKCiAgICAgICAgYXJyYXk8dWludDg+IHJhd01zZyA9IFNEUExpYi5lbmNvZGVTRFBNc2dWMShzZHBNZXNzYWdlKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJbc2VuZFVub3JkZXJlZE1lc3NhZ2VdIHJhd01zZzogIiwgcmF3TXNnKTsKCiAgICAgICAgcmVsYXlAZ2xvYmFsIChec2VuZGVySUQsIF5yYXdNc2cpIHsKICAgICAgICAgICAgSUF1dGhNZXNzYWdlLkF1dGhNZXNzYWdlSW50ZXJmYWNlIGFtID0gSUF1dGhNZXNzYWdlLkF1dGhNZXNzYWdlSW50ZXJmYWNlKGFtQ29udHJhY3RJZCk7CiAgICAgICAgICAgIGFtLnJlY3ZGcm9tUHJvdG9jb2woc2VuZGVySUQsIHJhd01zZyk7CiAgICAgICAgfQogICAgfQoKICAgIEBnbG9iYWwgZnVuY3Rpb24gcmVjdk1lc3NhZ2UoYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbiwgYXJyYXk8dWludDg+IHNlbmRlcklELCBhcnJheTx1aW50OD4gcGtnKSBwdWJsaWMgZXhwb3J0IHsKICAgICAgICBfX2RlYnVnLmFzc2VydChfX3RyYW5zYWN0aW9uLmdldF9zZW5kZXIoKSA9PSBhbUFkZHJlc3MpOwogICAgICAgIC8vIG9ubHkgU0RQdjEgbm93CiAgICAgICAgLy8gdWludDMyIHZlcnNpb24gPSBTRFBMaWIuZ2V0U0RQVmVyc2lvbkZyb20ocGtnKTsKICAgICAgICBfcHJvY2Vzc1NEUHYxKHNlbmRlckRvbWFpbiwgc2VuZGVySUQsIHBrZyk7CiAgICB9CgogICAgQGdsb2JhbCBmdW5jdGlvbiBfcHJvY2Vzc1NEUHYxKGFycmF5PHVpbnQ4PiBzZW5kZXJEb21haW4sIGFycmF5PHVpbnQ4PiBzZW5kZXJJRCwgYXJyYXk8dWludDg+IHBrZykgcHVibGljIHsKICAgICAgICBTRFBMaWIuU0RQTWVzc2FnZSBzZHBNZXNzYWdlID0gU0RQTGliLmRlY29kZVNEUE1zZ1YxKHBrZyk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgic2RwTWVzc2FnZTogIiwgc2RwTWVzc2FnZSk7CgogICAgICAgIGRlY29kZWRTZHBNc2cgPSBzZHBNZXNzYWdlOwoKICAgICAgICBfX2RlYnVnLmFzc2VydChVdGlscy5ieXRlc0VxdWFsKHNkcE1lc3NhZ2UucmVjZWl2ZXJEb21haW4sIGxvY2FsRG9tYWluKSk7CgogICAgICAgIGlmIChzZHBNZXNzYWdlLnNlcXVlbmNlID09IFVOT1JERVJFRF9TRVFVRU5DRSkgewogICAgICAgICAgICBfcm91dGVVbm9yZGVyZWRNZXNzYWdlKHNlbmRlckRvbWFpbiwgc2VuZGVySUQsIHNkcE1lc3NhZ2UpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIF9yb3V0ZU9yZGVyZWRNZXNzYWdlKHNlbmRlckRvbWFpbiwgc2VuZGVySUQsIHNkcE1lc3NhZ2UpOwogICAgICAgIH0KCiAgICAgICAgcmVsYXlAZXh0ZXJuYWwgcmVjdk1lc3NhZ2VJblByb3RvY29sKHNlbmRlckRvbWFpbiwgc2VuZGVySUQsIHBrZyk7CiAgICB9CgogICAgQGdsb2JhbCBmdW5jdGlvbiBfcm91dGVVbm9yZGVyZWRNZXNzYWdlKGFycmF5PHVpbnQ4PiBzZW5kZXJEb21haW4sIGFycmF5PHVpbnQ4PiBzZW5kZXJJRCwgU0RQTGliLlNEUE1lc3NhZ2Ugc2RwTWVzc2FnZSkgcHVibGljIHsKICAgICAgICAvLyDku44gcmVjZWl2ZXIgYnl0ZXMg5Lit6I635Y+W5o6l5pS25ZCI57qm55qE5Zyw5Z2ACiAgICAgICAgdWludDY0IHJlY2VpdmVySUQgPSB1aW50NjQoVXRpbHMuYnl0ZXMzMlRvVWludDI1NihzZHBNZXNzYWdlLnJlY2VpdmVyKSk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW19yb3V0ZVVub3JkZXJlZE1lc3NhZ2VdIHJlY2VpdmVySUQ6ICIsIHJlY2VpdmVySUQpOwogICAgICAgIF9fZGVidWcucHJpbnQoIltfcm91dGVVbm9yZGVyZWRNZXNzYWdlXSBzZW5kZXJEb21haW46ICIsIHNlbmRlckRvbWFpbik7CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW19yb3V0ZVVub3JkZXJlZE1lc3NhZ2VdIHNlbmRlcklEOiAiLCBzZW5kZXJJRCk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW19yb3V0ZVVub3JkZXJlZE1lc3NhZ2VdIG1lc3NhZ2U6ICIsIHNkcE1lc3NhZ2UubWVzc2FnZSk7CgogICAgICAgIC8vIOiwg+eUqOaOpeaUtuWQiOe6pueahCByZWN2VW5vcmRlcmVkTWVzc2FnZQogICAgICAgIElDb250cmFjdFVzaW5nU0RQLkNvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UgZGFwcCA9IElDb250cmFjdFVzaW5nU0RQLkNvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UocmVjZWl2ZXJJRCk7CiAgICAgICAgZGFwcC5yZWN2VW5vcmRlcmVkTWVzc2FnZShzZW5kZXJEb21haW4sIHNlbmRlcklELCBzZHBNZXNzYWdlLm1lc3NhZ2UpOwogICAgfQoKICAgIEBnbG9iYWwgZnVuY3Rpb24gX3JvdXRlT3JkZXJlZE1lc3NhZ2UoYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbiwgYXJyYXk8dWludDg+IHNlbmRlcklELCBTRFBMaWIuU0RQTWVzc2FnZSBzZHBNZXNzYWdlKSBwdWJsaWMgewogICAgICAgIHVpbnQzMiBzZXFFeHBlY3RlZCA9IF9nZXRBbmRVcGRhdGVSZWN2U2VxKHNlbmRlckRvbWFpbiwgc2VuZGVySUQsIHNkcE1lc3NhZ2UucmVjZWl2ZXIpOwogICAgICAgIF9fZGVidWcuYXNzZXJ0KHNkcE1lc3NhZ2Uuc2VxdWVuY2UgPT0gc2VxRXhwZWN0ZWQpOwoKICAgICAgICB1aW50NjQgcmVjZWl2ZXJJRCA9IHVpbnQ2NChVdGlscy5ieXRlczMyVG9VaW50MjU2KHNkcE1lc3NhZ2UucmVjZWl2ZXIpKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJbX3JvdXRlT3JkZXJlZE1lc3NhZ2VdIHJlY2VpdmVySUQ6ICIsIHJlY2VpdmVySUQpOwogICAgICAgIF9fZGVidWcucHJpbnQoIltfcm91dGVPcmRlcmVkTWVzc2FnZV0gc2VuZGVyRG9tYWluOiAiLCBzZW5kZXJEb21haW4pOwogICAgICAgIF9fZGVidWcucHJpbnQoIltfcm91dGVPcmRlcmVkTWVzc2FnZV0gc2VuZGVySUQ6ICIsIHNlbmRlcklEKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJbX3JvdXRlT3JkZXJlZE1lc3NhZ2VdIG1lc3NhZ2U6ICIsIHNkcE1lc3NhZ2UubWVzc2FnZSk7CgogICAgICAgIC8vIOiwg+eUqOaOpeaUtuWQiOe6pueahCByZWN2TWVzc2FnZQogICAgICAgIElDb250cmFjdFVzaW5nU0RQLkNvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UgZGFwcCA9IElDb250cmFjdFVzaW5nU0RQLkNvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UocmVjZWl2ZXJJRCk7CiAgICAgICAgZGFwcC5yZWN2TWVzc2FnZShzZW5kZXJEb21haW4sIHNlbmRlcklELCBzZHBNZXNzYWdlLm1lc3NhZ2UpOwogICAgfQoKCiAgICBAZ2xvYmFsIGZ1bmN0aW9uIGFkZHJlc3MgZ2V0QW1BZGRyZXNzKCkgZXhwb3J0IGNvbnN0IHsKICAgICAgICByZXR1cm4gYW1BZGRyZXNzOwogICAgfQoKICAgIEBnbG9iYWwgZnVuY3Rpb24gYXJyYXk8dWludDg+IGdldExvY2FsRG9tYWluKCkgZXhwb3J0IGNvbnN0IHsKICAgICAgICBhcnJheTx1aW50OD4gcmVzdWx0OwogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgbG9jYWxEb21haW4ubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICByZXN1bHQucHVzaChsb2NhbERvbWFpbltpXSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiByZXN1bHQ7CiAgICB9CgogICAgQGFkZHJlc3MgZnVuY3Rpb24gdWludDMyIHF1ZXJ5U0RQTWVzc2FnZVNlcShhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluLCBhcnJheTx1aW50OD4gc2VuZGVySUQsIGFycmF5PHVpbnQ4PiByZWNlaXZlckRvbWFpbiwgYXJyYXk8dWludDg+IHJlY2VpdmVySUQpIGV4cG9ydCB7CiAgICAgICAgcmVsYXlAZ2xvYmFsIChecmVjZWl2ZXJEb21haW4pIHsKICAgICAgICAgICAgX19kZWJ1Zy5hc3NlcnQoVXRpbHMuYnl0ZXNFcXVhbChyZWNlaXZlckRvbWFpbiwgbG9jYWxEb21haW4pKTsKICAgICAgICB9CiAgICAgICAgaGFzaCBzZXFLZXkgPSBTRFBMaWIuZ2V0UmVjZWl2aW5nU2VxSUQoc2VuZGVyRG9tYWluLCBzZW5kZXJJRCwgcmVjZWl2ZXJJRCk7CiAgICAgICAgCiAgICAgICAgdWludDMyIHNlcTsKICAgICAgICByZWxheUBnbG9iYWwgKF5zZXFLZXksIF5zZXEpIHsKICAgICAgICAgICAgc2VxID0gcmVjdlNlcVtzZXFLZXldOwogICAgICAgIH0KICAgICAgICByZXN1bHRJblF1ZXJ5U0RQTWVzc2FnZVNlcSA9IHNlcTsKICAgICAgICByZXR1cm4gc2VxOwogICAgfQoKICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHVpbnQzMiBfZ2V0QW5kVXBkYXRlU2VuZFNlcShhcnJheTx1aW50OD4gcmVjZWl2ZXJEb21haW4sIHVpbnQ2NCBzZW5kZXJJRCwgYXJyYXk8dWludDg+IHJlY2VpdmVyKSBwdWJsaWMgewogICAgICAgIGhhc2ggc2VxS2V5ID0gU0RQTGliLmdldFNlbmRpbmdTZXFJRChyZWNlaXZlckRvbWFpbiwgc2VuZGVySUQsIHJlY2VpdmVyKTsKICAgICAgICB1aW50MzIgc2VxOwogICAgICAgIHJlbGF5QGdsb2JhbCAoXnNlcUtleSwgXnNlcSkgewogICAgICAgICAgICBzZXEgPSBzZW5kU2VxW3NlcUtleV07CiAgICAgICAgICAgIHNlbmRTZXFbc2VxS2V5XSsrOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2VxOwogICAgfQoKICAgIEBnbG9iYWwgZnVuY3Rpb24gdWludDMyIF9nZXRBbmRVcGRhdGVSZWN2U2VxKGFycmF5PHVpbnQ4PiBzZW5kZXJEb21haW4sIGFycmF5PHVpbnQ4PiBzZW5kZXIsIGFycmF5PHVpbnQ4PiByZWNlaXZlcikgcHVibGljIHsKICAgICAgICBoYXNoIHNlcUtleSA9IFNEUExpYi5nZXRSZWNlaXZpbmdTZXFJRChzZW5kZXJEb21haW4sIHNlbmRlciwgcmVjZWl2ZXIpOwogICAgICAgIHVpbnQzMiBzZXEgPSByZWN2U2VxW3NlcUtleV07CiAgICAgICAgcmVjdlNlcVtzZXFLZXldKys7CiAgICAgICAgcmV0dXJuIHNlcTsKICAgIH0KCn0=";
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/GclCodegen.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/GclCodegen.java
new file mode 100644
index 00000000..e59a25b4
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/GclCodegen.java
@@ -0,0 +1,117 @@
+package com.alipay.antchain.bridge.plugins.dioxide.gcl;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.Base64;
+
+public class GclCodegen {
+
+ /**
+ * offchain-plugin 与 onchain-plugin 同级
+ */
+ private static final Path ONCHAIN_ROOT =
+ Paths.get("..", "onchain-plugin").normalize();
+
+ /**
+ * 生成到当前 package 目录
+ */
+ private static final Path OUTPUT_DIR =
+ Paths.get("src", "main", "java",
+ "com", "alipay", "antchain", "bridge",
+ "plugins", "dioxide", "gcl");
+
+ private static final String PKG =
+ "com.alipay.antchain.bridge.plugins.dioxide.gcl";
+
+ public static void main(String[] args) throws Exception {
+ if (!Files.exists(ONCHAIN_ROOT)) {
+ throw new IllegalStateException(
+ "onchain-plugin not found: " + ONCHAIN_ROOT.toAbsolutePath());
+ }
+
+ generateContracts();
+ generateDir("Interfaces", ONCHAIN_ROOT.resolve("interfaces"));
+ generateDir("Libs", ONCHAIN_ROOT.resolve("lib"));
+ }
+
+ /**
+ * onchain-plugin/*.gcl → Contracts.java
+ */
+ private static void generateContracts() throws IOException {
+ StringBuilder body = new StringBuilder();
+
+ try (DirectoryStream stream =
+ Files.newDirectoryStream(ONCHAIN_ROOT, "*.gcl")) {
+
+ for (Path p : stream) {
+ appendField(body, p);
+ }
+ }
+
+ writeJava("Contracts", body);
+ }
+
+ /**
+ * 子目录(interfaces / lib)
+ */
+ private static void generateDir(String className, Path dir) throws IOException {
+ if (!Files.exists(dir)) {
+ return;
+ }
+
+ StringBuilder body = new StringBuilder();
+
+ Files.walk(dir)
+ .filter(Files::isRegularFile)
+ .filter(p -> p.toString().endsWith(".gcl"))
+ .forEach(p -> {
+ try {
+ appendField(body, p);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ writeJava(className, body);
+ }
+
+ private static void appendField(StringBuilder sb, Path file) throws IOException {
+ String base64 = Base64.getEncoder()
+ .encodeToString(Files.readAllBytes(file));
+
+ String constName = file.getFileName()
+ .toString()
+ .replace(".gcl", "")
+ .replaceAll("[^a-zA-Z0-9]", "_")
+ .toUpperCase();
+
+ sb.append(" /** source: ")
+ .append(file.toString().replace("\\", "/"))
+ .append(" */\n");
+
+ sb.append(" public static final String ")
+ .append(constName)
+ .append(" = \"")
+ .append(base64)
+ .append("\";\n\n");
+ }
+
+ private static void writeJava(String className, StringBuilder body) throws IOException {
+ Files.createDirectories(OUTPUT_DIR);
+
+ Path javaFile = OUTPUT_DIR.resolve(className + ".java");
+
+ String code =
+ "package " + PKG + ";\n\n" +
+ "/**\n" +
+ " * AUTO-GENERATED BY GclCodegen\n" +
+ " * DO NOT EDIT MANUALLY\n" +
+ " */\n" +
+ "public final class " + className + " {\n\n" +
+ body +
+ "}\n";
+
+ Files.writeString(javaFile, code, StandardCharsets.UTF_8);
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Interfaces.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Interfaces.java
new file mode 100644
index 00000000..b4526494
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Interfaces.java
@@ -0,0 +1,21 @@
+package com.alipay.antchain.bridge.plugins.dioxide.gcl;
+
+/**
+ * AUTO-GENERATED BY GclCodegen
+ * DO NOT EDIT MANUALLY
+ */
+public final class Interfaces {
+
+ /** source: ../onchain-plugin/interfaces/IAuthMessage.gcl */
+ public static final String IAUTHMESSAGE = "Y29udHJhY3QgSUF1dGhNZXNzYWdlIHsKCi8qKgogKiBAZGV2IHtJQXV0aE1lc3NhZ2V9IGlzIHRoZSBiYXNpYyBwcm90b2NvbCBvZiB0aGUgY3Jvc3MtY2hhaW4gY29tbXVuaWNhdGlvbiBwcm90b2NvbCBzdGFjaywKICogYW5kIEFNIGlzIGEgdmVyaWZpYWJsZSBjcm9zcy1jaGFpbiBtZXNzYWdlIHdoaWNoIG1ha2VzIHRoZSB1cHBlciBsYXllciBwcm90b2NvbCBub3QgbmVlZAogKiB0byBjYXJlIGFib3V0IHRoZSBsZWdpdGltYWN5IG9mIHRoZSBjcm9zcy1jaGFpbiBtZXNzYWdlLgogKgogKiBXaGVuIHRoZSB7SUF1dGhNZXNzYWdlfSBjb250cmFjdCByZWNlaXZlcyB0aGUgY3Jvc3MtY2hhaW4gbWVzc2FnZSBzdWJtaXR0ZWQgYnkgdGhlIHJlbGF5ZXIsCiAqIGl0IHdpbGwgZGVjb2RlIGNyb3NzLWNoYWluIG1lc3NhZ2UuIFRoZW4sIHRoZSBJQXV0aE1lc3NhZ2UgY29udHJhY3Qgd2lsbCBwYXJzZSB0aGUgY3Jvc3MtY2hhaW4KICogbWVzc2FnZSBhbmQgZm9yd2FyZCBpdCB0byB0aGUgc3BlY2lmaWVkIHVwcGVyIGxheWVyIHByb3RvY29sIGNvbnRyYWN0LgogKi8KCiAgICBpbnRlcmZhY2UgQXV0aE1lc3NhZ2VJbnRlcmZhY2UgewoKICAgICAgICAvKioKICAgICAgICAqIEBkZXYgU2V0IHRoZSBTRFAgY29udHJhY3QgYWRkcmVzcyBmb3IgdmVyaWZ5aW5nIHRoZSBwcm9vZiBjb21lIGZyb20gb3V0c2lkZSB0aGUgYmxvY2tjaGFpbi4KICAgICAgICAqIEBwYXJhbSBwcm90b2NvbEFkZHJlc3MgdXBwZXIgcHJvdG9jb2wgY29udHJhY3QgYWRkcmVzcwogICAgICAgICogQHBhcmFtIHByb3RvY29sVHlwZSB0eXBlIG51bWJlciBmb3IgdXBwZXIgcHJvdG9jb2wuIGZvciBleGFtcGxlIHNkcCBwcm90b2NvbCBpcyB6ZXJvLgogICAgICAgICovCiAgICAgICAgQGFkZHJlc3MgZnVuY3Rpb24gc2V0UHJvdG9jb2wodWludDY0IHByb3RvY29sSUQsIGFkZHJlc3MgcHJvdG9jb2xBZGRyZXNzLCB1aW50MzIgcHJvdG9jb2xUeXBlKSBwdWJsaWMgZXhwb3J0OwoKICAgICAgICAvKioKICAgICAgICAqIEBkZXYgVGhlIHVwcGVyIHByb3RvY29sIGNhbGwgdGhpcyBtZXRob2QgdG8gc2VuZCBjcm9zcy1jaGFpbiBtZXNzYWdlLgogICAgICAgICoKICAgICAgICAqIFRoZSB1cHBlciBsYXllciBwcm90b2NvbCBwYXNzZXMgdGhlIG1lc3NhZ2UgY29uc3RydWN0ZWQgYnkgaXRzZWxmIHRvIHRoZSBBTSBjb250cmFjdC4KICAgICAgICAqIFRoZSBBTSBjb250cmFjdCB3aWxsIGdlbmVyYXRlIGV2ZW50IFNlbmRBdXRoTWVzc2FnZSBjb250YWluaW5nIHNlcmlhbGl6ZWQgQXV0aE1lc3NhZ2UuCiAgICAgICAgKgogICAgICAgICogQHBhcmFtIHNlbmRlcklEIHdobyBzZW5kIHRoZSBjcm9zcy1jaGFpbiBtZXNzYWdlIHRvIHVwcGVyIHByb3RvY29sLgogICAgICAgICogQHBhcmFtIG1lc3NhZ2UgbWVzc2FnZSBmcm9tIHVwcGVyIHByb3RvY29sLgogICAgICAgICovCiAgICAgICAgQGdsb2JhbCBmdW5jdGlvbiByZWN2RnJvbVByb3RvY29sKHVpbnQ2NCBzZW5kZXJJRCwgYXJyYXk8dWludDg+IG1lc3NhZ2UpIHB1YmxpYzsKCiAgICAgICAgLyoqCiAgICAgICAgKiBAZGV2IFRoZSByZWxheWVyIGNhbGwgdGhpcyBtZXRob2QgdG8gc3VibWl0IHJhdyBjcm9zcy1jaGFpbiBtZXNzYWdlLgogICAgICAgICoKICAgICAgICAqIFRoZSByZWxheWVyIHN1Ym1pdHMgdGhlIGNyb3NzLWNoYWluIG1lc3NhZ2VzIGluIHRoZSBuZXR3b3JrIHRvIHRoZSBBTSBjb250cmFjdC4KICAgICAgICAqIFRoZSBBTSBjb250cmFjdCBkZWNvZGVzIHRoZSBtZXNzYWdlLgogICAgICAgICogVGhlbiwgdGhlIHVwcGVyLWxheWVyIHByb3RvY29sIG1lc3NhZ2UgaXMgcGFyc2VkIGFuZCB0aGUKICAgICAgICAqIG1lc3NhZ2UgaXMgcGFzc2VkIHRvIHRoZSB1cHBlci1sYXllciBwcm90b2NvbCB0aHJvdWdoIGludGVyLWNvbnRyYWN0IGNhbGxzLgogICAgICAgICoKICAgICAgICAqIEBwYXJhbSBwa2cgcmF3IGNyb3NzLWNoYWluIG1lc3NhZ2Ugc3VibWl0dGVkIGJ5IHJlbGF5ZXIuCiAgICAgICAgKi8KICAgICAgICBAYWRkcmVzcyBmdW5jdGlvbiByZWN2UGtnRnJvbVJlbGF5ZXIoYXJyYXk8dWludDg+IHBrZykgcHVibGljOwoKICAgIH0KfQ==";
+
+ /** source: ../onchain-plugin/interfaces/IContractUsingSDP.gcl */
+ public static final String ICONTRACTUSINGSDP = "Y29udHJhY3QgSUNvbnRyYWN0VXNpbmdTRFAgewogICAgaW50ZXJmYWNlIENvbnRyYWN0VXNpbmdTRFBJbnRlcmZhY2UgewogICAgICAgIC8qKgogICAgICAgICogQGRldiBTRFAgY29udHJhY3Qgd291bGQgY2FsbCB0aGlzIGZ1bmN0aW9uIHRvIGRlbGl2ZXIgdGhlIG1lc3NhZ2UgZnJvbSBzZW5kZXIgY29udHJhY3QKICAgICAgICAqIG9uIHNlbmRlciBibG9ja2NoYWluLiBUaGlzIG1lc3NhZ2Ugc2VudCB3aXRoIG5vIG9yZGVyIGFuZCBpbiBwYXJhbGxlbC4KICAgICAgICAqCiAgICAgICAgKiBAcGFyYW0gc2VuZGVyRG9tYWluIHRoZSBkb21haW4gbmFtZSBvZiB0aGUgc2VuZGluZyBibG9ja2NoYWluLgogICAgICAgICogQHBhcmFtIGF1dGhvciB0aGUgaWQgb2YgdGhlIHNlbmRlci4KICAgICAgICAqIEBwYXJhbSBtZXNzYWdlIHRoZSByYXcgbWVzc2FnZSBmcm9tIHNlbmRlciBjb250cmFjdC4KICAgICAgICAqLwogICAgICAgIEBnbG9iYWwgZnVuY3Rpb24gcmVjdlVub3JkZXJlZE1lc3NhZ2UoYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbiwgYXJyYXk8dWludDg+IGF1dGhvciwgYXJyYXk8dWludDg+IG1lc3NhZ2UpIHB1YmxpYzsKCiAgICAgICAgLyoqCiAgICAgICAgKiBAZGV2IFNEUCBjb250cmFjdCB3b3VsZCBjYWxsIHRoaXMgZnVuY3Rpb24gdG8gZGVsaXZlciB0aGUgbWVzc2FnZSBmcm9tIHNlbmRlciBjb250cmFjdAogICAgICAgICogb24gc2VuZGVyIGJsb2NrY2hhaW4uIFRoaXMgbWVzc2FnZSBzZW50IHdpdGggb3JkZXIuCiAgICAgICAgKgogICAgICAgICogQHBhcmFtIHNlbmRlckRvbWFpbiB0aGUgZG9tYWluIG5hbWUgb2YgdGhlIHNlbmRpbmcgYmxvY2tjaGFpbi4KICAgICAgICAqIEBwYXJhbSBhdXRob3IgdGhlIGlkIG9mIHRoZSBzZW5kZXIuCiAgICAgICAgKiBAcGFyYW0gbWVzc2FnZSB0aGUgcmF3IG1lc3NhZ2UgZnJvbSBzZW5kZXIgY29udHJhY3QuCiAgICAgICAgKi8KICAgICAgICBAZ2xvYmFsIGZ1bmN0aW9uIHJlY3ZNZXNzYWdlKGFycmF5PHVpbnQ4PiBzZW5kZXJEb21haW4sIGFycmF5PHVpbnQ4PiBhdXRob3IsIGFycmF5PHVpbnQ4PiBtZXNzYWdlKSBwdWJsaWM7CiAgICB9Cn0=";
+
+ /** source: ../onchain-plugin/interfaces/ISDPMessage.gcl */
+ public static final String ISDPMESSAGE = "Y29udHJhY3QgSVNEUE1lc3NhZ2UgewogICAgaW50ZXJmYWNlIFNEUE1lc3NhZ2VJbnRlcmZhY2UgewogICAgICAgIC8qKgogICAgICAgICogQGRldiBTbWFydCBjb250cmFjdHMgbmVlZCB0byBjYWxsIHRoaXMgbWV0aG9kIHRvIHNlbmQgb3JkZXJseSBjcm9zcy1jaGFpbiBtZXNzYWdlcyBpbiBTRFB2MS4KICAgICAgICAqCiAgICAgICAgKiBUaGUgZG9tYWluIG5hbWUgb2YgdGhlIHNlbmRpbmcgYmxvY2tjaGFpbiwgdGhlIGFkZHJlc3Mgb2YgdGhlIHNlbmRlciwgdGhlIGRvbWFpbiBuYW1lIG9mIHRoZQogICAgICAgICogcmVjZWl2aW5nIGJsb2NrY2hhaW4gYW5kIHRoZSBhZGRyZXNzIG9mIHRoZSByZWNlaXZlciB1bmlxdWVseSBkZXRlcm1pbmUgYSBjcm9zcy1jaGFpbiBjaGFubmVsLgogICAgICAgICogRWFjaCBjaGFubmVsIG1haW50YWlucyBhIHNlcXVlbmNlIG51bWJlciB3aGljaCBpbmNyZWFzZXMgZnJvbSB6ZXJvIHRvIGVuc3VyZSB0aGF0IHRoZSBjcm9zcy1jaGFpbgogICAgICAgICogbWVzc2FnZXMgb2YgdGhlIGNoYW5uZWwgYXJlIHN1Ym1pdHRlZCBvbiBibG9ja2NoYWluIGluIGFuIG9yZGVybHkgbWFubmVyLgogICAgICAgICoKICAgICAgICAqIEBwYXJhbSByZWNlaXZlckRvbWFpbiB0aGUgZG9tYWluIG5hbWUgb2YgdGhlIHJlY2VpdmluZyBibG9ja2NoYWluLgogICAgICAgICogQHBhcmFtIHJlY2VpdmVySUQgdGhlIGFkZHJlc3Mgb2YgdGhlIHJlY2VpdmVyLgogICAgICAgICogQHBhcmFtIG1lc3NhZ2UgdGhlIHJhdyBtZXNzYWdlIGZyb20gREFwcCBjb250cmFjdHMKICAgICAgICAqLwogICAgICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHNlbmRNZXNzYWdlKGFycmF5PHVpbnQ4PiByZWNlaXZlckRvbWFpbiwgYXJyYXk8dWludDg+IHJlY2VpdmVySUQsIGFycmF5PHVpbnQ4PiBtZXNzYWdlLCB1aW50NjQgc2VuZGVySUQpIHB1YmxpYzsKCiAgICAgICAgLyoqCiAgICAgICAgKiBAZGV2IFNtYXJ0IGNvbnRyYWN0cyBjYWxsIHRoaXMgbWV0aG9kIHRvIHNlbmQgY3Jvc3MtY2hhaW4gbWVzc2FnZXMgb3V0IG9mIG9yZGVyIGluIFNEUHYxLgogICAgICAgICoKICAgICAgICAqIFRoZSBzZXF1ZW5jZSBudW1iZXIgZm9yIHVub3JkZXJlZCBtZXNzYWdlIGlzIGAweGZmZmZmZmZmYCBtZWFucyB0aGF0IHRoaXMgbWVzc2FnZSBpcyBvdXQgb2Ygb3JkZXIuCiAgICAgICAgKgogICAgICAgICogQHBhcmFtIHJlY2VpdmVyRG9tYWluIHRoZSBkb21haW4gbmFtZSBvZiB0aGUgcmVjZWl2aW5nIGJsb2NrY2hhaW4uCiAgICAgICAgKiBAcGFyYW0gcmVjZWl2ZXJJRCB0aGUgYWRkcmVzcyBvZiB0aGUgcmVjZWl2ZXIuCiAgICAgICAgKiBAcGFyYW0gbWVzc2FnZSB0aGUgcmF3IG1lc3NhZ2UgZnJvbSBEQXBwIGNvbnRyYWN0cwogICAgICAgICovCiAgICAgICAgQGFkZHJlc3MgZnVuY3Rpb24gc2VuZFVub3JkZXJlZE1lc3NhZ2UoYXJyYXk8dWludDg+IHJlY2VpdmVyRG9tYWluLCBhcnJheTx1aW50OD4gcmVjZWl2ZXJJRCwgYXJyYXk8dWludDg+IG1lc3NhZ2UsIHVpbnQ2NCBzZW5kZXJJRCkgcHVibGljOwoKICAgICAgICAvKioKICAgICAgICAqIEBkZXYgUXVlcnkgdGhlIGN1cnJlbnQgc2RwIG1lc3NhZ2Ugc2VxdWVuY2UgZm9yIHRoZSBjaGFubmVsIGlkZW50aXRlZCBieSBgc2VuZGVyRG9tYWluYCwgCiAgICAgICAgKiBgc2VuZGVySURgLCBgcmVjZWl2ZXJEb21haW5gIGFuZCBgcmVjZWl2ZXJJRGAuCiAgICAgICAgKgogICAgICAgICogQHBhcmFtIHNlbmRlckRvbWFpbiB0aGUgZG9tYWluIG5hbWUgb2YgdGhlIHNlbmRpbmcgYmxvY2tjaGFpbi4KICAgICAgICAqIEBwYXJhbSBzZW5kZXJJRCB0aGUgaWQgb2YgdGhlIHNlbmRlci4KICAgICAgICAqIEBwYXJhbSByZWNlaXZlckRvbWFpbiB0aGUgZG9tYWluIG5hbWUgb2YgdGhlIHJlY2VpdmluZyBibG9ja2NoYWluLgogICAgICAgICogQHBhcmFtIHJlY2VpdmVySUQgdGhlIGFkZHJlc3Mgb2YgdGhlIHJlY2VpdmVyLgogICAgICAgICovCiAgICAgICAgLy8gQGFkZHJlc3MgZnVuY3Rpb24gdWludDMyIHF1ZXJ5U0RQTWVzc2FnZVNlcShhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluLCBhcnJheTx1aW50OD4gc2VuZGVySUQsIGFycmF5PHVpbnQ4PiByZWNlaXZlckRvbWFpbiwgYXJyYXk8dWludDg+IHJlY2VpdmVySUQpIHB1YmxpYyBleHBvcnQ7CgogICAgICAgIC8qKgogICAgICAgICogQGRldiBTZXQgdGhlIGRvbWFpbiBvZiBsb2NhbCBjaGFpbiB0byBgU0RQYCBjb250cmFjdC4KICAgICAgICAqCiAgICAgICAgKiBAcGFyYW0gZG9tYWluIHRoZSBkb21haW4gbmFtZSBvZiBsb2NhbCBjaGFpbi4KICAgICAgICAqLwogICAgICAgIEBhZGRyZXNzIGZ1bmN0aW9uIHNldExvY2FsRG9tYWluKGFycmF5PHVpbnQ4PiBkb21haW4pIHB1YmxpYyBleHBvcnQ7CiAgICB9Cn0=";
+
+ /** source: ../onchain-plugin/interfaces/ISubProtocol.gcl */
+ public static final String ISUBPROTOCOL = "Y29udHJhY3QgSVN1YlByb3RvY29sIHsKICAgIGludGVyZmFjZSBTdWJQcm90b2NvbEludGVyZmFjZSB7CiAgICAgICAgLyoqCiAgICAgICAgKiBAZGV2IEFNIGNvbnRyYWN0IHRoYXQgd2FudCB0byBmb3J3YXJkIHRoZSBtZXNzYWdlIHRvIHRoZSByZWNlaXZpbmcgYmxvY2tjaGFpbiBuZWVkIHRvIGNhbGwgdGhpcyBtZXRob2QgdG8gc2VuZCBhdXRoIG1lc3NhZ2UuCiAgICAgICAgKgogICAgICAgICogQHBhcmFtIHNlbmRlckRvbWFpbiB0aGUgZG9tYWluIG5hbWUgb2YgdGhlIHNlbmRpbmcgYmxvY2tjaGFpbi4KICAgICAgICAqIEBwYXJhbSBzZW5kZXJJRCB0aGUgYWRkcmVzcyBvZiB0aGUgc2VuZGVyLgogICAgICAgICogQHBhcmFtIHBrZyB0aGUgcmF3IG1lc3NhZ2UgZnJvbSBBTSBjb250cmFjdAogICAgICAgICovCiAgICAgICAgQGdsb2JhbCBmdW5jdGlvbiByZWN2TWVzc2FnZShhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluLCBhcnJheTx1aW50OD4gc2VuZGVySUQsIGFycmF5PHVpbnQ4PiBwa2cpIHB1YmxpYzsKCiAgICAgICAgLyoqCiAgICAgICAgKiBAZGV2IFNEUCBjb250cmFjdCBhcmUgYmFzZWQgb24gdGhlIEF1dGhNZXNzYWdlIGNvbnRyYWN0LiBIZXJlIHdlIHNldCB0aGUgQXV0aE1lc3NhZ2UgY29udHJhY3QgYWRkcmVzcy4KICAgICAgICAqCiAgICAgICAgKiBAcGFyYW0gbmV3QW1Db250cmFjdCB0aGUgYWRkcmVzcyBvZiB0aGUgQXV0aE1lc3NhZ2UgY29udHJhY3QuCiAgICAgICAgKi8KICAgICAgICBAYWRkcmVzcyBmdW5jdGlvbiBzZXRBbUNvbnRyYWN0KHVpbnQ2NCBfYW1Db250cmFjdElkLCBhZGRyZXNzIF9hbUFkZHJlc3MpIHB1YmxpYzsKICAgIH0KfQ==";
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Libs.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Libs.java
new file mode 100644
index 00000000..f100026d
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/gcl/Libs.java
@@ -0,0 +1,30 @@
+package com.alipay.antchain.bridge.plugins.dioxide.gcl;
+
+/**
+ * AUTO-GENERATED BY GclCodegen
+ * DO NOT EDIT MANUALLY
+ */
+public final class Libs {
+
+ /** source: ../onchain-plugin/lib/am/AMLib.gcl */
+ public static final String AMLIB = "aW1wb3J0IFV0aWxzOwppbXBvcnQgU2l6ZU9mOwppbXBvcnQgVHlwZXNUb0J5dGVzOwppbXBvcnQgQnl0ZXNUb1R5cGVzOwppbXBvcnQgVExWVXRpbHM7Cgpjb250cmFjdCBBTUxpYiB7CiAgICAvLyBUTFYg5qCH562+5bi46YePCiAgICBjb25zdCB1aW50MTYgVExWX1BST09GX1JFUVVFU1QgPSA0dTE2OwogICAgY29uc3QgdWludDE2IFRMVl9QUk9PRl9SRVNQT05TRV9CT0RZID0gNXUxNjsKICAgIGNvbnN0IHVpbnQxNiBUTFZfUFJPT0ZfUkVTUE9OU0VfU0lHTkFUVVJFID0gNnUxNjsKICAgIGNvbnN0IHVpbnQxNiBUTFZfUFJPT0ZfRVJST1JfQ09ERSA9IDd1MTY7CiAgICBjb25zdCB1aW50MTYgVExWX1BST09GX0VSUk9SX01TRyA9IDh1MTY7CiAgICBjb25zdCB1aW50MTYgVExWX1BST09GX1NFTkRFUl9ET01BSU4gPSA5dTE2OwogICAgY29uc3QgdWludDE2IFRMVl9QUk9PRl9WRVJTSU9OID0gMTB1MTY7CiAgICBzdHJ1Y3QgQXV0aE1lc3NhZ2UgewogICAgICAgIHVpbnQzMiB2ZXJzaW9uOwogICAgICAgIGFycmF5PHVpbnQ4PiBhdXRob3I7CiAgICAgICAgdWludDMyIHByb3RvY29sVHlwZTsKICAgICAgICBhcnJheTx1aW50OD4gYm9keTsKICAgIH0KCiAgICBzdHJ1Y3QgTWVzc2FnZUZyb21SZWxheWVyIHsKICAgICAgICBhcnJheTx1aW50OD4gaGludHM7CiAgICAgICAgYXJyYXk8dWludDg+IHByb29mRGF0YTsKICAgIH0KCiAgICBzdHJ1Y3QgTWVzc2FnZUZvckFNIHsKICAgICAgICBhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluOwogICAgICAgIGFycmF5PHVpbnQ4PiByYXdNZXNzYWdlOwogICAgfQoKICAgIHN0cnVjdCBSZXF1ZXN0IHsKICAgICAgICBhcnJheTx1aW50OD4gcmVxSUQ7CiAgICAgICAgYXJyYXk8dWludDg+IHJhd1JlcUJvZHk7CiAgICB9CgogICAgc3RydWN0IFByb29mIHsKICAgICAgICBSZXF1ZXN0IHJlcTsKICAgICAgICBhcnJheTx1aW50OD4gcmF3UmVzcEJvZHk7CiAgICAgICAgdWludDMyIGVycm9yQ29kZTsKICAgICAgICBhcnJheTx1aW50OD4gZXJyb3JNc2c7CiAgICAgICAgYXJyYXk8dWludDg+IHNlbmRlckRvbWFpbjsKICAgICAgICB1aW50MTYgdmVyc2lvbjsKICAgIH0KCiAgICBzdHJ1Y3QgRGVjb2RlUmVzdWx0IHsKICAgICAgICBhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluOwogICAgICAgIGFycmF5PHVpbnQ4PiByYXdSZXNwOwogICAgfQoKICAgIC8qKgogICAgICogQG5vdGljZSDku44gVUNQIOWMheino+eggeWHuiBzZW5kZXJEb21haW4g5ZKMIHJhd1Jlc3AgKEF1dGhNZXNzYWdlIGJ5dGVzKQogICAgICogQHBhcmFtIHJhd01lc3NhZ2UgVUNQIOWMhQogICAgICogQHJldHVybiBEZWNvZGVSZXN1bHQg5YyF5ZCrIHNlbmRlckRvbWFpbiDlkowgcmF3UmVzcAogICAgICovCiAgICBmdW5jdGlvbiBEZWNvZGVSZXN1bHQgZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyKGFycmF5PHVpbnQ4PiByYXdNZXNzYWdlKSBwdWJsaWMgY29uc3QgewogICAgICAgIHVpbnQzMiBvZmZzZXQgPSAwdTMyOwogICAgICAgIF9fZGVidWcucHJpbnQoIltkZWNvZGVNZXNzYWdlRnJvbVJlbGF5ZXJdIHJhd01lc3NhZ2UgbGVuZ3RoOiAiLCByYXdNZXNzYWdlLmxlbmd0aCgpKTsKCiAgICAgICAgLy8g6K+75Y+WIGhpbnRzIGxlbmd0aCAoNCBieXRlcywgQklHLUVORElBTiDku47nrKwwLTTlrZfoioIpCiAgICAgICAgLy8g5rOo5oSP77yaVUNQIOWMheWktOS9v+eUqCBiaWctZW5kaWFuIQogICAgICAgIHVpbnQzMiBoaW50c0xlbiA9IDB1MzI7CiAgICAgICAgZm9yICh1aW50MzIgaSA9IDB1MzI7IGkgPCA0dTMyOyBpKyspIHsKICAgICAgICAgICAgaGludHNMZW4gPSAoaGludHNMZW4gPDwgOHUzMikgfCB1aW50MzIocmF3TWVzc2FnZVtvZmZzZXQgKyBpXSk7CiAgICAgICAgfQogICAgICAgIF9fZGVidWcucHJpbnQoIltkZWNvZGVNZXNzYWdlRnJvbVJlbGF5ZXJdIGhpbnRzTGVuOiAiLCBoaW50c0xlbik7CiAgICAgICAgb2Zmc2V0ICs9IDR1MzI7CgogICAgICAgIC8vIOi3s+i/hyBoaW50cwogICAgICAgIG9mZnNldCArPSBoaW50c0xlbjsKCiAgICAgICAgLy8g6K+75Y+WIHByb29mIGxlbmd0aCAoNCBieXRlcywgQklHLUVORElBTikKICAgICAgICB1aW50MzIgcHJvb2ZMZW4gPSAwdTMyOwogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgNHUzMjsgaSsrKSB7CiAgICAgICAgICAgIHByb29mTGVuID0gKHByb29mTGVuIDw8IDh1MzIpIHwgdWludDMyKHJhd01lc3NhZ2Vbb2Zmc2V0ICsgaV0pOwogICAgICAgIH0KICAgICAgICBfX2RlYnVnLnByaW50KCJbZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyXSBwcm9vZkxlbjogIiwgcHJvb2ZMZW4pOwogICAgICAgIG9mZnNldCArPSA0dTMyOwoKICAgICAgICAvLyDor7vlj5YgcHJvb2YgKOS7juW9k+WJjSBvZmZzZXQg5L2N572u5byA5aeLKQogICAgICAgIGFycmF5PHVpbnQ4PiBwcm9vZjsKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IHByb29mTGVuOyBpKyspIHsKICAgICAgICAgICAgcHJvb2YucHVzaChyYXdNZXNzYWdlW29mZnNldCArIGldKTsKICAgICAgICB9CgogICAgICAgIC8vIOino+eggSBwcm9vZgogICAgICAgIHJldHVybiBfZGVjb2RlUHJvb2YocHJvb2YpOwogICAgfQoKICAgIC8qKgogICAgICogQG5vdGljZSDku44gUHJvb2Yg55qEIFRMViDnu5PmnoTkuK3op6PmnpAgc2VuZGVyRG9tYWluIOWSjCByYXdSZXNwQm9keQogICAgICogQHBhcmFtIHJhd1Byb29mIFByb29mIOaVsOaNrgogICAgICogQHJldHVybiBEZWNvZGVSZXN1bHQg5YyF5ZCrIHNlbmRlckRvbWFpbiDlkoznu4/ov4cgVURBRyBSZXNwb25zZSDop6PnoIHnmoQgcmF3UmVzcAogICAgICovCiAgICBmdW5jdGlvbiBEZWNvZGVSZXN1bHQgX2RlY29kZVByb29mKGFycmF5PHVpbnQ4PiByYXdQcm9vZikgcHVibGljIGNvbnN0IHsKICAgICAgICBQcm9vZiBwcm9vZjsKICAgICAgICB1aW50MzIgb2Zmc2V0ID0gNnUzMjsgLy8g6Lez6L+HIFRMViBIZWFkZXIgKDYgYnl0ZXMpCgogICAgICAgIC8vIOino+aekCBUTFYg6aG5CiAgICAgICAgd2hpbGUgKG9mZnNldCA8IHJhd1Byb29mLmxlbmd0aCgpKSB7CiAgICAgICAgICAgIFRMVlV0aWxzLlRMVldpdGhPZmZzZXQgcmVzdWx0ID0gVExWVXRpbHMucGFyc2VUTFZJdGVtKHJhd1Byb29mLCBvZmZzZXQpOwoKICAgICAgICAgICAgaWYgKHJlc3VsdC50bHZJdGVtLnRhZ1R5cGUgPT0gVExWX1BST09GX1NFTkRFUl9ET01BSU4pIHsKICAgICAgICAgICAgICAgIHByb29mLnNlbmRlckRvbWFpbiA9IFV0aWxzLmJ5dGVzQ29weShyZXN1bHQudGx2SXRlbS52YWx1ZSk7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBpZiAocmVzdWx0LnRsdkl0ZW0udGFnVHlwZSA9PSBUTFZfUFJPT0ZfUkVTUE9OU0VfQk9EWSkgewogICAgICAgICAgICAgICAgcHJvb2YucmF3UmVzcEJvZHkgPSBVdGlscy5ieXRlc0NvcHkocmVzdWx0LnRsdkl0ZW0udmFsdWUpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0LnRsdkl0ZW0udGFnVHlwZSA9PSBUTFZfUFJPT0ZfRVJST1JfQ09ERSkgewogICAgICAgICAgICAgICAgcHJvb2YuZXJyb3JDb2RlID0gQnl0ZXNUb1R5cGVzLmJ5dGVzVG9VaW50MzIoMHUzMiwgcmVzdWx0LnRsdkl0ZW0udmFsdWUpOwogICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQudGx2SXRlbS50YWdUeXBlID09IFRMVl9QUk9PRl9FUlJPUl9NU0cpIHsKICAgICAgICAgICAgICAgIHByb29mLmVycm9yTXNnID0gVXRpbHMuYnl0ZXNDb3B5KHJlc3VsdC50bHZJdGVtLnZhbHVlKTsKICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQudGx2SXRlbS50YWdUeXBlID09IFRMVl9QUk9PRl9WRVJTSU9OKSB7CiAgICAgICAgICAgICAgICBwcm9vZi52ZXJzaW9uID0gQnl0ZXNUb1R5cGVzLmJ5dGVzVG9VaW50MTYoMHUzMiwgcmVzdWx0LnRsdkl0ZW0udmFsdWUpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vIFRMVl9QUk9PRl9SRVFVRVNUIOaaguaXtuW/veeVpQoKICAgICAgICAgICAgb2Zmc2V0ID0gcmVzdWx0Lm9mZnNldDsKICAgICAgICB9CgogICAgICAgIC8vIOS7jiBVREFHIFJlc3BvbnNlIOS4reaPkOWPliBBdXRoTWVzc2FnZQogICAgICAgIERlY29kZVJlc3VsdCBkcjsKICAgICAgICBkci5zZW5kZXJEb21haW4gPSBwcm9vZi5zZW5kZXJEb21haW47CiAgICAgICAgZHIucmF3UmVzcCA9IF9kZWNvZGVNc2dCb2R5RnJvbVVEQUdSZXNwKHByb29mLnJhd1Jlc3BCb2R5KTsKCiAgICAgICAgcmV0dXJuIGRyOwogICAgfQoKICAgIC8qKgogICAgICogQG5vdGljZSDku44gVURBRyBSZXNwb25zZSBCb2R5IOS4reaPkOWPliBBdXRoTWVzc2FnZQogICAgICogQHBhcmFtIHJhd0RhdGEgVURBRyBSZXNwb25zZSBCb2R5CiAgICAgKiBAcmV0dXJuIGFycmF5PHVpbnQ4PiBBdXRoTWVzc2FnZSBieXRlcwogICAgICovCiAgICBmdW5jdGlvbiBhcnJheTx1aW50OD4gX2RlY29kZU1zZ0JvZHlGcm9tVURBR1Jlc3AoYXJyYXk8dWludDg+IHJhd0RhdGEpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQocmF3RGF0YS5sZW5ndGgoKSA+IDEydTMyKTsKCiAgICAgICAgLy8gVURBRyBSZXNwb25zZSDmoLzlvI86IFs0IGJ5dGVzIHN0YXR1c11bNCBieXRlcyByZXNlcnZlZF1bNCBieXRlcyBib2R5IGxlbmd0aCAoQklHIEVORElBTildW2JvZHldCiAgICAgICAgLy8g6K+75Y+WIGJvZHkgbGVuZ3RoICjku47nrKw45a2X6IqC5byA5aeL55qENOWtl+iKgu+8jGJpZy1lbmRpYW4pCiAgICAgICAgLy8gYnl0ZXNUb1VpbnQzMihvZmZzZXQpIOivu+WPliBbb2Zmc2V0LTQsIG9mZnNldCnvvIzmiYDku6XopoHor7vlj5bnrKw4LTEy5a2X6IqC77yMb2Zmc2V05bqU6K+l5pivMTIKICAgICAgICB1aW50MzIgYm9keUxlbiA9IEJ5dGVzVG9UeXBlcy5ieXRlc1RvVWludDMyKDEydTMyLCByYXdEYXRhKTsKICAgICAgICAvLyDlj43ovazlrZfoioLluo8gKOS7jiBiaWctZW5kaWFuIOi9rCBsaXR0bGUtZW5kaWFuKQogICAgICAgIGJvZHlMZW4gPSBVdGlscy5yZXZlcnNlVWludDMyKGJvZHlMZW4pOwoKICAgICAgICBfX2RlYnVnLnByaW50KCJbX2RlY29kZU1zZ0JvZHlGcm9tVURBR1Jlc3BdIGJvZHlMZW46ICIsIGJvZHlMZW4pOwogICAgICAgIF9fZGVidWcuYXNzZXJ0KHJhd0RhdGEubGVuZ3RoKCkgPj0gMTJ1MzIgKyBib2R5TGVuKTsKCiAgICAgICAgLy8g5o+Q5Y+WIGJvZHkgKOS7juesrCAxMiDlrZfoioLlvIDlp4vvvIzplb/luqbkuLogYm9keUxlbikKICAgICAgICBhcnJheTx1aW50OD4gYm9keTsKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IGJvZHlMZW47IGkrKykgewogICAgICAgICAgICBib2R5LnB1c2gocmF3RGF0YVsxMnUzMiArIGldKTsKICAgICAgICB9CiAgICAgICAgX19kZWJ1Zy5wcmludCgiW19kZWNvZGVNc2dCb2R5RnJvbVVEQUdSZXNwXSBFeHRyYWN0ZWQgYm9keSBsZW5ndGg6ICIsIGJvZHkubGVuZ3RoKCkpOwoKICAgICAgICByZXR1cm4gYm9keTsKICAgIH0KCiAgICBmdW5jdGlvbiBBdXRoTWVzc2FnZSBkZWNvZGVBdXRoTWVzc2FnZShhcnJheTx1aW50OD4gcGtnKSBwdWJsaWMgY29uc3R7CiAgICAgICAgdWludDMyIG9mZnNldCA9IHBrZy5sZW5ndGgoKTsKICAgICAgICBBdXRoTWVzc2FnZSBhbU1zZzsKICAgICAgICBfX2RlYnVnLnByaW50KCJvZmZzZXQ6ICIsIG9mZnNldCk7CgogICAgICAgIC8vIHZlcnNpb24KICAgICAgICBhbU1zZy52ZXJzaW9uID0gQnl0ZXNUb1R5cGVzLmJ5dGVzVG9VaW50MzIob2Zmc2V0LCBwa2cpOwogICAgICAgIG9mZnNldCAtPSA0dTMyOwogICAgICAgIF9fZGVidWcucHJpbnQoImFtTXNnLnZlcnNpb246ICIsIGFtTXNnLnZlcnNpb24pOwogICAgICAgIF9fZGVidWcucHJpbnQoIm9mZnNldDogIiwgb2Zmc2V0KTsKCiAgICAgICAgX19kZWJ1Zy5hc3NlcnQoYW1Nc2cudmVyc2lvbiA+PSAxdTMyKTsKICAgICAgICBfX2RlYnVnLmFzc2VydChhbU1zZy52ZXJzaW9uIDw9IDJ1MzIpOwoKICAgICAgICAvLyBhdXRob3IKICAgICAgICBCeXRlc1RvVHlwZXMuYnl0ZXNUb0J5dGVzMzIob2Zmc2V0LCBwa2csIGFtTXNnLmF1dGhvcik7CiAgICAgICAgb2Zmc2V0IC09IDMydTMyOwogICAgICAgIF9fZGVidWcucHJpbnQoImFtTXNnLmF1dGhvcjogIiwgYW1Nc2cuYXV0aG9yKTsKICAgICAgICBfX2RlYnVnLnByaW50KCJvZmZzZXQ6ICIsIG9mZnNldCk7CgogICAgICAgIC8vIHByb3RvY29sVHlwZQogICAgICAgIGFtTXNnLnByb3RvY29sVHlwZSA9IEJ5dGVzVG9UeXBlcy5ieXRlc1RvVWludDMyKG9mZnNldCwgcGtnKTsKICAgICAgICBvZmZzZXQgLT0gNHUzMjsKICAgICAgICBfX2RlYnVnLnByaW50KCJhbU1zZy5wcm90b2NvbFR5cGU6ICIsIGFtTXNnLnByb3RvY29sVHlwZSk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgib2Zmc2V0OiAiLCBvZmZzZXQpOwoKICAgICAgICAvLyBib2R5CiAgICAgICAgaWYgKGFtTXNnLnZlcnNpb24gPT0gMXUzMikgewogICAgICAgICAgICBCeXRlc1RvVHlwZXMuYnl0ZXNUb1N1YkJ5dGVzKG9mZnNldCwgcGtnLCBhbU1zZy5ib2R5KTsKICAgICAgICB9IGVsc2UgaWYgKGFtTXNnLnZlcnNpb24gPT0gMnUzMikgewogICAgICAgICAgICBCeXRlc1RvVHlwZXMudmFyQnl0ZXNUb1N1YkJ5dGVzKG9mZnNldCwgcGtnLCBhbU1zZy5ib2R5KTsKICAgICAgICB9CiAgICAgICAgX19kZWJ1Zy5wcmludCgiYW1Nc2cuYm9keTogIiwgYW1Nc2cuYm9keSk7CiAgICAgICAgX19kZWJ1Zy5wcmludCgib2Zmc2V0OiAiLCBvZmZzZXQpOwoKICAgICAgICByZXR1cm4gYW1Nc2c7CiAgICB9CgogICAgLy8gZnVuY3Rpb24gYXJyYXk8dWludDg+IGVuY29kZVByb29mTWVzc2FnZShQcm9vZiBpdGVtKSBwdWJsaWMgY29uc3R7CiAgICAvLyAgICAgcmV0dXJuIGl0ZW0uc2VyaWFsaXplKCk7CiAgICAvLyB9CgogICAgLy8gZnVuY3Rpb24gUHJvb2YgZGVjb2RlUHJvb2ZNZXNzYWdlKGFycmF5PHVpbnQ4PiByYXdNc2cpIHB1YmxpYyBjb25zdHsKICAgIC8vICAgICBQcm9vZiBpdGVtOwogICAgLy8gICAgIGl0ZW0uZGVzZXJpYWxpemUocmF3TXNnKTsKICAgIC8vICAgICByZXR1cm4gaXRlbTsKICAgIC8vIH0KCiAgICAvLyBmdW5jdGlvbiBhcnJheTx1aW50OD4gZW5jb2RNZXNzYWdlRnJvbVJlbGF5ZXIoTWVzc2FnZUZyb21SZWxheWVyIGl0ZW0pIHB1YmxpYyBjb25zdHsKICAgIC8vICAgICByZXR1cm4gaXRlbS5zZXJpYWxpemUoKTsKICAgIC8vIH0KCiAgICAvLyBmdW5jdGlvbiBkZWNvZGVNZXNzYWdlRnJvbVJlbGF5ZXIoYXJyYXk8dWludDg+IHJhd01zZywgTWVzc2FnZUZyb21SZWxheWVyIHIpIHB1YmxpYyBjb25zdHsKICAgIC8vICAgICBfX2RlYnVnLnByaW50KHJhd01zZyk7CiAgICAvLyAgICAgci5kZXNlcmlhbGl6ZShyYXdNc2cpOwogICAgLy8gICAgIF9fZGVidWcucHJpbnQocik7CiAgICAvLyB9CgogICAgZnVuY3Rpb24gYXJyYXk8dWludDg+IGVuY29kZUF1dGhNZXNzYWdlKEF1dGhNZXNzYWdlIGFtTXNnKSBwdWJsaWMgY29uc3QgewogICAgICAgIF9fZGVidWcucHJpbnQoImFtTXNnOiAiLCBhbU1zZyk7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQoYW1Nc2cudmVyc2lvbiA+PSAxdTMyKTsKICAgICAgICBfX2RlYnVnLmFzc2VydChhbU1zZy52ZXJzaW9uIDw9IDJ1MzIpOwogICAgICAgIGFycmF5PHVpbnQ4PiBwa2c7CiAgICAgICAgaWYgKGFtTXNnLnZlcnNpb24gPT0gMXUzMikgewogICAgICAgICAgICBwa2cgPSBlbmNvZGVBdXRoTWVzc2FnZVYxKGFtTXNnKTsKICAgICAgICB9IGVsc2UgaWYgKGFtTXNnLnZlcnNpb24gPT0gMnUzMikgewogICAgICAgICAgICBwa2cgPSBlbmNvZGVBdXRoTWVzc2FnZVYyKGFtTXNnKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHBrZzsKICAgIH0KCiAgICBmdW5jdGlvbiBhcnJheTx1aW50OD4gZW5jb2RlQXV0aE1lc3NhZ2VWMShBdXRoTWVzc2FnZSBhbU1zZykgcHVibGljIGNvbnN0IHsKICAgICAgICB1aW50MzIgYm9keUxlbiA9IFNpemVPZi5zaXplT2ZCeXRlcyhhbU1zZy5ib2R5KTsKICAgICAgICB1aW50MzIgbGVuID0gYm9keUxlbiArIDR1MzIgKyAzMnUzMiArIDR1MzI7CgogICAgICAgIGFycmF5PHVpbnQ4PiBwa2c7CiAgICAgICAgcGtnLnNldF9sZW5ndGgobGVuKTsKICAgICAgICB1aW50MzIgb2Zmc2V0ID0gbGVuOwoKICAgICAgICAvLyB2ZXJzaW9uCiAgICAgICAgVHlwZXNUb0J5dGVzLnVpbnQzMlRvQnl0ZXMob2Zmc2V0LCBhbU1zZy52ZXJzaW9uLCBwa2cpOwogICAgICAgIG9mZnNldCAtPSA0dTMyOwoKICAgICAgICAvLyBhdXRob3IKICAgICAgICBUeXBlc1RvQnl0ZXMuYnl0ZXMzMlRvQnl0ZXMob2Zmc2V0LCBhbU1zZy5hdXRob3IsIHBrZyk7CiAgICAgICAgb2Zmc2V0IC09IDMydTMyOwoKICAgICAgICAvLyBwcm90b2NvbFR5cGUKICAgICAgICBUeXBlc1RvQnl0ZXMudWludDMyVG9CeXRlcyhvZmZzZXQsIGFtTXNnLnByb3RvY29sVHlwZSwgcGtnKTsKICAgICAgICBvZmZzZXQgLT0gNHUzMjsKCiAgICAgICAgLy8gYm9keQogICAgICAgIFR5cGVzVG9CeXRlcy5ieXRlc1RvTmV3Qnl0ZXMob2Zmc2V0LCBhbU1zZy5ib2R5LCBwa2cpOwogICAgICAgIG9mZnNldCAtPSBib2R5TGVuOwoKICAgICAgICByZXR1cm4gcGtnOwogICAgfQoKICAgIGZ1bmN0aW9uIGFycmF5PHVpbnQ4PiBlbmNvZGVBdXRoTWVzc2FnZVYyKEF1dGhNZXNzYWdlIGFtTXNnKSBwdWJsaWMgY29uc3QgewogICAgICAgIHVpbnQzMiBib2R5TGVuID0gYW1Nc2cuYm9keS5sZW5ndGgoKTsKICAgICAgICBfX2RlYnVnLmFzc2VydChib2R5TGVuIDw9IDB4ZmZmZmZmZmZ1MzIpOwogICAgICAgIHVpbnQzMiBsZW4gPSBib2R5TGVuICsgNHUzMiArIDR1MzIgKyAzMnUzMiArIDR1MzI7CgogICAgICAgIGFycmF5PHVpbnQ4PiBwa2c7CiAgICAgICAgcGtnLnNldF9sZW5ndGgobGVuKTsKICAgICAgICB1aW50MzIgb2Zmc2V0ID0gbGVuOwoKICAgICAgICAvLyB2ZXJzaW9uCiAgICAgICAgVHlwZXNUb0J5dGVzLnVpbnQzMlRvQnl0ZXMob2Zmc2V0LCBhbU1zZy52ZXJzaW9uLCBwa2cpOwogICAgICAgIG9mZnNldCAtPSA0dTMyOwoKICAgICAgICAvLyBhdXRob3IKICAgICAgICBUeXBlc1RvQnl0ZXMuYnl0ZXMzMlRvQnl0ZXMob2Zmc2V0LCBhbU1zZy5hdXRob3IsIHBrZyk7CiAgICAgICAgb2Zmc2V0IC09IDMydTMyOwoKICAgICAgICAvLyBwcm90b2NvbFR5cGUKICAgICAgICBUeXBlc1RvQnl0ZXMudWludDMyVG9CeXRlcyhvZmZzZXQsIGFtTXNnLnByb3RvY29sVHlwZSwgcGtnKTsKICAgICAgICBvZmZzZXQgLT0gNHUzMjsKCiAgICAgICAgLy8gYm9keQogICAgICAgIFR5cGVzVG9CeXRlcy52YXJCeXRlc1RvTmV3Qnl0ZXMob2Zmc2V0LCBhbU1zZy5ib2R5LCBwa2cpOwoKICAgICAgICByZXR1cm4gcGtnOwogICAgfQoKICAgIC8vIGZ1bmN0aW9uIEF1dGhNZXNzYWdlIGRlY29kZUF1dGhNZXNzYWdlKGFycmF5PHVpbnQ4PiByYXdNc2cpIHB1YmxpYyBjb25zdHsKICAgIC8vICAgICBBdXRoTWVzc2FnZSBhbU1zZzsKICAgIC8vICAgICBhbU1zZy5kZXNlcmlhbGl6ZShyYXdNc2cpOwogICAgLy8gICAgIHJldHVybiBhbU1zZzsKICAgIC8vIH0KCgogICAgLy8gZnVuY3Rpb24gTWVzc2FnZUZvckFNIGRlY29kZShhcnJheTx1aW50OD4gcmF3TWVzc2FnZSkgcHVibGljIGNvbnN0ewogICAgLy8gICAgIF9fZGVidWcucHJpbnQocmF3TWVzc2FnZSk7CiAgICAvLyAgICAgLy8gcmF3TXNnIC0tPiBbc3RydWN0XSBNZXNzYWdlRnJvbVJlbGF5ZXIKICAgIC8vICAgICBNZXNzYWdlRnJvbVJlbGF5ZXIgbWVzc2FnZUZyb21SZWxheWVyID0gZGVjb2RlTWVzc2FnZUZyb21SZWxheWVyKHJhd01lc3NhZ2UpOwogICAgLy8gICAgIC8vIE1lc3NhZ2VGcm9tUmVsYXllci5wcm9vZkRhdGEgLS0+IFtzdHJ1Y3RdIHByb29mIC0tPiBbYnl0ZXNdIHVuZGVjb2RlZCBBTSBNZXNzYWdlCiAgICAvLyAgICAgUHJvb2YgcHJvb2YgPSBkZWNvZGVQcm9vZk1lc3NhZ2UobWVzc2FnZUZyb21SZWxheWVyLnByb29mRGF0YSk7CiAgICAvLyAgICAgTWVzc2FnZUZvckFNIG1lc3NhZ2VGb3JBTSA9IGRlY29kZU1lc3NhZ2VGb3JBTShwcm9vZi5yYXdSZXNwQm9keSk7CiAgICAvLyAgICAgbWVzc2FnZUZvckFNLnNlbmRlckRvbWFpbiA9IHByb29mLnNlbmRlckRvbWFpbjsKCiAgICAvLyAgICAgcmV0dXJuIG1lc3NhZ2VGb3JBTTsKICAgIC8vIH0KCn0=";
+
+ /** source: ../onchain-plugin/lib/utils/SizeOf.gcl */
+ public static final String SIZEOF = "Y29udHJhY3QgU2l6ZU9mIHsKCiAgICBmdW5jdGlvbiB1aW50MzIgc2l6ZU9mQnl0ZXMoYXJyYXk8dWludDg+IGRhdGEpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDMyIGxlbiA9IGRhdGEubGVuZ3RoKCk7CiAgICAgICAgdWludDMyIHNpemUgPSBsZW4gLyAzMnUzMjsKCiAgICAgICAgaWYgKGxlbiAlIDMydTMyICE9IDB1MzIpIHsKICAgICAgICAgICAgc2l6ZSsrOwogICAgICAgIH0KICAgICAgICAvLyDpop3lpJYgMzIg5a2X6IqC5a2Y5YKo6ZW/5bqm5a2X5q61CiAgICAgICAgc2l6ZSsrOwogICAgICAgIC8vIOi9rOaNouS4uuWtl+iKguaAu+aVsAogICAgICAgIHNpemUgKj0gMzJ1MzI7CiAgICAgICAgcmV0dXJuIHNpemU7CiAgICB9Cn0K";
+
+ /** source: ../onchain-plugin/lib/utils/BytesToTypes.gcl */
+ public static final String BYTESTOTYPES = "aW1wb3J0IFV0aWxzOwoKY29udHJhY3QgQnl0ZXNUb1R5cGVzIHsKICAgIAogICAgZnVuY3Rpb24gdWludDI1NiBieXRlc1RvVWludDI1Nih1aW50MzIgb2Zmc2V0LCBhcnJheTx1aW50OD4gaW5wdXQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgcmV0dXJuIGJ5dGVzVG9VaW50KG9mZnNldCwgaW5wdXQsIDMydTMyKTsKICAgIH0KCiAgICBmdW5jdGlvbiB1aW50MzIgYnl0ZXNUb1VpbnQzMih1aW50MzIgb2Zmc2V0LCBhcnJheTx1aW50OD4gaW5wdXQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgcmV0dXJuIHVpbnQzMihieXRlc1RvVWludChvZmZzZXQsIGlucHV0LCA0dTMyKSk7CiAgICB9CgogICAgZnVuY3Rpb24gdWludDE2IGJ5dGVzVG9VaW50MTYodWludDMyIG9mZnNldCwgYXJyYXk8dWludDg+IGlucHV0KSBwdWJsaWMgY29uc3QgewogICAgICAgIHJldHVybiB1aW50MTYoYnl0ZXNUb1VpbnQob2Zmc2V0LCBpbnB1dCwgMnUzMikpOwogICAgfQoKICAgIGZ1bmN0aW9uIHVpbnQyNTYgYnl0ZXNUb1VpbnQodWludDMyIG9mZnNldCwgYXJyYXk8dWludDg+IGlucHV0LCB1aW50MzIgbGVuKSBwdWJsaWMgY29uc3QgewogICAgICAgIHVpbnQyNTYgb3V0cHV0OwogICAgICAgIGZvcih1aW50MzIgaSA9IG9mZnNldCAtIGxlbjsgaSA8IG9mZnNldDsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dCA9IChvdXRwdXQgPDwgOHUzMikgKyB1aW50MjU2KGlucHV0W2ldKTsKICAgICAgICB9CgogICAgICAgIHJldHVybiBvdXRwdXQ7CiAgICB9CgogICAgZnVuY3Rpb24gYnl0ZXNUb1N1YkJ5dGVzKHVpbnQzMiBvZmZzZXQsIGFycmF5PHVpbnQ4PiBpbnB1dCwgYXJyYXk8dWludDg+IG91dHB1dCkgcHVibGljIGNvbnN0IHsKICAgICAgICB1aW50MzIgbGVuID0gdWludDMyKGJ5dGVzVG9VaW50MjU2KG9mZnNldCwgaW5wdXQpKTsKICAgICAgICBvZmZzZXQgLT0gMzJ1MzI7CiAgICAgICAgd2hpbGUgKGxlbiA+IDB1MzIpIHsKICAgICAgICAgICAgb2Zmc2V0IC09IDMydTMyOwogICAgICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IDMydTMyICYmIGxlbiA+IDB1MzI7IGkrKykgewogICAgICAgICAgICAgICAgb3V0cHV0LnB1c2goaW5wdXRbb2Zmc2V0ICsgaV0pOwogICAgICAgICAgICAgICAgbGVuLS07CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9CgoKICAgIGZ1bmN0aW9uIHZhckJ5dGVzVG9TdWJCeXRlcyh1aW50MzIgb2Zmc2V0LCBhcnJheTx1aW50OD4gaW5wdXQsIGFycmF5PHVpbnQ4PiBvdXRwdXQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDMyIGxlbiA9IGJ5dGVzVG9VaW50MzIob2Zmc2V0LCBpbnB1dCk7CiAgICAgICAgb2Zmc2V0IC09IDR1MzI7CiAgICAgICAgX19kZWJ1Zy5hc3NlcnQob2Zmc2V0ID09IGxlbik7CiAgICAgICAgZm9yKHVpbnQzMiBpID0gMHUzMjsgaSA8IGxlbjsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dC5wdXNoKGlucHV0W2ldKTsKICAgICAgICB9CiAgICB9CgoKICAgIGZ1bmN0aW9uIGJ5dGVzVG9CeXRlczMyKHVpbnQzMiBvZmZzZXQsIGFycmF5PHVpbnQ4PiBpbnB1dCwgYXJyYXk8dWludDg+IG91dHB1dCkgcHVibGljIGNvbnN0IHsKICAgICAgICBvZmZzZXQgLT0gMzJ1MzI7CiAgICAgICAgZm9yICh1aW50MzIgaSA9IDB1MzI7IGkgPCAzMnUzMjsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dC5wdXNoKGlucHV0W29mZnNldCArIGldKTsKICAgICAgICB9CiAgICB9Cgp9";
+
+ /** source: ../onchain-plugin/lib/utils/TypesToBytes.gcl */
+ public static final String TYPESTOBYTES = "aW1wb3J0IFV0aWxzOwoKY29udHJhY3QgVHlwZXNUb0J5dGVzIHsKCiAgICBmdW5jdGlvbiBieXRlc1RvTmV3Qnl0ZXModWludDMyIG9mZnNldCwgYXJyYXk8dWludDg+IGlucHV0LCBhcnJheTx1aW50OD4gb3V0cHV0KSBwdWJsaWMgY29uc3QgewogICAgICAgIGFycmF5PHVpbnQ4PiBpbnB1dEJ5dGVzQWxpZ24zMiA9IFV0aWxzLmJ5dGVzQWxpZ24zMihpbnB1dCk7CiAgICAgICAgdWludDMyIGxlbiA9IGlucHV0Lmxlbmd0aCgpOwoKICAgICAgICAvLyBfX2RlYnVnLnByaW50KCJpbnB1dEJ5dGVzQWxpZ24zMjogIiwgaW5wdXRCeXRlc0FsaWduMzIpOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoImxlbjoiLCBsZW4sICIgaW5wdXRCeXRlc0FsaWduMzItbGVuOiAiLCBpbnB1dEJ5dGVzQWxpZ24zMi5sZW5ndGgoKSk7CgogICAgICAgIC8vIOWJjSAzMiDlrZfoioLlrZjlgqjplb/luqbkv6Hmga8KICAgICAgICBvZmZzZXQgLT0gMzJ1MzI7CiAgICAgICAgYXJyYXk8dWludDg+IGxlbkJ5dGVzID0gVXRpbHMudWludDI1NlRvQnl0ZXMzMih1aW50MjU2KGxlbikpOwogICAgICAgIGZvcih1aW50MzIgaSA9IDB1MzI7IGkgPCAzMnUzMjsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dFtvZmZzZXQgKyBpXSA9IGxlbkJ5dGVzW2ldOwogICAgICAgIH0KCiAgICAgICAgZm9yKHVpbnQzMiBpID0gMHUzMjsgaSA8IGlucHV0Qnl0ZXNBbGlnbjMyLmxlbmd0aCgpOykgewogICAgICAgICAgICBvZmZzZXQgLT0gMzJ1MzI7CiAgICAgICAgICAgIC8vIF9fZGVidWcucHJpbnQoIm9mZnNldDoiLCBvZmZzZXQpOwogICAgICAgICAgICBmb3IodWludDMyIGogPSAwdTMyOyBqIDwgMzJ1MzI7IGorKykgewogICAgICAgICAgICAgICAgb3V0cHV0W29mZnNldCtqXSA9IGlucHV0Qnl0ZXNBbGlnbjMyW2ldOwogICAgICAgICAgICAgICAgaSsrOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIHZhckJ5dGVzVG9OZXdCeXRlcyh1aW50MzIgb2Zmc2V0LCBhcnJheTx1aW50OD4gaW5wdXQsIGFycmF5PHVpbnQ4PiBvdXRwdXQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDMyIGxlbiA9IGlucHV0Lmxlbmd0aCgpOwoKICAgICAgICAvLyDliY0gNCDlrZfoioLlrZjlgqjplb/luqbkv6Hmga8KICAgICAgICBvZmZzZXQgLT0gNHUzMjsKICAgICAgICBhcnJheTx1aW50OD4gbGVuQnl0ZXMgPSBVdGlscy51aW50MjU2VG9CeXRlczMyKHVpbnQyNTYobGVuKSk7CiAgICAgICAgZm9yKHVpbnQzMiBpID0gMjh1MzI7IGkgPCAzMnUzMjsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dFtvZmZzZXQgKyBpIC0gMjh1MzJdID0gbGVuQnl0ZXNbaV07CiAgICAgICAgfQoKICAgICAgICBmb3IodWludDMyIGkgPSAwdTMyOyBpIDwgbGVuOyBpKyspIHsKICAgICAgICAgICAgb3V0cHV0W2ldID0gaW5wdXRbaV07CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIGJ5dGVzMzJUb0J5dGVzKHVpbnQzMiBvZmZzZXQsIGFycmF5PHVpbnQ4PiBpbnB1dCwgYXJyYXk8dWludDg+IG91dHB1dCkgcHVibGljIGNvbnN0IHsKICAgICAgICBvZmZzZXQgLT0gMzJ1MzI7CiAgICAgICAgdWludDMyIGlucHV0TGVuID0gaW5wdXQubGVuZ3RoKCk7CiAgICAgICAgZm9yKHVpbnQzMiBpID0gMHUzMjsgaSA8IDMydTMyOyBpKyspIHsKICAgICAgICAgICAgaWYgKGkgPCBpbnB1dExlbikgewogICAgICAgICAgICAgICAgb3V0cHV0W29mZnNldCArIGldID0gaW5wdXRbaV07CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBvdXRwdXRbb2Zmc2V0ICsgaV0gPSAwdTg7ICAvLyDloavlhYUwCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gdWludDMyVG9CeXRlcyh1aW50MzIgb2Zmc2V0LCB1aW50MzIgaW5wdXQsIGFycmF5PHVpbnQ4PiBvdXRwdXQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgYXJyYXk8dWludDg+IGlucHV0Qnl0ZXMgPSBVdGlscy51aW50MjU2VG9CeXRlczMyKHVpbnQyNTYoaW5wdXQpKTsKICAgICAgICB1aW50VG9CeXRlcyhvZmZzZXQsIGlucHV0Qnl0ZXMsIG91dHB1dCwgNHUzMik7CiAgICB9CgogICAgZnVuY3Rpb24gdWludFRvQnl0ZXModWludDMyIG9mZnNldCwgYXJyYXk8dWludDg+IGlucHV0LCBhcnJheTx1aW50OD4gb3V0cHV0LCB1aW50MzIgbGVuKSBwdWJsaWMgY29uc3QgewogICAgICAgIG9mZnNldCAtPSAzMnUzMjsKICAgICAgICBmb3IodWludDMyIGkgPSAzMnUzMi1sZW47IGkgPCAzMnUzMjsgaSsrKSB7CiAgICAgICAgICAgIG91dHB1dFtvZmZzZXQgKyBpXSA9IGlucHV0W2ldOwogICAgICAgIH0KICAgIH0KCn0=";
+
+ /** source: ../onchain-plugin/lib/utils/TLVUtils.gcl */
+ public static final String TLVUTILS = "aW1wb3J0IFV0aWxzOwppbXBvcnQgQnl0ZXNUb1R5cGVzOwoKY29udHJhY3QgVExWVXRpbHMgewoKICAgIHN0cnVjdCBUTFZJdGVtIHsKICAgICAgICB1aW50MTYgdGFnVHlwZTsKICAgICAgICB1aW50MzIgbGVuOwogICAgICAgIGFycmF5PHVpbnQ4PiB2YWx1ZTsKICAgIH0KCiAgICBzdHJ1Y3QgTFZJdGVtIHsKICAgICAgICB1aW50MzIgbGVuOwogICAgICAgIGFycmF5PHVpbnQ4PiB2YWx1ZTsKICAgIH0KCiAgICBzdHJ1Y3QgVExWV2l0aE9mZnNldCB7CiAgICAgICAgdWludDMyIG9mZnNldDsKICAgICAgICBUTFZJdGVtIHRsdkl0ZW07CiAgICB9CgogICAgc3RydWN0IExWV2l0aE9mZnNldCB7CiAgICAgICAgdWludDMyIG9mZnNldDsKICAgICAgICBMVkl0ZW0gbHZJdGVtOwogICAgfQoKICAgIC8qKgogICAgICogQG5vdGljZSDop6PmnpAgVExWIChUeXBlLUxlbmd0aC1WYWx1ZSkg5qC85byP55qE5pWw5o2u6aG5CiAgICAgKiBAcGFyYW0gcmF3RGF0YSDljp/lp4vmlbDmja4KICAgICAqIEBwYXJhbSBvZmZzZXQg5byA5aeL5L2N572uCiAgICAgKiBAcmV0dXJuIFRMVldpdGhPZmZzZXQg5YyF5ZCr6Kej5p6Q57uT5p6c5ZKM5paw55qE5YGP56e76YePCiAgICAgKi8KICAgIGZ1bmN0aW9uIFRMVldpdGhPZmZzZXQgcGFyc2VUTFZJdGVtKGFycmF5PHVpbnQ4PiByYXdEYXRhLCB1aW50MzIgb2Zmc2V0KSBwdWJsaWMgY29uc3QgewogICAgICAgIFRMVldpdGhPZmZzZXQgcmVzdWx0OwoKICAgICAgICAvLyDnoa7kv53mnInotrPlpJ/nmoTmlbDmja7or7vlj5YgdGFnIOWSjCBsZW5ndGggKOiHs+WwkSA2IOWtl+iKgikKICAgICAgICBfX2RlYnVnLmFzc2VydChvZmZzZXQgKyA2dTMyIDw9IHJhd0RhdGEubGVuZ3RoKCkpOwoKICAgICAgICAvLyDor7vlj5YgdGFnVHlwZSAoMiBieXRlcywgYmlnLWVuZGlhbiwgdGhlbiByZXZlcnNlIHRvIGxpdHRsZS1lbmRpYW4pCiAgICAgICAgLy8gUmVhZCBhcyBiaWctZW5kaWFuOiBbb2Zmc2V0XSBpcyBoaWdoIGJ5dGUsIFtvZmZzZXQrMV0gaXMgbG93IGJ5dGUKICAgICAgICB1aW50MTYgdGFnQkUgPSAodWludDE2KHJhd0RhdGFbb2Zmc2V0XSkgPDwgOHUxNikgfCB1aW50MTYocmF3RGF0YVtvZmZzZXQgKyAxdTMyXSk7CiAgICAgICAgcmVzdWx0LnRsdkl0ZW0udGFnVHlwZSA9ICgodGFnQkUgJiAweEZGMDB1MTYpID4+IDh1MTYpIHwgKCh0YWdCRSAmIDB4MDBGRnUxNikgPDwgOHUxNik7ICAvLyByZXZlcnNlIGJ5dGVzCiAgICAgICAgb2Zmc2V0ICs9IDJ1MzI7CgogICAgICAgIC8vIOivu+WPliBsZW5ndGggKDQgYnl0ZXMsIGJpZy1lbmRpYW4sIHRoZW4gcmV2ZXJzZSB0byBsaXR0bGUtZW5kaWFuKQogICAgICAgIHVpbnQzMiBsZW5CRSA9ICh1aW50MzIocmF3RGF0YVtvZmZzZXRdKSA8PCAyNHUzMikgCiAgICAgICAgICAgICAgICAgICAgIHwgKHVpbnQzMihyYXdEYXRhW29mZnNldCArIDF1MzJdKSA8PCAxNnUzMikKICAgICAgICAgICAgICAgICAgICAgfCAodWludDMyKHJhd0RhdGFbb2Zmc2V0ICsgMnUzMl0pIDw8IDh1MzIpCiAgICAgICAgICAgICAgICAgICAgIHwgdWludDMyKHJhd0RhdGFbb2Zmc2V0ICsgM3UzMl0pOwogICAgICAgIC8vIFJldmVyc2UgYnl0ZXM6IDB4MTIzNDU2NzggLT4gMHg3ODU2MzQxMgogICAgICAgIHJlc3VsdC50bHZJdGVtLmxlbiA9ICgobGVuQkUgJiAweEZGMDAwMDAwdTMyKSA+PiAyNHUzMikKICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAoKGxlbkJFICYgMHgwMEZGMDAwMHUzMikgPj4gOHUzMikKICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAoKGxlbkJFICYgMHgwMDAwRkYwMHUzMikgPDwgOHUzMikKICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAoKGxlbkJFICYgMHgwMDAwMDBGRnUzMikgPDwgMjR1MzIpOwogICAgICAgIG9mZnNldCArPSA0dTMyOwoKICAgICAgICAvLyDnoa7kv53mnInotrPlpJ/nmoTmlbDmja7or7vlj5YgdmFsdWUKICAgICAgICBfX2RlYnVnLmFzc2VydChvZmZzZXQgKyByZXN1bHQudGx2SXRlbS5sZW4gPD0gcmF3RGF0YS5sZW5ndGgoKSk7CgogICAgICAgIC8vIOivu+WPliB2YWx1ZSAo55u05o6l5LuO5b2T5YmNIG9mZnNldCDlpI3liLYgbGVuIOS4quWtl+iKgikKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IHJlc3VsdC50bHZJdGVtLmxlbjsgaSsrKSB7CiAgICAgICAgICAgIHJlc3VsdC50bHZJdGVtLnZhbHVlLnB1c2gocmF3RGF0YVtvZmZzZXQgKyBpXSk7CiAgICAgICAgfQogICAgICAgIG9mZnNldCArPSByZXN1bHQudGx2SXRlbS5sZW47CgogICAgICAgIHJlc3VsdC5vZmZzZXQgPSBvZmZzZXQ7CiAgICAgICAgcmV0dXJuIHJlc3VsdDsKICAgIH0KCiAgICAvKioKICAgICAqIEBub3RpY2Ug6Kej5p6QIExWIChMZW5ndGgtVmFsdWUpIOagvOW8j+eahOaVsOaNrumhuQogICAgICogQHBhcmFtIHJhd0RhdGEg5Y6f5aeL5pWw5o2uCiAgICAgKiBAcGFyYW0gb2Zmc2V0IOW8gOWni+S9jee9rgogICAgICogQHJldHVybiBMVldpdGhPZmZzZXQg5YyF5ZCr6Kej5p6Q57uT5p6c5ZKM5paw55qE5YGP56e76YePCiAgICAgKi8KICAgIGZ1bmN0aW9uIExWV2l0aE9mZnNldCBwYXJzZUxWSXRlbShhcnJheTx1aW50OD4gcmF3RGF0YSwgdWludDMyIG9mZnNldCkgcHVibGljIGNvbnN0IHsKICAgICAgICBMVldpdGhPZmZzZXQgcmVzdWx0OwoKICAgICAgICAvLyDor7vlj5YgbGVuZ3RoICg0IGJ5dGVzLCBiaWctZW5kaWFuLCB0aGVuIHJldmVyc2UgdG8gbGl0dGxlLWVuZGlhbikKICAgICAgICB1aW50MzIgbGVuQkUgPSAodWludDMyKHJhd0RhdGFbb2Zmc2V0XSkgPDwgMjR1MzIpIAogICAgICAgICAgICAgICAgICAgICB8ICh1aW50MzIocmF3RGF0YVtvZmZzZXQgKyAxdTMyXSkgPDwgMTZ1MzIpCiAgICAgICAgICAgICAgICAgICAgIHwgKHVpbnQzMihyYXdEYXRhW29mZnNldCArIDJ1MzJdKSA8PCA4dTMyKQogICAgICAgICAgICAgICAgICAgICB8IHVpbnQzMihyYXdEYXRhW29mZnNldCArIDN1MzJdKTsKICAgICAgICByZXN1bHQubHZJdGVtLmxlbiA9ICgobGVuQkUgJiAweEZGMDAwMDAwdTMyKSA+PiAyNHUzMikKICAgICAgICAgICAgICAgICAgICAgICAgICB8ICgobGVuQkUgJiAweDAwRkYwMDAwdTMyKSA+PiA4dTMyKQogICAgICAgICAgICAgICAgICAgICAgICAgIHwgKChsZW5CRSAmIDB4MDAwMEZGMDB1MzIpIDw8IDh1MzIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgfCAoKGxlbkJFICYgMHgwMDAwMDBGRnUzMikgPDwgMjR1MzIpOwogICAgICAgIG9mZnNldCArPSA0dTMyOwoKICAgICAgICAvLyDor7vlj5YgdmFsdWUgKOebtOaOpeS7juW9k+WJjSBvZmZzZXQg5aSN5Yi2IGxlbiDkuKrlrZfoioIpCiAgICAgICAgZm9yICh1aW50MzIgaSA9IDB1MzI7IGkgPCByZXN1bHQubHZJdGVtLmxlbjsgaSsrKSB7CiAgICAgICAgICAgIHJlc3VsdC5sdkl0ZW0udmFsdWUucHVzaChyYXdEYXRhW29mZnNldCArIGldKTsKICAgICAgICB9CiAgICAgICAgb2Zmc2V0ICs9IHJlc3VsdC5sdkl0ZW0ubGVuOwoKICAgICAgICByZXN1bHQub2Zmc2V0ID0gb2Zmc2V0OwoKICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgfQoKfQ==";
+
+ /** source: ../onchain-plugin/lib/utils/Utils.gcl */
+ public static final String UTILS = "Y29udHJhY3QgVXRpbHMgewoKICAgIGZ1bmN0aW9uIGJvb2wgYnl0ZXNFcXVhbChhcnJheTx1aW50OD4gZmlyc3QsIGFycmF5PHVpbnQ4PiBzZWNvbmQpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgaWYgKGZpcnN0Lmxlbmd0aCgpICE9IHNlY29uZC5sZW5ndGgoKSkgewogICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgfQogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgZmlyc3QubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICBpZiAoZmlyc3RbaV0gIT0gc2Vjb25kW2ldKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIHRydWU7CiAgICB9CgogICAgZnVuY3Rpb24gYXJyYXk8dWludDg+IGJ5dGVzQ29uY2F0KGFycmF5PHVpbnQ4PiBmaXJzdCwgYXJyYXk8dWludDg+IHNlY29uZCkgcHVibGljIGNvbnN0IHsKICAgICAgICBhcnJheTx1aW50OD4gcmV0OwogICAgICAgIGZvcih1aW50MzIgaSA9IDB1MzI7aTxmaXJzdC5sZW5ndGgoKTtpKyspCiAgICAgICAgewogICAgICAgICAgICByZXQucHVzaChmaXJzdFtpXSk7CiAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICBmb3IodWludDMyIGkgPSAwdTMyO2k8c2Vjb25kLmxlbmd0aCgpO2krKykKICAgICAgICB7CiAgICAgICAgICAgIHJldC5wdXNoKHNlY29uZFtpXSk7CiAgICAgICAgfQogICAgICAgICAgIAogICAgICAgIHJldHVybiByZXQ7CiAgICB9CgogICAgZnVuY3Rpb24gYXJyYXk8dWludDg+IGJ5dGVzQ29weShhcnJheTx1aW50OD4gc3JjKSBwdWJsaWMgY29uc3QgewogICAgICAgIGFycmF5PHVpbnQ4PiBkc3Q7CiAgICAgICAgZm9yKHVpbnQzMiBpPTB1MzI7IGkgPCBzcmMubGVuZ3RoKCk7IGkrKykgewogICAgICAgICAgICBkc3QucHVzaChzcmNbaV0pOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZHN0OwogICAgfQoKICAgIGZ1bmN0aW9uIGFycmF5PHVpbnQ4PiBieXRlc0FsaWduMzIoYXJyYXk8dWludDg+IHNyYykgcHVibGljIGNvbnN0IHsKICAgICAgICBhcnJheTx1aW50OD4gZHN0OwogICAgICAgIHVpbnQzMiBsZW4gPSBzcmMubGVuZ3RoKCk7CiAgICAgICAgdWludDMyIHJlbSA9IGxlbiAlIDMydTMyOwogICAgICAgIGlmIChyZW0gPT0gMHUzMikgewogICAgICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IGxlbjsgaSsrKSB7CiAgICAgICAgICAgICAgICBkc3QucHVzaChzcmNbaV0pOwogICAgICAgICAgICB9CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgdWludDMyIHBhZF9sZW4gPSAzMnUzMiAtIHJlbTsKICAgICAgICAgICAgZm9yICh1aW50MzIgaSA9IDB1MzI7IGkgPCBsZW47IGkrKykgewogICAgICAgICAgICAgICAgZHN0LnB1c2goc3JjW2ldKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IHBhZF9sZW47IGkrKykgewogICAgICAgICAgICAgICAgZHN0LnB1c2goMHU4KTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gZHN0OwogICAgfQoKICAgIGZ1bmN0aW9uIGFycmF5PHVpbnQ4PiB1aW50MjU2VG9CeXRlczMyKHVpbnQyNTYgc3JjKSBwdWJsaWMgY29uc3QgewogICAgICAgIGFycmF5PHVpbnQ4PiBkc3Q7CiAgICAgICAgZHN0LnNldF9sZW5ndGgoMzJ1MzIpOwogICAgICAgIGZvciAodWludDMyIGkgPSAzMnUzMjsgaSA+IDB1MzI7IGktLSkgewogICAgICAgICAgICB1aW50OCB0bXBfaW50ID0gdWludDgoKHNyYyA+PiAoKGktMXUzMikgKiA4dTMyKSkgJiAweEZGdTgpOwogICAgICAgICAgICBkc3RbMzJ1MzItaV0gPSB0bXBfaW50OwogICAgICAgICAgICAvLyBfX2RlYnVnLnByaW50KCJieXRlICIsIDMydTMyLWksICI6ICIsIHRtcF9pbnQpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZHN0OwogICAgfQoKICAgIGZ1bmN0aW9uIGFycmF5PHVpbnQ4PiBhZGRyZXNzVG9CeXRlczMyKGFkZHJlc3Mgc3JjKSBwdWJsaWMgY29uc3QgewogICAgICAgIC8vIOWcqCBHQ0wg5Lit77yMYWRkcmVzcyDpnIDopoHovazmjaLkuLogYnl0ZXMzMgogICAgICAgIC8vIOeUseS6jiBHQ0wg5LiN55u05o6l5pSv5oyBIGFkZHJlc3Mg5YiwIHVpbnQyNTYg55qE6L2s5o2i77yMCiAgICAgICAgLy8g5oiR5Lus6L+U5Zue5LiA5Liq5aGr5YWF5Li66Zu255qEIDMyIOWtl+iKguaVsOe7hAogICAgICAgIGFycmF5PHVpbnQ4PiBkc3Q7CiAgICAgICAgZHN0LnNldF9sZW5ndGgoMzJ1MzIpOwogICAgICAgIGZvciAodWludDMyIGkgPSAwdTMyOyBpIDwgMzJ1MzI7IGkrKykgewogICAgICAgICAgICBkc3RbaV0gPSAwdTg7CiAgICAgICAgfQogICAgICAgIC8vIOazqOaEj++8mui/meaYr+S4gOS4queugOWMluWunueOsO+8jOWunumZheW6lOeUqOS4reWPr+iDvemcgOimgeabtOWkjeadgueahOWcsOWdgOe8lueggQogICAgICAgIHJldHVybiBkc3Q7CiAgICB9CgogICAgZnVuY3Rpb24gdWludDI1NiBieXRlczMyVG9VaW50MjU2KGFycmF5PHVpbnQ4PiBzcmMpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDI1NiBkc3Q7CiAgICAgICAgdWludDMyIGxlbiA9IHNyYy5sZW5ndGgoKTsKICAgICAgICBmb3IgKHVpbnQzMiBpID0gMHUzMjsgaSA8IGxlbjsgaSsrKSB7CiAgICAgICAgICAgIGRzdCA9IChkc3QgPDwgOHUzMikgKyB1aW50MjU2KHNyY1tpXSk7CiAgICAgICAgICAgIC8vIF9fZGVidWcucHJpbnQoImJ5dGUgIiwgaSwgIjogIiwgc3JjW2ldLCAiIGRzdDogIiwgZHN0KTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIGRzdDsKICAgIH0KCiAgICBmdW5jdGlvbiB1aW50MzIgcmV2ZXJzZVVpbnQzMih1aW50MzIgdmFsdWUpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDMyIHJlc3VsdDsKICAgICAgICByZXN1bHQgPSAoKHZhbHVlICYgMHgwMDAwMDBGRnUzMikgPDwgMjR1MzIpIHwKICAgICAgICAgICAgICAgICAoKHZhbHVlICYgMHgwMDAwRkYwMHUzMikgPDwgOHUzMikgfAogICAgICAgICAgICAgICAgICgodmFsdWUgJiAweDAwRkYwMDAwdTMyKSA+PiA4dTMyKSB8CiAgICAgICAgICAgICAgICAgKCh2YWx1ZSAmIDB4RkYwMDAwMDB1MzIpID4+IDI0dTMyKTsKICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgfQoKICAgIGZ1bmN0aW9uIGFkZHJlc3MgYXJyYXlVaW50OFRvQWRkcmVzcyhhcnJheTx1aW50OD4gc3JjKSBwdWJsaWMgY29uc3QgewogICAgICAgIC8vIEdDTCDkuK0gYWRkcmVzcyDnsbvlnovkuI3mlK/mjIHku44gdWludDI1NiDnm7TmjqXovazmjaIKICAgICAgICAvLyDov5nph4znroDljJblrp7njrDvvIznm7TmjqXov5Tlm57pm7blnLDlnYAKICAgICAgICAvLyDlrp7pmYXkvb/nlKjkuK3lupTor6XpgJrov4flhbbku5bmlrnlvI/lpITnkIblnLDlnYDovazmjaIKICAgICAgICBhZGRyZXNzIHJlc3VsdDsKICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgfQp9";
+
+ /** source: ../onchain-plugin/lib/sdp/SDPLib.gcl */
+ public static final String SDPLIB = "aW1wb3J0IFV0aWxzOwppbXBvcnQgU2l6ZU9mOwppbXBvcnQgVHlwZXNUb0J5dGVzOwppbXBvcnQgQnl0ZXNUb1R5cGVzOwoKY29udHJhY3QgU0RQTGliIHsKCiAgICBzdHJ1Y3QgU0RQTWVzc2FnZSB7CiAgICAgICAgYXJyYXk8dWludDg+IHJlY2VpdmVyRG9tYWluOwogICAgICAgIGFycmF5PHVpbnQ4PiByZWNlaXZlcjsKICAgICAgICBhcnJheTx1aW50OD4gbWVzc2FnZTsKICAgICAgICB1aW50MzIgc2VxdWVuY2U7CiAgICB9CgogICAgLy8gY29tcGF0aWJsZSB3aXRoIFNEUHYyIChvbmx5IFNEUHYxIG5vdykKICAgIC8vIEBnbG9iYWwgZnVuY3Rpb24gZ2V0U0RQVmVyc2lvbkZyb20oYXJyYXk8dWludDg+KQoKICAgIGZ1bmN0aW9uIGhhc2ggZ2V0U2VuZGluZ1NlcUlEKGFycmF5PHVpbnQ4PiByZWNlaXZlckRvbWFpbiwgdWludDY0IHNlbmRlcklELCBhcnJheTx1aW50OD4gcmVjZWl2ZXIpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgYXJyYXk8dWludDg+IHNlbmRlckFsaWduMzIgPSBVdGlscy51aW50MjU2VG9CeXRlczMyKHVpbnQyNTYoc2VuZGVySUQpKTsKICAgICAgICByZXR1cm4gaGFzaChVdGlscy5ieXRlc0NvbmNhdChVdGlscy5ieXRlc0NvbmNhdChyZWNlaXZlckRvbWFpbiwgc2VuZGVyQWxpZ24zMiksIHJlY2VpdmVyKSk7CiAgICB9CgogICAgZnVuY3Rpb24gaGFzaCBnZXRSZWNlaXZpbmdTZXFJRChhcnJheTx1aW50OD4gc2VuZGVyRG9tYWluLCBhcnJheTx1aW50OD4gc2VuZGVyLCBhcnJheTx1aW50OD4gcmVjZWl2ZXIpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgcmV0dXJuIGhhc2goVXRpbHMuYnl0ZXNDb25jYXQoVXRpbHMuYnl0ZXNDb25jYXQoc2VuZGVyRG9tYWluLCBzZW5kZXIpLCByZWNlaXZlcikpOwogICAgfQoKICAgIGZ1bmN0aW9uIGFycmF5PHVpbnQ4PiBlbmNvZGVTRFBNc2dWMShTRFBNZXNzYWdlIHNkcE1lc3NhZ2UpIHB1YmxpYyBjb25zdCB7CiAgICAgICAgdWludDMyIG1lc3NhZ2VMZW4gPSBTaXplT2Yuc2l6ZU9mQnl0ZXMoc2RwTWVzc2FnZS5tZXNzYWdlKTsKICAgICAgICB1aW50MzIgZG9tYWluTGVuID0gU2l6ZU9mLnNpemVPZkJ5dGVzKHNkcE1lc3NhZ2UucmVjZWl2ZXJEb21haW4pOwogICAgICAgIHVpbnQzMiBsZW4gPSBtZXNzYWdlTGVuICsgNHUzMiArIDMydTMyICsgZG9tYWluTGVuOwoKICAgICAgICBhcnJheTx1aW50OD4gcGtnOwogICAgICAgIHBrZy5zZXRfbGVuZ3RoKGxlbik7CiAgICAgICAgdWludDMyIG9mZnNldCA9IGxlbjsKCiAgICAgICAgLy8gZG9tYWluCiAgICAgICAgVHlwZXNUb0J5dGVzLmJ5dGVzVG9OZXdCeXRlcyhvZmZzZXQsIHNkcE1lc3NhZ2UucmVjZWl2ZXJEb21haW4sIHBrZyk7CiAgICAgICAgb2Zmc2V0IC09IGRvbWFpbkxlbjsKCiAgICAgICAgLy8gcmVjZWl2ZXIKICAgICAgICBUeXBlc1RvQnl0ZXMuYnl0ZXMzMlRvQnl0ZXMob2Zmc2V0LCBzZHBNZXNzYWdlLnJlY2VpdmVyLCBwa2cpOwogICAgICAgIG9mZnNldCAtPSAzMnUzMjsKCiAgICAgICAgLy8gc2VxdWVuY2UKICAgICAgICBUeXBlc1RvQnl0ZXMudWludDMyVG9CeXRlcyhvZmZzZXQsIHNkcE1lc3NhZ2Uuc2VxdWVuY2UsIHBrZyk7CiAgICAgICAgb2Zmc2V0IC09IDR1MzI7CgogICAgICAgIC8vIG1lc3NhZ2UKICAgICAgICBUeXBlc1RvQnl0ZXMuYnl0ZXNUb05ld0J5dGVzKG9mZnNldCwgc2RwTWVzc2FnZS5tZXNzYWdlLCBwa2cpOwogICAgICAgIG9mZnNldCAtPSBtZXNzYWdlTGVuOwoKICAgICAgICByZXR1cm4gcGtnOwogICAgfQoKICAgIGZ1bmN0aW9uIFNEUE1lc3NhZ2UgZGVjb2RlU0RQTXNnVjEoYXJyYXk8dWludDg+IHBrZykgcHVibGljIGNvbnN0IHsKICAgICAgICB1aW50MzIgb2Zmc2V0ID0gcGtnLmxlbmd0aCgpOwogICAgICAgIFNEUE1lc3NhZ2Ugc2RwTWVzc2FnZTsKICAgICAgICAvLyBfX2RlYnVnLnByaW50KCJvZmZzZXQ6ICIsIG9mZnNldCk7CgogICAgICAgIC8vIGRvbWFpbgogICAgICAgIEJ5dGVzVG9UeXBlcy5ieXRlc1RvU3ViQnl0ZXMob2Zmc2V0LCBwa2csIHNkcE1lc3NhZ2UucmVjZWl2ZXJEb21haW4pOwogICAgICAgIG9mZnNldCAtPSBTaXplT2Yuc2l6ZU9mQnl0ZXMoc2RwTWVzc2FnZS5yZWNlaXZlckRvbWFpbik7CiAgICAgICAgLy8gX19kZWJ1Zy5wcmludCgic2RwTWVzc2FnZS5yZWNlaXZlckRvbWFpbjogIiwgc2RwTWVzc2FnZS5yZWNlaXZlckRvbWFpbik7CiAgICAgICAgLy8gX19kZWJ1Zy5wcmludCgib2Zmc2V0OiAiLCBvZmZzZXQpOwoKICAgICAgICAvLyByZWNlaXZlcgogICAgICAgIEJ5dGVzVG9UeXBlcy5ieXRlc1RvQnl0ZXMzMihvZmZzZXQsIHBrZywgc2RwTWVzc2FnZS5yZWNlaXZlcik7CiAgICAgICAgb2Zmc2V0IC09IDMydTMyOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoInNkcE1lc3NhZ2UucmVjZWl2ZXI6ICIsIHNkcE1lc3NhZ2UucmVjZWl2ZXIpOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoIm9mZnNldDogIiwgb2Zmc2V0KTsKCiAgICAgICAgLy8gc2VxdWVuY2UKICAgICAgICBzZHBNZXNzYWdlLnNlcXVlbmNlID0gQnl0ZXNUb1R5cGVzLmJ5dGVzVG9VaW50MzIob2Zmc2V0LCBwa2cpOwogICAgICAgIG9mZnNldCAtPSA0dTMyOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoInNkcE1lc3NhZ2Uuc2VxdWVuY2U6ICIsIHNkcE1lc3NhZ2Uuc2VxdWVuY2UpOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoIm9mZnNldDogIiwgb2Zmc2V0KTsKCiAgICAgICAgLy8gbWVzc2FnZQogICAgICAgIEJ5dGVzVG9UeXBlcy5ieXRlc1RvU3ViQnl0ZXMob2Zmc2V0LCBwa2csIHNkcE1lc3NhZ2UubWVzc2FnZSk7CiAgICAgICAgb2Zmc2V0IC09IFNpemVPZi5zaXplT2ZCeXRlcyhzZHBNZXNzYWdlLm1lc3NhZ2UpOwogICAgICAgIC8vIF9fZGVidWcucHJpbnQoInNkcE1lc3NhZ2UubWVzc2FnZTogIiwgc2RwTWVzc2FnZS5tZXNzYWdlKTsKICAgICAgICAvLyBfX2RlYnVnLnByaW50KCJvZmZzZXQ6ICIsIG9mZnNldCk7CgogICAgICAgIHJldHVybiBzZHBNZXNzYWdlOwogICAgfQogICAgCn0=";
+
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Crc32cPure.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Crc32cPure.java
new file mode 100644
index 00000000..f85d31bc
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Crc32cPure.java
@@ -0,0 +1,69 @@
+package com.alipay.antchain.bridge.plugins.dioxide.utils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public final class Crc32cPure {
+
+ // 反射用的多项式常量(Castagnoli 多项式的反射形式)
+ private static final int POLY = 0x82F63B78;
+ private static final int[] TABLE = makeTable();
+
+ private Crc32cPure() {}
+
+ // 生成 256 项查找表(table-driven CRC)
+ private static int[] makeTable() {
+ int[] table = new int[256];
+ for (int i = 0; i < 256; i++) {
+ int crc = i;
+ for (int j = 0; j < 8; j++) {
+ if ((crc & 1) != 0) {
+ crc = (crc >>> 1) ^ POLY;
+ } else {
+ crc = crc >>> 1;
+ }
+ }
+ table[i] = crc;
+ }
+ return table;
+ }
+
+ /**
+ * 计算 CRC32C(Castagnoli),并支持 seed(初始 CRC 值)。
+ *
+ * 语义等价于:Python `crc32c.crc32c(data, seed)` 的行为,
+ * 也即把 seed 当作先前的 CRC 值来继续计算。
+ *
+ * @param data 待计算的数据(非 null)
+ * @param seed 初始 CRC 值(作为"之前的"crc),通常 0 或一个先前的 crc32c 返回值
+ * @return 计算出的 CRC32C(32-bit int。若需无符号值,可用 `crc & 0xffffffffL`)
+ */
+ public static int crc32c(byte[] data, int seed) {
+ // 标准做法:先与 0xFFFFFFFF 异或,再按字节处理,最后再异或回 0xFFFFFFFF
+ int crc = seed ^ 0xFFFFFFFF;
+
+ for (int i = 0; i < data.length; i++) {
+ int b = data[i] & 0xFF;
+ int tblIdx = (crc ^ b) & 0xFF;
+ crc = (crc >>> 8) ^ TABLE[tblIdx];
+ }
+
+ return crc ^ 0xFFFFFFFF;
+ }
+
+ /**
+ * 便捷方法:计算完整数组,seed = 0 的常见情况。
+ */
+ public static int crc32c(byte[] data) {
+ return crc32c(data, 0);
+ }
+
+ /** 把 int 写成 4 字节的小端序数组 */
+ public static byte[] intToLittleEndianBytes(int v) {
+ ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
+ bb.putInt(v);
+ return bb.array();
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Decoder.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Decoder.java
new file mode 100644
index 00000000..ab603920
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Decoder.java
@@ -0,0 +1,305 @@
+package com.alipay.antchain.bridge.plugins.dioxide.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Java 重写的 krock32.Decoder(只实现你需要的解码相关逻辑)
+ *
+ * 使用:
+ * byte[] addr = Krock32Decoder.decodeAddress(key);
+ *
+ * 对应 Python:
+ * decoder = krock32.Decoder()
+ * decoder.update(key.split(":")[0])
+ * addr = decoder.finalize()
+ */
+public class Krock32Decoder {
+
+ // Exceptions 对应 Python 中的自定义异常
+ public static class DecoderAlreadyFinalizedException extends RuntimeException {
+ public DecoderAlreadyFinalizedException(String msg) { super(msg); }
+ }
+ public static class DecoderInvalidStringLengthException extends IllegalArgumentException {
+ public DecoderInvalidStringLengthException(String msg) { super(msg); }
+ }
+ public static class DecoderNonZeroCarryException extends IllegalStateException {
+ public DecoderNonZeroCarryException(String msg) { super(msg); }
+ }
+ public static class DecoderChecksumException extends RuntimeException {
+ public DecoderChecksumException(String msg) { super(msg); }
+ }
+
+ // 字母表字符串(与 Python 中一致)
+ private static final String ALPHABET_STRING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ*~$=U";
+
+ // 解码映射 char -> value
+ private final Map alphabet = new HashMap<>();
+
+ // 内部缓冲
+ private final StringBuilder stringBuffer = new StringBuilder();
+ private final ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
+
+ private boolean isFinished = false;
+ private final boolean doChecksum; // Python 默认这里传入的 checksum 参数,我们用 false
+ private int checksum = 0;
+
+ public Krock32Decoder() {
+ this(false, true, false);
+ }
+
+ public Krock32Decoder(boolean strict, boolean ignoreNonAlphabet, boolean checksum) {
+ this.doChecksum = checksum;
+ makeAlphabet(ALPHABET_STRING, strict);
+ }
+
+ // 复制 Python _make_alphabet 逻辑(包含 I/L -> 1, O -> 0 映射)
+ private void makeAlphabet(String alphabetString, boolean strict) {
+ for (int i = 0; i < alphabetString.length(); i++) {
+ char x = alphabetString.charAt(i);
+ alphabet.put(Character.toUpperCase(x), i);
+ if (!strict) {
+ alphabet.put(Character.toLowerCase(x), i);
+ }
+ }
+ if (!strict) {
+ alphabet.put('O', 0); alphabet.put('o', 0);
+ alphabet.put('I', 1); alphabet.put('i', 1);
+ alphabet.put('L', 1); alphabet.put('l', 1);
+ }
+ }
+
+ // 如果启用 checksum,这里实现与 Python 一致的更新逻辑
+ private void updateChecksum(int b) {
+ if (!doChecksum) return;
+ checksum = ((checksum << 8) + (b & 0xFF)) % 37;
+ }
+
+ // 一个内部记录结构,等价 namedtuple("ProcessedByte", ["byte","carry"])
+ private static final class PByte {
+ final int byteVal; // 0..255
+ final int carry; // carry bits as int
+ PByte(int b, int c) { this.byteVal = b; this.carry = c; }
+ }
+
+ // _decode_first_byte
+ private PByte decodeFirstByte(char s0, char s1) {
+ Integer v0 = alphabet.get(s0);
+ Integer v1 = alphabet.get(s1);
+ if (v0 == null || v1 == null) throw new IllegalArgumentException("Invalid symbol in input");
+ int b = (v0 << 3);
+ b += (v1 >> 2);
+ int carry = v1 & 0b11;
+ updateChecksum(b);
+ return new PByte(b & 0xFF, carry);
+ }
+
+ // _decode_second_byte
+ private PByte decodeSecondByte(char s0, char s1, int carry) {
+ Integer v0 = alphabet.get(s0);
+ Integer v1 = alphabet.get(s1);
+ if (v0 == null || v1 == null) throw new IllegalArgumentException("Invalid symbol in input");
+ int b = (v0 << 1) + (carry << 6);
+ b += (v1 >> 4);
+ int newCarry = v1 & 0b1111;
+ updateChecksum(b);
+ return new PByte(b & 0xFF, newCarry);
+ }
+
+ // _decode_third_byte
+ private PByte decodeThirdByte(char s, int carry) {
+ Integer v = alphabet.get(s);
+ if (v == null) throw new IllegalArgumentException("Invalid symbol in input");
+ int b = (v >> 1) + (carry << 4);
+ int newCarry = v & 1;
+ updateChecksum(b);
+ return new PByte(b & 0xFF, newCarry);
+ }
+
+ // _decode_fourth_byte
+ private PByte decodeFourthByte(char s0, char s1, int carry) {
+ Integer v0 = alphabet.get(s0);
+ Integer v1 = alphabet.get(s1);
+ if (v0 == null || v1 == null) throw new IllegalArgumentException("Invalid symbol in input");
+ int b = (v0 << 2) + (carry << 7);
+ b += (v1 >> 3);
+ int newCarry = v1 & 0b111;
+ updateChecksum(b);
+ return new PByte(b & 0xFF, newCarry);
+ }
+
+ // _decode_fifth_byte
+ private PByte decodeFifthByte(char s, int carry) {
+ Integer v = alphabet.get(s);
+ if (v == null) throw new IllegalArgumentException("Invalid symbol in input");
+ int b = (carry << 5) + v;
+ updateChecksum(b);
+ return new PByte(b & 0xFF, 0);
+ }
+
+ // _return_quantum 检查 carry 是否为 0
+ private void returnQuantumOrThrow(String quantum, PByte p) {
+ if (p.carry != 0) {
+ throw new DecoderNonZeroCarryException(
+ String.format("Quantum %s decoded with non-zero carry %d", quantum, p.carry)
+ );
+ }
+ // 否则正常(调用方将把 byte 写入)
+ }
+
+ // _decode_quantum 实现
+ private byte[] decodeQuantum(String quantum) {
+ int len = quantum.length();
+ if (len != 2 && len != 4 && len != 5 && len != 7 && len != 8) {
+ throw new DecoderInvalidStringLengthException("Quantum length must be one of 2,4,5,7,8");
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ // first 2 chars
+ PByte p1 = decodeFirstByte(quantum.charAt(0), quantum.charAt(1));
+ out.write(p1.byteVal);
+ if (len == 2) {
+ return checkReturnQuantum(quantum, p1, out);
+ }
+
+ // next 2 chars (positions 2..3)
+ PByte p2 = decodeSecondByte(quantum.charAt(2), quantum.charAt(3), p1.carry);
+ out.write(p2.byteVal);
+ if (len == 4) {
+ return checkReturnQuantum(quantum, p2, out);
+ }
+
+ // 5th char at index 4
+ PByte p3 = decodeThirdByte(quantum.charAt(4), p2.carry);
+ out.write(p3.byteVal);
+ if (len == 5) {
+ return checkReturnQuantum(quantum, p3, out);
+ }
+
+ // 6..6 (positions 5..6)
+ PByte p4 = decodeFourthByte(quantum.charAt(5), quantum.charAt(6), p3.carry);
+ out.write(p4.byteVal);
+ if (len == 7) {
+ return checkReturnQuantum(quantum, p4, out);
+ }
+
+ // final 8th char at index 7
+ PByte p5 = decodeFifthByte(quantum.charAt(7), p4.carry);
+ out.write(p5.byteVal);
+ return out.toByteArray();
+ }
+
+ // wrapper used so we can check carry or return array
+ private byte[] checkReturnQuantum(String quantum, PByte p, ByteArrayOutputStream out) {
+ if (p.carry == 0) {
+ return out.toByteArray();
+ } else {
+ throw new DecoderNonZeroCarryException(
+ String.format("Quantum %s decoded with non-zero carry %d", quantum, p.carry)
+ );
+ }
+ }
+
+ // _consume() - 对每一个完整 8 字符块(quantum)之外的部分保留到 buffer
+ private void consume() {
+ int tail = 0;
+ int len = stringBuffer.length();
+ for (int head = 8; head < len; head += 8) {
+ String quantum = stringBuffer.substring(tail, head);
+ byte[] decoded = decodeQuantum(quantum);
+ byteArray.writeBytes(decoded);
+ tail = head;
+ }
+ // 将剩余部分保留
+ if (tail == 0) {
+ // nothing consumed
+ } else {
+ // remove consumed part: keep substring from tail..end
+ String remain = stringBuffer.substring(tail);
+ stringBuffer.setLength(0);
+ stringBuffer.append(remain);
+ return;
+ }
+ // if nothing consumed, keep original buffer unchanged
+ }
+
+ /**
+ * update 方法:等价 Python 的 update()
+ * 它会把字符串追加到内部缓冲并尝试消费完整的 8 字符量子
+ */
+ public void update(String s) {
+ if (isFinished) throw new DecoderAlreadyFinalizedException("Decoder already finalized");
+ Objects.requireNonNull(s);
+ stringBuffer.append(s);
+ consume();
+ }
+
+ // _check_checksum (如果 doChecksum 为 true,这里会比较)
+ private byte[] checkChecksum(char checkSymbol) {
+ Integer expected = alphabet.get(checkSymbol);
+ if (expected == null) throw new IllegalArgumentException("Invalid checksum symbol");
+ if (checksum == expected) {
+ return byteArray.toByteArray();
+ } else {
+ throw new DecoderChecksumException(
+ String.format("Calculated checksum %d, expected %d", checksum, expected)
+ );
+ }
+ }
+
+ /**
+ * finalize 的 Java 实现(命名为 finalizeDecode 避免与 Object.finalize 冲突)
+ */
+ public byte[] finalizeDecode() {
+ if (isFinished) throw new DecoderAlreadyFinalizedException("Decoder already finalized");
+ isFinished = true;
+
+ char checkSymbol = 0;
+ if (doChecksum) {
+ if (stringBuffer.length() == 0) {
+ throw new DecoderChecksumException("No checksum symbol present");
+ }
+ checkSymbol = stringBuffer.charAt(stringBuffer.length() - 1);
+ stringBuffer.setLength(stringBuffer.length() - 1);
+ }
+
+ if (stringBuffer.length() > 0) {
+ byte[] decoded = decodeQuantum(stringBuffer.toString());
+ byteArray.writeBytes(decoded);
+ }
+
+ if (doChecksum) {
+ return checkChecksum(checkSymbol);
+ } else {
+ return byteArray.toByteArray();
+ }
+ }
+
+ // 辅助静态方法:直接模拟你给出的三行代码
+ public static byte[] decodeAddress(String key) {
+ String part = key.split(":", 2)[0];
+ Krock32Decoder dec = new Krock32Decoder();
+ dec.update(part);
+ return dec.finalizeDecode();
+ }
+
+ // 简单测试(示例) - 真实使用时请用单元测试验证
+ public static void main(String[] args) {
+ // 示例:请替换为真实的 krock32 编码字符串以测试
+ String sampleKey = "CI2FM2DV:other";
+ try {
+ byte[] addr = Krock32Decoder.decodeAddress(sampleKey);
+ System.out.println("Decoded bytes length = " + addr.length);
+ System.out.print("Decoded bytes hex: ");
+ for (byte b : addr) {
+ System.out.printf("%02x", b);
+ }
+ System.out.println();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Encoder.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Encoder.java
new file mode 100644
index 00000000..87e9c755
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/dioxide/utils/Krock32Encoder.java
@@ -0,0 +1,213 @@
+package com.alipay.antchain.bridge.plugins.dioxide.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.*;
+
+/**
+ * Java 重写的 krock32.Encoder
+ *
+ * 对应 Python:
+ * encoder = krock32.Encoder()
+ * encoder.update(data)
+ * encoded = encoder.finalize()
+ *
+ */
+public class Krock32Encoder {
+
+ // 异常定义
+ public static class EncoderAlreadyFinalizedException extends RuntimeException {
+ public EncoderAlreadyFinalizedException(String msg) { super(msg); }
+ }
+
+ // 字母表字符串
+ private static final String ALPHABET_STRING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ*~$=U";
+
+ // value → char 的映射(与 Python 一致)
+ private final Map alphabet = new HashMap<>();
+
+ // 内部缓冲
+ private final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
+ private final List stringList = new ArrayList<>();
+
+ private boolean isFinished = false;
+ private final boolean doChecksum;
+ private int checksum = 0;
+
+ public Krock32Encoder() {
+ this(false);
+ }
+
+ public Krock32Encoder(boolean checksum) {
+ this.doChecksum = checksum;
+ makeAlphabet(ALPHABET_STRING);
+ }
+
+ // 复制 Python _make_alphabet
+ private void makeAlphabet(String alphabetString) {
+ for (int i = 0; i < alphabetString.length(); i++) {
+ alphabet.put(i, alphabetString.charAt(i));
+ }
+ }
+
+ // _update_checksum
+ private void updateChecksum(int b) {
+ if (!doChecksum) return;
+ checksum = ((checksum << 8) + (b & 0xFF)) % 37;
+ }
+
+ // 对应 Python 的 namedtuple("ProcessedQuin", ["sym", "rem"])
+ private static final class PQuin {
+ final String sym;
+ final int rem;
+ PQuin(String s, int r) { this.sym = s; this.rem = r; }
+ }
+
+ // _encode_first_quin
+ private PQuin encodeFirstQuin(int b) {
+ updateChecksum(b);
+ int quin = (b >> 3);
+ int rem = (b & 0b111) << 2;
+ return new PQuin(String.valueOf(alphabet.get(quin)), rem);
+ }
+
+ // _encode_second_quin
+ private PQuin encodeSecondQuin(int b, int remainder) {
+ updateChecksum(b);
+ StringBuilder sym = new StringBuilder();
+ int quin = (b >> 6) + remainder;
+ sym.append(alphabet.get(quin));
+ quin = (b >> 1) & 0b11111;
+ int rem = (b & 0b1) << 4;
+ sym.append(alphabet.get(quin));
+ return new PQuin(sym.toString(), rem);
+ }
+
+ // _encode_third_quin
+ private PQuin encodeThirdQuin(int b, int remainder) {
+ updateChecksum(b);
+ int quin = (b >> 4) + remainder;
+ int rem = (b & 0b1111) << 1;
+ return new PQuin(String.valueOf(alphabet.get(quin)), rem);
+ }
+
+ // _encode_fourth_quin
+ private PQuin encodeFourthQuin(int b, int remainder) {
+ updateChecksum(b);
+ StringBuilder sym = new StringBuilder();
+ int quin = (b >> 7) + remainder;
+ sym.append(alphabet.get(quin));
+ quin = (b >> 2) & 0b11111;
+ sym.append(alphabet.get(quin));
+ int rem = (b & 0b11) << 3;
+ return new PQuin(sym.toString(), rem);
+ }
+
+ // _encode_fifth_quin
+ private PQuin encodeFifthQuin(int b, int remainder) {
+ updateChecksum(b);
+ StringBuilder sym = new StringBuilder();
+ int quin = (b >> 5) + remainder;
+ sym.append(alphabet.get(quin));
+ quin = b & 0b11111;
+ sym.append(alphabet.get(quin));
+ return new PQuin(sym.toString(), 0);
+ }
+
+ // _encode_quantum
+ private List encodeQuantum(byte[] quantum) {
+ List slist = new ArrayList<>();
+
+ // 第一个字节
+ PQuin p1 = encodeFirstQuin(quantum[0] & 0xFF);
+ slist.add(p1.sym);
+ if (quantum.length == 1) {
+ slist.add(String.valueOf(alphabet.get(p1.rem)));
+ return slist;
+ }
+
+ // 第二个字节
+ PQuin p2 = encodeSecondQuin(quantum[1] & 0xFF, p1.rem);
+ slist.add(p2.sym);
+ if (quantum.length == 2) {
+ slist.add(String.valueOf(alphabet.get(p2.rem)));
+ return slist;
+ }
+
+ // 第三个字节
+ PQuin p3 = encodeThirdQuin(quantum[2] & 0xFF, p2.rem);
+ slist.add(p3.sym);
+ if (quantum.length == 3) {
+ slist.add(String.valueOf(alphabet.get(p3.rem)));
+ return slist;
+ }
+
+ // 第四个字节
+ PQuin p4 = encodeFourthQuin(quantum[3] & 0xFF, p3.rem);
+ slist.add(p4.sym);
+ if (quantum.length == 4) {
+ slist.add(String.valueOf(alphabet.get(p4.rem)));
+ return slist;
+ }
+
+ // 第五个字节
+ PQuin p5 = encodeFifthQuin(quantum[4] & 0xFF, p4.rem);
+ slist.add(p5.sym);
+ return slist;
+ }
+
+ // _consume
+ private void consume() {
+ byte[] buffer = byteBuffer.toByteArray();
+ int tail = 0;
+ for (int head = 5; head < buffer.length; head += 5) {
+ byte[] quantum = Arrays.copyOfRange(buffer, tail, head);
+ stringList.addAll(encodeQuantum(quantum));
+ tail = head;
+ }
+ byte[] remaining = Arrays.copyOfRange(buffer, tail, buffer.length);
+ byteBuffer.reset();
+ try {
+ byteBuffer.write(remaining);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // update
+ public void update(byte[] data) {
+ if (isFinished) throw new EncoderAlreadyFinalizedException("Encoder already finalized");
+ Objects.requireNonNull(data);
+ try {
+ byteBuffer.write(data);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ consume();
+ }
+
+ // finalizeEncode (对应 Python 的 finalize)
+ public String finalizeEncode() {
+ if (isFinished) throw new EncoderAlreadyFinalizedException("Encoder already finalized");
+ isFinished = true;
+
+ byte[] remaining = byteBuffer.toByteArray();
+ if (remaining.length > 0) {
+ stringList.addAll(encodeQuantum(remaining));
+ }
+
+ if (doChecksum) {
+ stringList.add(String.valueOf(alphabet.get(checksum)));
+ }
+
+ return String.join("", stringList);
+ }
+
+ /**
+ * 静态便捷方法:直接编码
+ */
+ public static String encode(byte[] data) {
+ Krock32Encoder enc = new Krock32Encoder();
+ enc.update(data);
+ return enc.finalizeEncode();
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCServiceTest.java b/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCServiceTest.java
new file mode 100644
index 00000000..99cc21cf
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/dioxide/DioxideBBCServiceTest.java
@@ -0,0 +1,646 @@
+package com.alipay.antchain.bridge.plugins.dioxide;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext;
+import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.AuthMessageContract;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
+import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
+import com.alipay.antchain.bridge.commons.core.am.IAuthMessage;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt;
+import com.alipay.antchain.bridge.commons.core.sdp.ISDPMessage;
+import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
+import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVTypeEnum;
+import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVUtils;
+import com.alipay.antchain.bridge.commons.utils.codec.tlv.annotation.TLVField;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideConfig;
+import com.alipay.antchain.bridge.plugins.dioxide.conf.DioxideTypes;
+import com.alipay.antchain.bridge.plugins.dioxide.core.*;
+import com.alipay.antchain.bridge.plugins.dioxide.gcl.Contracts;
+import com.alipay.antchain.bridge.plugins.spi.bbc.AbstractBBCService;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public class DioxideBBCServiceTest {
+
+ private static final String RPC_URL = "http://127.0.0.1:62222/api";
+
+ private static final String INVALID_RPC_URL = "http://invalid_rpc_url/api";
+
+ private static final String WS_RPC = "ws://127.0.0.1:62222/api";
+
+ private static final String DAPP_NAME = "AcbDapp";
+
+ private static final String BBC_DIO_PRIVATE_KEY = "WTKi+W99TEEt153Zt8isUznwXqYkA0aVWEbd7edk6AvivGov5hBLJLQbS2hk8bnC3FM8Et6+Axaw1uukce+ZEQ==";
+
+ private static final String CHAIN_DOMAIN = "123456";
+
+ private static final String AM_CONTRACT = "AuthMsg";
+
+ private static final String SDP_CONTRACT = "SDPMsg";
+
+ private static final String APP_CONTRACT = "AppContract";
+
+ private static final String TEST_MESSAGE = "awesome antchain-bridge";
+
+ private static boolean setupBBC;
+
+ private static DioxideBBCService dioxideBBCService;
+
+ private static String random_dapp_name;
+
+ private static long dappCid = 12341234;
+
+ @BeforeClass
+ public static void init() throws Exception {
+
+ dioxideBBCService = new DioxideBBCService();
+ Method method = AbstractBBCService.class.getDeclaredMethod("setLogger", Logger.class);
+ method.setAccessible(true);
+ method.invoke(dioxideBBCService, log);
+
+ random_dapp_name = ranDomDappId();
+ log.info("random_dapp_name: {}", random_dapp_name);
+ }
+
+ public static String ranDomDappId() {
+ final String prefix = "Dapp";
+ final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ final int base = chars.length();
+
+ java.security.SecureRandom random = new java.security.SecureRandom();
+
+ // 后缀长度:1~4
+ int suffixLen = 1 + random.nextInt(4);
+
+ // 秒级时间因子(防同一秒大量重复)
+ long timeFactor = (System.currentTimeMillis() / 1000) % base;
+
+ StringBuilder suffix = new StringBuilder(suffixLen);
+
+ // 第一位混入时间(弱相关)
+ suffix.append(chars.charAt((int) timeFactor));
+
+ // 剩余位全部真随机
+ while (suffix.length() < suffixLen) {
+ suffix.append(chars.charAt(random.nextInt(base)));
+ }
+
+ return prefix + suffix.toString();
+ }
+
+ @Test
+ public void testStartup() {
+ // start up success
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ DioxideBBCService dioxideBBCService = new DioxideBBCService();
+ dioxideBBCService.startup(mockValidCtx);
+ Assert.assertNull(dioxideBBCService.getBbcContext().getAuthMessageContract());
+ Assert.assertNull(dioxideBBCService.getBbcContext().getSdpContract());
+
+ // start up failed
+ AbstractBBCContext mockInvalidCtx = mockInvalidCtx();
+ try {
+ dioxideBBCService.startup(mockInvalidCtx);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testStartupWithDeployedContract() {
+ // start up a tmp
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ DioxideBBCService bbcServiceTmp = new DioxideBBCService();
+ bbcServiceTmp.startup(mockValidCtx);
+
+ // set up am and sdp
+ bbcServiceTmp.setupAuthMessageContract();
+ bbcServiceTmp.setupSDPMessageContract();
+ String amAddr = bbcServiceTmp.getContext().getAuthMessageContract().getContractAddress();
+ String sdpAddr = bbcServiceTmp.getContext().getSdpContract().getContractAddress();
+
+ // start up success
+ AbstractBBCContext ctx = mockValidCtxWithPreDeployedContracts(amAddr, sdpAddr);
+ DioxideBBCService dioxideBBCService = new DioxideBBCService();
+ dioxideBBCService.startup(ctx);
+
+ Assert.assertEquals(amAddr, dioxideBBCService.getBbcContext().getAuthMessageContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, dioxideBBCService.getBbcContext().getAuthMessageContract().getStatus());
+ Assert.assertEquals(sdpAddr, dioxideBBCService.getBbcContext().getSdpContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, dioxideBBCService.getBbcContext().getSdpContract().getStatus());
+ }
+
+ @Test
+ public void testStartupWithReadyContract() {
+ // start up a tmp
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ DioxideBBCService bbcServiceTmp = new DioxideBBCService();
+ bbcServiceTmp.startup(mockValidCtx);
+
+ // set up am and sdp
+ bbcServiceTmp.setupAuthMessageContract();
+ bbcServiceTmp.setupSDPMessageContract();
+ String amAddr = bbcServiceTmp.getContext().getAuthMessageContract().getContractAddress();
+ String sdpAddr = bbcServiceTmp.getContext().getSdpContract().getContractAddress();
+
+ // start up success
+ DioxideBBCService dioxideBBCService = new DioxideBBCService();
+ AbstractBBCContext ctx = mockValidCtxWithPreReadyContracts(amAddr, sdpAddr);
+ dioxideBBCService.startup(ctx);
+ Assert.assertEquals(amAddr, dioxideBBCService.getBbcContext().getAuthMessageContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, dioxideBBCService.getBbcContext().getAuthMessageContract().getStatus());
+ Assert.assertEquals(sdpAddr, dioxideBBCService.getBbcContext().getSdpContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, dioxideBBCService.getBbcContext().getSdpContract().getStatus());
+ }
+
+ @Test
+ public void testShutdown() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ DioxideBBCService dioxideBBCService = new DioxideBBCService();
+ dioxideBBCService.startup(mockValidCtx);
+ dioxideBBCService.shutdown();
+ }
+
+ @Test
+ public void testGetContext() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ DioxideBBCService dioxideBBCService = new DioxideBBCService();
+ dioxideBBCService.startup(mockValidCtx);
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertNotNull(ctx);
+ Assert.assertNull(ctx.getAuthMessageContract());
+ }
+
+ @Test
+ public void testQueryLatestHeight() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ Long height = dioxideBBCService.queryLatestHeight();
+ log.info("height: {}", height);
+ }
+
+ @Test
+ public void testSetupAuthMessageContract() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ dioxideBBCService.setupAuthMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getAuthMessageContract().getStatus());
+ log.info("am contract cid: {}", ctx.getAuthMessageContract().getContractAddress());
+ }
+
+
+ @Test
+ public void testSetupSDPMessageContract() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ dioxideBBCService.setupSDPMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ log.info("sdp contract cid: {}", ctx.getSdpContract().getContractAddress());
+ }
+
+ @Test
+ public void testQuerySDPMessageSeq() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ // set up sdp
+ dioxideBBCService.setupSDPMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ log.info("sdp contract cid: {}", ctx.getSdpContract().getContractAddress());
+
+ long seq = dioxideBBCService.querySDPMessageSeq(
+ "senderDomain",
+ DigestUtil.sha256Hex("senderID"),
+ CHAIN_DOMAIN,
+ DigestUtil.sha256Hex("receiverID")
+ );
+ Assert.assertEquals(0L, seq);
+ }
+
+ @Test
+ public void testSetAmContractAndLocalDomain() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ // set up am and sdp
+ dioxideBBCService.setupAuthMessageContract();
+ dioxideBBCService.setupSDPMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getAuthMessageContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ log.info("am contract cid: {}", ctx.getAuthMessageContract().getContractAddress());
+ log.info("sdp contract cid: {}", ctx.getSdpContract().getContractAddress());
+
+ // set am to sdp
+ dioxideBBCService.setAmContract(ctx.getAuthMessageContract().getContractAddress());
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+
+ // set domain to sdp
+ dioxideBBCService.setLocalDomain(CHAIN_DOMAIN);
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getSdpContract().getStatus());
+ }
+
+ @Test
+ public void testSetProtocol() {
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ // set up am and sdp
+ dioxideBBCService.setupAuthMessageContract();
+ dioxideBBCService.setupSDPMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getAuthMessageContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ log.info("am contract cid: {}", ctx.getAuthMessageContract().getContractAddress());
+ log.info("sdp contract cid: {}", ctx.getSdpContract().getContractAddress());
+
+ // set protocol to am
+ dioxideBBCService.setProtocol(ctx.getSdpContract().getContractAddress(), "0");
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getAuthMessageContract().getStatus());
+ }
+
+ @Test
+ public void testReadCrossChainMessagesByHeight_sendUnordered() {
+ setupBbc();
+
+ // app send msg
+ int[] receiverDomainArray = toIntArray("112233".getBytes(StandardCharsets.UTF_8));
+ int[] receiverIDArray = toIntArray(HexUtil.decodeHex(DigestUtil.sha256Hex("receiverID")));
+ int[] msgArray = toIntArray("abcd".getBytes(StandardCharsets.UTF_8));
+ String txHash = dioxideBBCService.getDioxideClient().sendTransaction(
+ JSON.toJSONString(Map.of(
+ "sender", dioxideBBCService.getDioxideClient().getDioxideAccount().getAddressInString(),
+ "function", String.format("%s.%s.sendUnorderedMessage", random_dapp_name, APP_CONTRACT),
+ "args", Map.of(
+ "receiverDomain", receiverDomainArray,
+ "receiver", receiverIDArray,
+ "message", msgArray
+ )
+ )),
+ true
+ );
+
+ // test relay tx info
+ DioxideTransaction dtx = dioxideBBCService.getDioxideClient().getTransactionByHash(txHash);
+ log.info("[sendUnorderedMessage] tx info: \n{}", JSON.toJSONString(dtx, SerializerFeature.PrettyFormat));
+
+ List relayTx = dtx.getInvocation().getRelays();
+ if (CollUtil.isNotEmpty(relayTx)) {
+ log.info("relay tx: {}", relayTx);
+ relayTx.forEach(tx -> {
+ String realTx = tx.split(":")[0];
+ DioxideTransaction dtx2 = dioxideBBCService.getDioxideClient().getTransactionByHash(realTx);
+ log.info("relayTx info-{}: \n{}", realTx, JSON.toJSONString(dtx2, SerializerFeature.PrettyFormat));
+ });
+ }
+ // and get relay tx by block height
+ long height = dtx.getHeight();
+ DioxideTransactionBlock block = dioxideBBCService.getDioxideClient().getTransactionBlockInGlobalShardByHeight(height);
+ log.info("block-info-height-{}: \n{}", height, JSON.toJSONString(block, SerializerFeature.PrettyFormat));
+
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // 恢复中断状态,这在 Java 多线程中是最佳实践
+ }
+ List messageList = ListUtil.toList();
+ for (long i = height-5; i < height+5; i++) {
+ messageList.addAll(dioxideBBCService.readCrossChainMessagesByHeight(i));
+ }
+ Assert.assertEquals(1, messageList.size());
+ Assert.assertEquals(CrossChainMessage.CrossChainMessageType.AUTH_MSG, messageList.getFirst().getType());
+
+ log.info("messageList:");
+ messageList.forEach(message -> {
+ log.info("\n{}", JSON.toJSONString(message, SerializerFeature.PrettyFormat));
+ });
+
+ log.info("parse am event:\n{}", toIntArray(messageList.get(0).getMessage()));
+ }
+
+ @Test
+ public void testReadCrossChainMessagesByHeight_sendOrdered() {
+ setupBbc();
+
+ // app send msg
+ int[] receiverDomainArray = toIntArray("112233".getBytes(StandardCharsets.UTF_8));
+ int[] receiverIDArray = toIntArray(HexUtil.decodeHex(DigestUtil.sha256Hex("receiverID")));
+ int[] msgArray = toIntArray("abcd".getBytes(StandardCharsets.UTF_8));
+ String txHash = dioxideBBCService.getDioxideClient().sendTransaction(
+ JSON.toJSONString(Map.of(
+ "sender", dioxideBBCService.getDioxideClient().getDioxideAccount().getAddressInString(),
+ "function", String.format("%s.%s.sendMessage", random_dapp_name, APP_CONTRACT),
+ "args", Map.of(
+ "receiverDomain", receiverDomainArray,
+ "receiver", receiverIDArray,
+ "message", msgArray
+ )
+ )),
+ true
+ );
+
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // 恢复中断状态,这在 Java 多线程中是最佳实践
+ }
+ long height = dioxideBBCService.getDioxideClient().getTransactionByHash(txHash).getHeight();
+ List messageList = ListUtil.toList();
+ for (long i = height-5; i < height+10; i++) {
+ messageList.addAll(dioxideBBCService.readCrossChainMessagesByHeight(i));
+ }
+ Assert.assertEquals(1, messageList.size());
+ Assert.assertEquals(CrossChainMessage.CrossChainMessageType.AUTH_MSG, messageList.getFirst().getType());
+
+ DioxideTransaction dtx = dioxideBBCService.getDioxideClient().getTransactionByHash(txHash);
+ log.info("[sendMessage] tx info:\n{}", JSON.toJSONString(dtx, SerializerFeature.PrettyFormat));
+
+ log.info("messageList:");
+ messageList.forEach(message -> {
+ log.info("\n{}", JSON.toJSONString(message, SerializerFeature.PrettyFormat));
+ });
+ }
+
+ @Test
+ public void testRelayAuthMessageAndReadCrossChainMessageReceipt() throws Exception {
+ setupBbc();
+
+ // relay am msg
+ CrossChainMessageReceipt receipt = dioxideBBCService.relayAuthMessage(getRawMsgFromRelayer());
+ Assert.assertTrue(receipt.isSuccessful());
+
+ // wait for tx success (ReadCrossChainMessageReceipt)
+ while (true) {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ CrossChainMessageReceipt receipt1 = dioxideBBCService.readCrossChainMessageReceipt(receipt.getTxhash());
+ if (receipt1.isConfirmed() && receipt1.isSuccessful()) {
+ break;
+ }
+ }
+
+ // check variable status in dapp
+ JSONObject resp3 = dioxideBBCService.getDioxideClient().getContractState(dioxideBBCService.getConfig().getDappName(), APP_CONTRACT, DioxideTypes.Scope.Global, "");
+ int[] receiveUnorderedMsg = resp3.getJSONObject("State").getJSONArray("last_uo_msg").toJavaObject(int[].class);
+ log.info("receiveUnorderedMsg:\n{}", receiveUnorderedMsg);
+ Assert.assertArrayEquals(receiveUnorderedMsg, toIntArray(TEST_MESSAGE.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @SneakyThrows
+ private void setupBbc() {
+ if (setupBBC) {
+ return;
+ }
+
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ dioxideBBCService.startup(mockValidCtx);
+
+ // set up am and sdp
+ dioxideBBCService.setupAuthMessageContract();
+ dioxideBBCService.setupSDPMessageContract();
+
+ AbstractBBCContext ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getAuthMessageContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ log.info("am contract cid: {}", ctx.getAuthMessageContract().getContractAddress());
+ log.info("sdp contract cid: {}", ctx.getSdpContract().getContractAddress());
+
+ // set am to sdp
+ dioxideBBCService.setAmContract(ctx.getAuthMessageContract().getContractAddress());
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+
+ // set domain to sdp
+ dioxideBBCService.setLocalDomain(CHAIN_DOMAIN);
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getSdpContract().getStatus());
+
+ // set protocol to am
+ dioxideBBCService.setProtocol(ctx.getSdpContract().getContractAddress(), "0");
+
+ // check contract status
+ ctx = dioxideBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getAuthMessageContract().getStatus());
+
+ // set up app
+ String dappContractSource = new String(
+ Base64.getDecoder().decode(Contracts.APPCONTRACT),
+ StandardCharsets.UTF_8
+ );
+ dappCid = dioxideBBCService.getDioxideClient().deployContract(APP_CONTRACT, dappContractSource, JSON.toJSONString(Map.of(
+ "_owner", dioxideBBCService.getDioxideClient().getDioxideAccount().getAddressInString()
+ )));
+ log.info("setup dapp, cid: {}", dappCid);
+
+ // set protocol to dapp
+ long protocolCid = Long.parseLong(ctx.getSdpContract().getContractAddress());
+ String protocolAddress = String.format("0x%016X:contract", protocolCid);
+ dioxideBBCService.getDioxideClient().sendTransaction(
+ JSON.toJSONString(Map.of(
+ "sender", dioxideBBCService.getDioxideClient().getDioxideAccount().getAddressInString(),
+ "function", String.format("%s.%s.setProtocol", random_dapp_name, APP_CONTRACT),
+ "args", Map.of(
+ "_protocolContractId", protocolCid,
+ "_protocolAddress", protocolAddress
+ )
+ )),
+ true
+ );
+ log.info("set protocol to app contract");
+
+ setupBBC = true;
+ }
+
+ private int[] toIntArray(byte[] bytes) {
+ int[] arr = new int[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ arr[i] = bytes[i] & 0xFF; // 转无符号 uint8
+ }
+ return arr;
+ }
+
+ private AbstractBBCContext mockValidCtx() {
+ DioxideConfig mockConf = new DioxideConfig();
+ mockConf.setRpcUrl(RPC_URL);
+ mockConf.setWsRpc(WS_RPC);
+ mockConf.setPrivateKey(BBC_DIO_PRIVATE_KEY);
+ mockConf.setDappName(random_dapp_name);
+ mockConf.setIsPreContractDeployed(false);
+
+ AbstractBBCContext mockCtx = new DefaultBBCContext();
+ mockCtx.setConfForBlockchainClient(mockConf.toJsonString().getBytes(StandardCharsets.UTF_8));
+ return mockCtx;
+ }
+
+ private AbstractBBCContext mockValidCtxWithPreDeployedContracts(String amAddr, String sdpAddr) {
+ DioxideConfig mockConf = new DioxideConfig();
+ mockConf.setRpcUrl(RPC_URL);
+ mockConf.setWsRpc(WS_RPC);
+ mockConf.setPrivateKey(BBC_DIO_PRIVATE_KEY);
+ mockConf.setDappName(DAPP_NAME);
+ mockConf.setIsPreContractDeployed(true);
+
+ mockConf.setAmContractAddressDeployed(amAddr);
+ mockConf.setSdpContractAddressDeployed(sdpAddr);
+
+ AbstractBBCContext mockCtx = new DefaultBBCContext();
+ mockCtx.setConfForBlockchainClient(mockConf.toJsonString().getBytes());
+
+ return mockCtx;
+ }
+
+ private AbstractBBCContext mockValidCtxWithPreReadyContracts(String amAddr, String sdpAddr) {
+ DioxideConfig mockConf = new DioxideConfig();
+ mockConf.setRpcUrl(RPC_URL);
+ mockConf.setWsRpc(WS_RPC);
+ mockConf.setPrivateKey(BBC_DIO_PRIVATE_KEY);
+ mockConf.setDappName(DAPP_NAME);
+ mockConf.setIsPreContractDeployed(true);
+
+ mockConf.setAmContractAddressDeployed(amAddr);
+ mockConf.setSdpContractAddressDeployed(sdpAddr);
+
+ AbstractBBCContext mockCtx = new DefaultBBCContext();
+ mockCtx.setConfForBlockchainClient(mockConf.toJsonString().getBytes());
+
+ AuthMessageContract authMessageContract = new AuthMessageContract();
+ authMessageContract.setContractAddress(mockConf.getAmContractAddressDeployed());
+ authMessageContract.setStatus(ContractStatusEnum.CONTRACT_READY);
+ mockCtx.setAuthMessageContract(authMessageContract);
+ SDPContract sdpContract = new SDPContract();
+ sdpContract.setContractAddress(mockConf.getSdpContractAddressDeployed());
+ sdpContract.setStatus(ContractStatusEnum.CONTRACT_READY);
+ mockCtx.setSdpContract(sdpContract);
+
+ return mockCtx;
+ }
+
+ private AbstractBBCContext mockInvalidCtx() {
+ DioxideConfig mockConf = new DioxideConfig();
+ mockConf.setRpcUrl(INVALID_RPC_URL);
+ mockConf.setWsRpc(WS_RPC);
+ mockConf.setPrivateKey(BBC_DIO_PRIVATE_KEY);
+ mockConf.setDappName(random_dapp_name);
+ mockConf.setIsPreContractDeployed(false);
+
+ AbstractBBCContext mockCtx = new DefaultBBCContext();
+ mockCtx.setConfForBlockchainClient(mockConf.toJsonString().getBytes(StandardCharsets.UTF_8));
+ return mockCtx;
+ }
+
+ private byte[] getRawMsgFromRelayer() throws IOException {
+ String dappCidHex = String.format("%064x", dappCid);
+
+ ISDPMessage sdpMessage = SDPMessageFactory.createSDPMessage(
+ 1,
+ new byte[32],
+ CHAIN_DOMAIN,
+ HexUtil.decodeHex(dappCidHex),
+ -1,
+ TEST_MESSAGE.getBytes(StandardCharsets.UTF_8)
+ );
+
+ IAuthMessage am = AuthMessageFactory.createAuthMessage(
+ 1,
+ HexUtil.decodeHex(
+ String.format("000000000000000000000000%s", StrUtil.removePrefix("0x0000000000000000000000000000001111111111", "0x"))
+ ),
+ 0,
+ sdpMessage.encode()
+ );
+ MockResp resp = new MockResp();
+ resp.setRawResponse(am.encode());
+
+ MockProof proof = new MockProof();
+ proof.setResp(resp);
+ proof.setDomain("112233");
+
+ byte[] rawProof = TLVUtils.encode(proof);
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ stream.write(new byte[]{0, 0, 0, 0});
+
+ int len = rawProof.length;
+ stream.write((len >>> 24) & 0xFF);
+ stream.write((len >>> 16) & 0xFF);
+ stream.write((len >>> 8) & 0xFF);
+ stream.write((len) & 0xFF);
+
+ stream.write(rawProof);
+
+ return stream.toByteArray();
+ }
+
+ @Getter
+ @Setter
+ public static class MockProof {
+
+ @TLVField(tag = 5, type = TLVTypeEnum.BYTES)
+ private MockResp resp;
+
+ @TLVField(tag = 9, type = TLVTypeEnum.STRING)
+ private String domain;
+ }
+
+ @Getter
+ @Setter
+ public static class MockResp {
+
+ @TLVField(tag = 0, type = TLVTypeEnum.BYTES)
+ private byte[] rawResponse;
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/resources/logback.xml b/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/resources/logback.xml
new file mode 100644
index 00000000..3616df0d
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/offchain-plugin/src/test/resources/logback.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %msg%n
+
+
+
+
+ java_sdk_result.log
+ true
+
+
+ %d{HH:mm:ss.SSS} %msg%n
+
+
+
+
+
+
+
+
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/AppContract.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/AppContract.gcl
new file mode 100644
index 00000000..9ae2389d
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/AppContract.gcl
@@ -0,0 +1,186 @@
+import IContractUsingSDP;
+import ISDPMessage;
+
+contract AppContract implements IContractUsingSDP.ContractUsingSDPInterface {
+
+ // 消息列表结构 - 用于存储多条消息
+ struct MessageList {
+ array> messages;
+ }
+
+ @global address owner;
+ @global uint64 sdpContractId;
+ @global address sdpAddress;
+
+ @global array last_uo_msg;
+ @global array last_msg;
+
+ // 使用 hash 作为键(对应 Solidity 的 bytes32)
+ // 通过 struct 封装来存储消息数组列表
+ @global map recvMsg; // hash(author) -> MessageList
+ @global map sendMsg; // hash(receiver) -> MessageList
+
+ @global function on_deploy(address _owner) {
+ owner = _owner;
+ __debug.print("[on_deploy] AppContract deployed - address:", __address(), " id:", __id());
+ }
+
+ // 辅助函数:将 array 转换为 hash(用作 map 键)
+ @global function hash bytesToHash(array data) const {
+ // GCL 可以直接从 array 构造 hash
+ return hash(data);
+ }
+
+ @address function setProtocol(uint64 _protocolContractId, address _protocolAddress) public export {
+ __debug.assert(__transaction.get_sender() == owner);
+ relay@global (^_protocolContractId, ^_protocolAddress) {
+ sdpContractId = _protocolContractId;
+ sdpAddress = _protocolAddress;
+ __debug.print("[setProtocol] contractId: ", sdpContractId, " address: ", sdpAddress);
+ }
+ }
+
+ // 私有辅助函数:处理接收到的消息
+ @global function _processReceivedMessage(array senderDomain, array author, array message, bool isOrdered) {
+ __debug.assert(__transaction.get_sender() == sdpAddress);
+
+ // 存储最新消息到对应的变量
+ if (isOrdered) {
+ last_msg.set_length(0u32);
+ for(uint32 i=0u32; i < message.length(); i++) {
+ last_msg.push(message[i]);
+ }
+ } else {
+ last_uo_msg.set_length(0u32);
+ for(uint32 i=0u32; i < message.length(); i++) {
+ last_uo_msg.push(message[i]);
+ }
+ }
+
+ // 将消息添加到历史记录(使用 hash 作为键)
+ hash authorHash = bytesToHash(author);
+ array msgCopy;
+ for(uint32 i=0u32; i < message.length(); i++) {
+ msgCopy.push(message[i]);
+ }
+ // 确保 MessageList 存在,如果不存在则创建
+ if (!recvMsg.has(authorHash)) {
+ MessageList newList;
+ recvMsg[authorHash] = newList;
+ }
+ recvMsg[authorHash].messages.push(msgCopy);
+
+ // 打印事件日志
+ __debug.print("[Event] recvCrosschainMsg - senderDomain:", senderDomain, " author:", author, " message:", message, " isOrdered:", isOrdered);
+ }
+
+ @global function recvUnorderedMessage(array senderDomain, array author, array message) public export {
+ _processReceivedMessage(senderDomain, author, message, false);
+ }
+
+ @global function recvMessage(array senderDomain, array author, array message) public export {
+ _processReceivedMessage(senderDomain, author, message, true);
+ }
+
+ // 私有辅助函数:处理发送消息
+ @address function _processSendMessage(array receiverDomain, array receiver, array message, bool isOrdered) {
+ uint64 senderId = uint64(__id());
+ ISDPMessage.SDPMessageInterface SDPMsg = ISDPMessage.SDPMessageInterface(sdpContractId);
+
+ // 直接调用 SDPMsg 的发送函数
+ if (isOrdered) {
+ SDPMsg.sendMessage(receiverDomain, receiver, message, senderId);
+ } else {
+ SDPMsg.sendUnorderedMessage(receiverDomain, receiver, message, senderId);
+ }
+
+ // 将发送的消息添加到历史记录
+ relay@global (^receiver, ^message) {
+ hash receiverHash = bytesToHash(receiver);
+ array msgCopy;
+ for(uint32 i=0u32; i < message.length(); i++) {
+ msgCopy.push(message[i]);
+ }
+ // 确保 MessageList 存在,如果不存在则创建
+ if (!sendMsg.has(receiverHash)) {
+ MessageList newList;
+ sendMsg[receiverHash] = newList;
+ }
+ sendMsg[receiverHash].messages.push(msgCopy);
+ }
+
+ // 打印事件日志
+ __debug.print("[Event] sendCrosschainMsg - receiverDomain:", receiverDomain, " receiver:", receiver, " message:", message, " isOrdered:", isOrdered);
+ }
+
+ @address function sendUnorderedMessage(array receiverDomain, array receiver, array message) public export {
+ _processSendMessage(receiverDomain, receiver, message, false);
+ }
+
+ @address function sendMessage(array receiverDomain, array receiver, array message) public export {
+ _processSendMessage(receiverDomain, receiver, message, true);
+ }
+
+ // Getter 函数
+ @global function array getLastUoMsg() export const {
+ array result;
+ for (uint32 i = 0u32; i < last_uo_msg.length(); i++) {
+ result.push(last_uo_msg[i]);
+ }
+ return result;
+ }
+
+ @global function array getLastMsg() export const {
+ array result;
+ for (uint32 i = 0u32; i < last_msg.length(); i++) {
+ result.push(last_msg[i]);
+ }
+ return result;
+ }
+
+ // Getter 函数:获取接收到的消息列表(对应 Solidity 的 public mapping)
+ @global function array> getRecvMsg(array author) export const {
+ hash authorHash = bytesToHash(author);
+ array> result;
+ if (!recvMsg.has(authorHash)) {
+ return result; // 返回空数组
+ }
+ const MessageList msgList = recvMsg[authorHash];
+ for (uint32 i = 0u32; i < msgList.messages.length(); i++) {
+ result.push(msgList.messages[i]);
+ }
+ return result;
+ }
+
+ // Getter 函数:获取发送的消息列表(对应 Solidity 的 public mapping)
+ @global function array> getSendMsg(array receiver) export const {
+ hash receiverHash = bytesToHash(receiver);
+ array> result;
+ if (!sendMsg.has(receiverHash)) {
+ return result; // 返回空数组
+ }
+ const MessageList msgList = sendMsg[receiverHash];
+ for (uint32 i = 0u32; i < msgList.messages.length(); i++) {
+ result.push(msgList.messages[i]);
+ }
+ return result;
+ }
+
+ // Getter 函数:获取接收消息的数量
+ @global function uint32 getRecvMsgCount(array author) export const {
+ hash authorHash = bytesToHash(author);
+ if (!recvMsg.has(authorHash)) {
+ return 0u32;
+ }
+ return recvMsg[authorHash].messages.length();
+ }
+
+ // Getter 函数:获取发送消息的数量
+ @global function uint32 getSendMsgCount(array receiver) export const {
+ hash receiverHash = bytesToHash(receiver);
+ if (!sendMsg.has(receiverHash)) {
+ return 0u32;
+ }
+ return sendMsg[receiverHash].messages.length();
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/AuthMsg.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/AuthMsg.gcl
new file mode 100644
index 00000000..042f0f26
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/AuthMsg.gcl
@@ -0,0 +1,123 @@
+import IAuthMessage;
+import ISubProtocol;
+import AMLib;
+import Utils;
+
+contract AuthMsg implements IAuthMessage.AuthMessageInterface {
+
+ struct SubProtocol {
+ uint32 protocolType;
+ uint64 protocolID;
+ address protocolAddress;
+ bool exist;
+ }
+
+ @global address owner;
+ @global address relayer;
+
+ @global map subProtocols;
+ @global map protocolRoutes;
+ @global map protocolIDs;
+
+ @global AMLib.AuthMessage decodeAuthMsg;
+
+ @global function on_deploy(address _owner, address _relayer) {
+ owner = _owner;
+ relayer = _relayer;
+
+ __debug.print("[on_deploy] AuthMsg deployed - address:", __address(), " id:", __id());
+ __debug.print("[on_deploy] Owner:", owner, " Relayer:", relayer);
+ }
+
+ @address function setRelayer(address _relayer) public export {
+ __debug.assert(__transaction.get_sender() == owner);
+ relay@global (^_relayer){
+ relayer = _relayer;
+ }
+ }
+
+ @address function setProtocol(uint64 protocolID, address protocolAddress, uint32 protocolType) public export {
+ __debug.assert(__transaction.get_sender() == owner);
+ __debug.assert(!subProtocols[protocolAddress].exist);
+
+ relay@global (^protocolID, ^protocolAddress, ^protocolType) {
+ SubProtocol p;
+ p.exist = true;
+ p.protocolType = protocolType;
+ p.protocolID = protocolID;
+ p.protocolAddress = protocolAddress;
+ subProtocols[protocolAddress] = p;
+ protocolRoutes[protocolType] = protocolAddress;
+ protocolIDs[protocolType] = protocolID;
+
+ relay@external SubProtocolUpdate(protocolType, protocolID, protocolAddress);
+ __debug.print("[Event] SubProtocolUpdate - type:", protocolType, " id:", protocolID, " address:", protocolAddress);
+ }
+
+ array test;
+ test.push(100u8);
+ test.push(101u8);
+ test.push(102u8);
+ test.push(103u8);
+ test.push(104u8);
+ test.push(1u8);
+ test.push(2u8);
+ test.push(30u8);
+ test.push(40u8);
+ relay@global (^test) {
+ relay@external testEvent(test);
+ }
+ }
+
+ @global function recvFromProtocol(uint64 senderID, array message) public export {
+ address protocol = __transaction.get_sender();
+ __debug.assert(subProtocols[protocol].exist);
+
+ // use version 1 for now
+ AMLib.AuthMessage amV1;
+ amV1.version = 1u32;
+ amV1.author = Utils.uint256ToBytes32(uint256(senderID));
+ amV1.protocolType = subProtocols[protocol].protocolType;
+ amV1.body = Utils.bytesCopy(message);
+
+ array AMMsg = AMLib.encodeAuthMessage(amV1);
+ __debug.print("[recvFromProtocol] AuthMsg encoded: ", AMMsg);
+ relay@external SendAuthMessage(AMMsg);
+ }
+
+ @address function recvPkgFromRelayer(array pkg) public export {
+ __debug.assert(__transaction.get_sender() == relayer);
+
+ __debug.print("[recvPkgFromRelayer] Received UCP package, length: ", pkg.length());
+ __debug.print("[recvPkgFromRelayer] First 20 bytes: ", pkg);
+
+ // 1. 解码 MessageFromRelayer,获取 senderDomain 和 rawResp (AuthMessage bytes)
+ __debug.print("[recvPkgFromRelayer] About to call AMLib.decodeMessageFromRelayer");
+ AMLib.DecodeResult dr = AMLib.decodeMessageFromRelayer(pkg);
+ __debug.print("[recvPkgFromRelayer] AMLib.decodeMessageFromRelayer completed");
+ __debug.print("[recvPkgFromRelayer] senderDomain: ", dr.senderDomain);
+ __debug.print("[recvPkgFromRelayer] rawResp length: ", dr.rawResp.length());
+
+ // 2. 解码 AuthMessage
+ AMLib.AuthMessage msg = AMLib.decodeAuthMessage(dr.rawResp);
+ __debug.print("[recvPkgFromRelayer] Decoded AuthMessage: ", msg);
+
+ relay@global (^msg) {
+ decodeAuthMsg = msg;
+ }
+
+ // 3. 验证协议路由存在
+ address zeroAddress;
+ __debug.assert(protocolRoutes[msg.protocolType] != zeroAddress);
+
+ // 4. 调用上层协议(SDPMsg)的 recvMessage, 触发接收事件
+ relay@global (^msg, ^dr) {
+ uint64 protocolID = protocolIDs[msg.protocolType];
+ ISubProtocol.SubProtocolInterface sdp = ISubProtocol.SubProtocolInterface(protocolID);
+ sdp.recvMessage(dr.senderDomain, msg.author, msg.body);
+
+ relay@external recvAuthMessage(dr.senderDomain, dr.rawResp);
+ __debug.print("[recvPkgFromRelayer] Message routed successfully");
+ }
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/SDPMsg.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/SDPMsg.gcl
new file mode 100644
index 00000000..bc25195f
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/SDPMsg.gcl
@@ -0,0 +1,180 @@
+import SDPLib;
+import IAuthMessage;
+import IContractUsingSDP;
+import ISDPMessage;
+import ISubProtocol;
+import Utils;
+
+contract SDPMsg implements ISDPMessage.SDPMessageInterface, ISubProtocol.SubProtocolInterface {
+
+ @global address owner;
+
+ @global uint64 amContractId;
+ @global address amAddress;
+ @global array localDomain;
+
+ @global map sendSeq;
+ @global map recvSeq;
+
+ @address uint32 resultInQuerySDPMessageSeq;
+
+ @global SDPLib.SDPMessage decodedSdpMsg;
+
+ const uint32 UNORDERED_SEQUENCE = 0xffffffffu32;
+
+ // @notice only for orderred msg
+ const uint64 MAX_NONCE = 0xffffffffffffffffu64;
+
+ @global function on_deploy(address _owner) {
+ owner = _owner;
+ __debug.print("[on_deploy] SDPMsg deployed - address:", __address(), " id:", __id());
+ }
+
+ @address function setAmContract(uint64 _amContractId, address _amAddress) public export {
+ __debug.assert(__transaction.get_sender() == owner);
+ relay@global (^_amContractId, ^_amAddress) {
+ amContractId = _amContractId;
+ amAddress = _amAddress;
+ __debug.print("[setAmContract] AM Contract reconfigured - contractId: ", amContractId, " address: ", amAddress);
+ }
+ }
+
+ @address function setLocalDomain(array domain) public export {
+ __debug.assert(__transaction.get_sender() == owner);
+ relay@global (^domain) {
+ localDomain = domain;
+ __debug.print("setLocalDomain: ", localDomain);
+ }
+ }
+
+ // notice: in GCL, receiverID is converted from uint64 to bytes32
+ @address function sendMessage(array receiverDomain, array receiverID, array message, uint64 senderID) public export {
+ SDPLib.SDPMessage sdpMessage;
+ sdpMessage.receiverDomain = Utils.bytesCopy(receiverDomain);
+ sdpMessage.receiver = Utils.bytesCopy(receiverID);
+ sdpMessage.message = Utils.bytesCopy(message);
+ sdpMessage.sequence = _getAndUpdateSendSeq(receiverDomain, senderID, receiverID);
+
+ array rawMsg = SDPLib.encodeSDPMsgV1(sdpMessage);
+ __debug.print("[sendMessage] rawMsg: ", rawMsg);
+
+ relay@global (^senderID, ^rawMsg) {
+ IAuthMessage.AuthMessageInterface am = IAuthMessage.AuthMessageInterface(amContractId);
+ am.recvFromProtocol(senderID, rawMsg);
+ }
+ }
+
+ // notice: in GCL, receiverID is converted from uint64 to bytes32
+ @address function sendUnorderedMessage(array receiverDomain, array receiverID, array message, uint64 senderID) public export {
+ SDPLib.SDPMessage sdpMessage;
+ sdpMessage.receiverDomain = Utils.bytesCopy(receiverDomain);
+ sdpMessage.receiver = Utils.bytesCopy(receiverID);
+ sdpMessage.message = Utils.bytesCopy(message);
+ sdpMessage.sequence = UNORDERED_SEQUENCE;
+
+ array rawMsg = SDPLib.encodeSDPMsgV1(sdpMessage);
+ __debug.print("[sendUnorderedMessage] rawMsg: ", rawMsg);
+
+ relay@global (^senderID, ^rawMsg) {
+ IAuthMessage.AuthMessageInterface am = IAuthMessage.AuthMessageInterface(amContractId);
+ am.recvFromProtocol(senderID, rawMsg);
+ }
+ }
+
+ @global function recvMessage(array senderDomain, array senderID, array pkg) public export {
+ __debug.assert(__transaction.get_sender() == amAddress);
+ // only SDPv1 now
+ // uint32 version = SDPLib.getSDPVersionFrom(pkg);
+ _processSDPv1(senderDomain, senderID, pkg);
+ }
+
+ @global function _processSDPv1(array senderDomain, array senderID, array pkg) public {
+ SDPLib.SDPMessage sdpMessage = SDPLib.decodeSDPMsgV1(pkg);
+ __debug.print("sdpMessage: ", sdpMessage);
+
+ decodedSdpMsg = sdpMessage;
+
+ __debug.assert(Utils.bytesEqual(sdpMessage.receiverDomain, localDomain));
+
+ if (sdpMessage.sequence == UNORDERED_SEQUENCE) {
+ _routeUnorderedMessage(senderDomain, senderID, sdpMessage);
+ } else {
+ _routeOrderedMessage(senderDomain, senderID, sdpMessage);
+ }
+
+ relay@external recvMessageInProtocol(senderDomain, senderID, pkg);
+ }
+
+ @global function _routeUnorderedMessage(array senderDomain, array senderID, SDPLib.SDPMessage sdpMessage) public {
+ // 从 receiver bytes 中获取接收合约的地址
+ uint64 receiverID = uint64(Utils.bytes32ToUint256(sdpMessage.receiver));
+ __debug.print("[_routeUnorderedMessage] receiverID: ", receiverID);
+ __debug.print("[_routeUnorderedMessage] senderDomain: ", senderDomain);
+ __debug.print("[_routeUnorderedMessage] senderID: ", senderID);
+ __debug.print("[_routeUnorderedMessage] message: ", sdpMessage.message);
+
+ // 调用接收合约的 recvUnorderedMessage
+ IContractUsingSDP.ContractUsingSDPInterface dapp = IContractUsingSDP.ContractUsingSDPInterface(receiverID);
+ dapp.recvUnorderedMessage(senderDomain, senderID, sdpMessage.message);
+ }
+
+ @global function _routeOrderedMessage(array senderDomain, array senderID, SDPLib.SDPMessage sdpMessage) public {
+ uint32 seqExpected = _getAndUpdateRecvSeq(senderDomain, senderID, sdpMessage.receiver);
+ __debug.assert(sdpMessage.sequence == seqExpected);
+
+ uint64 receiverID = uint64(Utils.bytes32ToUint256(sdpMessage.receiver));
+ __debug.print("[_routeOrderedMessage] receiverID: ", receiverID);
+ __debug.print("[_routeOrderedMessage] senderDomain: ", senderDomain);
+ __debug.print("[_routeOrderedMessage] senderID: ", senderID);
+ __debug.print("[_routeOrderedMessage] message: ", sdpMessage.message);
+
+ // 调用接收合约的 recvMessage
+ IContractUsingSDP.ContractUsingSDPInterface dapp = IContractUsingSDP.ContractUsingSDPInterface(receiverID);
+ dapp.recvMessage(senderDomain, senderID, sdpMessage.message);
+ }
+
+
+ @global function address getAmAddress() export const {
+ return amAddress;
+ }
+
+ @global function array getLocalDomain() export const {
+ array result;
+ for (uint32 i = 0u32; i < localDomain.length(); i++) {
+ result.push(localDomain[i]);
+ }
+ return result;
+ }
+
+ @address function uint32 querySDPMessageSeq(array senderDomain, array senderID, array receiverDomain, array receiverID) export {
+ relay@global (^receiverDomain) {
+ __debug.assert(Utils.bytesEqual(receiverDomain, localDomain));
+ }
+ hash seqKey = SDPLib.getReceivingSeqID(senderDomain, senderID, receiverID);
+
+ uint32 seq;
+ relay@global (^seqKey, ^seq) {
+ seq = recvSeq[seqKey];
+ }
+ resultInQuerySDPMessageSeq = seq;
+ return seq;
+ }
+
+ @address function uint32 _getAndUpdateSendSeq(array receiverDomain, uint64 senderID, array receiver) public {
+ hash seqKey = SDPLib.getSendingSeqID(receiverDomain, senderID, receiver);
+ uint32 seq;
+ relay@global (^seqKey, ^seq) {
+ seq = sendSeq[seqKey];
+ sendSeq[seqKey]++;
+ }
+ return seq;
+ }
+
+ @global function uint32 _getAndUpdateRecvSeq(array senderDomain, array sender, array receiver) public {
+ hash seqKey = SDPLib.getReceivingSeqID(senderDomain, sender, receiver);
+ uint32 seq = recvSeq[seqKey];
+ recvSeq[seqKey]++;
+ return seq;
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IAuthMessage.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IAuthMessage.gcl
new file mode 100644
index 00000000..70170fd1
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IAuthMessage.gcl
@@ -0,0 +1,46 @@
+contract IAuthMessage {
+
+/**
+ * @dev {IAuthMessage} is the basic protocol of the cross-chain communication protocol stack,
+ * and AM is a verifiable cross-chain message which makes the upper layer protocol not need
+ * to care about the legitimacy of the cross-chain message.
+ *
+ * When the {IAuthMessage} contract receives the cross-chain message submitted by the relayer,
+ * it will decode cross-chain message. Then, the IAuthMessage contract will parse the cross-chain
+ * message and forward it to the specified upper layer protocol contract.
+ */
+
+ interface AuthMessageInterface {
+
+ /**
+ * @dev Set the SDP contract address for verifying the proof come from outside the blockchain.
+ * @param protocolAddress upper protocol contract address
+ * @param protocolType type number for upper protocol. for example sdp protocol is zero.
+ */
+ @address function setProtocol(uint64 protocolID, address protocolAddress, uint32 protocolType) public export;
+
+ /**
+ * @dev The upper protocol call this method to send cross-chain message.
+ *
+ * The upper layer protocol passes the message constructed by itself to the AM contract.
+ * The AM contract will generate event SendAuthMessage containing serialized AuthMessage.
+ *
+ * @param senderID who send the cross-chain message to upper protocol.
+ * @param message message from upper protocol.
+ */
+ @global function recvFromProtocol(uint64 senderID, array message) public;
+
+ /**
+ * @dev The relayer call this method to submit raw cross-chain message.
+ *
+ * The relayer submits the cross-chain messages in the network to the AM contract.
+ * The AM contract decodes the message.
+ * Then, the upper-layer protocol message is parsed and the
+ * message is passed to the upper-layer protocol through inter-contract calls.
+ *
+ * @param pkg raw cross-chain message submitted by relayer.
+ */
+ @address function recvPkgFromRelayer(array pkg) public;
+
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IContractUsingSDP.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IContractUsingSDP.gcl
new file mode 100644
index 00000000..7f8c1aac
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/IContractUsingSDP.gcl
@@ -0,0 +1,23 @@
+contract IContractUsingSDP {
+ interface ContractUsingSDPInterface {
+ /**
+ * @dev SDP contract would call this function to deliver the message from sender contract
+ * on sender blockchain. This message sent with no order and in parallel.
+ *
+ * @param senderDomain the domain name of the sending blockchain.
+ * @param author the id of the sender.
+ * @param message the raw message from sender contract.
+ */
+ @global function recvUnorderedMessage(array senderDomain, array author, array message) public;
+
+ /**
+ * @dev SDP contract would call this function to deliver the message from sender contract
+ * on sender blockchain. This message sent with order.
+ *
+ * @param senderDomain the domain name of the sending blockchain.
+ * @param author the id of the sender.
+ * @param message the raw message from sender contract.
+ */
+ @global function recvMessage(array senderDomain, array author, array message) public;
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISDPMessage.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISDPMessage.gcl
new file mode 100644
index 00000000..3e21293e
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISDPMessage.gcl
@@ -0,0 +1,46 @@
+contract ISDPMessage {
+ interface SDPMessageInterface {
+ /**
+ * @dev Smart contracts need to call this method to send orderly cross-chain messages in SDPv1.
+ *
+ * The domain name of the sending blockchain, the address of the sender, the domain name of the
+ * receiving blockchain and the address of the receiver uniquely determine a cross-chain channel.
+ * Each channel maintains a sequence number which increases from zero to ensure that the cross-chain
+ * messages of the channel are submitted on blockchain in an orderly manner.
+ *
+ * @param receiverDomain the domain name of the receiving blockchain.
+ * @param receiverID the address of the receiver.
+ * @param message the raw message from DApp contracts
+ */
+ @address function sendMessage(array receiverDomain, array receiverID, array message, uint64 senderID) public;
+
+ /**
+ * @dev Smart contracts call this method to send cross-chain messages out of order in SDPv1.
+ *
+ * The sequence number for unordered message is `0xffffffff` means that this message is out of order.
+ *
+ * @param receiverDomain the domain name of the receiving blockchain.
+ * @param receiverID the address of the receiver.
+ * @param message the raw message from DApp contracts
+ */
+ @address function sendUnorderedMessage(array receiverDomain, array receiverID, array message, uint64 senderID) public;
+
+ /**
+ * @dev Query the current sdp message sequence for the channel identited by `senderDomain`,
+ * `senderID`, `receiverDomain` and `receiverID`.
+ *
+ * @param senderDomain the domain name of the sending blockchain.
+ * @param senderID the id of the sender.
+ * @param receiverDomain the domain name of the receiving blockchain.
+ * @param receiverID the address of the receiver.
+ */
+ // @address function uint32 querySDPMessageSeq(array senderDomain, array senderID, array receiverDomain, array receiverID) public export;
+
+ /**
+ * @dev Set the domain of local chain to `SDP` contract.
+ *
+ * @param domain the domain name of local chain.
+ */
+ @address function setLocalDomain(array domain) public export;
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISubProtocol.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISubProtocol.gcl
new file mode 100644
index 00000000..0e579ecf
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/interfaces/ISubProtocol.gcl
@@ -0,0 +1,19 @@
+contract ISubProtocol {
+ interface SubProtocolInterface {
+ /**
+ * @dev AM contract that want to forward the message to the receiving blockchain need to call this method to send auth message.
+ *
+ * @param senderDomain the domain name of the sending blockchain.
+ * @param senderID the address of the sender.
+ * @param pkg the raw message from AM contract
+ */
+ @global function recvMessage(array senderDomain, array senderID, array pkg) public;
+
+ /**
+ * @dev SDP contract are based on the AuthMessage contract. Here we set the AuthMessage contract address.
+ *
+ * @param newAmContract the address of the AuthMessage contract.
+ */
+ @address function setAmContract(uint64 _amContractId, address _amAddress) public;
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/am/AMLib.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/am/AMLib.gcl
new file mode 100644
index 00000000..605fb474
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/am/AMLib.gcl
@@ -0,0 +1,308 @@
+import Utils;
+import SizeOf;
+import TypesToBytes;
+import BytesToTypes;
+import TLVUtils;
+
+contract AMLib {
+ // TLV 标签常量
+ const uint16 TLV_PROOF_REQUEST = 4u16;
+ const uint16 TLV_PROOF_RESPONSE_BODY = 5u16;
+ const uint16 TLV_PROOF_RESPONSE_SIGNATURE = 6u16;
+ const uint16 TLV_PROOF_ERROR_CODE = 7u16;
+ const uint16 TLV_PROOF_ERROR_MSG = 8u16;
+ const uint16 TLV_PROOF_SENDER_DOMAIN = 9u16;
+ const uint16 TLV_PROOF_VERSION = 10u16;
+ struct AuthMessage {
+ uint32 version;
+ array author;
+ uint32 protocolType;
+ array body;
+ }
+
+ struct MessageFromRelayer {
+ array hints;
+ array proofData;
+ }
+
+ struct MessageForAM {
+ array senderDomain;
+ array rawMessage;
+ }
+
+ struct Request {
+ array reqID;
+ array rawReqBody;
+ }
+
+ struct Proof {
+ Request req;
+ array rawRespBody;
+ uint32 errorCode;
+ array errorMsg;
+ array senderDomain;
+ uint16 version;
+ }
+
+ struct DecodeResult {
+ array senderDomain;
+ array rawResp;
+ }
+
+ /**
+ * @notice 从 UCP 包解码出 senderDomain 和 rawResp (AuthMessage bytes)
+ * @param rawMessage UCP 包
+ * @return DecodeResult 包含 senderDomain 和 rawResp
+ */
+ function DecodeResult decodeMessageFromRelayer(array rawMessage) public const {
+ uint32 offset = 0u32;
+ __debug.print("[decodeMessageFromRelayer] rawMessage length: ", rawMessage.length());
+
+ // 读取 hints length (4 bytes, BIG-ENDIAN 从第0-4字节)
+ // 注意:UCP 包头使用 big-endian!
+ uint32 hintsLen = 0u32;
+ for (uint32 i = 0u32; i < 4u32; i++) {
+ hintsLen = (hintsLen << 8u32) | uint32(rawMessage[offset + i]);
+ }
+ __debug.print("[decodeMessageFromRelayer] hintsLen: ", hintsLen);
+ offset += 4u32;
+
+ // 跳过 hints
+ offset += hintsLen;
+
+ // 读取 proof length (4 bytes, BIG-ENDIAN)
+ uint32 proofLen = 0u32;
+ for (uint32 i = 0u32; i < 4u32; i++) {
+ proofLen = (proofLen << 8u32) | uint32(rawMessage[offset + i]);
+ }
+ __debug.print("[decodeMessageFromRelayer] proofLen: ", proofLen);
+ offset += 4u32;
+
+ // 读取 proof (从当前 offset 位置开始)
+ array proof;
+ for (uint32 i = 0u32; i < proofLen; i++) {
+ proof.push(rawMessage[offset + i]);
+ }
+
+ // 解码 proof
+ return _decodeProof(proof);
+ }
+
+ /**
+ * @notice 从 Proof 的 TLV 结构中解析 senderDomain 和 rawRespBody
+ * @param rawProof Proof 数据
+ * @return DecodeResult 包含 senderDomain 和经过 UDAG Response 解码的 rawResp
+ */
+ function DecodeResult _decodeProof(array rawProof) public const {
+ Proof proof;
+ uint32 offset = 6u32; // 跳过 TLV Header (6 bytes)
+
+ // 解析 TLV 项
+ while (offset < rawProof.length()) {
+ TLVUtils.TLVWithOffset result = TLVUtils.parseTLVItem(rawProof, offset);
+
+ if (result.tlvItem.tagType == TLV_PROOF_SENDER_DOMAIN) {
+ proof.senderDomain = Utils.bytesCopy(result.tlvItem.value);
+ } else {
+ if (result.tlvItem.tagType == TLV_PROOF_RESPONSE_BODY) {
+ proof.rawRespBody = Utils.bytesCopy(result.tlvItem.value);
+ } else {
+ if (result.tlvItem.tagType == TLV_PROOF_ERROR_CODE) {
+ proof.errorCode = BytesToTypes.bytesToUint32(0u32, result.tlvItem.value);
+ } else {
+ if (result.tlvItem.tagType == TLV_PROOF_ERROR_MSG) {
+ proof.errorMsg = Utils.bytesCopy(result.tlvItem.value);
+ } else {
+ if (result.tlvItem.tagType == TLV_PROOF_VERSION) {
+ proof.version = BytesToTypes.bytesToUint16(0u32, result.tlvItem.value);
+ }
+ }
+ }
+ }
+ }
+ // TLV_PROOF_REQUEST 暂时忽略
+
+ offset = result.offset;
+ }
+
+ // 从 UDAG Response 中提取 AuthMessage
+ DecodeResult dr;
+ dr.senderDomain = proof.senderDomain;
+ dr.rawResp = _decodeMsgBodyFromUDAGResp(proof.rawRespBody);
+
+ return dr;
+ }
+
+ /**
+ * @notice 从 UDAG Response Body 中提取 AuthMessage
+ * @param rawData UDAG Response Body
+ * @return array AuthMessage bytes
+ */
+ function array _decodeMsgBodyFromUDAGResp(array rawData) public const {
+ __debug.assert(rawData.length() > 12u32);
+
+ // UDAG Response 格式: [4 bytes status][4 bytes reserved][4 bytes body length (BIG ENDIAN)][body]
+ // 读取 body length (从第8字节开始的4字节,big-endian)
+ // bytesToUint32(offset) 读取 [offset-4, offset),所以要读取第8-12字节,offset应该是12
+ uint32 bodyLen = BytesToTypes.bytesToUint32(12u32, rawData);
+ // 反转字节序 (从 big-endian 转 little-endian)
+ bodyLen = Utils.reverseUint32(bodyLen);
+
+ __debug.print("[_decodeMsgBodyFromUDAGResp] bodyLen: ", bodyLen);
+ __debug.assert(rawData.length() >= 12u32 + bodyLen);
+
+ // 提取 body (从第 12 字节开始,长度为 bodyLen)
+ array body;
+ for (uint32 i = 0u32; i < bodyLen; i++) {
+ body.push(rawData[12u32 + i]);
+ }
+ __debug.print("[_decodeMsgBodyFromUDAGResp] Extracted body length: ", body.length());
+
+ return body;
+ }
+
+ function AuthMessage decodeAuthMessage(array pkg) public const{
+ uint32 offset = pkg.length();
+ AuthMessage amMsg;
+ __debug.print("offset: ", offset);
+
+ // version
+ amMsg.version = BytesToTypes.bytesToUint32(offset, pkg);
+ offset -= 4u32;
+ __debug.print("amMsg.version: ", amMsg.version);
+ __debug.print("offset: ", offset);
+
+ __debug.assert(amMsg.version >= 1u32);
+ __debug.assert(amMsg.version <= 2u32);
+
+ // author
+ BytesToTypes.bytesToBytes32(offset, pkg, amMsg.author);
+ offset -= 32u32;
+ __debug.print("amMsg.author: ", amMsg.author);
+ __debug.print("offset: ", offset);
+
+ // protocolType
+ amMsg.protocolType = BytesToTypes.bytesToUint32(offset, pkg);
+ offset -= 4u32;
+ __debug.print("amMsg.protocolType: ", amMsg.protocolType);
+ __debug.print("offset: ", offset);
+
+ // body
+ if (amMsg.version == 1u32) {
+ BytesToTypes.bytesToSubBytes(offset, pkg, amMsg.body);
+ } else if (amMsg.version == 2u32) {
+ BytesToTypes.varBytesToSubBytes(offset, pkg, amMsg.body);
+ }
+ __debug.print("amMsg.body: ", amMsg.body);
+ __debug.print("offset: ", offset);
+
+ return amMsg;
+ }
+
+ // function array encodeProofMessage(Proof item) public const{
+ // return item.serialize();
+ // }
+
+ // function Proof decodeProofMessage(array rawMsg) public const{
+ // Proof item;
+ // item.deserialize(rawMsg);
+ // return item;
+ // }
+
+ // function array encodMessageFromRelayer(MessageFromRelayer item) public const{
+ // return item.serialize();
+ // }
+
+ // function decodeMessageFromRelayer(array rawMsg, MessageFromRelayer r) public const{
+ // __debug.print(rawMsg);
+ // r.deserialize(rawMsg);
+ // __debug.print(r);
+ // }
+
+ function array encodeAuthMessage(AuthMessage amMsg) public const {
+ __debug.print("amMsg: ", amMsg);
+ __debug.assert(amMsg.version >= 1u32);
+ __debug.assert(amMsg.version <= 2u32);
+ array pkg;
+ if (amMsg.version == 1u32) {
+ pkg = encodeAuthMessageV1(amMsg);
+ } else if (amMsg.version == 2u32) {
+ pkg = encodeAuthMessageV2(amMsg);
+ }
+ return pkg;
+ }
+
+ function array encodeAuthMessageV1(AuthMessage amMsg) public const {
+ uint32 bodyLen = SizeOf.sizeOfBytes(amMsg.body);
+ uint32 len = bodyLen + 4u32 + 32u32 + 4u32;
+
+ array pkg;
+ pkg.set_length(len);
+ uint32 offset = len;
+
+ // version
+ TypesToBytes.uint32ToBytes(offset, amMsg.version, pkg);
+ offset -= 4u32;
+
+ // author
+ TypesToBytes.bytes32ToBytes(offset, amMsg.author, pkg);
+ offset -= 32u32;
+
+ // protocolType
+ TypesToBytes.uint32ToBytes(offset, amMsg.protocolType, pkg);
+ offset -= 4u32;
+
+ // body
+ TypesToBytes.bytesToNewBytes(offset, amMsg.body, pkg);
+ offset -= bodyLen;
+
+ return pkg;
+ }
+
+ function array encodeAuthMessageV2(AuthMessage amMsg) public const {
+ uint32 bodyLen = amMsg.body.length();
+ __debug.assert(bodyLen <= 0xffffffffu32);
+ uint32 len = bodyLen + 4u32 + 4u32 + 32u32 + 4u32;
+
+ array pkg;
+ pkg.set_length(len);
+ uint32 offset = len;
+
+ // version
+ TypesToBytes.uint32ToBytes(offset, amMsg.version, pkg);
+ offset -= 4u32;
+
+ // author
+ TypesToBytes.bytes32ToBytes(offset, amMsg.author, pkg);
+ offset -= 32u32;
+
+ // protocolType
+ TypesToBytes.uint32ToBytes(offset, amMsg.protocolType, pkg);
+ offset -= 4u32;
+
+ // body
+ TypesToBytes.varBytesToNewBytes(offset, amMsg.body, pkg);
+
+ return pkg;
+ }
+
+ // function AuthMessage decodeAuthMessage(array rawMsg) public const{
+ // AuthMessage amMsg;
+ // amMsg.deserialize(rawMsg);
+ // return amMsg;
+ // }
+
+
+ // function MessageForAM decode(array rawMessage) public const{
+ // __debug.print(rawMessage);
+ // // rawMsg --> [struct] MessageFromRelayer
+ // MessageFromRelayer messageFromRelayer = decodeMessageFromRelayer(rawMessage);
+ // // MessageFromRelayer.proofData --> [struct] proof --> [bytes] undecoded AM Message
+ // Proof proof = decodeProofMessage(messageFromRelayer.proofData);
+ // MessageForAM messageForAM = decodeMessageForAM(proof.rawRespBody);
+ // messageForAM.senderDomain = proof.senderDomain;
+
+ // return messageForAM;
+ // }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/sdp/SDPLib.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/sdp/SDPLib.gcl
new file mode 100644
index 00000000..b7db9b45
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/sdp/SDPLib.gcl
@@ -0,0 +1,87 @@
+import Utils;
+import SizeOf;
+import TypesToBytes;
+import BytesToTypes;
+
+contract SDPLib {
+
+ struct SDPMessage {
+ array receiverDomain;
+ array receiver;
+ array message;
+ uint32 sequence;
+ }
+
+ // compatible with SDPv2 (only SDPv1 now)
+ // @global function getSDPVersionFrom(array)
+
+ function hash getSendingSeqID(array receiverDomain, uint64 senderID, array receiver) public const {
+ array senderAlign32 = Utils.uint256ToBytes32(uint256(senderID));
+ return hash(Utils.bytesConcat(Utils.bytesConcat(receiverDomain, senderAlign32), receiver));
+ }
+
+ function hash getReceivingSeqID(array senderDomain, array sender, array receiver) public const {
+ return hash(Utils.bytesConcat(Utils.bytesConcat(senderDomain, sender), receiver));
+ }
+
+ function array encodeSDPMsgV1(SDPMessage sdpMessage) public const {
+ uint32 messageLen = SizeOf.sizeOfBytes(sdpMessage.message);
+ uint32 domainLen = SizeOf.sizeOfBytes(sdpMessage.receiverDomain);
+ uint32 len = messageLen + 4u32 + 32u32 + domainLen;
+
+ array pkg;
+ pkg.set_length(len);
+ uint32 offset = len;
+
+ // domain
+ TypesToBytes.bytesToNewBytes(offset, sdpMessage.receiverDomain, pkg);
+ offset -= domainLen;
+
+ // receiver
+ TypesToBytes.bytes32ToBytes(offset, sdpMessage.receiver, pkg);
+ offset -= 32u32;
+
+ // sequence
+ TypesToBytes.uint32ToBytes(offset, sdpMessage.sequence, pkg);
+ offset -= 4u32;
+
+ // message
+ TypesToBytes.bytesToNewBytes(offset, sdpMessage.message, pkg);
+ offset -= messageLen;
+
+ return pkg;
+ }
+
+ function SDPMessage decodeSDPMsgV1(array pkg) public const {
+ uint32 offset = pkg.length();
+ SDPMessage sdpMessage;
+ // __debug.print("offset: ", offset);
+
+ // domain
+ BytesToTypes.bytesToSubBytes(offset, pkg, sdpMessage.receiverDomain);
+ offset -= SizeOf.sizeOfBytes(sdpMessage.receiverDomain);
+ // __debug.print("sdpMessage.receiverDomain: ", sdpMessage.receiverDomain);
+ // __debug.print("offset: ", offset);
+
+ // receiver
+ BytesToTypes.bytesToBytes32(offset, pkg, sdpMessage.receiver);
+ offset -= 32u32;
+ // __debug.print("sdpMessage.receiver: ", sdpMessage.receiver);
+ // __debug.print("offset: ", offset);
+
+ // sequence
+ sdpMessage.sequence = BytesToTypes.bytesToUint32(offset, pkg);
+ offset -= 4u32;
+ // __debug.print("sdpMessage.sequence: ", sdpMessage.sequence);
+ // __debug.print("offset: ", offset);
+
+ // message
+ BytesToTypes.bytesToSubBytes(offset, pkg, sdpMessage.message);
+ offset -= SizeOf.sizeOfBytes(sdpMessage.message);
+ // __debug.print("sdpMessage.message: ", sdpMessage.message);
+ // __debug.print("offset: ", offset);
+
+ return sdpMessage;
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/BytesToTypes.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/BytesToTypes.gcl
new file mode 100644
index 00000000..6024a77a
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/BytesToTypes.gcl
@@ -0,0 +1,56 @@
+import Utils;
+
+contract BytesToTypes {
+
+ function uint256 bytesToUint256(uint32 offset, array input) public const {
+ return bytesToUint(offset, input, 32u32);
+ }
+
+ function uint32 bytesToUint32(uint32 offset, array input) public const {
+ return uint32(bytesToUint(offset, input, 4u32));
+ }
+
+ function uint16 bytesToUint16(uint32 offset, array input) public const {
+ return uint16(bytesToUint(offset, input, 2u32));
+ }
+
+ function uint256 bytesToUint(uint32 offset, array input, uint32 len) public const {
+ uint256 output;
+ for(uint32 i = offset - len; i < offset; i++) {
+ output = (output << 8u32) + uint256(input[i]);
+ }
+
+ return output;
+ }
+
+ function bytesToSubBytes(uint32 offset, array input, array output) public const {
+ uint32 len = uint32(bytesToUint256(offset, input));
+ offset -= 32u32;
+ while (len > 0u32) {
+ offset -= 32u32;
+ for (uint32 i = 0u32; i < 32u32 && len > 0u32; i++) {
+ output.push(input[offset + i]);
+ len--;
+ }
+ }
+ }
+
+
+ function varBytesToSubBytes(uint32 offset, array input, array output) public const {
+ uint32 len = bytesToUint32(offset, input);
+ offset -= 4u32;
+ __debug.assert(offset == len);
+ for(uint32 i = 0u32; i < len; i++) {
+ output.push(input[i]);
+ }
+ }
+
+
+ function bytesToBytes32(uint32 offset, array input, array output) public const {
+ offset -= 32u32;
+ for (uint32 i = 0u32; i < 32u32; i++) {
+ output.push(input[offset + i]);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/SizeOf.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/SizeOf.gcl
new file mode 100644
index 00000000..9af49f2c
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/SizeOf.gcl
@@ -0,0 +1,16 @@
+contract SizeOf {
+
+ function uint32 sizeOfBytes(array data) public const {
+ uint32 len = data.length();
+ uint32 size = len / 32u32;
+
+ if (len % 32u32 != 0u32) {
+ size++;
+ }
+ // 额外 32 字节存储长度字段
+ size++;
+ // 转换为字节总数
+ size *= 32u32;
+ return size;
+ }
+}
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TLVUtils.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TLVUtils.gcl
new file mode 100644
index 00000000..9d4a80e5
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TLVUtils.gcl
@@ -0,0 +1,101 @@
+import Utils;
+import BytesToTypes;
+
+contract TLVUtils {
+
+ struct TLVItem {
+ uint16 tagType;
+ uint32 len;
+ array value;
+ }
+
+ struct LVItem {
+ uint32 len;
+ array value;
+ }
+
+ struct TLVWithOffset {
+ uint32 offset;
+ TLVItem tlvItem;
+ }
+
+ struct LVWithOffset {
+ uint32 offset;
+ LVItem lvItem;
+ }
+
+ /**
+ * @notice 解析 TLV (Type-Length-Value) 格式的数据项
+ * @param rawData 原始数据
+ * @param offset 开始位置
+ * @return TLVWithOffset 包含解析结果和新的偏移量
+ */
+ function TLVWithOffset parseTLVItem(array rawData, uint32 offset) public const {
+ TLVWithOffset result;
+
+ // 确保有足够的数据读取 tag 和 length (至少 6 字节)
+ __debug.assert(offset + 6u32 <= rawData.length());
+
+ // 读取 tagType (2 bytes, big-endian, then reverse to little-endian)
+ // Read as big-endian: [offset] is high byte, [offset+1] is low byte
+ uint16 tagBE = (uint16(rawData[offset]) << 8u16) | uint16(rawData[offset + 1u32]);
+ result.tlvItem.tagType = ((tagBE & 0xFF00u16) >> 8u16) | ((tagBE & 0x00FFu16) << 8u16); // reverse bytes
+ offset += 2u32;
+
+ // 读取 length (4 bytes, big-endian, then reverse to little-endian)
+ uint32 lenBE = (uint32(rawData[offset]) << 24u32)
+ | (uint32(rawData[offset + 1u32]) << 16u32)
+ | (uint32(rawData[offset + 2u32]) << 8u32)
+ | uint32(rawData[offset + 3u32]);
+ // Reverse bytes: 0x12345678 -> 0x78563412
+ result.tlvItem.len = ((lenBE & 0xFF000000u32) >> 24u32)
+ | ((lenBE & 0x00FF0000u32) >> 8u32)
+ | ((lenBE & 0x0000FF00u32) << 8u32)
+ | ((lenBE & 0x000000FFu32) << 24u32);
+ offset += 4u32;
+
+ // 确保有足够的数据读取 value
+ __debug.assert(offset + result.tlvItem.len <= rawData.length());
+
+ // 读取 value (直接从当前 offset 复制 len 个字节)
+ for (uint32 i = 0u32; i < result.tlvItem.len; i++) {
+ result.tlvItem.value.push(rawData[offset + i]);
+ }
+ offset += result.tlvItem.len;
+
+ result.offset = offset;
+ return result;
+ }
+
+ /**
+ * @notice 解析 LV (Length-Value) 格式的数据项
+ * @param rawData 原始数据
+ * @param offset 开始位置
+ * @return LVWithOffset 包含解析结果和新的偏移量
+ */
+ function LVWithOffset parseLVItem(array rawData, uint32 offset) public const {
+ LVWithOffset result;
+
+ // 读取 length (4 bytes, big-endian, then reverse to little-endian)
+ uint32 lenBE = (uint32(rawData[offset]) << 24u32)
+ | (uint32(rawData[offset + 1u32]) << 16u32)
+ | (uint32(rawData[offset + 2u32]) << 8u32)
+ | uint32(rawData[offset + 3u32]);
+ result.lvItem.len = ((lenBE & 0xFF000000u32) >> 24u32)
+ | ((lenBE & 0x00FF0000u32) >> 8u32)
+ | ((lenBE & 0x0000FF00u32) << 8u32)
+ | ((lenBE & 0x000000FFu32) << 24u32);
+ offset += 4u32;
+
+ // 读取 value (直接从当前 offset 复制 len 个字节)
+ for (uint32 i = 0u32; i < result.lvItem.len; i++) {
+ result.lvItem.value.push(rawData[offset + i]);
+ }
+ offset += result.lvItem.len;
+
+ result.offset = offset;
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TypesToBytes.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TypesToBytes.gcl
new file mode 100644
index 00000000..81c9ab31
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/TypesToBytes.gcl
@@ -0,0 +1,68 @@
+import Utils;
+
+contract TypesToBytes {
+
+ function bytesToNewBytes(uint32 offset, array input, array output) public const {
+ array inputBytesAlign32 = Utils.bytesAlign32(input);
+ uint32 len = input.length();
+
+ // __debug.print("inputBytesAlign32: ", inputBytesAlign32);
+ // __debug.print("len:", len, " inputBytesAlign32-len: ", inputBytesAlign32.length());
+
+ // 前 32 字节存储长度信息
+ offset -= 32u32;
+ array lenBytes = Utils.uint256ToBytes32(uint256(len));
+ for(uint32 i = 0u32; i < 32u32; i++) {
+ output[offset + i] = lenBytes[i];
+ }
+
+ for(uint32 i = 0u32; i < inputBytesAlign32.length();) {
+ offset -= 32u32;
+ // __debug.print("offset:", offset);
+ for(uint32 j = 0u32; j < 32u32; j++) {
+ output[offset+j] = inputBytesAlign32[i];
+ i++;
+ }
+ }
+ }
+
+ function varBytesToNewBytes(uint32 offset, array input, array output) public const {
+ uint32 len = input.length();
+
+ // 前 4 字节存储长度信息
+ offset -= 4u32;
+ array lenBytes = Utils.uint256ToBytes32(uint256(len));
+ for(uint32 i = 28u32; i < 32u32; i++) {
+ output[offset + i - 28u32] = lenBytes[i];
+ }
+
+ for(uint32 i = 0u32; i < len; i++) {
+ output[i] = input[i];
+ }
+ }
+
+ function bytes32ToBytes(uint32 offset, array input, array output) public const {
+ offset -= 32u32;
+ uint32 inputLen = input.length();
+ for(uint32 i = 0u32; i < 32u32; i++) {
+ if (i < inputLen) {
+ output[offset + i] = input[i];
+ } else {
+ output[offset + i] = 0u8; // 填充0
+ }
+ }
+ }
+
+ function uint32ToBytes(uint32 offset, uint32 input, array output) public const {
+ array inputBytes = Utils.uint256ToBytes32(uint256(input));
+ uintToBytes(offset, inputBytes, output, 4u32);
+ }
+
+ function uintToBytes(uint32 offset, array input, array output, uint32 len) public const {
+ offset -= 32u32;
+ for(uint32 i = 32u32-len; i < 32u32; i++) {
+ output[offset + i] = input[i];
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/Utils.gcl b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/Utils.gcl
new file mode 100644
index 00000000..42a7ae5a
--- /dev/null
+++ b/acb-sdk/pluginset/dioxide/onchain-plugin/lib/utils/Utils.gcl
@@ -0,0 +1,108 @@
+contract Utils {
+
+ function bool bytesEqual(array first, array second) public const {
+ if (first.length() != second.length()) {
+ return false;
+ }
+ for (uint32 i = 0u32; i < first.length(); i++) {
+ if (first[i] != second[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function array bytesConcat(array first, array second) public const {
+ array ret;
+ for(uint32 i = 0u32;i bytesCopy(array src) public const {
+ array dst;
+ for(uint32 i=0u32; i < src.length(); i++) {
+ dst.push(src[i]);
+ }
+ return dst;
+ }
+
+ function array bytesAlign32(array src) public const {
+ array dst;
+ uint32 len = src.length();
+ uint32 rem = len % 32u32;
+ if (rem == 0u32) {
+ for (uint32 i = 0u32; i < len; i++) {
+ dst.push(src[i]);
+ }
+ } else {
+ uint32 pad_len = 32u32 - rem;
+ for (uint32 i = 0u32; i < len; i++) {
+ dst.push(src[i]);
+ }
+ for (uint32 i = 0u32; i < pad_len; i++) {
+ dst.push(0u8);
+ }
+ }
+ return dst;
+ }
+
+ function array uint256ToBytes32(uint256 src) public const {
+ array dst;
+ dst.set_length(32u32);
+ for (uint32 i = 32u32; i > 0u32; i--) {
+ uint8 tmp_int = uint8((src >> ((i-1u32) * 8u32)) & 0xFFu8);
+ dst[32u32-i] = tmp_int;
+ // __debug.print("byte ", 32u32-i, ": ", tmp_int);
+ }
+ return dst;
+ }
+
+ function array addressToBytes32(address src) public const {
+ // 在 GCL 中,address 需要转换为 bytes32
+ // 由于 GCL 不直接支持 address 到 uint256 的转换,
+ // 我们返回一个填充为零的 32 字节数组
+ array dst;
+ dst.set_length(32u32);
+ for (uint32 i = 0u32; i < 32u32; i++) {
+ dst[i] = 0u8;
+ }
+ // 注意:这是一个简化实现,实际应用中可能需要更复杂的地址编码
+ return dst;
+ }
+
+ function uint256 bytes32ToUint256(array src) public const {
+ uint256 dst;
+ uint32 len = src.length();
+ for (uint32 i = 0u32; i < len; i++) {
+ dst = (dst << 8u32) + uint256(src[i]);
+ // __debug.print("byte ", i, ": ", src[i], " dst: ", dst);
+ }
+ return dst;
+ }
+
+ function uint32 reverseUint32(uint32 value) public const {
+ uint32 result;
+ result = ((value & 0x000000FFu32) << 24u32) |
+ ((value & 0x0000FF00u32) << 8u32) |
+ ((value & 0x00FF0000u32) >> 8u32) |
+ ((value & 0xFF000000u32) >> 24u32);
+ return result;
+ }
+
+ function address arrayUint8ToAddress(array src) public const {
+ // GCL 中 address 类型不支持从 uint256 直接转换
+ // 这里简化实现,直接返回零地址
+ // 实际使用中应该通过其他方式处理地址转换
+ address result;
+ return result;
+ }
+}
\ No newline at end of file