genlayer 0.12.4 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.env.example +4 -0
  2. package/CHANGELOG.md +8 -0
  3. package/dist/index.js +17970 -17628
  4. package/esbuild.config.dev.js +1 -2
  5. package/esbuild.config.prod.js +1 -1
  6. package/eslint.config.js +2 -1
  7. package/package.json +5 -3
  8. package/src/commands/contracts/call.ts +23 -33
  9. package/src/commands/contracts/deploy.ts +109 -33
  10. package/src/commands/contracts/index.ts +14 -3
  11. package/src/commands/general/init.ts +0 -1
  12. package/src/commands/keygen/create.ts +5 -25
  13. package/src/commands/scaffold/index.ts +16 -0
  14. package/src/commands/scaffold/new.ts +34 -0
  15. package/src/index.ts +2 -0
  16. package/src/lib/accounts/KeypairManager.ts +43 -0
  17. package/src/lib/actions/BaseAction.ts +34 -7
  18. package/src/lib/config/simulator.ts +2 -2
  19. package/templates/default/LICENSE +21 -0
  20. package/templates/default/README.md +101 -0
  21. package/templates/default/__init__.py +0 -0
  22. package/templates/default/app/.env.example +2 -0
  23. package/templates/default/app/.vscode/extensions.json +3 -0
  24. package/templates/default/app/README.md +5 -0
  25. package/templates/default/app/index.html +17 -0
  26. package/templates/default/app/package-lock.json +4920 -0
  27. package/templates/default/app/package.json +23 -0
  28. package/templates/default/app/postcss.config.js +6 -0
  29. package/templates/default/app/public/favicon.png +0 -0
  30. package/templates/default/app/src/App.vue +16 -0
  31. package/templates/default/app/src/components/Address.vue +38 -0
  32. package/templates/default/app/src/components/BetsScreen.vue +329 -0
  33. package/templates/default/app/src/logic/FootballBets.js +100 -0
  34. package/templates/default/app/src/main.js +5 -0
  35. package/templates/default/app/src/services/genlayer.js +19 -0
  36. package/templates/default/app/src/style.css +3 -0
  37. package/templates/default/app/tailwind.config.js +8 -0
  38. package/templates/default/app/vite.config.js +7 -0
  39. package/templates/default/config/__init__.py +0 -0
  40. package/templates/default/config/genlayer_config.py +14 -0
  41. package/templates/default/contracts/__init__.py +0 -0
  42. package/templates/default/contracts/football_bets.py +119 -0
  43. package/templates/default/deploy/deployScript.ts +31 -0
  44. package/templates/default/package-lock.json +3231 -0
  45. package/templates/default/package.json +7 -0
  46. package/templates/default/requirements.txt +6 -0
  47. package/templates/default/test/__init__.py +0 -0
  48. package/templates/default/test/football_bets_get_contract_schema_for_code.py +124 -0
  49. package/templates/default/test/test_football_bet_success_draw.py +108 -0
  50. package/templates/default/test/test_football_bet_success_win.py +106 -0
  51. package/templates/default/test/test_football_bet_unsuccess.py +107 -0
  52. package/templates/default/tools/__init__.py +0 -0
  53. package/templates/default/tools/accounts.py +5 -0
  54. package/templates/default/tools/calldata.py +224 -0
  55. package/templates/default/tools/request.py +134 -0
  56. package/templates/default/tools/response.py +52 -0
  57. package/templates/default/tools/structure.py +39 -0
  58. package/templates/default/tools/transactions.py +28 -0
  59. package/templates/default/tools/types.py +214 -0
  60. package/templates/default/tsconfig.json +7 -0
  61. package/tests/actions/call.test.ts +39 -79
  62. package/tests/actions/create.test.ts +11 -72
  63. package/tests/actions/deploy.test.ts +201 -33
  64. package/tests/actions/new.test.ts +80 -0
  65. package/tests/commands/call.test.ts +6 -1
  66. package/tests/commands/deploy.test.ts +12 -1
  67. package/tests/commands/new.test.ts +68 -0
  68. package/tests/index.test.ts +4 -0
  69. package/tests/libs/accounts/KeypairManager.test.ts +110 -0
  70. package/tests/libs/baseAction.test.ts +40 -0
  71. package/vitest.config.ts +1 -1
  72. package/src/lib/accounts/getPrivateKey.ts +0 -21
  73. package/tests/libs/getPrivateKey.test.ts +0 -96
@@ -0,0 +1,134 @@
1
+ # tests/common/request.py
2
+ import os
3
+ import json
4
+ import requests
5
+ import time
6
+ from dotenv import load_dotenv
7
+ from eth_account import Account
8
+ import base64
9
+
10
+ from tools.transactions import sign_transaction, encode_transaction_data
11
+
12
+ import tools.calldata as calldata
13
+
14
+ load_dotenv()
15
+
16
+
17
+ def payload(function_name: str, *args) -> dict:
18
+ return {
19
+ "jsonrpc": "2.0",
20
+ "method": function_name,
21
+ "params": [*args],
22
+ "id": 1,
23
+ }
24
+
25
+
26
+ def post_request(
27
+ payload: dict,
28
+ protocol: str = os.environ["RPCPROTOCOL"],
29
+ host: str = os.environ["RPCHOST"],
30
+ port: str = os.environ["RPCPORT"],
31
+ ):
32
+ return requests.post(
33
+ protocol + "://" + host + ":" + port + "/api",
34
+ data=json.dumps(payload),
35
+ headers={"Content-Type": "application/json"},
36
+ )
37
+
38
+
39
+ def get_transaction_by_hash(transaction_hash: str):
40
+ payload_data = payload("eth_getTransactionByHash", transaction_hash)
41
+ raw_response = post_request(payload_data)
42
+ parsed_raw_response = raw_response.json()
43
+ return parsed_raw_response["result"]
44
+
45
+
46
+ def get_transaction_count(account_address: str):
47
+ payload_data = payload("eth_getTransactionCount", account_address)
48
+ raw_response = post_request(payload_data)
49
+ parsed_raw_response = raw_response.json()
50
+ return parsed_raw_response["result"]
51
+
52
+
53
+ def call_contract_method(
54
+ contract_address: str,
55
+ from_account: Account,
56
+ method_name: str,
57
+ method_args: list,
58
+ ):
59
+ encoded_data = encode_transaction_data(
60
+ [calldata.encode({"method": method_name, "args": method_args})]
61
+ )
62
+ method_response = post_request(
63
+ payload(
64
+ "eth_call",
65
+ {
66
+ "to": contract_address,
67
+ "from": from_account.address,
68
+ "data": encoded_data,
69
+ },
70
+ )
71
+ ).json()
72
+ print("method_response", method_response)
73
+ enc_result = method_response["result"]
74
+ return calldata.decode(base64.b64decode(enc_result))
75
+
76
+
77
+ def send_transaction(
78
+ account: Account,
79
+ contract_address: str,
80
+ method_name: str | None,
81
+ method_args: list | None,
82
+ value: int = 0,
83
+ ):
84
+ call_data = (
85
+ None
86
+ if method_name is None and method_args is None
87
+ else [calldata.encode({"method": method_name, "args": method_args})]
88
+ )
89
+ nonce = get_transaction_count(account.address)
90
+ signed_transaction = sign_transaction(
91
+ account, call_data, contract_address, value, nonce
92
+ )
93
+ return send_raw_transaction(signed_transaction)
94
+
95
+
96
+ def deploy_intelligent_contract(
97
+ account: Account, contract_code: str, method_args: list
98
+ ) -> tuple[str, dict]:
99
+ nonce = get_transaction_count(account.address)
100
+ deploy_data = [
101
+ contract_code,
102
+ calldata.encode({"method": "__init__", "args": method_args}),
103
+ ]
104
+ signed_transaction = sign_transaction(account, deploy_data, nonce=nonce)
105
+ result = send_raw_transaction(signed_transaction)
106
+ contract_address = result["data"]["contract_address"]
107
+ return contract_address, result
108
+
109
+
110
+ def send_raw_transaction(signed_transaction: str):
111
+ payload_data = payload("eth_sendRawTransaction", signed_transaction)
112
+ raw_response = post_request(payload_data)
113
+ call_method_response = raw_response.json()
114
+ print("call_method_response", call_method_response)
115
+ transaction_hash = call_method_response["result"]
116
+
117
+ transaction_response = wait_for_transaction(transaction_hash)
118
+ return transaction_response
119
+
120
+
121
+ def wait_for_transaction(transaction_hash: str, interval: int = 10, retries: int = 15):
122
+ attempts = 0
123
+ while attempts < retries:
124
+ transaction_response = get_transaction_by_hash(str(transaction_hash))
125
+ print("transaction_response", transaction_response)
126
+ status = transaction_response["status"]
127
+ if status == "FINALIZED":
128
+ return transaction_response
129
+ time.sleep(interval)
130
+ attempts += 1
131
+
132
+ raise TimeoutError(
133
+ f"Transaction {transaction_hash} not finalized after {retries} retries"
134
+ )
@@ -0,0 +1,52 @@
1
+ def assert_dict_struct(data, structure):
2
+ if isinstance(structure, dict):
3
+ assert_is_instance(data, dict)
4
+ for key, value in structure.items():
5
+ assert key in data
6
+ assert_dict_struct(data[key], value)
7
+ elif isinstance(structure, list):
8
+ assert_is_instance(data, list)
9
+ for item in data:
10
+ assert_dict_struct(item, structure[0])
11
+ else:
12
+ assert_is_instance(data, structure)
13
+
14
+
15
+ def assert_is_instance(data, structure):
16
+ assert isinstance(data, structure), f"Expected {structure}, but got {data}"
17
+
18
+
19
+ def assert_dict_exact(data, expected):
20
+ assert data == expected, f"Expected {expected}, but got {data}"
21
+
22
+
23
+ def has_error_status(result: dict) -> bool:
24
+ return "error" in result
25
+
26
+
27
+ def has_success_status(result: dict) -> bool:
28
+ return "error" not in result
29
+
30
+
31
+ def has_message(result: dict) -> bool:
32
+ return "message" in result
33
+
34
+
35
+ def has_data(result: dict) -> bool:
36
+ return "data" in result
37
+
38
+
39
+ def message_is(result: dict, message: dict) -> bool:
40
+ return result["message"] == message
41
+
42
+
43
+ def data_is(result: dict, data: dict) -> bool:
44
+ return result["data"] == data
45
+
46
+
47
+ def message_contains(result: dict, message: dict) -> bool:
48
+ return message in result["message"]
49
+
50
+
51
+ def data_contains(result: dict, data: dict) -> bool:
52
+ return data in result["data"]
@@ -0,0 +1,39 @@
1
+ execute_icontract_function_response_structure = {
2
+ "consensus_data": {
3
+ "final": bool,
4
+ "leader_receipt": {
5
+ "args": list,
6
+ "class_name": str,
7
+ "contract_state": str,
8
+ "eq_outputs": {"leader": dict},
9
+ "error": str | None,
10
+ "execution_result": str,
11
+ "gas_used": int,
12
+ "method": str,
13
+ "mode": str,
14
+ "node_config": {
15
+ "address": str,
16
+ "config": dict,
17
+ "model": str,
18
+ "provider": str,
19
+ "stake": int,
20
+ "plugin": str,
21
+ "plugin_config": dict,
22
+ },
23
+ "vote": str,
24
+ },
25
+ "validators": list,
26
+ "votes": dict,
27
+ },
28
+ "created_at": str,
29
+ "data": {
30
+ "function_args": str, # TODO: can we make this a list?
31
+ "function_name": str,
32
+ },
33
+ "from_address": str,
34
+ "hash": str,
35
+ "status": str,
36
+ "to_address": str,
37
+ "type": int,
38
+ "value": int,
39
+ }
@@ -0,0 +1,28 @@
1
+ from eth_account import Account
2
+ from eth_utils import to_hex
3
+ import rlp
4
+ from eth_account._utils.legacy_transactions import Transaction
5
+
6
+
7
+ def encode_transaction_data(data: list) -> str:
8
+ serialized_data = rlp.encode(data)
9
+ return to_hex(serialized_data)
10
+
11
+
12
+ def sign_transaction(
13
+ account: Account, data: list = None, to: str = None, value: int = 0, nonce: int = 0
14
+ ) -> dict:
15
+ transaction = {
16
+ "nonce": nonce,
17
+ "gasPrice": 0,
18
+ "gas": 0,
19
+ "to": to,
20
+ "value": value,
21
+ }
22
+
23
+ if data is not None:
24
+ encoded_data = encode_transaction_data(data)
25
+ transaction["data"] = encoded_data
26
+
27
+ signed_transaction = Account.sign_transaction(transaction, account.key)
28
+ return to_hex(signed_transaction.raw_transaction)
@@ -0,0 +1,214 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Iterable, Optional
4
+ import base64
5
+
6
+ import collections.abc
7
+
8
+ from eth_hash.auto import keccak
9
+
10
+
11
+ class Address:
12
+ SIZE = 20
13
+
14
+ __slots__ = ("_as_bytes", "_as_hex")
15
+
16
+ _as_bytes: bytes
17
+ _as_hex: str | None
18
+
19
+ def __init__(self, val: str | collections.abc.Buffer):
20
+ self._as_hex = None
21
+ if isinstance(val, str):
22
+ if len(val) == 2 + Address.SIZE * 2 and val.startswith("0x"):
23
+ val = bytes.fromhex(val[2:])
24
+ elif len(val) > Address.SIZE:
25
+ val = base64.b64decode(val)
26
+ else:
27
+ val = bytes(val)
28
+ if not isinstance(val, bytes) or len(val) != Address.SIZE:
29
+ raise Exception(f"invalid address {val}")
30
+ self._as_bytes = val
31
+
32
+ @property
33
+ def as_bytes(self) -> bytes:
34
+ return self._as_bytes
35
+
36
+ @property
37
+ def as_hex(self) -> str:
38
+ if self._as_hex is None:
39
+ simple = self._as_bytes.hex()
40
+ low_up = keccak(simple.encode("ascii")).hex()
41
+ res = ["0", "x"]
42
+ for i in range(len(simple)):
43
+ if low_up[i] in ["0", "1", "2", "3", "4", "5", "6", "7"]:
44
+ res.append(simple[i])
45
+ else:
46
+ res.append(simple[i].upper())
47
+ self._as_hex = "".join(res)
48
+ return self._as_hex
49
+
50
+ @property
51
+ def as_b64(self) -> str:
52
+ return str(base64.b64encode(self.as_bytes), encoding="ascii")
53
+
54
+ @property
55
+ def as_int(self) -> int:
56
+ return int.from_bytes(self._as_bytes, "little", signed=False)
57
+
58
+ def __hash__(self):
59
+ return hash(self._as_bytes)
60
+
61
+ def __lt__(self, r):
62
+ assert isinstance(r, Address)
63
+ return self._as_bytes < r._as_bytes
64
+
65
+ def __le__(self, r):
66
+ assert isinstance(r, Address)
67
+ return self._as_bytes <= r._as_bytes
68
+
69
+ def __eq__(self, r):
70
+ if not isinstance(r, Address):
71
+ return False
72
+ return self._as_bytes == r._as_bytes
73
+
74
+ def __ge__(self, r):
75
+ assert isinstance(r, Address)
76
+ return self._as_bytes >= r._as_bytes
77
+
78
+ def __gt__(self, r):
79
+ assert isinstance(r, Address)
80
+ return self._as_bytes > r._as_bytes
81
+
82
+ def __repr__(self) -> str:
83
+ return "addr#" + "".join(["{:02x}".format(x) for x in self._as_bytes])
84
+
85
+
86
+ class Vote(Enum):
87
+ AGREE = "agree"
88
+ DISAGREE = "disagree"
89
+
90
+ @classmethod
91
+ def from_string(cls, value: str) -> "Vote":
92
+ try:
93
+ return cls(value.lower())
94
+ except ValueError:
95
+ raise ValueError(f"Invalid vote value: {value}")
96
+
97
+
98
+ class ExecutionMode(Enum):
99
+ LEADER = "leader"
100
+ VALIDATOR = "validator"
101
+
102
+ @classmethod
103
+ def from_string(cls, value: str) -> "ExecutionMode":
104
+ try:
105
+ return cls(value.lower())
106
+ except ValueError:
107
+ raise ValueError(f"Invalid execution mode value: {value}")
108
+
109
+
110
+ class ExecutionResultStatus(Enum):
111
+ SUCCESS = "SUCCESS"
112
+ ERROR = "ERROR"
113
+
114
+ @classmethod
115
+ def from_string(cls, value: str) -> "ExecutionResultStatus":
116
+ try:
117
+ return cls(value.upper())
118
+ except ValueError:
119
+ raise ValueError(f"Invalid execution result status value: {value}")
120
+
121
+
122
+ @dataclass
123
+ class PendingTransaction:
124
+ address: str # Address of the contract to call
125
+ calldata: bytes
126
+ code: bytes | None
127
+ salt_nonce: int
128
+
129
+ def is_deploy(self) -> bool:
130
+ return self.code is not None
131
+
132
+ def to_dict(self):
133
+ if self.code is None:
134
+ return {
135
+ "address": self.address,
136
+ "calldata": str(base64.b64encode(self.calldata), encoding="ascii"),
137
+ }
138
+ else:
139
+ return {
140
+ "code": str(base64.b64encode(self.code), encoding="ascii"),
141
+ "calldata": str(base64.b64encode(self.calldata), encoding="ascii"),
142
+ "salt_nonce": self.salt_nonce,
143
+ }
144
+
145
+ @classmethod
146
+ def from_dict(cls, input: dict) -> "PendingTransaction":
147
+ if "code" in input:
148
+ return cls(
149
+ address="0x",
150
+ calldata=base64.b64decode(input["calldata"]),
151
+ code=base64.b64decode(input["code"]),
152
+ salt_nonce=input.get("salt_nonce", 0),
153
+ )
154
+ else:
155
+ return cls(
156
+ address=input["address"],
157
+ calldata=base64.b64decode(input["calldata"]),
158
+ code=None,
159
+ salt_nonce=0,
160
+ )
161
+
162
+
163
+ @dataclass
164
+ class Receipt:
165
+ result: bytes
166
+ calldata: bytes
167
+ gas_used: int
168
+ mode: ExecutionMode
169
+ contract_state: dict[str, dict[str, str]]
170
+ node_config: dict
171
+ eq_outputs: dict[int, str]
172
+ execution_result: ExecutionResultStatus
173
+ vote: Optional[Vote] = None
174
+ pending_transactions: Iterable[PendingTransaction] = ()
175
+
176
+ def to_dict(self):
177
+ return {
178
+ "vote": self.vote.value,
179
+ "execution_result": self.execution_result.value,
180
+ "result": base64.b64encode(self.result).decode("ascii"),
181
+ "calldata": str(base64.b64encode(self.calldata), encoding="ascii"),
182
+ "gas_used": self.gas_used,
183
+ "mode": self.mode.value,
184
+ "contract_state": self.contract_state,
185
+ "node_config": self.node_config,
186
+ "eq_outputs": self.eq_outputs,
187
+ "pending_transactions": [
188
+ pending_transaction.to_dict()
189
+ for pending_transaction in self.pending_transactions
190
+ ],
191
+ }
192
+
193
+ @classmethod
194
+ def from_dict(cls, input: dict) -> Optional["Receipt"]:
195
+ if input:
196
+ return cls(
197
+ vote=Vote.from_string(input.get("vote")),
198
+ execution_result=ExecutionResultStatus.from_string(
199
+ input.get("execution_result")
200
+ ),
201
+ result=base64.b64decode(input.get("result")),
202
+ calldata=base64.b64decode(input.get("calldata")),
203
+ gas_used=input.get("gas_used"),
204
+ mode=ExecutionMode.from_string(input.get("mode")),
205
+ contract_state=input.get("contract_state"),
206
+ node_config=input.get("node_config"),
207
+ eq_outputs={int(k): v for k, v in input.get("eq_outputs", {}).items()},
208
+ pending_transactions=[
209
+ PendingTransaction.from_dict(pending_transaction)
210
+ for pending_transaction in input.get("pending_transactions", [])
211
+ ],
212
+ )
213
+ else:
214
+ return None
@@ -0,0 +1,7 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ES2022",
4
+ "moduleResolution": "bundler",
5
+ "outDir": "dist"
6
+ }
7
+ }
@@ -1,18 +1,16 @@
1
1
  import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
2
  import { createClient, createAccount } from "genlayer-js";
3
- import { CallAction, CallOptions } from "../../src/commands/contracts/call";
4
- import { getPrivateKey } from "../../src/lib/accounts/getPrivateKey";
3
+ import { CallAction } from "../../src/commands/contracts/call";
5
4
 
6
5
  vi.mock("genlayer-js");
7
- vi.mock("../../src/lib/accounts/getPrivateKey");
8
6
 
9
- describe("Call Action", () => {
10
- let caller: CallAction;
7
+ describe("CallAction", () => {
8
+ let callActions: CallAction;
11
9
  const mockClient = {
12
10
  readContract: vi.fn(),
13
11
  writeContract: vi.fn(),
14
12
  waitForTransactionReceipt: vi.fn(),
15
- getContractSchema: vi.fn()
13
+ getContractSchema: vi.fn(),
16
14
  };
17
15
 
18
16
  const mockPrivateKey = "mocked_private_key";
@@ -21,8 +19,13 @@ describe("Call Action", () => {
21
19
  vi.clearAllMocks();
22
20
  vi.mocked(createClient).mockReturnValue(mockClient as any);
23
21
  vi.mocked(createAccount).mockReturnValue({ privateKey: mockPrivateKey } as any);
24
- vi.mocked(getPrivateKey).mockReturnValue(mockPrivateKey);
25
- caller = new CallAction();
22
+ callActions = new CallAction();
23
+ vi.spyOn(callActions as any, "getPrivateKey").mockResolvedValue(mockPrivateKey);
24
+
25
+ vi.spyOn(callActions as any, "startSpinner").mockImplementation(() => {});
26
+ vi.spyOn(callActions as any, "succeedSpinner").mockImplementation(() => {});
27
+ vi.spyOn(callActions as any, "failSpinner").mockImplementation(() => {});
28
+ vi.spyOn(callActions as any, "log").mockImplementation(() => {});
26
29
  });
27
30
 
28
31
  afterEach(() => {
@@ -30,15 +33,13 @@ describe("Call Action", () => {
30
33
  });
31
34
 
32
35
  test("calls readContract successfully", async () => {
33
- const options: CallOptions = {
34
- args: [1, 2, "Hello"]
35
- };
36
+ const options = { args: [1, 2, "Hello"] };
36
37
  const mockResult = "mocked_result";
37
38
 
39
+ vi.mocked(mockClient.getContractSchema).mockResolvedValue({ methods: { getData: { readonly: true } } });
38
40
  vi.mocked(mockClient.readContract).mockResolvedValue(mockResult);
39
- vi.mocked(mockClient.getContractSchema).mockResolvedValue({methods: {getData: {readonly: true}}});
40
41
 
41
- await caller.call({
42
+ await callActions.call({
42
43
  contractAddress: "0xMockedContract",
43
44
  method: "getData",
44
45
  ...options,
@@ -49,21 +50,19 @@ describe("Call Action", () => {
49
50
  functionName: "getData",
50
51
  args: [1, 2, "Hello"],
51
52
  });
52
- expect(mockClient.readContract).toHaveResolvedWith(mockResult);
53
+ expect(callActions["succeedSpinner"]).toHaveBeenCalledWith("Read operation successfully executed", "mocked_result");
53
54
  });
54
55
 
55
56
  test("calls writeContract successfully", async () => {
56
- const options: CallOptions = {
57
- args: [42, "Update"]
58
- };
57
+ const options = { args: [42, "Update"] };
59
58
  const mockHash = "0xMockedTransactionHash";
60
59
  const mockReceipt = { status: "success" };
61
60
 
61
+ vi.mocked(mockClient.getContractSchema).mockResolvedValue({ methods: { updateData: { readonly: false } } });
62
62
  vi.mocked(mockClient.writeContract).mockResolvedValue(mockHash);
63
63
  vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
64
- vi.mocked(mockClient.getContractSchema).mockResolvedValue({methods: {updateData: {readonly: false}}});
65
64
 
66
- await caller.call({
65
+ await callActions.call({
67
66
  contractAddress: "0xMockedContract",
68
67
  method: "updateData",
69
68
  ...options,
@@ -75,72 +74,33 @@ describe("Call Action", () => {
75
74
  args: [42, "Update"],
76
75
  value: 0n,
77
76
  });
78
- expect(mockClient.waitForTransactionReceipt).toHaveBeenCalledWith({
79
- hash: mockHash,
80
- retries: 15,
81
- interval: 2000,
82
- });
83
- expect(mockClient.writeContract).toHaveResolvedWith(mockHash);
77
+ expect(callActions["log"]).toHaveBeenCalledWith("Write transaction hash:", mockHash);
78
+ expect(callActions["succeedSpinner"]).toHaveBeenCalledWith("Write operation successfully executed", mockReceipt);
84
79
  });
85
80
 
86
- test("throws error when method is not found", async () => {
87
- const options: CallOptions = {
88
- args: []
89
- };
81
+ test("fails when method is not found", async () => {
82
+ vi.mocked(mockClient.getContractSchema).mockResolvedValue({ methods: { updateData: { readonly: false } } });
90
83
 
91
- vi.mocked(mockClient.getContractSchema).mockResolvedValue({methods: {updateData: {readonly: false}}});
84
+ await callActions.call({ contractAddress: "0xMockedContract", method: "getData", args: [] });
92
85
 
93
- await expect(
94
- caller.call({
95
- contractAddress: "0xMockedContract",
96
- method: "getData",
97
- ...options,
98
- })
99
- ).rejects.toThrowError('process.exit unexpectedly called with "1"');
100
-
101
- expect(mockClient.readContract).not.toHaveBeenCalled();
102
- expect(mockClient.writeContract).not.toHaveBeenCalled();
86
+ expect(callActions["failSpinner"]).toHaveBeenCalledWith("method getData not found.");
103
87
  });
104
88
 
105
- test("handles errors during readContract", async () => {
106
- const options: CallOptions = {
107
- args: [1]
108
- };
109
-
110
- vi.mocked(mockClient.getContractSchema).mockResolvedValue({methods: {getData: {readonly: true}}});
111
- vi.mocked(mockClient.readContract).mockRejectedValue(
112
- new Error("Mocked read error")
113
- );
114
-
115
- await expect(
116
- caller.call({
117
- contractAddress: "0xMockedContract",
118
- method: "getData",
119
- ...options,
120
- })
121
- ).rejects.toThrowError("Mocked read error");
122
-
123
- expect(mockClient.readContract).toHaveBeenCalled();
89
+ test("handles readContract errors", async () => {
90
+ vi.mocked(mockClient.getContractSchema).mockResolvedValue({ methods: { getData: { readonly: true } } });
91
+ vi.mocked(mockClient.readContract).mockRejectedValue(new Error("Mocked read error"));
92
+
93
+ await callActions.call({ contractAddress: "0xMockedContract", method: "getData", args: [1] });
94
+
95
+ expect(callActions["failSpinner"]).toHaveBeenCalledWith("Error during read operation", expect.any(Error));
124
96
  });
125
97
 
126
- test("handles errors during writeContract", async () => {
127
- const options: CallOptions = {
128
- args: [1]
129
- };
130
-
131
- vi.mocked(mockClient.getContractSchema).mockResolvedValue({methods: {updateData: {readonly: false}}});
132
- vi.mocked(mockClient.writeContract).mockRejectedValue(
133
- new Error("Mocked write error")
134
- );
135
-
136
- await expect(
137
- caller.call({
138
- contractAddress: "0xMockedContract",
139
- method: "updateData",
140
- ...options,
141
- })
142
- ).rejects.toThrowError("Mocked write error");
143
-
144
- expect(mockClient.writeContract).toHaveBeenCalled();
98
+ test("handles writeContract errors", async () => {
99
+ vi.mocked(mockClient.getContractSchema).mockResolvedValue({ methods: { updateData: { readonly: false } } });
100
+ vi.mocked(mockClient.writeContract).mockRejectedValue(new Error("Mocked write error"));
101
+
102
+ await callActions.call({ contractAddress: "0xMockedContract", method: "updateData", args: [1] });
103
+
104
+ expect(callActions["failSpinner"]).toHaveBeenCalledWith("Error during write operation", expect.any(Error));
145
105
  });
146
- });
106
+ });