Skip to content

Commit 55f8ab2

Browse files
Implement vet.domains (#839) (#867)
* feat: init name lookup prototype using vet.domains * refactor: improve name resolving * test: improve test process * feat: add support for vet.domains names in clauses * refactor: improve name lookup on transaction clauses * refactor: simplify resolving process * feat: add support for vet.domains primary name lookups * refactor: improve ethers interface compatibility on provider & signer * refactor: remove duplicate definition * test: add vet.domains vns seeding to thor solo-node * test: improve testing process for vet.domains --------- Co-authored-by: Rodolfo Pietro Calabrò <33911400+rodolfopietro97@users.noreply.github.com>
1 parent 4686168 commit 55f8ab2

File tree

29 files changed

+1031
-24
lines changed

29 files changed

+1031
-24
lines changed

docs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
"expect": "^29.7.0",
2828
"ts-node-test": "^0.4.3"
2929
}
30-
}
30+
}

packages/core/src/utils/transaction/transaction.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ function intrinsicGas(clauses: TransactionClause[]): number {
2525
// Some clauses
2626
return clauses.reduce((sum, clause: TransactionClause) => {
2727
if (clause.to !== null) {
28-
// Invalid address
28+
// Invalid address or no vet.domains name
2929
assert(
3030
'intrinsicGas',
31-
addressUtils.isAddress(clause.to),
31+
addressUtils.isAddress(clause.to) || clause.to.includes('.'),
3232
DATA.INVALID_DATA_TYPE,
3333
`Invalid data type in clause. Each 'to' field must be a valid address.`,
3434
{ clause }

packages/network/solo-seeding/thor-solo-seeding.ts

+321-5
Large diffs are not rendered by default.

packages/network/src/provider/providers/vechain-provider/vechain-provider.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
type SubscriptionManager
1717
} from './types';
1818
import { Quantity } from '@vechain/sdk-core';
19-
import { type EventPoll, Poll } from '../../../utils';
19+
import { type EventPoll, Poll, vnsUtils } from '../../../utils';
2020
import {
2121
type CompressedBlockDetail,
2222
type ThorClient
@@ -283,6 +283,24 @@ class VechainProvider extends EventEmitter implements EIP1193ProviderMessage {
283283
}
284284
return await this.wallet?.getSigner(this, address);
285285
}
286+
287+
/**
288+
* Use vet.domains to resolve name to adress
289+
* @param vnsName - The name to resolve
290+
* @returns the address for a name or null
291+
*/
292+
async resolveName(vnsName: string): Promise<null | string> {
293+
return await vnsUtils.resolveName(this.thorClient, vnsName);
294+
}
295+
296+
/**
297+
* Use vet.domains to lookup a verified primary name for an address
298+
* @param address - The address to lookup
299+
* @returns the primary name for an address or null
300+
*/
301+
async lookupAddress(address: string): Promise<null | string> {
302+
return await vnsUtils.lookupAddress(this.thorClient, address);
303+
}
286304
}
287305

288306
export { VechainProvider };

packages/network/src/signer/signers/types.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,9 @@ interface VechainSigner {
370370
// ) => Promise<string>;
371371

372372
/**
373-
* Resolves an ENS Name to an address.
373+
* Resolves an VNS Name to an address.
374374
*/
375-
// resolveName: (name: string) => Promise<null | string>;
375+
resolveName: (vnsName: string) => Promise<null | string>;
376376
}
377377

378378
export {

packages/network/src/signer/signers/vechain-base-signer/vechain-base-signer.ts

+14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { RPC_METHODS } from '../../../provider';
2323
import { assert, DATA, JSONRPC, TRANSACTION } from '@vechain/sdk-errors';
2424
import { assertTransactionCanBeSigned } from '../../../assertions';
25+
import { vnsUtils } from '../../../utils';
2526

2627
/**
2728
* Basic vechain signer.
@@ -476,6 +477,19 @@ class VechainBaseSigner implements VechainSigner {
476477
// Return new signed transaction
477478
return Hex0x.of(new Transaction(unsignedTx.body, signature).encoded);
478479
}
480+
481+
/**
482+
* Use vet.domains to resolve name to adress
483+
* @param vnsName - The name to resolve
484+
* @returns the address for a name or null
485+
*/
486+
async resolveName(vnsName: string): Promise<null | string> {
487+
if (this.provider === null) {
488+
return null;
489+
}
490+
491+
return await vnsUtils.resolveName(this.provider.thorClient, vnsName);
492+
}
479493
}
480494

481495
export { VechainBaseSigner };

packages/network/src/thor-client/transactions/transactions-module.ts

+62-8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
ERROR_SELECTOR,
1717
PANIC_SELECTOR,
1818
Poll,
19-
thorest
19+
thorest,
20+
vnsUtils
2021
} from '../../utils';
2122
import {
2223
type GetTransactionInputOptions,
@@ -241,7 +242,7 @@ class TransactionsModule {
241242
return {
242243
blockRef,
243244
chainTag,
244-
clauses,
245+
clauses: await this.resolveNamesInClauses(clauses),
245246
dependsOn: options?.dependsOn ?? null,
246247
expiration: options?.expiration ?? 32,
247248
gas,
@@ -252,6 +253,57 @@ class TransactionsModule {
252253
};
253254
}
254255

256+
/**
257+
* Ensures that names in clauses are resolved to addresses
258+
*
259+
* @param clauses - The clauses of the transaction.
260+
* @returns A promise that resolves to clauses with resolved addresses
261+
*/
262+
public async resolveNamesInClauses(
263+
clauses: TransactionClause[]
264+
): Promise<TransactionClause[]> {
265+
// find unique names in the clause list
266+
const uniqueNames = clauses.reduce((map, clause) => {
267+
if (
268+
typeof clause.to === 'string' &&
269+
!map.has(clause.to) &&
270+
clause.to.includes('.')
271+
) {
272+
map.set(clause.to, clause.to);
273+
}
274+
return map;
275+
}, new Map<string, string>());
276+
277+
const nameList = [...uniqueNames.keys()];
278+
279+
// no names, return the original clauses
280+
if (uniqueNames.size === 0) {
281+
return clauses;
282+
}
283+
284+
// resolve the names to addresses
285+
const addresses = await vnsUtils.resolveNames(this.thor, nameList);
286+
287+
// map unique names with resolved addresses
288+
addresses.forEach((address, index) => {
289+
if (address !== null) {
290+
uniqueNames.set(nameList[index], address);
291+
}
292+
});
293+
294+
// replace names with resolved addresses, or leave unchanged
295+
return clauses.map((clause) => {
296+
if (typeof clause.to !== 'string') {
297+
return clause;
298+
}
299+
300+
return {
301+
...clause,
302+
to: uniqueNames.get(clause.to) ?? clause.to
303+
};
304+
});
305+
}
306+
255307
/**
256308
* Simulates the execution of a transaction.
257309
* Allows to estimate the gas cost of a transaction without sending it, as well as to retrieve the return value(s) of the transaction.
@@ -292,12 +344,14 @@ class TransactionsModule {
292344
{
293345
query: buildQuery({ revision }),
294346
body: {
295-
clauses: clauses.map((clause) => {
296-
return {
297-
...clause,
298-
value: BigInt(clause.value).toString()
299-
};
300-
}),
347+
clauses: await this.resolveNamesInClauses(
348+
clauses.map((clause) => {
349+
return {
350+
...clause,
351+
value: BigInt(clause.value).toString()
352+
};
353+
})
354+
),
301355
gas,
302356
gasPrice,
303357
caller,

packages/network/src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './http';
44
export * from './poll';
55
export * from './thorest';
66
export * from './subscriptions';
7+
export * from './vns';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
MAINNET_NETWORK,
3+
SOLO_NETWORK,
4+
TESTNET_NETWORK
5+
} from '../../../../core';
6+
7+
const NetworkContracts: Record<
8+
string,
9+
{ registry: string; resolveUtils: string }
10+
> = {
11+
[MAINNET_NETWORK.genesisBlock.id]: {
12+
registry: '0xa9231da8BF8D10e2df3f6E03Dd5449caD600129b',
13+
resolveUtils: '0xA11413086e163e41901bb81fdc5617c975Fa5a1A'
14+
},
15+
16+
[TESTNET_NETWORK.genesisBlock.id]: {
17+
registry: '0xcBFB30c1F267914816668d53AcBA7bA7c9806D13',
18+
resolveUtils: '0xc403b8EA53F707d7d4de095f0A20bC491Cf2bc94'
19+
},
20+
21+
[SOLO_NETWORK.genesisBlock.id]: {
22+
registry: '0x1c4a602ed21f3d1dddd1142c81f231ef1a08c921',
23+
resolveUtils: '0xb2f08bbfa8a42b1fbe63feec604cb147385203d7'
24+
}
25+
};
26+
27+
export { NetworkContracts };
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { NetworkContracts } from './addresses';
2+
import { type ThorClient } from '../../thor-client';
3+
import {
4+
addressUtils,
5+
ZERO_ADDRESS,
6+
type FunctionFragment
7+
} from '../../../../core';
8+
9+
/**
10+
* Returns a single address or null for a name resolved at vet.domains
11+
*
12+
* @param thorClient - The thor client instance to use.
13+
* @param name - The name to resolve
14+
* @returns The address or null
15+
*/
16+
const resolveName = async (
17+
thorClient: ThorClient,
18+
name: string
19+
): Promise<null | string> => {
20+
const [address] = await vnsUtils.resolveNames(thorClient, [name]);
21+
return address ?? null;
22+
};
23+
24+
/**
25+
* Returns a list of addresses or null for names resolved by vet.domains
26+
*
27+
* @param thorClient - The thor client instance to use.
28+
* @param names - The names to resolve
29+
* @returns The list of the same size of names with the resolved address or null
30+
*/
31+
const resolveNames = async (
32+
thorClient: ThorClient,
33+
names: string[]
34+
): Promise<Array<null | string>> => {
35+
// identify current chain
36+
const genesisBlock = await thorClient.blocks.getGenesisBlock();
37+
38+
// verify configuration for chain exists
39+
if (
40+
genesisBlock === null ||
41+
!addressUtils.isAddress(NetworkContracts[genesisBlock.id]?.resolveUtils)
42+
) {
43+
return names.map(() => null);
44+
}
45+
46+
const resolveUtilsAddress = NetworkContracts[genesisBlock.id].resolveUtils;
47+
48+
// use the resolveUtils to lookup names
49+
const [addresses] = (await thorClient.contracts.executeCall(
50+
resolveUtilsAddress,
51+
'function getAddresses(string[] names) returns (address[] addresses)' as unknown as FunctionFragment,
52+
[names]
53+
)) as string[][];
54+
55+
return addresses.map((address) => {
56+
// zero addresses are missing configuration entries
57+
if (address === ZERO_ADDRESS || !addressUtils.isAddress(address)) {
58+
return null;
59+
}
60+
61+
return address;
62+
});
63+
};
64+
65+
/**
66+
* Returns a single primary name for a given address resolved at vet.domains
67+
*
68+
* @param thorClient - The thor client instance to use.
69+
* @param address - The address to lookup
70+
* @returns The name or null
71+
*/
72+
const lookupAddress = async (
73+
thorClient: ThorClient,
74+
address: string
75+
): Promise<null | string> => {
76+
const [name] = await vnsUtils.lookupAddresses(thorClient, [address]);
77+
return name ?? null;
78+
};
79+
80+
/**
81+
* Returns a list of names or null for addresses primary names resolved by vet.domains. Reverse lookup of name to address is verified.
82+
*
83+
* @param thorClient - The thor client instance to use.
84+
* @param addresses - The addresses to lookup
85+
* @returns The list of the same size of addresses with the resolved primary names or null
86+
*/
87+
const lookupAddresses = async (
88+
thorClient: ThorClient,
89+
addresses: string[]
90+
): Promise<Array<null | string>> => {
91+
// identify current chain
92+
const genesisBlock = await thorClient.blocks.getGenesisBlock();
93+
94+
// verify configuration for chain exists
95+
if (
96+
genesisBlock === null ||
97+
!addressUtils.isAddress(NetworkContracts[genesisBlock.id]?.resolveUtils)
98+
) {
99+
return addresses.map(() => null);
100+
}
101+
102+
const resolveUtilsAddress = NetworkContracts[genesisBlock.id].resolveUtils;
103+
104+
// use the resolveUtils to lookup names
105+
const [names] = (await thorClient.contracts.executeCall(
106+
resolveUtilsAddress,
107+
'function getNames(address[] addresses) returns (string[] names)' as unknown as FunctionFragment,
108+
[addresses]
109+
)) as string[][];
110+
111+
return names.map((name) => {
112+
// empty strings indicate a missing entry
113+
if (name === '') {
114+
return null;
115+
}
116+
117+
return name;
118+
});
119+
};
120+
121+
const vnsUtils = { resolveName, resolveNames, lookupAddress, lookupAddresses };
122+
export { vnsUtils };

packages/network/tests/provider/providers/vechain/vechain-provider.solo.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -358,4 +358,32 @@ describe('Vechain provider tests - solo', () => {
358358
})
359359
).rejects.toThrowError(InvalidDataTypeError);
360360
});
361+
362+
describe('resolveName(vnsName)', () => {
363+
test('Should be able to resolve an address by name', async () => {
364+
const name = 'test-sdk.vet';
365+
const address = await provider.resolveName(name);
366+
expect(address).toBe('0xF02f557c753edf5fcdCbfE4c1c3a448B3cC84D54');
367+
});
368+
369+
test('Should resolve to null for unknown names', async () => {
370+
const name = 'unknown.test-sdk.vet';
371+
const address = await provider.resolveName(name);
372+
expect(address).toBe(null);
373+
});
374+
});
375+
376+
describe('lookupAddress(address)', () => {
377+
test('Should be able to lookup a name for an address', async () => {
378+
const address = '0xF02f557c753edf5fcdCbfE4c1c3a448B3cC84D54';
379+
const name = await provider.lookupAddress(address);
380+
expect(name).toBe('test-sdk.vet');
381+
});
382+
383+
test('Should resolve to null for unknown names', async () => {
384+
const address = '0x0000000000000000000000000000000000000001';
385+
const name = await provider.resolveName(address);
386+
expect(name).toBe(null);
387+
});
388+
});
361389
});

0 commit comments

Comments
 (0)