Skip to content

Commit

Permalink
Add Massa name resolution (#609)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
AurelienFT and Ben-Rey authored May 22, 2024
1 parent 6c655cf commit e0a54ab
Show file tree
Hide file tree
Showing 19 changed files with 398 additions and 23 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/massa-web3/examples/smartContracts/deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> => {
Expand Down
8 changes: 4 additions & 4 deletions packages/massa-web3/examples/smartContracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ const pollAsyncEvents = async (
],
web3Client,
baseAccount,
0n,
fromMAS(0.01),
3000_000_000n,
fromMAS(1.5)
)
Expand Down Expand Up @@ -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)
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
161 changes: 161 additions & 0 deletions packages/massa-web3/examples/smartContracts/nameResolver.ts
Original file line number Diff line number Diff line change
@@ -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)
}
})()
4 changes: 2 additions & 2 deletions packages/massa-web3/examples/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion packages/massa-web3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/massa-web3/src/interfaces/ICallData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>` or an Args represents the parameters to pass to the target function.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/massa-web3/src/interfaces/IClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<IProvider>): void
setNewDefaultProvider(provider: DefaultProviderUrls): void
setNewMnsResolver(contractAddress: string): void
}
2 changes: 1 addition & 1 deletion packages/massa-web3/src/interfaces/IContractData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uint8Array, Uint8Array> | undefined` represents the smart contract's operational storage data (optional).
*/
export interface IContractData {
Expand Down
4 changes: 2 additions & 2 deletions packages/massa-web3/src/interfaces/IEventFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions packages/massa-web3/src/interfaces/IMnsResolver.ts
Original file line number Diff line number Diff line change
@@ -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<string>
getMnsResolverAddress(): string | undefined
setMnsResolver(contractAddress: string): void
}
2 changes: 1 addition & 1 deletion packages/massa-web3/src/interfaces/IReadData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit e0a54ab

Please sign in to comment.