diff --git a/package-lock.json b/package-lock.json index cfb2eab6..c2b7f541 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12188,7 +12188,7 @@ "license": "MIT", "dependencies": { "@massalabs/wallet-provider": "^2.0.0", - "@massalabs/web3-utils": "^1.4.9", + "@massalabs/web3-utils": "file:../web3-utils", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.2.0", "@types/ws": "^8.5.4", diff --git a/packages/massa-web3/examples/smartContracts/deployer.ts b/packages/massa-web3/examples/smartContracts/deployer.ts index 46d1989a..c95e9fb3 100644 --- a/packages/massa-web3/examples/smartContracts/deployer.ts +++ b/packages/massa-web3/examples/smartContracts/deployer.ts @@ -79,7 +79,7 @@ export const deploySmartContracts = async ( contractsToDeploy: ISCData[], web3Client: Client, deployerAccount: IBaseAccount, - fee = 0n, + fee = fromMAS(0.01), maxGas = MAX_GAS_DEPLOYMENT, maxCoins = fromMAS(0.1) ): Promise => { diff --git a/packages/massa-web3/examples/smartContracts/index.ts b/packages/massa-web3/examples/smartContracts/index.ts index 0fa69762..0a0765e8 100644 --- a/packages/massa-web3/examples/smartContracts/index.ts +++ b/packages/massa-web3/examples/smartContracts/index.ts @@ -214,7 +214,7 @@ const pollAsyncEvents = async ( ], web3Client, baseAccount, - 0n, + fromMAS(0.01), 3000_000_000n, fromMAS(1.5) ) @@ -270,7 +270,7 @@ const pollAsyncEvents = async ( targetFunction: 'getMusicAlbum', parameter: args.serialize(), coins: 0n, - fee: 0n, + fee: fromMAS(0.01), }) const res = new Args(result.returnValue, 0) @@ -288,7 +288,7 @@ const pollAsyncEvents = async ( const deleteMusicAlbumCallOperationId = await web3Client .smartContracts() .callSmartContract({ - fee: 0n, + fee: fromMAS(0.01), coins: 0n, targetAddress: scAddress, targetFunction: 'deleteMusicAlbum', @@ -317,7 +317,7 @@ const pollAsyncEvents = async ( const createMusicAlbumCallOperationId = await web3Client .smartContracts() .callSmartContract({ - fee: 0n, + fee: fromMAS(0.01), coins: 0n, targetAddress: scAddress, targetFunction: 'addMusicAlbum', diff --git a/packages/massa-web3/examples/smartContracts/nameResolver.ts b/packages/massa-web3/examples/smartContracts/nameResolver.ts new file mode 100644 index 00000000..3bda352a --- /dev/null +++ b/packages/massa-web3/examples/smartContracts/nameResolver.ts @@ -0,0 +1,161 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-console */ +import { IAccount } from '../../src/interfaces/IAccount' +import { ClientFactory } from '../../src/web3/ClientFactory' +import { WalletClient } from '../../src/web3/WalletClient' +import { Client } from '../../src/web3/Client' +import { ICallData } from '../../src/interfaces/ICallData' +import * as dotenv from 'dotenv' +import { IProvider, ProviderType } from '../../src/interfaces/IProvider' +import { + Args, + CHAIN_ID, + EOperationStatus, + ITransactionData, + fromMAS, +} from '../../src' +import { getEnvVariable } from '../utils' + +const path = require('path') +const chalk = require('chalk') +const ora = require('ora') + +dotenv.config({ + path: path.resolve(__dirname, '..', '.env'), +}) + +const publicApi = getEnvVariable('JSON_RPC_URL_PUBLIC') +const privateApi = getEnvVariable('JSON_RPC_URL_PRIVATE') +const chainId = CHAIN_ID.BuildNet +const privateKey = getEnvVariable('DEPLOYER_PRIVATE_KEY') + +;(async () => { + const header = '='.repeat(process.stdout.columns - 1) + console.log(header) + console.log(`${chalk.green.bold('Massa Name Service Resolver Example')}`) + console.log(header) + + let spinner + try { + // init client + const deployerAccount: IAccount = + await WalletClient.getAccountFromSecretKey(privateKey) + const web3Client: Client = await ClientFactory.createCustomClient( + [ + { url: publicApi, type: ProviderType.PUBLIC } as IProvider, + { url: privateApi, type: ProviderType.PRIVATE } as IProvider, + ], + chainId, + true, + deployerAccount + ) + + // Try to register a domain + const domain = 'example' + const address = deployerAccount.address as string + + spinner = ora('Trying to create the domain').start() + try { + const domainCreationOpId = await web3Client + .smartContracts() + .callSmartContract({ + fee: fromMAS(0.01), + coins: fromMAS(2), + targetAddress: web3Client.mnsResolver().getMnsResolverAddress(), + targetFunction: 'dnsAlloc', + parameter: new Args() + .addString(domain) + .addString(address) + .serialize(), + } as ICallData) + await web3Client + .smartContracts() + .awaitMultipleRequiredOperationStatus(domainCreationOpId, [ + EOperationStatus.SPECULATIVE_SUCCESS, + EOperationStatus.SPECULATIVE_ERROR, + ]) + spinner.succeed('Domain created successfully') + } catch (ex) { + spinner.succeed('Domain already exists') + } + + // Try to resolve the domain + spinner = ora('Trying to resolve the domain').start() + const resolvedAddress = await web3Client + .mnsResolver() + .resolve(domain + '.massa') + spinner.succeed(`Domain resolved to address ${resolvedAddress}`) + + // Try to send a transaction to the resolved address + spinner = ora( + 'Trying to send a transaction to the resolved address' + ).start() + const txData = { + amount: fromMAS(1), + recipientAddress: resolvedAddress, + fee: fromMAS(0.01), + } as ITransactionData + const txOpId = await web3Client.wallet().sendTransaction(txData) + await web3Client + .smartContracts() + .awaitMultipleRequiredOperationStatus(txOpId[0], [ + EOperationStatus.SPECULATIVE_SUCCESS, + ]) + spinner.succeed('Transaction sent successfully') + // Try to call a function on the resolved address + spinner = ora('Trying to call a function on the resolved address').start() + const callData = { + fee: fromMAS(0.01), + coins: fromMAS(1), + targetAddress: resolvedAddress, + targetFunction: 'getBalance', + parameter: new Args().serialize(), + } + + try { + await web3Client.smartContracts().callSmartContract(callData) + } catch (ex) { + if ( + ex.message.endsWith( + `The called address ${resolvedAddress} is not a smart contract address` + ) + ) { + spinner.succeed('Call smart contract resolved address successfully') + } else { + spinner.fail('Call smart contract resolved address failed') + throw ex + } + } + + // Try to call a read only function on the resolved address + spinner = ora( + 'Trying to call a read only function on the resolved address' + ).start() + const callReadOnlyData = { + fee: fromMAS(0.01), + targetAddress: resolvedAddress, + targetFunction: 'getBalance', + parameter: new Args().serialize(), + } + + try { + await web3Client.smartContracts().readSmartContract(callReadOnlyData) + } catch (ex) { + if ( + ex.message.endsWith( + `The called address ${resolvedAddress} is not a smart contract address` + ) + ) { + spinner.succeed('Read smart contract resolved address successfully') + } else { + spinner.fail('Read smart contract resolved address failed') + throw ex + } + } + } catch (ex) { + console.error(ex) + const msg = chalk.red(`Error = ${ex}`) + if (spinner) spinner.fail(msg) + process.exit(-1) + } +})() diff --git a/packages/massa-web3/examples/wallet/index.ts b/packages/massa-web3/examples/wallet/index.ts index 3830cbac..a6ef7d07 100644 --- a/packages/massa-web3/examples/wallet/index.ts +++ b/packages/massa-web3/examples/wallet/index.ts @@ -117,7 +117,7 @@ const receiverPrivateKey = getEnvVariable('RECEIVER_PRIVATE_KEY') // send from base account to receiver const txId = await web3Client.wallet().sendTransaction({ amount: fromMAS(1), - fee: 0n, + fee: fromMAS(0.01), recipientAddress: receiverAccount.address, } as ITransactionData) console.log('Money Transfer:: TxId ', txId[0]) @@ -164,7 +164,7 @@ const receiverPrivateKey = getEnvVariable('RECEIVER_PRIVATE_KEY') // sender buys some rolls const buyRollsTxId = await web3Client.wallet().buyRolls({ amount: 2n, - fee: fromMAS(0), + fee: fromMAS(0.01), } as IRollsData) console.log('Buy Rolls Tx Id ', buyRollsTxId) diff --git a/packages/massa-web3/package.json b/packages/massa-web3/package.json index 0908d595..51d04a8d 100644 --- a/packages/massa-web3/package.json +++ b/packages/massa-web3/package.json @@ -14,12 +14,14 @@ "build-commonjs": "tsc --project tsconfig.commonjs.json", "build": "npm-run-all clean-dist build-*", "test": "jest --detectOpenHandles --forceExit", - "test:all": "npm run test && npm run test-smart-contract-example && npm run test-wallet", + "test:all": "npm run test && npm run test-smart-contract-example && npm run test-mns && npm run test-wallet", "test:watch": "jest --watch", "test:watch:all": "jest --watchAll", "test:cov": "jest --coverage --silent --detectOpenHandles --forceExit", "test-smart-contract-example": "ts-node ./examples/smartContracts/index.ts", + "test-mns": "ts-node ./examples/smartContracts/nameResolver.ts", "test-wallet": "ts-node ./examples/wallet/index.ts", + "test-name-service-resolver": "ts-node ./examples/smartContracts/nameResolver.ts", "check-types": "tsc --noEmit", "doc": "typedoc src/index.ts --name massa-web3 --out docs/documentation/html --tsconfig tsconfig.json" }, diff --git a/packages/massa-web3/src/interfaces/ICallData.ts b/packages/massa-web3/src/interfaces/ICallData.ts index 239cc2db..12eac441 100644 --- a/packages/massa-web3/src/interfaces/ICallData.ts +++ b/packages/massa-web3/src/interfaces/ICallData.ts @@ -11,7 +11,7 @@ import { Args } from '@massalabs/web3-utils' * @see fee of type `bigint` represents the transaction fee. * @see maxGas of type `bigint` represents the maximum amount of gas that the execution of the contract is allowed to cost. * @see coins of type `bigint` represents the extra coins in `nanoMassa` that are spent from the caller's balance and transferred to the target. - * @see targetAddress of type `string` represents the target smart contract address. + * @see targetAddress of type `string` represents the target smart contract address or the MNS domain associated. * @see targetFunction of type `string` represents the target function name. No function is called if empty. * @see parameter of type `Array` or an Args represents the parameters to pass to the target function. */ diff --git a/packages/massa-web3/src/interfaces/IClient.ts b/packages/massa-web3/src/interfaces/IClient.ts index 70004def..68e6bb3c 100644 --- a/packages/massa-web3/src/interfaces/IClient.ts +++ b/packages/massa-web3/src/interfaces/IClient.ts @@ -4,6 +4,7 @@ import { IProvider } from './IProvider' import { IPublicApiClient } from './IPublicApiClient' import { ISmartContractsClient } from './ISmartContractsClient' import { IWalletClient } from './IWalletClient' +import { MnsResolver } from '../web3/MnsResolver' /** * Represents the client object. @@ -17,14 +18,18 @@ import { IWalletClient } from './IWalletClient' * @see publicApi() - A function that returns an instance of the public API client. * @see wallet() - A function that returns an instance of the wallet client. * @see smartContracts() - A function that returns an instance of the smart contracts client. + * @see mnsResolver() - A function that returns an instance of the MNS resolver. * @see setCustomProviders - A method for setting custom providers. * @see setNewDefaultProvider - A method for setting a new default provider. + * @see setNewMnsResolver - A method for setting a new MNS resolver. */ export interface IClient { privateApi(): IPrivateApiClient publicApi(): IPublicApiClient wallet(): IWalletClient smartContracts(): ISmartContractsClient + mnsResolver(): MnsResolver setCustomProviders(providers: Array): void setNewDefaultProvider(provider: DefaultProviderUrls): void + setNewMnsResolver(contractAddress: string): void } diff --git a/packages/massa-web3/src/interfaces/IContractData.ts b/packages/massa-web3/src/interfaces/IContractData.ts index 1a0044bd..420fc452 100644 --- a/packages/massa-web3/src/interfaces/IContractData.ts +++ b/packages/massa-web3/src/interfaces/IContractData.ts @@ -12,7 +12,7 @@ * @see maxCoins of type `bigint` represents maximum amount of coins allowed to be spent by the execution * @see contractDataText of type `string | undefined` represents the contract's data as string (optional). * @see contractDataBinary of type `Uint8Array | undefined` represents the contract's data as bytecode (optional). - * @see address of type `string | undefined` represents the smart contract address (optional). + * @see address of type `string | undefined` represents the smart contract address (optional) or the MNS domain associated. * @see datastore of type `Map | undefined` represents the smart contract's operational storage data (optional). */ export interface IContractData { diff --git a/packages/massa-web3/src/interfaces/IEventFilter.ts b/packages/massa-web3/src/interfaces/IEventFilter.ts index b8f454b7..22abacf1 100644 --- a/packages/massa-web3/src/interfaces/IEventFilter.ts +++ b/packages/massa-web3/src/interfaces/IEventFilter.ts @@ -5,8 +5,8 @@ import { ISlot } from '@massalabs/web3-utils' * * @see start of type `ISlot` represents the start of the time interval (can be null). * @see end of type `ISlot` represents the end of the time interval (can be null). - * @see emitter_address of type `string` represents the address that emitted the event (can be null). - * @see original_caller_address of type `string` represents the operation id that generated the event (can be null). + * @see emitter_address of type `string` represents the address that emitted the event (can be null) or the MNS domain associated. + * @see original_caller_address of type `string` represents the operation id that generated the event (can be null) or the MNS domain associated. * @see is_final of type `boolean` to filter final events (true), candidate events (false) or both (null). */ export interface IEventFilter { diff --git a/packages/massa-web3/src/interfaces/IMnsResolver.ts b/packages/massa-web3/src/interfaces/IMnsResolver.ts new file mode 100644 index 00000000..5117e16a --- /dev/null +++ b/packages/massa-web3/src/interfaces/IMnsResolver.ts @@ -0,0 +1,16 @@ +/** + * Represents the MNS resolver object. + * + * @remarks + * This interface is used to resolve domain name to addresses. It also provides methods for setting + * custom mns resolver instead of the default one provided for MAINNET and BUILDNET. + * + * @see resolve - A function that returns the address of the domain if it exists or throws an error if it does not. + * @see setMnsResolver - A method for setting a custom mns resolver. + */ + +export interface IMnsResolver { + resolve(domain: string): Promise + getMnsResolverAddress(): string | undefined + setMnsResolver(contractAddress: string): void +} diff --git a/packages/massa-web3/src/interfaces/IReadData.ts b/packages/massa-web3/src/interfaces/IReadData.ts index 3d80dd01..c22496aa 100644 --- a/packages/massa-web3/src/interfaces/IReadData.ts +++ b/packages/massa-web3/src/interfaces/IReadData.ts @@ -4,7 +4,7 @@ import { Args } from '@massalabs/web3-utils' * Represents the data of a read operation. * * @see maxGas - The maximum amount of gas that the execution of the contract is allowed to cost. - * @see targetAddress - Target smart contract address + * @see targetAddress - Target smart contract address or the mns associated * @see targetFunction - Target function name. No function is called if empty. * @see parameter - Parameter to pass to the target function * @see callerAddress - Caller address diff --git a/packages/massa-web3/src/web3/Client.ts b/packages/massa-web3/src/web3/Client.ts index 775f6cdf..1b50e539 100755 --- a/packages/massa-web3/src/web3/Client.ts +++ b/packages/massa-web3/src/web3/Client.ts @@ -7,11 +7,13 @@ import { IProvider, ProviderType } from '../interfaces/IProvider' import { IClient } from '../interfaces/IClient' import { IBaseAccount } from '../interfaces/IBaseAccount' import { DefaultProviderUrls } from '@massalabs/web3-utils' +import { MnsResolver } from './MnsResolver' /** * Massa Web3 Client object wraps all public, private, wallet and smart-contracts-related functionalities. */ export class Client implements IClient { + private mnsResolverVar: MnsResolver private publicApiClient: PublicApiClient private privateApiClient: PrivateApiClient private walletClient: WalletClient @@ -22,23 +24,29 @@ export class Client implements IClient { * * @param clientConfig - client configuration object. * @param baseAccount - base account to use for signing transactions (optional). + * @param publicApiClient - public api client to use (optional). + * @param mnsResolverAddress - MNS resolver address to use (optional). */ public constructor( private clientConfig: IClientConfig, baseAccount?: IBaseAccount, - publicApiClient?: PublicApiClient + publicApiClient?: PublicApiClient, + mnsResolverAddress?: string ) { + this.mnsResolverVar = new MnsResolver(clientConfig, mnsResolverAddress) this.publicApiClient = publicApiClient || new PublicApiClient(clientConfig) this.privateApiClient = new PrivateApiClient(clientConfig) this.walletClient = new WalletClient( clientConfig, this.publicApiClient, + this.mnsResolverVar, baseAccount ) this.smartContractsClient = new SmartContractsClient( clientConfig, this.publicApiClient, - this.walletClient + this.walletClient, + this.mnsResolverVar ) // subclients @@ -46,6 +54,7 @@ export class Client implements IClient { this.publicApi = this.publicApi.bind(this) this.wallet = this.wallet.bind(this) this.smartContracts = this.smartContracts.bind(this) + this.mnsResolver = this.mnsResolver.bind(this) // setters this.setCustomProviders = this.setCustomProviders.bind(this) this.setNewDefaultProvider = this.setNewDefaultProvider.bind(this) @@ -91,6 +100,15 @@ export class Client implements IClient { return this.smartContractsClient } + /** + * Get the MNS resolver object. + * + * @returns MnsResolver object. + */ + public mnsResolver(): MnsResolver { + return this.mnsResolverVar + } + /** * Set new providers. * @@ -155,4 +173,13 @@ export class Client implements IClient { this.walletClient.setProviders(providers) this.smartContractsClient.setProviders(providers) } + + /** + * Set a new MNS resolver address. + * + * @param contractAddress - The new MNS resolver contract address. + */ + public setNewMnsResolver(contractAddress: string): void { + this.mnsResolverVar.setMnsResolver(contractAddress) + } } diff --git a/packages/massa-web3/src/web3/ClientFactory.ts b/packages/massa-web3/src/web3/ClientFactory.ts index 1a170d32..fb5e7c51 100644 --- a/packages/massa-web3/src/web3/ClientFactory.ts +++ b/packages/massa-web3/src/web3/ClientFactory.ts @@ -9,7 +9,10 @@ import { import { Web3Account } from './accounts/Web3Account' import { PublicApiClient } from './PublicApiClient' import { WalletProviderAccount } from './accounts/WalletProviderAccount' -import { DefaultProviderUrls } from '@massalabs/web3-utils' +import { + DefaultProviderUrls, + CHAIN_ID_DNS_ADDRESS_MAP, +} from '@massalabs/web3-utils' /** * Massa Web3 ClientFactory class allows you to easily initialize a client to @@ -77,7 +80,8 @@ export class ClientFactory { providers, } as IClientConfig, account, - publicApi + publicApi, + CHAIN_ID_DNS_ADDRESS_MAP[chainId.toString()] ) return client } @@ -110,7 +114,12 @@ export class ClientFactory { if (baseAccount) { account = new Web3Account(baseAccount, publicApi, chainId) } - const client: Client = new Client(clientConfig, account, publicApi) + const client: Client = new Client( + clientConfig, + account, + publicApi, + CHAIN_ID_DNS_ADDRESS_MAP[chainId.toString()] + ) return client } @@ -142,7 +151,9 @@ export class ClientFactory { retryStrategyOn, providers, }, - new WalletProviderAccount(baseAccount) + new WalletProviderAccount(baseAccount), + undefined, + CHAIN_ID_DNS_ADDRESS_MAP[provider.getChainId().toString()] ) return client diff --git a/packages/massa-web3/src/web3/MnsResolver.ts b/packages/massa-web3/src/web3/MnsResolver.ts new file mode 100644 index 00000000..f50339c3 --- /dev/null +++ b/packages/massa-web3/src/web3/MnsResolver.ts @@ -0,0 +1,105 @@ +import { + Args, + IContractReadOperationData, + MAX_GAS_CALL, + bytesToStr, +} from '@massalabs/web3-utils' +import { IClientConfig } from '../interfaces/IClientConfig' +import { IMnsResolver } from '../interfaces/IMnsResolver' +import { BaseClient } from './BaseClient' +import { JSON_RPC_REQUEST_METHOD } from '../interfaces/JsonRpcMethods' +import { trySafeExecute } from '../utils/retryExecuteFunction' + +export class MnsResolver extends BaseClient implements IMnsResolver { + private contractResolver?: string + + /** + * Constructor of the MnsResolver class. + * + * @param clientConfig - client configuration object. + * @param dnsAddress - DNS address to use for resolving MNS records. + */ + public constructor(clientConfig: IClientConfig, dnsAddress?: string) { + super(clientConfig) + this.contractResolver = dnsAddress + } + + /** + * Resolves the domain to an address. + * + * @param domain - Domain to resolve. + * + * @returns A promise that resolves to the address of the domain if it exists or throws an error if it does not. + */ + public async resolve(domain: string): Promise { + if (!this.contractResolver) { + throw new Error( + 'MNS resolver contract address is not set. Use setMnsResolver method to set the contract address.' + ) + } + if (!domain || !domain.endsWith('.massa')) { + throw new Error( + 'Invalid domain name. Domain name should end with ".massa"' + ) + } + + // remove .massa from domain + domain = domain.slice(0, -6) + const data = { + max_gas: Number(MAX_GAS_CALL), + target_address: this.contractResolver, + target_function: 'dnsResolve', + parameter: new Args().addString(domain).serialize(), + caller_address: this.contractResolver, + } + + // returns operation ids + const jsonRpcRequestMethod = JSON_RPC_REQUEST_METHOD.EXECUTE_READ_ONLY_CALL + let jsonRpcCallResult: Array = [] + if (this.clientConfig.retryStrategyOn) { + jsonRpcCallResult = await trySafeExecute< + Array + >(this.sendJsonRPCRequest, [jsonRpcRequestMethod, [[data]]]) + } else { + jsonRpcCallResult = await this.sendJsonRPCRequest(jsonRpcRequestMethod, [ + [data], + ]) + } + + if (jsonRpcCallResult.length <= 0) { + throw new Error( + 'Read operation bad response. No results array in json rpc response. Inspect smart contract' + ) + } + if (jsonRpcCallResult[0].result.Error) { + throw new Error(jsonRpcCallResult[0].result.Error) + } + + return bytesToStr(jsonRpcCallResult[0].result.Ok) + } + + resolveOrFallback = async (domain?: string): Promise => { + try { + return await this.resolve(domain) + } catch (e) { + return domain + } + } + /** + * Sets the MNS resolver contract address. + * + * @param contractAddress - Contract address to use for resolving MNS records. + */ + public setMnsResolver(contractAddress: string): void { + this.contractResolver = contractAddress + } + + /** + * Get the MNS resolver contract address. + * + * @returns The MNS resolver contract address. + */ + public getMnsResolverAddress(): string { + return this.contractResolver + } +} diff --git a/packages/massa-web3/src/web3/SmartContractsClient.ts b/packages/massa-web3/src/web3/SmartContractsClient.ts index eb128fed..09ea8b15 100644 --- a/packages/massa-web3/src/web3/SmartContractsClient.ts +++ b/packages/massa-web3/src/web3/SmartContractsClient.ts @@ -44,6 +44,7 @@ import { isSpeculativeSuccess, isUnexecutedOrExpired, } from './helpers/operationStatus' +import { MnsResolver } from './MnsResolver' const WAIT_STATUS_TIMEOUT = 60000 const WAIT_OPERATION_TIMEOUT = 160000 @@ -70,7 +71,8 @@ export class SmartContractsClient public constructor( clientConfig: IClientConfig, private readonly publicApiClient: PublicApiClient, - private readonly walletClient: IWalletClient + private readonly walletClient: IWalletClient, + private readonly mnsResolver: MnsResolver ) { super(clientConfig) @@ -167,6 +169,10 @@ export class SmartContractsClient } } + callData.targetAddress = await this.mnsResolver.resolveOrFallback( + callData.targetAddress + ) + return await sender.callSmartContract(callData) } @@ -188,6 +194,10 @@ export class SmartContractsClient ) } + readData.targetAddress = await this.mnsResolver.resolveOrFallback( + readData.targetAddress + ) + const data = { max_gas: readData.maxGas === null || readData.maxGas === undefined @@ -241,11 +251,13 @@ export class SmartContractsClient /** * Returns the balance of the smart contract. * - * @param address - The address of the smart contract. + * @param address - The address of the smart contract or the MNS domain associated. * * @returns A promise that resolves to the balance of the smart contract. */ public async getContractBalance(address: string): Promise { + address = await this.mnsResolver.resolveOrFallback(address) + const addresses: Array = await this.publicApiClient.getAddresses([address]) if (addresses.length === 0) return null @@ -267,6 +279,15 @@ export class SmartContractsClient public async getFilteredScOutputEvents( eventFilterData: IEventFilter ): Promise> { + eventFilterData.emitter_address = await this.mnsResolver.resolveOrFallback( + eventFilterData.emitter_address + ) + + eventFilterData.original_caller_address = + await this.mnsResolver.resolveOrFallback( + eventFilterData.original_caller_address + ) + const data = { start: eventFilterData.start, end: eventFilterData.end, @@ -314,6 +335,12 @@ export class SmartContractsClient public async executeReadOnlySmartContract( contractData: IContractData ): Promise { + if (contractData.address) { + contractData.address = await this.mnsResolver.resolveOrFallback( + contractData.address + ) + } + if (!contractData.contractDataBinary) { throw new Error('Expected non-null contract bytecode, but received null.') } diff --git a/packages/massa-web3/src/web3/WalletClient.ts b/packages/massa-web3/src/web3/WalletClient.ts index 7dedbdbd..f2ed8a24 100755 --- a/packages/massa-web3/src/web3/WalletClient.ts +++ b/packages/massa-web3/src/web3/WalletClient.ts @@ -27,6 +27,7 @@ import { SECRET_KEY_PREFIX, fromMAS, } from '@massalabs/web3-utils' +import { MnsResolver } from './MnsResolver' const MAX_WALLET_ACCOUNTS = 256 @@ -40,6 +41,7 @@ const MAX_WALLET_ACCOUNTS = 256 */ export class WalletClient extends BaseClient implements IWalletClient { private wallet: Array = [] + private mnsResolver: MnsResolver private baseAccount?: IBaseAccount /** @@ -52,6 +54,7 @@ export class WalletClient extends BaseClient implements IWalletClient { public constructor( clientConfig: IClientConfig, private readonly publicApiClient: PublicApiClient, + mnsResolver: MnsResolver, baseAccount?: IBaseAccount ) { super(clientConfig) @@ -59,6 +62,8 @@ export class WalletClient extends BaseClient implements IWalletClient { this.baseAccount = baseAccount } + this.mnsResolver = mnsResolver + // ========== bind wallet methods ========= // // wallet methods @@ -513,6 +518,10 @@ export class WalletClient extends BaseClient implements IWalletClient { txData: ITransactionData, executor?: IBaseAccount ): Promise> { + txData.recipientAddress = await this.mnsResolver.resolveOrFallback( + txData.recipientAddress + ) + if (!new Address(txData.recipientAddress).isUser) { throw new Error( `Invalid recipient address: "${txData.recipientAddress}". The address must be a user address, starting with "${ADDRESS_USER_PREFIX}".` diff --git a/packages/massa-web3/test/web3/smartContractsClient.spec.ts b/packages/massa-web3/test/web3/smartContractsClient.spec.ts index f6d69d7d..b76c4ed6 100644 --- a/packages/massa-web3/test/web3/smartContractsClient.spec.ts +++ b/packages/massa-web3/test/web3/smartContractsClient.spec.ts @@ -29,6 +29,7 @@ import { } from './mockData' import { IExecuteReadOnlyResponse } from '../../src/interfaces/IExecuteReadOnlyResponse' import { Web3Account } from '../../src/web3/accounts/Web3Account' +import { MnsResolver } from '../../src/web3/MnsResolver' // Mock to not wait for the timeout to finish jest.mock('../../src/utils/time', () => { @@ -43,9 +44,11 @@ describe('SmartContractsClient', () => { let mockPublicApiClient: PublicApiClient let mockWalletClient: WalletClient let mockDeployerAccount: Web3Account + let mockMnsResolver: MnsResolver beforeEach(() => { // Initialize the mock objects + mockMnsResolver = new MnsResolver(mockClientConfig) mockPublicApiClient = new PublicApiClient(mockClientConfig) mockDeployerAccount = new Web3Account( importedMockDeployerAccount, @@ -55,12 +58,14 @@ describe('SmartContractsClient', () => { mockWalletClient = new WalletClient( mockClientConfig, mockPublicApiClient, + mockMnsResolver, mockDeployerAccount ) smartContractsClient = new SmartContractsClient( mockClientConfig, mockPublicApiClient, - mockWalletClient + mockWalletClient, + mockMnsResolver ) // Mock getOperations diff --git a/packages/web3-utils/src/constants.ts b/packages/web3-utils/src/constants.ts index 58e9347a..07d49659 100644 --- a/packages/web3-utils/src/constants.ts +++ b/packages/web3-utils/src/constants.ts @@ -76,3 +76,10 @@ export const CHAIN_ID_RPC_URL_MAP = { [BUILDNET_CHAIN_ID.toString()]: DefaultProviderUrls.BUILDNET, [SANDBOX_CHAIN_ID.toString()]: DefaultProviderUrls.LOCALNET, } as const // type is inferred as the specific, unchangeable structure + +export const CHAIN_ID_DNS_ADDRESS_MAP = { + [MAINNET_CHAIN_ID.toString()]: + 'AS12mdKsjAqcWC5DjabWZp7tG9s5wgkrwDGDuh6xUCSc53SrmfY9r', + [BUILDNET_CHAIN_ID.toString()]: + 'AS12qKAVjU1nr66JSkQ6N4Lqu4iwuVc6rAbRTrxFoynPrPdP1sj3G', +} as const // type is inferred as the specific, unchangeable structure