-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.ts
133 lines (116 loc) · 3.82 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { Server } from '@chainlink/ccip-read-server';
import { ethers, BytesLike } from 'ethers';
import { hexConcat, Result } from 'ethers/lib/utils';
import { ETH_COIN_TYPE } from './utils';
import { addr as getAddr, text, contenthash as getContentHash } from './json'
import { abi as IResolverService_abi } from '@ensdomains/offchain-resolver-contracts/artifacts/contracts/OffchainResolver.sol/IResolverService.json';
import { abi as Resolver_abi } from '@ensdomains/ens-contracts/artifacts/contracts/resolvers/Resolver.sol/Resolver.json';
const Resolver = new ethers.utils.Interface(Resolver_abi);
interface DatabaseResult {
result: any[];
ttl: number;
}
type PromiseOrResult<T> = T | Promise<T>;
export interface Database {
addr(
name: string,
coinType: number
): PromiseOrResult<{ addr: string; ttl: number }>;
text(
name: string,
key: string
): PromiseOrResult<{ value: string; ttl: number }>;
contenthash(
name: string
): PromiseOrResult<{ contenthash: string; ttl: number }>;
}
function decodeDnsName(dnsname: Buffer) {
const labels = [];
let idx = 0;
while (true) {
const len = dnsname.readUInt8(idx);
if (len === 0) break;
labels.push(dnsname.slice(idx + 1, idx + len + 1).toString('utf8'));
idx += len + 1;
}
return labels.join('.');
}
const queryHandlers: {
[key: string]: (
name: string,
args: Result
) => Promise<DatabaseResult>;
} = {
'addr(bytes32)': async (name, _args) => {
const { addr, ttl } = await getAddr(name, ETH_COIN_TYPE);
return { result: [addr], ttl };
},
'addr(bytes32,uint256)': async (name, args) => {
const { addr, ttl } = await getAddr(name, args[0]);
return { result: [addr], ttl };
},
'text(bytes32,string)': async (name, args) => {
const { value, ttl } = await text(name, args[0]);
return { result: [value], ttl };
},
'contenthash(bytes32)': async (name, _args) => {
const { contenthash, ttl } = await getContentHash(name);
return { result: [contenthash], ttl };
},
};
async function query(
name: string,
data: string
): Promise<{ result: BytesLike; validUntil: number }> {
// Parse the data nested inside the second argument to `resolve`
const { signature, args } = Resolver.parseTransaction({ data });
if (ethers.utils.nameprep(name) !== name) {
throw new Error('Name must be normalised');
}
if (ethers.utils.namehash(name) !== args[0]) {
throw new Error('Name does not match namehash');
}
const handler = queryHandlers[signature];
if (handler === undefined) {
throw new Error(`Unsupported query function ${signature}`);
}
const { result, ttl } = await handler(name, args.slice(1));
return {
result: Resolver.encodeFunctionResult(signature, result),
validUntil: Math.floor(Date.now() / 1000 + ttl),
};
}
export function makeServer(signer: ethers.utils.SigningKey) {
const server = new Server();
server.add(IResolverService_abi, [
{
type: 'resolve',
func: async ([encodedName, data]: Result, request) => {
const name = decodeDnsName(Buffer.from(encodedName.slice(2), 'hex'));
// Query the database
const { result, validUntil } = await query(name, data);
// Hash and sign the response
let messageHash = ethers.utils.solidityKeccak256(
['bytes', 'address', 'uint64', 'bytes32', 'bytes32'],
[
'0x1900',
request?.to,
validUntil,
ethers.utils.keccak256(request?.data || '0x'),
ethers.utils.keccak256(result),
]
);
const sig = signer.signDigest(messageHash);
const sigData = hexConcat([sig.r, sig._vs]);
return [result, validUntil, sigData];
},
},
]);
return server;
}
export function makeApp(
signer: ethers.utils.SigningKey,
path: string,
) {
return makeServer(signer).makeApp(path);
}