-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathmns.js
122 lines (107 loc) · 3.48 KB
/
mns.js
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
import axios from 'axios';
import {keccak} from 'ethereumjs-util/dist/hash';
import {ecrecover} from 'ethereumjs-util/dist/signature';
import {MNS_API_URL, MNS_PUBLIC_KEY} from "~/assets/variables";
const mns = axios.create({
baseURL: MNS_API_URL,
});
/**
* @TODO add cache @see https://github.com/RasCarlito/axios-cache-adapter/issues/99
* @return {function}
*/
export function ResolveDomain() {
let resolveLatestValue;
let resolveLatestPromise;
let resolveDelayForce;
let resolveDelayCancel;
let resolveRequestCancel;
/**
* @param value
* @param [throttle]
* @return {Promise<DomainData>}
*/
return function resolveDomain(value, {throttle} = {}) {
// not cancel request and reuse it if value is same
if (resolveLatestValue === value) {
// force delay
if (!throttle) {
tryCall(resolveDelayForce);
resolveDelayForce = null;
}
return resolveLatestPromise;
}
resolveLatestValue = value;
// cancel previous resolve
tryCall(resolveDelayCancel, {isCancel: true});
tryCall(resolveRequestCancel, {isCancel: true});
resolveDelayCancel = null;
resolveRequestCancel = null;
// delay if `throttle` and save resolve promise
if (throttle) {
return resolveLatestPromise = delay(700)
.then(() => resolveDomainDirect(value));
}
return resolveLatestPromise = resolveDomainDirect(value);
};
/**
* @param value
* @return {Promise<DomainData>}
*/
function resolveDomainDirect(value) {
return mns.get('resolve', {
params: {
domain: value,
},
cancelToken: new axios.CancelToken((cancelFn) => {
resolveRequestCancel = cancelFn;
}),
}).then((response) => response.data);
}
function delay(ms) {
return new Promise((resolve, reject) => {
// resolveDelayForce can be used outside to resolve promise
resolveDelayForce = resolve;
// resolveDelayCancel can be used outside to reject promise
resolveDelayCancel = reject;
setTimeout(resolve, ms);
});
}
}
function tryCall(fn, {isCancel} = {}) {
if (typeof fn === 'function') {
fn(isCancel ? 'Request aborted' : undefined);
}
}
// allow "a." to be handled by domain validation instead of address validation
const DOMAIN_REG_EXP = /^([a-z0-9][a-z0-9-_]*\.)+[a-z]*$/i;
const DOMAIN_REG_EXP_VALID = /^([a-z0-9][a-z0-9-_]*\.)+[a-z]{2,}$/i;
/**
* @param {string} domain
* @return {boolean}
*/
export function isDomain(domain) {
return DOMAIN_REG_EXP.test(domain);
}
export function isValidDomain(domain) {
return DOMAIN_REG_EXP_VALID.test(domain);
}
/**
* @param {DomainData} resolveResult
* @return {boolean}
*/
export function checkDomainSignature(resolveResult) {
const { address, publickey, ticker, signature } = resolveResult;
const messageForSinature = address + publickey + ticker;
const hash = keccak(messageForSinature);
const r = Buffer.from(signature.r, 'hex');
const s = Buffer.from(signature.s, 'hex');
const recovered = ecrecover(hash, signature.v, r, s);
return 'Mp' + recovered.toString('hex') === MNS_PUBLIC_KEY;
}
/**
* @typedef DomainData
* @param {string} address
* @param {string} publickey
* @param {string} ticker
* @param {{v: number, r: string, s: string}} signature
*/