From db85dd2cf90f8e5ebba7d4283a8d01c89aa3f6f1 Mon Sep 17 00:00:00 2001 From: Aziz Date: Thu, 16 Oct 2025 13:32:55 -0700 Subject: [PATCH 1/3] Added Msa Pallet, specifically the create msa call, with e2e tests and added stuff to the jot-examples --- .../java/com/method5/jot/examples/Config.java | 4 + .../BalancesTransferAllowDeathExample.java | 6 +- ...llowDeathSignAndWaitForResultsExample.java | 52 ++++++++++++ .../examples/extrinsic/CreateMsaExample.java | 48 +++++++++++ jot/pom.xml | 6 ++ .../com/method5/jot/entity/DispatchError.java | 21 ++++- .../method5/jot/extrinsic/call/MsaPallet.java | 24 ++++++ .../jot/extrinsic/call/Transaction.java | 6 ++ .../com/method5/jot/extrinsic/E2ETest.java | 80 +++++++++++++++++++ .../method5/jot/extrinsic/MsaPalletTest.java | 21 +++++ .../jot/metadata/MetadataParserTest.java | 4 + 11 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathSignAndWaitForResultsExample.java create mode 100644 jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java create mode 100644 jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java create mode 100644 jot/src/test/java/com/method5/jot/extrinsic/MsaPalletTest.java diff --git a/jot-examples/src/main/java/com/method5/jot/examples/Config.java b/jot-examples/src/main/java/com/method5/jot/examples/Config.java index d3d930e..dc792a6 100644 --- a/jot-examples/src/main/java/com/method5/jot/examples/Config.java +++ b/jot-examples/src/main/java/com/method5/jot/examples/Config.java @@ -17,4 +17,8 @@ public class Config { "wss://asset-hub-westend-rpc.n.dwellir.com", "wss://asset-hub-westend.rpc.permanence.io") .toArray(String[]::new); + + public static final String[] FREQUENCY_WSS_SERVER = List.of( + "ws://localhost:9944" + ).toArray(String[]::new); } diff --git a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathExample.java b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathExample.java index 07ec134..eacba22 100644 --- a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathExample.java +++ b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathExample.java @@ -1,6 +1,9 @@ package com.method5.jot.examples.extrinsic; +import com.method5.jot.entity.DispatchError; +import com.method5.jot.events.EventRecord; import com.method5.jot.examples.Config; +import com.method5.jot.extrinsic.ExtrinsicResult; import com.method5.jot.extrinsic.call.Call; import com.method5.jot.query.model.AccountId; import com.method5.jot.rpc.PolkadotWs; @@ -11,6 +14,7 @@ import org.slf4j.LoggerFactory; import java.math.BigDecimal; +import java.util.List; public class BalancesTransferAllowDeathExample extends ExampleBase { private static final Logger logger = LoggerFactory.getLogger(BalancesTransferAllowDeathExample.class); @@ -18,7 +22,7 @@ public class BalancesTransferAllowDeathExample extends ExampleBase { public static void main(String[] args) throws Exception { Wallet wallet = Wallet.fromMnemonic(Config.MNEMONIC_PHRASE); - try (PolkadotWs api = new PolkadotWs(Config.WSS_SERVER, 10000)) { + try (PolkadotWs api = new PolkadotWs(Config.FREQUENCY_WSS_SERVER, 10000)) { execute(api, wallet.getSigner()); } } diff --git a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathSignAndWaitForResultsExample.java b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathSignAndWaitForResultsExample.java new file mode 100644 index 0000000..047d083 --- /dev/null +++ b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/BalancesTransferAllowDeathSignAndWaitForResultsExample.java @@ -0,0 +1,52 @@ +package com.method5.jot.examples.extrinsic; + +import com.method5.jot.events.EventRecord; +import com.method5.jot.examples.Config; +import com.method5.jot.extrinsic.ExtrinsicResult; +import com.method5.jot.extrinsic.call.Call; +import com.method5.jot.query.model.AccountId; +import com.method5.jot.rpc.PolkadotWs; +import com.method5.jot.signing.SigningProvider; +import com.method5.jot.wallet.Wallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.util.List; + +public class BalancesTransferAllowDeathSignAndWaitForResultsExample { + private static final Logger logger = LoggerFactory.getLogger(BalancesTransferAllowDeathSignAndWaitForResultsExample.class); + + public static void main(String[] args) throws Exception { + Wallet wallet = Wallet.fromMnemonic(Config.MNEMONIC_PHRASE); + + try (PolkadotWs api = new PolkadotWs(Config.FREQUENCY_WSS_SERVER, 10000)) { + execute(api, wallet.getSigner()); + } + } + + public static void execute(PolkadotWs api, SigningProvider signingProvider) throws Exception { + logger.info("Balances Transfer Allow Death (Using signAndWaitForResults) Example"); + logger.info("------------------------"); + + // Destination address + AccountId destination = AccountId.fromSS58("13NHcoGFJsHJoCYVsJrrv2ygLtz2XJSR17KrnA9QTNYz3Zkz"); + // Amount + BigDecimal amount = new BigDecimal("0.001"); + + Call call = api.tx().balances().transferAllowDeath(destination, amount); + + ExtrinsicResult result = call.signAndWaitForResults(signingProvider); + + List eventRecordList = result.getEvents(); + for (EventRecord eventRecord : eventRecordList) { + logger.info("Event: " + eventRecord.method()); + } + + String hash = result.getHash(); + + + logger.info("Extrinsic hash: {}", hash); + } + +} diff --git a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java new file mode 100644 index 0000000..3b3d851 --- /dev/null +++ b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java @@ -0,0 +1,48 @@ +package com.method5.jot.examples.extrinsic; + +import com.method5.jot.events.EventRecord; +import com.method5.jot.examples.Config; +import com.method5.jot.extrinsic.ExtrinsicResult; +import com.method5.jot.extrinsic.call.Call; +import com.method5.jot.rpc.PolkadotWs; +import com.method5.jot.signing.SigningProvider; +import com.method5.jot.wallet.Wallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.List; + +public class CreateMsaExample { + private static final Logger logger = LoggerFactory.getLogger(CreateMsaExample.class); + + public static void main(String[] args) throws Exception { + Wallet wallet = Wallet.fromMnemonic(Config.MNEMONIC_PHRASE); + + try (PolkadotWs api = new PolkadotWs(Config.FREQUENCY_WSS_SERVER, 10000)) { + execute(api, wallet.getSigner()); + } + } + + public static void execute(PolkadotWs api, SigningProvider signingProvider) throws Exception { + logger.info("Create MSA Example"); + logger.info("------------------------"); + + Call call = api.tx().msa().createMsa(); + + ExtrinsicResult result = call.signAndWaitForResults(signingProvider); + + List eventRecordList = result.getEvents(); + for (EventRecord eventRecord : eventRecordList) { + logger.info("Event: " + eventRecord.method()); + } + + String hash = result.getHash(); + + logger.info("Extrinsic hash: {}", hash); + + //Now try and get an error by sending the createMsa transaction again + + ExtrinsicResult failure = call.signAndWaitForResults(signingProvider); + + logger.info("Extrinsic dispatch error: " + failure.getError()); + } +} diff --git a/jot/pom.xml b/jot/pom.xml index 16560e3..4633b40 100644 --- a/jot/pom.xml +++ b/jot/pom.xml @@ -410,5 +410,11 @@ 5.20.0 test + + org.testcontainers + testcontainers-junit-jupiter + 2.0.1 + test + \ No newline at end of file diff --git a/jot/src/main/java/com/method5/jot/entity/DispatchError.java b/jot/src/main/java/com/method5/jot/entity/DispatchError.java index 6ecffd8..b6c7942 100644 --- a/jot/src/main/java/com/method5/jot/entity/DispatchError.java +++ b/jot/src/main/java/com/method5/jot/entity/DispatchError.java @@ -2,7 +2,10 @@ import com.method5.jot.metadata.CallIndexResolver; import com.method5.jot.metadata.RuntimeTypeDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.Map; import java.util.Objects; @@ -10,6 +13,7 @@ * DispatchError — class for dispatch error in the Jot SDK. Provides types and data models. */ public class DispatchError { + private static final Logger logger = LoggerFactory.getLogger(DispatchError.class); public enum Kind { MODULE, NAMED, @@ -59,9 +63,22 @@ public static DispatchError decode(RuntimeTypeDecoder.TypeAndValue tv, CallIndex if ("Module".equals(variant)) { Map field0 = (Map) outer.get("field0"); + if (field0 != null) { - int index = (int) field0.getOrDefault("index", -1); - int error = (int) field0.getOrDefault("error", -1); + int index; + int error; + if(field0.getOrDefault("index", -1) instanceof Byte indexByte) { + index = ((Number) indexByte).intValue(); + } else { + index = (int) field0.getOrDefault("index", -1); + } + //Is there a better way to do this? + if(field0.get("error") instanceof ArrayList errorList) { + error = ((Number) errorList.getFirst()).intValue(); + } else { + error = (int) field0.getOrDefault("error", -1); + } + String name = resolver != null ? resolver.getModuleError(index, error) : "Unknown module error"; return module(index, error, name); } diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java b/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java new file mode 100644 index 0000000..6b1a04f --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java @@ -0,0 +1,24 @@ +package com.method5.jot.extrinsic.call; + +import com.method5.jot.rpc.Api; +import com.method5.jot.rpc.CallOrQuery; +import com.method5.jot.scale.ScaleWriter; + +public class MsaPallet extends CallOrQuery { + public MsaPallet(Api api) { + super(api); + } + + public Call createMsa() { + return new Call(api, createMsaWriter( + getResolver().resolveCallIndex("Msa", "create") + )); + } + + private byte[] createMsaWriter(byte[] callIndex) { + ScaleWriter writer = new ScaleWriter(); + writer.writeBytes(callIndex); + return writer.toByteArray(); + } + +} diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/Transaction.java b/jot/src/main/java/com/method5/jot/extrinsic/call/Transaction.java index 1681992..bd279c9 100644 --- a/jot/src/main/java/com/method5/jot/extrinsic/call/Transaction.java +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/Transaction.java @@ -10,6 +10,7 @@ public class Transaction { protected BalancesPallet balances; protected ConvictionVotingPallet convictionVoting; protected MultisigPallet multisig; + protected MsaPallet msa; protected StakingPallet staking; protected SystemPallet system; protected UtilityPallet utility; @@ -20,6 +21,7 @@ public Transaction(Api api) { balances = new BalancesPallet(api); convictionVoting = new ConvictionVotingPallet(api); multisig = new MultisigPallet(api); + msa = new MsaPallet(api); staking = new StakingPallet(api); system = new SystemPallet(api); utility = new UtilityPallet(api); @@ -37,6 +39,10 @@ public MultisigPallet multisig() { return multisig; } + public MsaPallet msa() { + return msa; + } + public StakingPallet staking() { return staking; } diff --git a/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java new file mode 100644 index 0000000..44948ad --- /dev/null +++ b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java @@ -0,0 +1,80 @@ +package com.method5.jot.extrinsic; + +import com.method5.jot.events.EventRecord; +import com.method5.jot.extrinsic.call.Call; +import com.method5.jot.rpc.PolkadotWs; +import com.method5.jot.signing.SigningProvider; +import com.method5.jot.util.HexUtil; +import com.method5.jot.wallet.Wallet; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.List; +import java.util.Objects; + +@Testcontainers +public class E2ETest { + public final static String FREQUENCY_VERSION = "v1.17.5"; + + @Container + public static GenericContainer frequencyTestContainer = new GenericContainer<>(DockerImageName.parse(String.format("frequencychain/standalone-node:%s", FREQUENCY_VERSION))) + .withExposedPorts(30333, 9944, 9933) + .waitingFor(org.testcontainers.containers.wait.strategy.Wait.forLogMessage(".*Running JSON-RPC server.*", 1)); + + @BeforeAll + public static void setup() { + frequencyTestContainer.start(); + } + + public static String getWsAddress() { + return String.format("ws://%s:%s", frequencyTestContainer.getHost(), frequencyTestContainer.getMappedPort(9944)); + } + + @Test + public void msaTest() { + try(PolkadotWs api = new PolkadotWs(getWsAddress())) { + String chain = api.query().system().chain(); + Assertions.assertEquals("Frequency Development (No Relay)", chain); + + Wallet alice = Wallet.fromSr25519Seed(HexUtil.hexToBytes("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a")); + Assertions.assertEquals("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", alice.getAddress(42)); + + SigningProvider aliceSigningProvider = alice.getSigner(); + + Call call = api.tx().msa().createMsa(); + + ExtrinsicResult result = call.signAndWaitForResults(aliceSigningProvider); + + List eventRecordList = result.getEvents(); + for (EventRecord eventRecord : eventRecordList) { + System.out.println("Event: " + eventRecord.method()); + } + + EventRecord msaCreated = eventRecordList.stream().filter(r -> Objects.equals(r.method(), "MsaCreated")).toList().getFirst(); + Assertions.assertNotNull(msaCreated); + + String hash = result.getHash(); + + System.out.println("Extrinsic hash: " + hash); + + ExtrinsicResult failure = call.signAndWaitForResults(aliceSigningProvider); + + System.out.println(failure.getError().toHuman()); + Assertions.assertEquals("Module[60] Error[0]: KeyAlreadyRegistered", failure.getError().toHuman()); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterAll + public static void teardown() { + frequencyTestContainer.stop(); + } +} diff --git a/jot/src/test/java/com/method5/jot/extrinsic/MsaPalletTest.java b/jot/src/test/java/com/method5/jot/extrinsic/MsaPalletTest.java new file mode 100644 index 0000000..7e6d50a --- /dev/null +++ b/jot/src/test/java/com/method5/jot/extrinsic/MsaPalletTest.java @@ -0,0 +1,21 @@ +package com.method5.jot.extrinsic; + +import com.method5.jot.TestBase; +import com.method5.jot.util.HexUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class MsaPalletTest extends TestBase { + + @Disabled("Metadata has to be updated to use a frequency one, but that'll screw up all the other tests") + @Test + public void testCreateMsa() { + byte[] callData = api.tx().msa().createMsa().callData(); + assertNotNull(callData); + assertEquals("3c00", HexUtil.bytesToHex(callData)); + } +} diff --git a/jot/src/test/java/com/method5/jot/metadata/MetadataParserTest.java b/jot/src/test/java/com/method5/jot/metadata/MetadataParserTest.java index 5ebc7e3..4706d76 100644 --- a/jot/src/test/java/com/method5/jot/metadata/MetadataParserTest.java +++ b/jot/src/test/java/com/method5/jot/metadata/MetadataParserTest.java @@ -22,16 +22,20 @@ public void testParseMetadata() { byte[] nonExistentCheck = resolver.resolveCallIndex("Null", "null"); byte[] transferIndex = resolver.resolveCallIndex("Balances", "force_transfer"); byte[] remarkIndex = resolver.resolveCallIndex("System", "remark"); +// byte[] createMsaIndex = resolver.resolveCallIndex("Msa", "create"); assertNull(nonExistentCheck); assertNotNull(transferIndex, "Balances.forceTransfer not registered"); assertNotNull(remarkIndex, "System.remark not registered"); +// assertNotNull(createMsaIndex, "Msa.remark not registered"); assertEquals(2, transferIndex.length, "Call index should be 2 bytes"); assertEquals(2, remarkIndex.length, "Call index should be 2 bytes"); +// assertEquals(2, createMsaIndex.length, "Call index should be 2 bytes"); logger.info("Balances.transfer = [{}, {}]", transferIndex[0], transferIndex[1]); logger.info("System.remark = [{}, {}]", remarkIndex[0], remarkIndex[1]); +// logger.info("Msa.create = [{}, {}]", createMsaIndex[0], createMsaIndex[1]); } @Test From a8d0b966404d1834c86ffaadd44495071fa1081e Mon Sep 17 00:00:00 2001 From: Tristan Willard Date: Tue, 28 Oct 2025 11:47:39 -0400 Subject: [PATCH 2/3] Initial Approach --- .../examples/extrinsic/CreateMsaExample.java | 5 +-- .../com/method5/jot/entity/DispatchError.java | 8 ++--- .../method5/jot/extrinsic/call/AccountId.java | 28 +++++++++++++++ .../jot/extrinsic/call/EventClass.java | 12 +++++++ .../jot/extrinsic/call/ExtrinsicError.java | 4 +++ .../call/KeyAlreadyRegisteredError.java | 3 ++ .../jot/extrinsic/call/MessageSourceId.java | 29 +++++++++++++++ .../method5/jot/extrinsic/call/MsaPallet.java | 36 +++++++++++++++++++ .../jot/metadata/CallIndexResolver.java | 2 +- .../com/method5/jot/extrinsic/E2ETest.java | 36 ++++++++++++++++++- 10 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/AccountId.java create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/EventClass.java create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/ExtrinsicError.java create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/KeyAlreadyRegisteredError.java create mode 100644 jot/src/main/java/com/method5/jot/extrinsic/call/MessageSourceId.java diff --git a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java index 3b3d851..ade83b5 100644 --- a/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java +++ b/jot-examples/src/main/java/com/method5/jot/examples/extrinsic/CreateMsaExample.java @@ -6,6 +6,7 @@ import com.method5.jot.extrinsic.call.Call; import com.method5.jot.rpc.PolkadotWs; import com.method5.jot.signing.SigningProvider; +import com.method5.jot.util.HexUtil; import com.method5.jot.wallet.Wallet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,10 +16,10 @@ public class CreateMsaExample { private static final Logger logger = LoggerFactory.getLogger(CreateMsaExample.class); public static void main(String[] args) throws Exception { - Wallet wallet = Wallet.fromMnemonic(Config.MNEMONIC_PHRASE); + Wallet alice = Wallet.fromSr25519Seed(HexUtil.hexToBytes("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a")); try (PolkadotWs api = new PolkadotWs(Config.FREQUENCY_WSS_SERVER, 10000)) { - execute(api, wallet.getSigner()); + execute(api, alice.getSigner()); } } diff --git a/jot/src/main/java/com/method5/jot/entity/DispatchError.java b/jot/src/main/java/com/method5/jot/entity/DispatchError.java index b6c7942..cf84902 100644 --- a/jot/src/main/java/com/method5/jot/entity/DispatchError.java +++ b/jot/src/main/java/com/method5/jot/entity/DispatchError.java @@ -20,10 +20,10 @@ public enum Kind { UNKNOWN } - private final Kind kind; - private final int moduleIndex; - private final int errorCode; - private final String name; + public final Kind kind; + public final int moduleIndex; + public final int errorCode; + public final String name; private DispatchError(Kind kind, int moduleIndex, int errorCode, String name) { this.kind = kind; diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/AccountId.java b/jot/src/main/java/com/method5/jot/extrinsic/call/AccountId.java new file mode 100644 index 0000000..4159f82 --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/AccountId.java @@ -0,0 +1,28 @@ +package com.method5.jot.extrinsic.call; + +import java.util.Arrays; + +public class AccountId { + private final byte[] publicKeyBytes; + + public AccountId(byte[] publicKeyBytes) { + this.publicKeyBytes = publicKeyBytes; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof AccountId)) return false; + AccountId that = (AccountId) other; + return Arrays.equals(this.publicKeyBytes, that.publicKeyBytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(publicKeyBytes); + } + + public static AccountId fromBytes(byte[] data) { + return new AccountId(data); + } +} diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/EventClass.java b/jot/src/main/java/com/method5/jot/extrinsic/call/EventClass.java new file mode 100644 index 0000000..5334628 --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/EventClass.java @@ -0,0 +1,12 @@ +package com.method5.jot.extrinsic.call; + +import com.method5.jot.metadata.RuntimeTypeDecoder; + +import java.util.Map; + +public interface EventClass { + // Factory method that must create a new instance + static T create(Map attributes) { + throw new UnsupportedOperationException("Must be implemented by subclass"); + } +} \ No newline at end of file diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/ExtrinsicError.java b/jot/src/main/java/com/method5/jot/extrinsic/call/ExtrinsicError.java new file mode 100644 index 0000000..5171646 --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/ExtrinsicError.java @@ -0,0 +1,4 @@ +package com.method5.jot.extrinsic.call; + +public interface ExtrinsicError {} + diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/KeyAlreadyRegisteredError.java b/jot/src/main/java/com/method5/jot/extrinsic/call/KeyAlreadyRegisteredError.java new file mode 100644 index 0000000..b6b65e7 --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/KeyAlreadyRegisteredError.java @@ -0,0 +1,3 @@ +package com.method5.jot.extrinsic.call; + +public class KeyAlreadyRegisteredError implements ExtrinsicError{} diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/MessageSourceId.java b/jot/src/main/java/com/method5/jot/extrinsic/call/MessageSourceId.java new file mode 100644 index 0000000..d1069b5 --- /dev/null +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/MessageSourceId.java @@ -0,0 +1,29 @@ +package com.method5.jot.extrinsic.call; + +import java.math.BigInteger; +import java.util.Objects; + +public class MessageSourceId { + private final BigInteger value; + + public MessageSourceId(BigInteger value) { + this.value = value; + } + + public BigInteger getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + MessageSourceId that = (MessageSourceId) other; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java b/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java index 6b1a04f..55d9d36 100644 --- a/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java +++ b/jot/src/main/java/com/method5/jot/extrinsic/call/MsaPallet.java @@ -1,9 +1,13 @@ package com.method5.jot.extrinsic.call; +import com.method5.jot.metadata.RuntimeTypeDecoder; import com.method5.jot.rpc.Api; import com.method5.jot.rpc.CallOrQuery; import com.method5.jot.scale.ScaleWriter; +import java.math.BigInteger; +import java.util.*; + public class MsaPallet extends CallOrQuery { public MsaPallet(Api api) { super(api); @@ -21,4 +25,36 @@ private byte[] createMsaWriter(byte[] callIndex) { return writer.toByteArray(); } + public static class MsaCreated implements EventClass{ + private final MessageSourceId msaId; + private final AccountId accountId; + + public MsaCreated(MessageSourceId msaId, AccountId accountId) { + this.msaId = msaId; + this.accountId = accountId; + } + + public MessageSourceId getMsaId() { + return msaId; + } + + public AccountId getAccountId() { + return accountId; + } + + public static MsaCreated create(Map attributes) { + BigInteger msaIdValue = new BigInteger(attributes.get("MessageSourceId").getValue().toString()); + MessageSourceId msaId = new MessageSourceId(msaIdValue); + ArrayList byteList = (ArrayList) ((HashMap) attributes.get("T::AccountId").getValue()).get("field1"); + byte[] accountIdValue = new byte[byteList.size()]; + for (int i = 0; i < byteList.size(); i++) { + accountIdValue[i] = byteList.get(i); + } + AccountId accountId = new AccountId(accountIdValue); + + return new MsaCreated(msaId, accountId); + } + } + } + diff --git a/jot/src/main/java/com/method5/jot/metadata/CallIndexResolver.java b/jot/src/main/java/com/method5/jot/metadata/CallIndexResolver.java index f2e18f8..520a56a 100644 --- a/jot/src/main/java/com/method5/jot/metadata/CallIndexResolver.java +++ b/jot/src/main/java/com/method5/jot/metadata/CallIndexResolver.java @@ -18,7 +18,7 @@ public class CallIndexResolver { private final Map callMap = new HashMap<>(); private final Map moduleIndexToName = new HashMap<>(); private final Map> moduleFunctions = new HashMap<>(); - private final Map> moduleEvents = new HashMap<>(); + public final Map> moduleEvents = new HashMap<>(); private final Map> moduleErrors = new HashMap<>(); public CallIndexResolver() {} diff --git a/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java index 44948ad..6c6e2c1 100644 --- a/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java +++ b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java @@ -1,7 +1,8 @@ package com.method5.jot.extrinsic; +import com.method5.jot.entity.DispatchError; import com.method5.jot.events.EventRecord; -import com.method5.jot.extrinsic.call.Call; +import com.method5.jot.extrinsic.call.*; import com.method5.jot.rpc.PolkadotWs; import com.method5.jot.signing.SigningProvider; import com.method5.jot.util.HexUtil; @@ -16,6 +17,7 @@ import org.testcontainers.utility.DockerImageName; import java.util.List; +import java.util.Map; import java.util.Objects; @Testcontainers @@ -36,6 +38,24 @@ public static String getWsAddress() { return String.format("ws://%s:%s", frequencyTestContainer.getHost(), frequencyTestContainer.getMappedPort(9944)); } + public Class> mapEventNameToEventClass(String methodName){ + switch (methodName) { + case "MsaCreated": + return MsaPallet.MsaCreated.class; + default: + return null; + } + } + + public Class mapErrorNameToErrorClass(String errorName, int moduleIndex){ + switch (errorName) { + case "KeyAlreadyRegistered": + return KeyAlreadyRegisteredError.class; + default: + return null; + } + } + @Test public void msaTest() { try(PolkadotWs api = new PolkadotWs(getWsAddress())) { @@ -59,15 +79,29 @@ public void msaTest() { EventRecord msaCreated = eventRecordList.stream().filter(r -> Objects.equals(r.method(), "MsaCreated")).toList().getFirst(); Assertions.assertNotNull(msaCreated); + Class> eventClass = mapEventNameToEventClass(msaCreated.method()); + + Object msaCreatedObject = eventClass.getMethod("create", Map.class).invoke(null, msaCreated.attributes()); + + Assertions.assertInstanceOf(MsaPallet.MsaCreated.class, msaCreatedObject); + Assertions.assertEquals(1, ((MsaPallet.MsaCreated) msaCreatedObject).getMsaId().getValue().intValue()); + String hash = result.getHash(); System.out.println("Extrinsic hash: " + hash); ExtrinsicResult failure = call.signAndWaitForResults(aliceSigningProvider); + //Step 1 dereference Error name to Error object type (moduleIndex, name) should be unique enough + //Step 2 There are no error attributes so you can just use an enum from step 1 + + System.out.println(failure.getError().toHuman()); Assertions.assertEquals("Module[60] Error[0]: KeyAlreadyRegistered", failure.getError().toHuman()); + Object error = mapErrorNameToErrorClass(failure.getError().name, failure.getError().moduleIndex); + Assertions.assertInstanceOf(KeyAlreadyRegisteredError.class, error); + } catch (Exception e) { throw new RuntimeException(e); } From e05ef2a6867ff324616d786b0e33aee08cb83ec6 Mon Sep 17 00:00:00 2001 From: Tristan Willard Date: Tue, 4 Nov 2025 11:53:43 -0500 Subject: [PATCH 3/3] Draft cont --- .../com/method5/jot/entity/DispatchError.java | 16 ++++++++++++---- .../java/com/method5/jot/extrinsic/E2ETest.java | 3 +-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/jot/src/main/java/com/method5/jot/entity/DispatchError.java b/jot/src/main/java/com/method5/jot/entity/DispatchError.java index cf84902..d276ae3 100644 --- a/jot/src/main/java/com/method5/jot/entity/DispatchError.java +++ b/jot/src/main/java/com/method5/jot/entity/DispatchError.java @@ -20,10 +20,18 @@ public enum Kind { UNKNOWN } - public final Kind kind; - public final int moduleIndex; - public final int errorCode; - public final String name; + private final Kind kind; + private final int moduleIndex; + private final int errorCode; + private final String name; + + public String getName() { + return name; + } + + public int getModuleIndex() { + return moduleIndex; + } private DispatchError(Kind kind, int moduleIndex, int errorCode, String name) { this.kind = kind; diff --git a/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java index 6c6e2c1..d6e3461 100644 --- a/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java +++ b/jot/src/test/java/com/method5/jot/extrinsic/E2ETest.java @@ -1,6 +1,5 @@ package com.method5.jot.extrinsic; -import com.method5.jot.entity.DispatchError; import com.method5.jot.events.EventRecord; import com.method5.jot.extrinsic.call.*; import com.method5.jot.rpc.PolkadotWs; @@ -99,7 +98,7 @@ public void msaTest() { System.out.println(failure.getError().toHuman()); Assertions.assertEquals("Module[60] Error[0]: KeyAlreadyRegistered", failure.getError().toHuman()); - Object error = mapErrorNameToErrorClass(failure.getError().name, failure.getError().moduleIndex); + Object error = mapErrorNameToErrorClass(failure.getError().getName(), failure.getError().getModuleIndex()); Assertions.assertInstanceOf(KeyAlreadyRegisteredError.class, error); } catch (Exception e) {