From e0a54ab7218842d756b50c94a618397ce193029a Mon Sep 17 00:00:00 2001
From: AurelienFT <32803821+AurelienFT@users.noreply.github.com>
Date: Wed, 22 May 2024 13:51:05 +0200
Subject: [PATCH] Add Massa name resolution (#609)
* Add resolve of dns in smart contracts interactions
* Add mns resolver in send transaction
* fix tests
* Test the mns resolver with an example
* Add try or fallback function
* Add the mns test to the test all command and remove dead code
---------
Co-authored-by: BenRey
---
package-lock.json | 2 +-
.../examples/smartContracts/deployer.ts | 2 +-
.../examples/smartContracts/index.ts | 8 +-
.../examples/smartContracts/nameResolver.ts | 161 ++++++++++++++++++
packages/massa-web3/examples/wallet/index.ts | 4 +-
packages/massa-web3/package.json | 4 +-
.../massa-web3/src/interfaces/ICallData.ts | 2 +-
packages/massa-web3/src/interfaces/IClient.ts | 5 +
.../src/interfaces/IContractData.ts | 2 +-
.../massa-web3/src/interfaces/IEventFilter.ts | 4 +-
.../massa-web3/src/interfaces/IMnsResolver.ts | 16 ++
.../massa-web3/src/interfaces/IReadData.ts | 2 +-
packages/massa-web3/src/web3/Client.ts | 31 +++-
packages/massa-web3/src/web3/ClientFactory.ts | 19 ++-
packages/massa-web3/src/web3/MnsResolver.ts | 105 ++++++++++++
.../src/web3/SmartContractsClient.ts | 31 +++-
packages/massa-web3/src/web3/WalletClient.ts | 9 +
.../test/web3/smartContractsClient.spec.ts | 7 +-
packages/web3-utils/src/constants.ts | 7 +
19 files changed, 398 insertions(+), 23 deletions(-)
create mode 100644 packages/massa-web3/examples/smartContracts/nameResolver.ts
create mode 100644 packages/massa-web3/src/interfaces/IMnsResolver.ts
create mode 100644 packages/massa-web3/src/web3/MnsResolver.ts
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