@mentaproject/client 0.0.3 → 0.0.5

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.
@@ -2,10 +2,10 @@ import type { Address, CoreClient } from "@mentaproject/core/types";
2
2
  import { Transaction } from "./Transaction";
3
3
  import { GetTransactionsReturnType } from "../types/Account";
4
4
  /**
5
- * Represents a user account with methods for interacting with the blockchain.
5
+ * Represents an account with methods for interacting with it.
6
6
  */
7
7
  export declare class Account {
8
- protected rpcClient: CoreClient;
8
+ rpcClient: CoreClient;
9
9
  address: Address;
10
10
  /**
11
11
  * Creates an instance of Account.
@@ -13,6 +13,12 @@ export declare class Account {
13
13
  * @param address The blockchain address of the account.
14
14
  */
15
15
  constructor(rpcClient: CoreClient, address: Address);
16
+ /**
17
+ * If the account is a contract, returns a ContractAccount instance. Otherwise, returns undefined.
18
+ * @returns
19
+ */
20
+ isContract(): Promise<boolean>;
21
+ contractType(): Promise<any>;
16
22
  /**
17
23
  * Sends native cryptocurrency (ETH) to the account.
18
24
  * @param amount The amount of ETH to send (in wei).
@@ -23,12 +29,12 @@ export declare class Account {
23
29
  * Gets the native cryptocurrency (ETH) balance of the account.
24
30
  * @returns A Promise that resolves to the ETH balance (in wei).
25
31
  */
26
- getETHBalance(): Promise<bigint>;
32
+ ETHBalance(): Promise<bigint>;
27
33
  /**
28
34
  * Gets the transaction count (nonce) for the account.
29
35
  * @returns A Promise that resolves to the transaction count.
30
36
  **/
31
- getTransactionCount(): Promise<number>;
37
+ transactionCount(): Promise<number>;
32
38
  /**
33
39
  * Get all transactions involving this account in the given block range.
34
40
  * NOTE: This method relies on the non-standard 'trace_filter' method of the RPC. It may not be supported by all nodes.
@@ -37,5 +43,6 @@ export declare class Account {
37
43
  *
38
44
  * @returns A paginated result containing the transaction hashes.
39
45
  */
40
- getTransactions(toRetreive: number): Promise<GetTransactionsReturnType>;
46
+ transactions(toRetreive?: number): Promise<GetTransactionsReturnType>;
47
+ toJSON(depth?: number): Promise<any>;
41
48
  }
@@ -1,8 +1,10 @@
1
- import { createBlockRangePager, getBalance, getBlockNumber, getTransaction, getTransactionCount, sendTransaction, traceFilter } from "@mentaproject/core/actions";
1
+ import { createBlockRangePager, getBalance, getBlockNumber, getCode, getTransaction, getTransactionCount, sendTransaction, traceFilter } from "@mentaproject/core/actions";
2
2
  import { Transaction } from "./Transaction";
3
3
  import { toHex } from "@mentaproject/core/utils";
4
+ import { getContractType } from "@mentaproject/contracts";
5
+ import { toJSON } from "src/utils/toJSON";
4
6
  /**
5
- * Represents a user account with methods for interacting with the blockchain.
7
+ * Represents an account with methods for interacting with it.
6
8
  */
7
9
  export class Account {
8
10
  /**
@@ -14,6 +16,24 @@ export class Account {
14
16
  this.rpcClient = rpcClient;
15
17
  this.address = address;
16
18
  }
19
+ /**
20
+ * If the account is a contract, returns a ContractAccount instance. Otherwise, returns undefined.
21
+ * @returns
22
+ */
23
+ async isContract() {
24
+ const code = await getCode(this.rpcClient, { address: this.address });
25
+ if (!code || code === "0x")
26
+ return false;
27
+ return true;
28
+ }
29
+ ;
30
+ async contractType() {
31
+ const isContract = await this.isContract();
32
+ if (!isContract)
33
+ return undefined;
34
+ return await getContractType(this.rpcClient, this.address);
35
+ }
36
+ ;
17
37
  /**
18
38
  * Sends native cryptocurrency (ETH) to the account.
19
39
  * @param amount The amount of ETH to send (in wei).
@@ -34,7 +54,7 @@ export class Account {
34
54
  * Gets the native cryptocurrency (ETH) balance of the account.
35
55
  * @returns A Promise that resolves to the ETH balance (in wei).
36
56
  */
37
- async getETHBalance() {
57
+ async ETHBalance() {
38
58
  // Assurez-vous que rpcClient est bien un PublicClient ou WalletClient
39
59
  return await getBalance(this.rpcClient, {
40
60
  address: this.address,
@@ -45,7 +65,7 @@ export class Account {
45
65
  * Gets the transaction count (nonce) for the account.
46
66
  * @returns A Promise that resolves to the transaction count.
47
67
  **/
48
- async getTransactionCount() {
68
+ async transactionCount() {
49
69
  return await getTransactionCount(this.rpcClient, { address: this.address });
50
70
  }
51
71
  /**
@@ -56,7 +76,7 @@ export class Account {
56
76
  *
57
77
  * @returns A paginated result containing the transaction hashes.
58
78
  */
59
- async getTransactions(toRetreive) {
79
+ async transactions(toRetreive = 10) {
60
80
  const currentBlock = await getBlockNumber(this.rpcClient);
61
81
  const onBlockRange = async ({ fromBlock, toBlock }) => {
62
82
  const outgoing = await traceFilter(this.rpcClient, {
@@ -83,4 +103,18 @@ export class Account {
83
103
  itemsPerPage: toRetreive
84
104
  }, onBlockRange);
85
105
  }
106
+ ;
107
+ async toJSON(depth = 1) {
108
+ return await toJSON({
109
+ obj: {
110
+ ...this,
111
+ isContract: this.isContract,
112
+ contractType: this.contractType,
113
+ ETHBalance: this.ETHBalance,
114
+ transactionCount: this.transactionCount,
115
+ },
116
+ depth
117
+ });
118
+ }
86
119
  }
120
+ ;
@@ -33,4 +33,5 @@ export declare class Block implements Omit<IBlockData, "miner" | "transactions">
33
33
  miner: Account;
34
34
  transactions?: Transaction[];
35
35
  constructor(rpcClient: CoreClient, data: IBlockData, includeTransactions?: boolean);
36
+ toJSON(depth?: number): Promise<any>;
36
37
  }
@@ -1,5 +1,6 @@
1
1
  import { Account } from "./Account";
2
2
  import { Transaction } from "./Transaction";
3
+ import { toJSON } from "src/utils/toJSON";
3
4
  export class Block {
4
5
  constructor(rpcClient, data, includeTransactions = false) {
5
6
  this.rpcClient = rpcClient;
@@ -33,5 +34,11 @@ export class Block {
33
34
  this.transactions = data.transactions.map((txData) => new Transaction(this.rpcClient, txData));
34
35
  }
35
36
  ;
37
+ async toJSON(depth = 1) {
38
+ return await toJSON({
39
+ obj: this,
40
+ depth
41
+ });
42
+ }
36
43
  }
37
44
  ;
@@ -0,0 +1,6 @@
1
+ import { Address, CoreClient } from "src/types";
2
+ import { Account } from "./Account";
3
+ export declare class ContractAccount extends Account {
4
+ constructor(rpcClient: CoreClient, address: Address);
5
+ getType(): Promise<any>;
6
+ }
@@ -0,0 +1,12 @@
1
+ import { Account } from "./Account";
2
+ import { getContractType } from "@mentaproject/contracts";
3
+ export class ContractAccount extends Account {
4
+ constructor(rpcClient, address) {
5
+ super(rpcClient, address);
6
+ }
7
+ ;
8
+ async getType() {
9
+ return await getContractType(this.rpcClient, this.address);
10
+ }
11
+ }
12
+ ;
@@ -24,7 +24,16 @@ export declare class Transaction implements Omit<ITransactionData, "from" | "to"
24
24
  from?: Account;
25
25
  to?: Account;
26
26
  constructor(rpcClient: CoreClient, data: ITransactionData);
27
+ calls(): Promise<{
28
+ from: Account;
29
+ to: Account;
30
+ value: bigint;
31
+ input: Hex;
32
+ gas: bigint;
33
+ callType: string | undefined;
34
+ }[]>;
27
35
  waitForReceipt(confirmations?: number): Promise<WaitForTransactionReceiptReturnType>;
28
36
  receipt(): Promise<WaitForTransactionReceiptReturnType>;
29
37
  block(): Promise<Block>;
38
+ toJSON(depth?: number): Promise<any>;
30
39
  }
@@ -1,6 +1,8 @@
1
1
  import { Account } from './Account';
2
2
  import { Block } from './Block';
3
- import { getBlock, getTransactionReceipt, waitForTransactionReceipt } from '@mentaproject/core/actions';
3
+ import { getBlock, getTransactionReceipt, traceTransaction, waitForTransactionReceipt } from '@mentaproject/core/actions';
4
+ import { hexToBigInt } from '@mentaproject/core/utils';
5
+ import { toJSON } from 'src/utils/toJSON';
4
6
  export class Transaction {
5
7
  constructor(rpcClient, data) {
6
8
  this.rpcClient = rpcClient;
@@ -25,6 +27,18 @@ export class Transaction {
25
27
  this.to = data.to ? new Account(this.rpcClient, data.to) : undefined;
26
28
  }
27
29
  ;
30
+ async calls() {
31
+ const traces = await traceTransaction(this.rpcClient, this.hash);
32
+ const calls = traces.filter(t => t.type === "call" && t.action !== undefined);
33
+ return calls.map(c => ({
34
+ from: new Account(this.rpcClient, c.action.from),
35
+ to: new Account(this.rpcClient, c.action.to),
36
+ value: hexToBigInt(c.action.value),
37
+ input: c.action.input,
38
+ gas: hexToBigInt(c.action.gas),
39
+ callType: c.action.callType,
40
+ }));
41
+ }
28
42
  async waitForReceipt(confirmations) {
29
43
  return await waitForTransactionReceipt(this.rpcClient, {
30
44
  hash: this.hash,
@@ -41,5 +55,19 @@ export class Transaction {
41
55
  return new Block(this.rpcClient, data);
42
56
  }
43
57
  ;
58
+ async toJSON(depth = 1) {
59
+ return await toJSON({
60
+ obj: {
61
+ ...this,
62
+ block: this.block,
63
+ receipt: this.receipt,
64
+ calls: async () => {
65
+ const calls = await this.calls();
66
+ return Promise.all(calls.map(async (c) => await toJSON({ obj: c, depth: depth })));
67
+ }
68
+ },
69
+ depth
70
+ });
71
+ }
44
72
  }
45
73
  ;
@@ -0,0 +1,20 @@
1
+ declare class BaseService {
2
+ id: string;
3
+ rpcClient: any;
4
+ address: string;
5
+ constructor(id: string, rpcClient: any, address: string);
6
+ protected fetchCode(): Promise<string | null>;
7
+ }
8
+ type ContractService = BaseService & {
9
+ isContractMarker: true;
10
+ getContractDetails: () => string;
11
+ };
12
+ declare class AccountVerifier extends BaseService {
13
+ isContractMarker?: true;
14
+ getContractDetails?: () => string;
15
+ /**
16
+ * Vérifie si le compte est un contrat.
17
+ */
18
+ isContractAccount(): Promise<this is ContractService>;
19
+ processAccount(): Promise<void>;
20
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // test-guard.ts
3
+ // Simulez vos dépendances externes si nécessaire pour ce test
4
+ // Par exemple :
5
+ // declare function getCode(rpcClient: any, params: { address: string }): Promise<string | null | undefined>;
6
+ // interface RpcClientType {}
7
+ // Définition de la classe de base
8
+ class BaseService {
9
+ constructor(id, rpcClient, address) {
10
+ this.id = id;
11
+ this.rpcClient = rpcClient;
12
+ this.address = address;
13
+ }
14
+ // Méthode pour simuler l'appel externe
15
+ async fetchCode() {
16
+ // Remplacez par votre vrai appel à getCode si possible pour le test,
17
+ // ou utilisez ce mock :
18
+ console.log(`Workspaceing code for ${this.address} using client ${this.rpcClient}`);
19
+ if (this.address === "contract_address") {
20
+ return "0x12345"; // code de contrat valide
21
+ }
22
+ return "0x"; // pas de code ou compte externe
23
+ }
24
+ }
25
+ // Classe contenant le type guard asynchrone
26
+ class AccountVerifier extends BaseService {
27
+ /**
28
+ * Vérifie si le compte est un contrat.
29
+ */
30
+ async isContractAccount() {
31
+ const code = await this.fetchCode(); // Utilise la méthode de la classe (ou de la base)
32
+ if (code && typeof code === 'string' && code !== '0x' && code.length > 2) {
33
+ // Logique pour rendre 'this' conforme à ContractService
34
+ this.isContractMarker = true;
35
+ this.getContractDetails = () => `Details for contract ${this.address}`;
36
+ return true;
37
+ }
38
+ else {
39
+ delete this.isContractMarker;
40
+ delete this.getContractDetails;
41
+ return false;
42
+ }
43
+ }
44
+ async processAccount() {
45
+ if (await this.isContractAccount()) {
46
+ // Ici, 'this' est affiné en ContractService
47
+ console.log(`Account ${this.id} (${this.address}) is a contract.`);
48
+ console.log(this.getContractDetails());
49
+ console.log(`Marker: ${this.isContractMarker}`);
50
+ }
51
+ else {
52
+ // Ici, 'this' est AccountVerifier (ou BaseService)
53
+ console.log(`Account ${this.id} (${this.address}) is not a contract.`);
54
+ }
55
+ }
56
+ }
57
+ // Test
58
+ // const mockRpcClient: RpcClientType = {}; // Votre client RPC
59
+ // const verifier1 = new AccountVerifier("service1", mockRpcClient, "contract_address");
60
+ // const verifier2 = new AccountVerifier("service2", mockRpcClient, "eoa_address");
61
+ // verifier1.processAccount();
62
+ // verifier2.processAccount();
@@ -0,0 +1,9 @@
1
+ export interface IToJSONParams<T extends {
2
+ [key: string]: any;
3
+ } = {
4
+ [key: string]: any;
5
+ }> {
6
+ obj: T;
7
+ depth?: number;
8
+ }
9
+ export declare function toJSON({ obj, depth }: IToJSONParams): Promise<any>;
@@ -0,0 +1,71 @@
1
+ function isClassInstance(obj) {
2
+ if (obj === null || typeof obj !== 'object' || !obj.constructor) {
3
+ return false;
4
+ }
5
+ // Les constructeurs de classes ES6 commencent par "class " quand on les convertit en string.
6
+ // Attention: cela peut être sensible à la minification ou à des transpilers très spécifiques,
7
+ // mais c'est généralement fiable pour les classes définies avec le mot-clé `class`.
8
+ return obj.constructor.toString().startsWith('class ');
9
+ }
10
+ ;
11
+ function convertBigintsToStrings(obj) {
12
+ if (typeof obj === 'bigint') {
13
+ return obj.toString();
14
+ }
15
+ if (typeof obj === 'object') {
16
+ for (const key in obj) {
17
+ obj[key] = convertBigintsToStrings(obj[key]);
18
+ }
19
+ }
20
+ if (Array.isArray(obj)) {
21
+ for (let i = 0; i < obj.length; i++) {
22
+ obj[i] = convertBigintsToStrings(obj[i]);
23
+ }
24
+ }
25
+ return obj;
26
+ }
27
+ export async function toJSON({ obj, depth = 0 }) {
28
+ // copy the properties of the object
29
+ const data = Object.assign({}, obj);
30
+ for (const key in obj) {
31
+ // skip class related properties
32
+ if (key === "toJSON" || key === "constructor" || key === "rpcClient") {
33
+ delete data[key];
34
+ continue;
35
+ }
36
+ ;
37
+ const prop = obj[key];
38
+ if (typeof prop === "function") {
39
+ // do not fetch getters if depth is 0
40
+ if (depth === 0) {
41
+ delete data[key];
42
+ continue;
43
+ }
44
+ // fetch getters
45
+ const value = await obj[key]();
46
+ // if the value is a class instance, convert it to JSON recursively
47
+ if (typeof value === "object" && isClassInstance(value)) {
48
+ data[key] = await toJSON({ obj: value, depth: depth - 1 });
49
+ continue;
50
+ }
51
+ data[key] = value;
52
+ continue;
53
+ }
54
+ if (typeof prop === "object" && isClassInstance(prop)) {
55
+ // If depth is 0, just flatten the object using recursion
56
+ if (depth === 0) {
57
+ data[key] = await toJSON({
58
+ obj: prop,
59
+ depth: depth - 1
60
+ });
61
+ continue;
62
+ }
63
+ ;
64
+ // If depth is not 0, fetch the object
65
+ data[key] = await prop.toJSON({ depth: depth - 1 });
66
+ }
67
+ }
68
+ ;
69
+ return convertBigintsToStrings(data);
70
+ }
71
+ ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentaproject/client",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "High level EVM library used into the Menta App to facilitate Blockchain interactions. ",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -29,14 +29,15 @@
29
29
  "author": "@mentaproject",
30
30
  "license": "ISC",
31
31
  "dependencies": {
32
- "@shazow/whatsabi": "^0.21.1",
33
- "@mentaproject/contracts": "0.0.2",
34
- "@mentaproject/core": "0.0.3"
32
+ "@mentaproject/contracts": "^0.0.8",
33
+ "@mentaproject/core": "^0.0.4",
34
+ "@shazow/whatsabi": "^0.21.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@rollup/plugin-commonjs": "^28.0.3",
38
38
  "@rollup/plugin-node-resolve": "^16.0.1",
39
39
  "@rollup/plugin-typescript": "^12.1.2",
40
+ "@types/node": "^22.15.29",
40
41
  "rollup": "^4.40.1",
41
42
  "tslib": "^2.8.1",
42
43
  "tsx": "^4.19.4",
package/test.ts CHANGED
@@ -1,11 +1,9 @@
1
- import { Address, erc20Abi, getAddress, http, toFunctionSelector, zeroAddress } from "viem";
1
+ import { http } from "viem";
2
2
  import { MentaClient } from "./src";
3
3
  import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"
4
4
  import { mainnet } from "@mentaproject/core/chains";
5
- import { getCode, getStorageAt } from "@mentaproject/core/actions";
6
- import { selectorsFromBytecode } from "@shazow/whatsabi";
7
- import { toFunctionSignature } from "viem"
8
- import { bytes32ToAddress } from "@mentaproject/core/utils";
5
+
6
+ import { writeFileSync } from "fs";
9
7
 
10
8
  const viemAccount = privateKeyToAccount(generatePrivateKey());
11
9
 
@@ -16,45 +14,8 @@ const client = new MentaClient({
16
14
  });
17
15
 
18
16
  (async () => {
19
- // const firstTx = await client.transactions.get({ hash: "0x1ac2418d94a069d3460b7dd52cfc251879da4948bb0df8362598b5595d28cbf1" });
20
- // const receipt = await firstTx.receipt();
21
-
22
- // const log = receipt.logs[0];
23
-
24
- // if (!log) return;
25
-
26
- // const decoded = decodeEventLog({
27
- // abi: erc20Abi,
28
- // data: receipt.logs[0].data,
29
- // topics: receipt.logs[0].topics,
30
- // });
31
-
32
- // console.log(decoded.args)
33
-
34
- const erc20Signatures = erc20Abi.filter(item => item.type === "function").map(item => ({ signature:toFunctionSignature(item), selector: toFunctionSelector(item)}));
35
-
36
- const code = await getCode(client.rpcClient, { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" });
37
- const infos = selectorsFromBytecode(code as string);
38
-
39
- console.log(infos);
40
-
41
- async function getImplementationAddress(proxyAddr: Address) {
42
- const storageValue = await getStorageAt(client.rpcClient, {
43
- address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
44
- slot: "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"
45
- });
46
-
47
- if (storageValue) {
48
-
49
- console.log(storageValue);
50
- // La valeur retournée est un hex de 32 octets. L'adresse est dans les 20 derniers octets.
51
- const address = getAddress(bytes32ToAddress(storageValue))
52
- if (address !== zeroAddress) {
53
- return address;
54
- }
55
- }
56
- return null; // Pas d'adresse trouvée ou contrat non EIP-1967
57
- };
17
+ const firstTx = await client.transactions.get({ hash: "0xeae261aeada2db19fee5cc4dfd47d575ff2380a5fb6a4039685d5044fdc8e2bb" });
18
+ const json = await firstTx.toJSON(2);
58
19
 
59
- getImplementationAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").then(console.log);
20
+ writeFileSync("./test.json", JSON.stringify(json, null, 2));
60
21
  })()