From 6af72639308573800c6175f16e0d126c7c3b3717 Mon Sep 17 00:00:00 2001 From: Sydhds Date: Thu, 28 Dec 2023 17:32:53 +0100 Subject: [PATCH] Add initial code for chain id support (#510) * Add initial code for chain id support * Add chain id for roll op too * Prettier pass * Fix some unit tests * format code * Factorize buffer to sign code * Update mock data with chain id * Update wallet example * Remove duplication * Refactor for code duplication * Add chainId to network * Remove chainId from ClientFactory ts-docs * Update web3-utils to 1.4.5 * Update packages/massa-web3/src/web3/accounts/Web3Account.ts Co-authored-by: Nathan Seva * Update packages/massa-web3/src/web3/accounts/Web3Account.ts Co-authored-by: Nathan Seva * Update packages/massa-web3/src/web3/accounts/Web3Account.ts Co-authored-by: Nathan Seva --------- Co-authored-by: sydhds Co-authored-by: Nathan Seva Co-authored-by: Senji888 <44082144+Ben-Rey@users.noreply.github.com> --- .gitignore | 5 +- package-lock.json | 2 +- packages/massa-web3/examples/.env.example | 2 + .../examples/smartContracts/index.ts | 24 +- packages/massa-web3/examples/utils.ts | 7 + packages/massa-web3/examples/wallet/index.ts | 26 +- .../massa-web3/src/interfaces/INodeStatus.ts | 1 + .../src/interfaces/IWalletClient.ts | 1 + packages/massa-web3/src/web3/ClientFactory.ts | 8 +- packages/massa-web3/src/web3/WalletClient.ts | 3 +- .../src/web3/accounts/Web3Account.ts | 323 +++++++----------- .../test/web3/clientFactory.spec.ts | 16 +- .../massa-web3/test/web3/evenPoller.spec.ts | 2 + .../massa-web3/test/web3/eventPoller.spec.ts | 2 + packages/massa-web3/test/web3/mockData.ts | 3 + .../test/web3/smartContractsClient.spec.ts | 4 +- .../massa-web3/test/web3/walletClient.spec.ts | 61 +++- packages/web3-utils/package.json | 2 +- packages/web3-utils/src/constants.ts | 17 + 19 files changed, 251 insertions(+), 258 deletions(-) create mode 100644 packages/massa-web3/examples/utils.ts diff --git a/.gitignore b/.gitignore index dcb9d61c..1a22401e 100644 --- a/.gitignore +++ b/.gitignore @@ -69,8 +69,9 @@ bundle.* # docs files docs -#.env files -.env +# IDE +.idea # Misc .DS_Store + diff --git a/package-lock.json b/package-lock.json index dae7d762..14f65d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11932,7 +11932,7 @@ }, "packages/web3-utils": { "name": "@massalabs/web3-utils", - "version": "1.4.4", + "version": "1.4.5", "license": "MIT", "dependencies": { "buffer": "^6.0.3", diff --git a/packages/massa-web3/examples/.env.example b/packages/massa-web3/examples/.env.example index 356cf3e1..37cc7e50 100644 --- a/packages/massa-web3/examples/.env.example +++ b/packages/massa-web3/examples/.env.example @@ -4,5 +4,7 @@ RECEIVER_PRIVATE_KEY="S1ykLaxXyMnJoaWLYds8UntqKTamZ4vcxrZ1fdToR8WpWEpk3FC" JSON_RPC_URL_PUBLIC=https://buildnet.massa.net/api/v2 JSON_RPC_URL_PRIVATE=https://buildnet.massa.net/api/v2 +CHAIN_ID=77658366 + #JSON_RPC_URL_PUBLIC=http://127.0.0.1:33035 #JSON_RPC_URL_PRIVATE=http://127.0.0.1:33034 diff --git a/packages/massa-web3/examples/smartContracts/index.ts b/packages/massa-web3/examples/smartContracts/index.ts index d970ef42..a6574b47 100644 --- a/packages/massa-web3/examples/smartContracts/index.ts +++ b/packages/massa-web3/examples/smartContracts/index.ts @@ -28,6 +28,7 @@ import { strToBytes, toMAS, } from '../../src'; +import { getEnvVariable } from '../utils'; const path = require('path'); const chalk = require('chalk'); @@ -84,23 +85,11 @@ dotenv.config({ path: path.resolve(__dirname, '..', '.env'), }); -const publicApi = process.env.JSON_RPC_URL_PUBLIC; -if (!publicApi) { - throw new Error('Missing JSON_RPC_URL_PUBLIC in .env file'); -} -const privateApi = process.env.JSON_RPC_URL_PRIVATE; -if (!privateApi) { - throw new Error('Missing JSON_RPC_URL_PRIVATE in .env file'); -} -const deployerPrivateKey = process.env.DEPLOYER_PRIVATE_KEY; -if (!deployerPrivateKey) { - throw new Error('Missing DEPLOYER_PRIVATE_KEY in .env file'); -} -const receiverPrivateKey = process.env.RECEIVER_PRIVATE_KEY; -if (!receiverPrivateKey) { - throw new Error('Missing RECEIVER_PRIVATE_KEY in .env file'); -} - +const publicApi = getEnvVariable('JSON_RPC_URL_PUBLIC'); +const privateApi = getEnvVariable('JSON_RPC_URL_PRIVATE'); +const chainId_ = getEnvVariable('CHAIN_ID'); +const chainId = BigInt(chainId_); +const deployerPrivateKey = getEnvVariable('DEPLOYER_PRIVATE_KEY'); const MASSA_EXEC_ERROR = 'massa_execution_error'; interface IEventPollerResult { @@ -184,6 +173,7 @@ const pollAsyncEvents = async ( { url: publicApi, type: ProviderType.PUBLIC } as IProvider, { url: privateApi, type: ProviderType.PRIVATE } as IProvider, ], + chainId, true, deployerAccount, ); diff --git a/packages/massa-web3/examples/utils.ts b/packages/massa-web3/examples/utils.ts new file mode 100644 index 00000000..8fb7d835 --- /dev/null +++ b/packages/massa-web3/examples/utils.ts @@ -0,0 +1,7 @@ +export function getEnvVariable(key: string): string { + const value = process.env[key]; + if (!value) { + throw new Error(`Missing ${key} in .env file`); + } + return value; +} diff --git a/packages/massa-web3/examples/wallet/index.ts b/packages/massa-web3/examples/wallet/index.ts index 3db5f49b..f0753ceb 100644 --- a/packages/massa-web3/examples/wallet/index.ts +++ b/packages/massa-web3/examples/wallet/index.ts @@ -12,6 +12,7 @@ import * as dotenv from 'dotenv'; import { Client } from '../../src/web3/Client'; import { IProvider, ProviderType } from '../../src/interfaces/IProvider'; import { fromMAS } from '../../src'; +import { getEnvVariable } from '../utils'; const path = require('path'); const chalk = require('chalk'); @@ -19,22 +20,12 @@ dotenv.config({ path: path.resolve(__dirname, '..', '.env'), }); -const publicApi = process.env.JSON_RPC_URL_PUBLIC; -if (!publicApi) { - throw new Error('Missing JSON_RPC_URL_PUBLIC in .env file'); -} -const privateApi = process.env.JSON_RPC_URL_PRIVATE; -if (!privateApi) { - throw new Error('Missing JSON_RPC_URL_PRIVATE in .env file'); -} -const deployerPrivateKey = process.env.DEPLOYER_PRIVATE_KEY; -if (!deployerPrivateKey) { - throw new Error('Missing DEPLOYER_PRIVATE_KEY in .env file'); -} -const receiverPrivateKey = process.env.RECEIVER_PRIVATE_KEY; -if (!receiverPrivateKey) { - throw new Error('Missing RECEIVER_PRIVATE_KEY in .env file'); -} +const publicApi = getEnvVariable('JSON_RPC_URL_PUBLIC'); +const privateApi = getEnvVariable('JSON_RPC_URL_PRIVATE'); +const chainId_ = getEnvVariable('CHAIN_ID'); +const chainId = BigInt(chainId_); +const deployerPrivateKey = getEnvVariable('DEPLOYER_PRIVATE_KEY'); +const receiverPrivateKey = getEnvVariable('RECEIVER_PRIVATE_KEY'); (async () => { const header = '='.repeat(process.stdout.columns - 1); @@ -57,6 +48,7 @@ if (!receiverPrivateKey) { { url: publicApi, type: ProviderType.PUBLIC } as IProvider, { url: privateApi, type: ProviderType.PRIVATE } as IProvider, ], + chainId, true, deployerAccount, ); @@ -113,7 +105,7 @@ if (!receiverPrivateKey) { // sign a random wallet message using account2 const signedMessage = await web3Client .wallet() - .signMessage(message, receiverAccount.address); + .signMessage(message, chainId, receiverAccount.address); console.log('Wallet sender signing a message... ', signedMessage); if (!deployerAccount?.publicKey || !signedMessage) { diff --git a/packages/massa-web3/src/interfaces/INodeStatus.ts b/packages/massa-web3/src/interfaces/INodeStatus.ts index 79dfdf6b..3a2b6c4c 100644 --- a/packages/massa-web3/src/interfaces/INodeStatus.ts +++ b/packages/massa-web3/src/interfaces/INodeStatus.ts @@ -70,4 +70,5 @@ export interface INodeStatus { node_ip: string | null; pool_stats: { endorsement_count: number; operation_count: number }; version: string; + chain_id: bigint; } diff --git a/packages/massa-web3/src/interfaces/IWalletClient.ts b/packages/massa-web3/src/interfaces/IWalletClient.ts index 34c8ff04..b11e304b 100644 --- a/packages/massa-web3/src/interfaces/IWalletClient.ts +++ b/packages/massa-web3/src/interfaces/IWalletClient.ts @@ -103,6 +103,7 @@ export interface IWalletClient extends BaseClient { */ signMessage( data: string | Buffer, + chainId: bigint, accountSignerAddress: string, ): Promise; diff --git a/packages/massa-web3/src/web3/ClientFactory.ts b/packages/massa-web3/src/web3/ClientFactory.ts index 552b0455..22a57576 100644 --- a/packages/massa-web3/src/web3/ClientFactory.ts +++ b/packages/massa-web3/src/web3/ClientFactory.ts @@ -32,6 +32,7 @@ export class ClientFactory { * Creates a default client using a default provider (MAINNET, TESTNET, LABNET, LOCALNET and BUILDNET). * * @param provider - Default provider to be used by the client. + * @param chainId - Chain id matching the network used by the provider * @param retryStrategyOn - Whether to retry failed requests. * @param baseAccount - Base account to use with the client (optional). * @@ -39,6 +40,7 @@ export class ClientFactory { */ public static async createDefaultClient( provider: DefaultProviderUrls, + chainId: bigint, retryStrategyOn = true, baseAccount?: IAccount, ): Promise { @@ -75,7 +77,7 @@ export class ClientFactory { let publicApi = new PublicApiClient(clientConfig); let account: Web3Account = null; if (baseAccount) { - account = new Web3Account(baseAccount, publicApi); + account = new Web3Account(baseAccount, publicApi, chainId); } const client: Client = new Client( { @@ -95,6 +97,7 @@ export class ClientFactory { * Suitable for local node interactions. * * @param providers - Array of providers to be used by the client. + * @param chainId - Chain id matching the network used by the provider * @param retryStrategyOn - Whether to retry failed requests. * @param baseAccount - Base account to be used by the client (optional). * @@ -102,6 +105,7 @@ export class ClientFactory { */ public static async createCustomClient( providers: Array, + chainId: bigint, retryStrategyOn = true, baseAccount?: IAccount, ): Promise { @@ -112,7 +116,7 @@ export class ClientFactory { let publicApi = new PublicApiClient(clientConfig); let account: Web3Account = null; if (baseAccount) { - account = new Web3Account(baseAccount, publicApi); + account = new Web3Account(baseAccount, publicApi, chainId); } const client: Client = new Client(clientConfig, account, publicApi); return client; diff --git a/packages/massa-web3/src/web3/WalletClient.ts b/packages/massa-web3/src/web3/WalletClient.ts index 5a3ac82f..b413040f 100755 --- a/packages/massa-web3/src/web3/WalletClient.ts +++ b/packages/massa-web3/src/web3/WalletClient.ts @@ -350,6 +350,7 @@ export class WalletClient extends BaseClient implements IWalletClient { */ public async signMessage( data: string | Buffer, + chainId: bigint, accountSignerAddress: string, ): Promise { let signerAccount = this.getWalletAccountByAddress(accountSignerAddress); @@ -363,7 +364,7 @@ export class WalletClient extends BaseClient implements IWalletClient { ); } } else { - account = new Web3Account(signerAccount, this.publicApiClient); + account = new Web3Account(signerAccount, this.publicApiClient, chainId); } if (typeof data === 'string') { data = Buffer.from(data); diff --git a/packages/massa-web3/src/web3/accounts/Web3Account.ts b/packages/massa-web3/src/web3/accounts/Web3Account.ts index 43bfb80a..7df63fdc 100644 --- a/packages/massa-web3/src/web3/accounts/Web3Account.ts +++ b/packages/massa-web3/src/web3/accounts/Web3Account.ts @@ -15,13 +15,104 @@ import { ICallData } from '../../interfaces/ICallData'; import { IContractData } from '../../interfaces/IContractData'; import { trySafeExecute } from '../../utils/retryExecuteFunction'; +function getOperationBufferToSign( + chainId: bigint, + bytesPublicKey: Uint8Array, + bytesCompact: Buffer, +): Buffer { + // Chain id is an 64-bit unsigned integer, convert to byte array (big endian) + const chainIdBuffer = new ArrayBuffer(8); + const view = new DataView(chainIdBuffer); + view.setBigUint64(0, chainId, false); + + return Buffer.concat([ + Buffer.from(chainIdBuffer), + bytesPublicKey, + bytesCompact, + ]); +} + export class Web3Account extends BaseClient implements IBaseAccount { private account: IAccount; private publicApiClient: IPublicApiClient; - constructor(account: IAccount, publicApiClient: IPublicApiClient) { + private chainId: bigint; + + constructor( + account: IAccount, + publicApiClient: IPublicApiClient, + chainId: bigint, + ) { super(publicApiClient.clientConfig); this.account = account; this.publicApiClient = publicApiClient; + this.chainId = chainId; + } + + /** + * Executes a blockchain operation + * + * @param txData - The transaction data for the operation. + * @param operationType - The type of operation to be executed. + * @param useRetry - Determines whether to use retry logic in case of failures. + * @param errorMessage - Custom error message to throw if operation fails. + * @param preExecutionCallback - An optional callback function to be executed before the operation, for any pre-execution logic or validation. + * @returns Returns a promise that resolves to the operation ID. + */ + private async executeOperation( + txData: IRollsData | ICallData | IContractData, + operationType: OperationTypeId, + useRetry: boolean = false, + errorMessage: string = 'Operation did not return a valid response', + preExecutionCallback?: ( + data: IRollsData | ICallData | IContractData, + ) => Promise, + ): Promise { + // Run pre-execution logic if provided + if (preExecutionCallback) { + await preExecutionCallback(txData); + } + + const nodeStatusInfo: INodeStatus = + await this.publicApiClient.getNodeStatus(); + + const expiryPeriod: number = + nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; + + const bytesCompact: Buffer = this.compactBytesForOperation( + txData, + operationType, + expiryPeriod, + ); + + const signature: ISignature = await this.sign( + getOperationBufferToSign( + this.chainId, + getBytesPublicKey(this.account.publicKey), + bytesCompact, + ), + ); + + const data = { + serialized_content: Array.prototype.slice.call(bytesCompact), + creator_public_key: this.account.publicKey, + signature: signature.base58Encoded, + }; + + let opIds: Array; + const jsonRpcRequestMethod = JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS; + + if (useRetry) { + opIds = await trySafeExecute>(this.sendJsonRPCRequest, [ + jsonRpcRequestMethod, + [[data]], + ]); + } else { + opIds = await this.sendJsonRPCRequest(jsonRpcRequestMethod, [[data]]); + } + + if (opIds.length <= 0) throw new Error(errorMessage); + + return opIds[0]; } public async verify(): Promise { @@ -117,225 +208,55 @@ export class Web3Account extends BaseClient implements IBaseAccount { } public async sellRolls(txData: IRollsData): Promise { - if (!this.account) { - throw new Error(`No tx sender available`); - } - - // get next period info - const nodeStatusInfo: INodeStatus = - await this.publicApiClient.getNodeStatus(); - const expiryPeriod: number = - nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; - - // bytes compaction - const bytesCompact: Buffer = this.compactBytesForOperation( - txData, - OperationTypeId.RollSell, - expiryPeriod, - ); - - // sign payload - const signature: ISignature = await this.sign( - Buffer.concat([getBytesPublicKey(this.account.publicKey), bytesCompact]), - ); - - const data = { - serialized_content: Array.prototype.slice.call(bytesCompact), - creator_public_key: this.account.publicKey, - signature: signature.base58Encoded, - }; - // returns operation ids - const opIds: Array = await this.sendJsonRPCRequest( - JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS, - [[data]], - ); - return opIds[0]; + return this.executeOperation(txData, OperationTypeId.RollSell); } - public async buyRolls(txData: IRollsData): Promise { - // get next period info - const nodeStatusInfo: INodeStatus = - await this.publicApiClient.getNodeStatus(); - const expiryPeriod: number = - nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; - - // bytes compaction - const bytesCompact: Buffer = this.compactBytesForOperation( - txData, - OperationTypeId.RollBuy, - expiryPeriod, - ); - - // sign payload - const signature: ISignature = await this.sign( - Buffer.concat([getBytesPublicKey(this.account.publicKey), bytesCompact]), - ); - - const data = { - serialized_content: Array.prototype.slice.call(bytesCompact), - creator_public_key: this.account.publicKey, - signature: signature.base58Encoded, - }; - // returns operation ids - const opIds: Array = await this.sendJsonRPCRequest( - JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS, - [[data]], - ); - return opIds[0]; + return this.executeOperation(txData, OperationTypeId.RollBuy); } public async sendTransaction(txData: IRollsData): Promise { - // get next period info - const nodeStatusInfo: INodeStatus = - await this.publicApiClient.getNodeStatus(); - const expiryPeriod: number = - nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; - - // bytes compaction - const bytesCompact: Buffer = this.compactBytesForOperation( - txData, - OperationTypeId.Transaction, - expiryPeriod, - ); - - // sign payload - const bytesPublicKey: Uint8Array = getBytesPublicKey( - this.account.publicKey, - ); - const signature: ISignature = await this.sign( - Buffer.concat([bytesPublicKey, bytesCompact]), - ); - - // prepare tx data - const data = { - serialized_content: Array.prototype.slice.call(bytesCompact), - creator_public_key: this.account.publicKey, - signature: signature.base58Encoded, - }; - // returns operation ids - const opIds: Array = await this.sendJsonRPCRequest( - JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS, - [[data]], - ); - return opIds[0]; + return this.executeOperation(txData, OperationTypeId.Transaction); } public async callSmartContract(callData: ICallData): Promise { - let serializedParam: number[]; // serialized parameter - if (callData.parameter instanceof Array) { - serializedParam = callData.parameter; - } else { - serializedParam = callData.parameter.serialize(); - } - const call: ICallData = { - fee: callData.fee, - maxGas: callData.maxGas, - coins: callData.coins, - targetAddress: callData.targetAddress, - functionName: callData.functionName, - parameter: serializedParam, - }; - // get next period info - const nodeStatusInfo: INodeStatus = - await this.publicApiClient.getNodeStatus(); - const expiryPeriod: number = - nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; - - // bytes compaction - const bytesCompact: Buffer = this.compactBytesForOperation( - call, + return this.executeOperation( + callData, OperationTypeId.CallSC, - expiryPeriod, - ); - - // sign payload - const bytesPublicKey: Uint8Array = getBytesPublicKey( - this.account.publicKey, - ); - const signature: ISignature = await this.sign( - Buffer.concat([bytesPublicKey, bytesCompact]), + this.clientConfig.retryStrategyOn, + 'Call smart contract operation bad response. No results array in json rpc response. Inspect smart contract', ); - // request data - const data = { - serialized_content: Array.prototype.slice.call(bytesCompact), - creator_public_key: this.account.publicKey, - signature: signature.base58Encoded, - }; - // returns operation ids - let opIds: Array = []; - const jsonRpcRequestMethod = JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS; - if (this.clientConfig.retryStrategyOn) { - opIds = await trySafeExecute>(this.sendJsonRPCRequest, [ - jsonRpcRequestMethod, - [[data]], - ]); - } else { - opIds = await this.sendJsonRPCRequest(jsonRpcRequestMethod, [[data]]); - } - if (opIds.length <= 0) { - throw new Error( - `Call smart contract operation bad response. No results array in json rpc response. Inspect smart contract`, - ); - } - return opIds[0]; } public async deploySmartContract( contractData: IContractData, ): Promise { - // get next period info - const nodeStatusInfo: INodeStatus = - await this.publicApiClient.getNodeStatus(); - const expiryPeriod: number = - nodeStatusInfo.next_slot.period + this.clientConfig.periodOffset; - - // Check if SC data exists - if (!contractData.contractDataBinary) { - throw new Error( - `Expected non-null contract bytecode, but received null.`, - ); - } + const preExecutionLogic = async (data: IContractData) => { + // Check if SC data exists + if (!data.contractDataBinary) { + throw new Error( + 'Expected non-null contract bytecode, but received null.', + ); + } - // get the block size - if ( - contractData.contractDataBinary.length > - nodeStatusInfo.config.max_block_size / 2 - ) { - console.warn( - 'bytecode size exceeded half of the maximum size of a block, operation will certainly be rejected', - ); - } + // Get the block size + const nodeStatusInfo: INodeStatus = + await this.publicApiClient.getNodeStatus(); + if ( + data.contractDataBinary.length > + nodeStatusInfo.config.max_block_size / 2 + ) { + console.warn( + 'Bytecode size exceeded half of the maximum size of a block, operation will certainly be rejected', + ); + } + }; - // bytes compaction - const bytesCompact: Buffer = this.compactBytesForOperation( + return this.executeOperation( contractData, OperationTypeId.ExecuteSC, - expiryPeriod, + false, + 'Deploy smart contract operation bad response. No results array in json rpc response. Inspect smart contract', + preExecutionLogic, ); - - // sign payload - const bytesPublicKey: Uint8Array = getBytesPublicKey( - this.account.publicKey, - ); - const signature: ISignature = await this.sign( - Buffer.concat([bytesPublicKey, bytesCompact]), - ); - - const data = { - serialized_content: Array.prototype.slice.call(bytesCompact), - creator_public_key: this.account.publicKey, - signature: signature.base58Encoded, - }; - // returns operation ids - const opIds: Array = await this.sendJsonRPCRequest( - JSON_RPC_REQUEST_METHOD.SEND_OPERATIONS, - [[data]], - ); - if (opIds.length <= 0) { - throw new Error( - `Deploy smart contract operation bad response. No results array in json rpc response. Inspect smart contract`, - ); - } - return opIds[0]; } } diff --git a/packages/massa-web3/test/web3/clientFactory.spec.ts b/packages/massa-web3/test/web3/clientFactory.spec.ts index f40140d9..c53a71e9 100644 --- a/packages/massa-web3/test/web3/clientFactory.spec.ts +++ b/packages/massa-web3/test/web3/clientFactory.spec.ts @@ -6,9 +6,11 @@ import { WalletClient } from '../../src/web3/WalletClient'; import { ProviderType } from '../../src/interfaces/IProvider'; import { Client } from '../../src/web3/Client'; import { IAccount } from '../../src/interfaces/IAccount'; +import { BUILDNET_CHAIN_ID } from './mockData'; const publicApi = 'https://mock-public-api.com'; const privateApi = 'https://mock-private-api.com'; +const chainId = BUILDNET_CHAIN_ID; describe('ClientFactory', () => { describe('createDefaultClient', () => { @@ -19,6 +21,7 @@ describe('ClientFactory', () => { const client = await ClientFactory.createDefaultClient( provider, + chainId, true, baseAccount, ); @@ -36,7 +39,11 @@ describe('ClientFactory', () => { test('should create a default client without a base account if not provided', async () => { const provider = DefaultProviderUrls.MAINNET; - const client = await ClientFactory.createDefaultClient(provider, true); + const client = await ClientFactory.createDefaultClient( + provider, + chainId, + true, + ); expect(client).toBeInstanceOf(Client); expect(client.getProviders()).toHaveLength(2); @@ -55,6 +62,7 @@ describe('ClientFactory', () => { const client = await ClientFactory.createCustomClient( providers, + chainId, true, baseAccount, ); @@ -70,7 +78,11 @@ describe('ClientFactory', () => { { url: privateApi, type: ProviderType.PRIVATE }, ]; - const client = await ClientFactory.createCustomClient(providers, true); + const client = await ClientFactory.createCustomClient( + providers, + chainId, + true, + ); expect(client).toBeInstanceOf(Client); expect(client.getProviders()).toHaveLength(2); diff --git a/packages/massa-web3/test/web3/evenPoller.spec.ts b/packages/massa-web3/test/web3/evenPoller.spec.ts index 2ccda9aa..b877d32d 100644 --- a/packages/massa-web3/test/web3/evenPoller.spec.ts +++ b/packages/massa-web3/test/web3/evenPoller.spec.ts @@ -14,6 +14,7 @@ import { import { IAccount } from '../../src/interfaces/IAccount'; import { Timeout } from '../../src/utils/time'; import { IEvent, ISlot } from '@massalabs/web3-utils'; +import { BUILDNET_CHAIN_ID } from './mockData'; // mock axios to intercept any axios POST request and resolve it immediately with an empty object, so // no request is pending before Jest finishes the test @@ -86,6 +87,7 @@ describe('EventPoller', () => { const provider = DefaultProviderUrls.TESTNET; web3Client = await ClientFactory.createDefaultClient( provider, + BUILDNET_CHAIN_ID, true, baseAccount, ); diff --git a/packages/massa-web3/test/web3/eventPoller.spec.ts b/packages/massa-web3/test/web3/eventPoller.spec.ts index 78598612..dde0be99 100644 --- a/packages/massa-web3/test/web3/eventPoller.spec.ts +++ b/packages/massa-web3/test/web3/eventPoller.spec.ts @@ -14,6 +14,7 @@ import { import { IAccount } from '../../src/interfaces/IAccount'; import { Timeout } from '../../src/utils/time'; import { IEvent, ISlot } from '@massalabs/web3-utils'; +import { BUILDNET_CHAIN_ID } from './mockData'; // Mock the Timeout class jest.mock('../../src/utils/time', () => { @@ -113,6 +114,7 @@ describe('EventPoller', () => { const provider = DefaultProviderUrls.TESTNET; web3Client = await ClientFactory.createDefaultClient( provider, + BUILDNET_CHAIN_ID, true, baseAccount, ); diff --git a/packages/massa-web3/test/web3/mockData.ts b/packages/massa-web3/test/web3/mockData.ts index fdb1a618..230e9f21 100644 --- a/packages/massa-web3/test/web3/mockData.ts +++ b/packages/massa-web3/test/web3/mockData.ts @@ -25,6 +25,8 @@ import { } from '@massalabs/web3-utils'; import { IBalance } from '../../src/interfaces/IBalance'; +export const BUILDNET_CHAIN_ID = BigInt(77658366); + // util function to create an event, only for that test file to avoid code duplication function createEvent( id: string, @@ -88,6 +90,7 @@ export const mockNodeStatusInfo: INodeStatus = { pos_lock_cycles: 64, pos_lookback_cycles: 64, }, + chain_id: BUILDNET_CHAIN_ID, }; export const mockGraphInterval: IGetGraphInterval = { diff --git a/packages/massa-web3/test/web3/smartContractsClient.spec.ts b/packages/massa-web3/test/web3/smartContractsClient.spec.ts index 203b4ab9..5e119db2 100644 --- a/packages/massa-web3/test/web3/smartContractsClient.spec.ts +++ b/packages/massa-web3/test/web3/smartContractsClient.spec.ts @@ -25,6 +25,7 @@ import { mockContractReadOperationDataWithError, mockAddresses, mockBalance, + BUILDNET_CHAIN_ID, } from './mockData'; import { IExecuteReadOnlyResponse } from '../../src/interfaces/IExecuteReadOnlyResponse'; import { Web3Account } from '../../src/web3/accounts/Web3Account'; @@ -49,6 +50,7 @@ describe('SmartContractsClient', () => { mockDeployerAccount = new Web3Account( importedMockDeployerAccount, mockPublicApiClient, + BUILDNET_CHAIN_ID, ); mockWalletClient = new WalletClient( mockClientConfig, @@ -165,7 +167,7 @@ describe('SmartContractsClient', () => { await smartContractsClient.deploySmartContract(mockContractData); expect(consoleWarnSpy).toHaveBeenCalledWith( - 'bytecode size exceeded half of the maximum size of a block, operation will certainly be rejected', + 'Bytecode size exceeded half of the maximum size of a block, operation will certainly be rejected', ); }); diff --git a/packages/massa-web3/test/web3/walletClient.spec.ts b/packages/massa-web3/test/web3/walletClient.spec.ts index c7b554e6..1784b560 100644 --- a/packages/massa-web3/test/web3/walletClient.spec.ts +++ b/packages/massa-web3/test/web3/walletClient.spec.ts @@ -8,7 +8,10 @@ import { expect, test, describe, beforeEach, afterEach } from '@jest/globals'; import * as ed from '@noble/ed25519'; import { ISignature } from '../../src/interfaces/ISignature'; import { IFullAddressInfo } from '../../src/interfaces/IFullAddressInfo'; -import { mockResultSendJsonRPCRequestWalletInfo } from './mockData'; +import { + BUILDNET_CHAIN_ID, + mockResultSendJsonRPCRequestWalletInfo, +} from './mockData'; import { ITransactionData } from '../../src/interfaces/ITransactionData'; import { OperationTypeId } from '../../src/interfaces/OperationTypes'; import { JSON_RPC_REQUEST_METHOD } from '../../src/interfaces/JsonRpcMethods'; @@ -27,6 +30,7 @@ const receiverPrivateKey = // for CI testing: const publicApi = 'https://mock-public-api.com'; const privateApi = 'https://mock-private-api.com'; +const chainId = BUILDNET_CHAIN_ID; const MAX_WALLET_ACCOUNTS = 256; @@ -39,6 +43,7 @@ export async function initializeClient() { { url: publicApi, type: ProviderType.PUBLIC } as IProvider, { url: privateApi, type: ProviderType.PRIVATE } as IProvider, ], + chainId, true, deployerAccount, // setting deployer account as base account ); @@ -98,7 +103,9 @@ describe('WalletClient', () => { ); await web3Client .wallet() - .setBaseAccount(new Web3Account(account, web3Client.publicApi())); + .setBaseAccount( + new Web3Account(account, web3Client.publicApi(), chainId), + ); const baseAccount = web3Client.wallet().getBaseAccount(); expect(baseAccount).not.toBeNull(); expect(baseAccount?.address()).toEqual(account.address); @@ -118,7 +125,7 @@ describe('WalletClient', () => { web3Client .wallet() .setBaseAccount( - new Web3Account(incorrectAccount, web3Client.publicApi()), + new Web3Account(incorrectAccount, web3Client.publicApi(), chainId), ), ).rejects.toThrow(); }); @@ -129,14 +136,18 @@ describe('WalletClient', () => { ); await web3Client .wallet() - .setBaseAccount(new Web3Account(firstAccount, web3Client.publicApi())); + .setBaseAccount( + new Web3Account(firstAccount, web3Client.publicApi(), chainId), + ); const secondAccount = await WalletClient.getAccountFromSecretKey( deployerPrivateKey, ); await web3Client .wallet() - .setBaseAccount(new Web3Account(secondAccount, web3Client.publicApi())); + .setBaseAccount( + new Web3Account(secondAccount, web3Client.publicApi(), chainId), + ); const baseAccount = web3Client.wallet().getBaseAccount(); expect(baseAccount).not.toBeNull(); @@ -459,7 +470,11 @@ describe('WalletClient', () => { describe('walletSignMessage', () => { test('should sign a message with a valid signer', async () => { const data = 'Test message'; - const signer = new Web3Account(baseAccount, web3Client.publicApi()); + const signer = new Web3Account( + baseAccount, + web3Client.publicApi(), + chainId, + ); const modelSignedMessage = '1TXucC8nai7BYpAnMPYrotVcKCZ5oxkfWHb2ykKj2tXmaGMDL1XTU5AbC6Z13RH3q59F8QtbzKq4gzBphGPWpiDonownxE'; @@ -479,6 +494,7 @@ describe('WalletClient', () => { const signer = new Web3Account( baseAccountWithoutSecretKey, web3Client.publicApi(), + chainId, ); await expect( @@ -495,6 +511,7 @@ describe('WalletClient', () => { const signer = new Web3Account( baseAccountWithoutPublicKey, web3Client.publicApi(), + chainId, ); await expect( @@ -504,7 +521,11 @@ describe('WalletClient', () => { test('should throw an error when signature length is invalid', async () => { const data = 'Test message'; - const signer = new Web3Account(baseAccount, web3Client.publicApi()); + const signer = new Web3Account( + baseAccount, + web3Client.publicApi(), + chainId, + ); // Create a spy on the 'sign' function to provide an incorrect mock implementation for this test const signSpy = jest.spyOn(ed, 'sign'); @@ -522,7 +543,11 @@ describe('WalletClient', () => { const data = Buffer.from('Test message'); const modelSignedMessage = '1TXucC8nai7BYpAnMPYrotVcKCZ5oxkfWHb2ykKj2tXmaGMDL1XTU5AbC6Z13RH3q59F8QtbzKq4gzBphGPWpiDonownxE'; - const signer = new Web3Account(baseAccount, web3Client.publicApi()); + const signer = new Web3Account( + baseAccount, + web3Client.publicApi(), + chainId, + ); const signedMessage = await WalletClient.walletSignMessage(data, signer); @@ -533,7 +558,11 @@ describe('WalletClient', () => { test('should throw an error if the signature could not be verified with the public key', async () => { const data = 'Test message'; - const signer = new Web3Account(baseAccount, web3Client.publicApi()); + const signer = new Web3Account( + baseAccount, + web3Client.publicApi(), + chainId, + ); // Create a spy on the 'verify' function to provide an incorrect mock implementation for this test const verifySpy = jest.spyOn(ed, 'verify'); @@ -560,7 +589,7 @@ describe('WalletClient', () => { const signedMessage = await web3Client .wallet() - .signMessage(data, accountSignerAddress); + .signMessage(data, chainId, accountSignerAddress); expect(signedMessage).toHaveProperty('base58Encoded'); expect(typeof signedMessage.base58Encoded).toBe('string'); @@ -572,7 +601,9 @@ describe('WalletClient', () => { const nonExistentSignerAddress = 'nonExistentSignerAddress'; await expect( - web3Client.wallet().signMessage(data, nonExistentSignerAddress), + web3Client + .wallet() + .signMessage(data, chainId, nonExistentSignerAddress), ).rejects.toThrow( `No signer account ${nonExistentSignerAddress} found in wallet`, ); @@ -587,7 +618,7 @@ describe('WalletClient', () => { const signedMessage = await web3Client .wallet() - .signMessage(data, accountSignerAddress); + .signMessage(data, chainId, accountSignerAddress); expect(signedMessage).toHaveProperty('base58Encoded'); expect(typeof signedMessage.base58Encoded).toBe('string'); @@ -935,7 +966,11 @@ describe('WalletClient', () => { fee: 1n, amount: 100n, }; - mockAccount = new Web3Account(baseAccount, web3Client.publicApi()); + mockAccount = new Web3Account( + baseAccount, + web3Client.publicApi(), + chainId, + ); }); describe('sendTransaction', () => { diff --git a/packages/web3-utils/package.json b/packages/web3-utils/package.json index 18857a39..143febe2 100644 --- a/packages/web3-utils/package.json +++ b/packages/web3-utils/package.json @@ -1,6 +1,6 @@ { "name": "@massalabs/web3-utils", - "version": "1.4.4", + "version": "1.4.5", "description": "Set of utilities shared between multiple @massalabs packages", "main": "dist/cmd/index.js", "module": "dist/esm/index.js", diff --git a/packages/web3-utils/src/constants.ts b/packages/web3-utils/src/constants.ts index 9a9795f0..7a90594e 100644 --- a/packages/web3-utils/src/constants.ts +++ b/packages/web3-utils/src/constants.ts @@ -23,3 +23,20 @@ export const BASE_ACCOUNT_CREATION_COST = fromMAS(0.001); export const MAX_GAS_EXECUTE_SC = 3_980_167_295n; export const MAX_GAS_DEPLOYMENT = MAX_GAS_EXECUTE_SC; export const MAX_GAS_CALL = 4_294_167_295n; + +/* -------------------------------------------------------------------------- */ +/* NETWORK */ +/* -------------------------------------------------------------------------- */ +export const MAINNET = 'MainNet'; +export const BUILDNET = 'BuildNet'; +export const SECURENET = 'SecureNet'; +export const LABNET = 'LabNet'; +export const SANDBOX = 'Sandbox'; + +export const CHAIN_ID_TO_NETWORK_NAME = { + 77658377: MAINNET, + 77658366: BUILDNET, + 77658383: SECURENET, + 77658376: LABNET, + 77: SANDBOX, +} as const; // type is inferred as the specific, unchangeable structure