This repository has been archived by the owner on Feb 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
services.ts
242 lines (212 loc) · 8.06 KB
/
services.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import { WalletName, WalletRiskAssessment } from "./types";
import detectEthereumProvider from '@metamask/detect-provider';
import WalletConnectProvider from "@walletconnect/ethereum-provider";
import { BigNumber, ethers, Contract } from "ethers";
import { OverrideTypes } from "@utilities";
import { DateTime } from "luxon";
export type SignatureResponse = {
signature?: string;
timestamp?: string;
walletAddress?: string;
}
const unavailableText = 'Service unavailable, please try again shortly';
/**
* Will throw an error if the connected ethereum network does not match required network set in .env file
* @param provider - The ethers-wrapped provider
*/
export const checkForCorrectNetwork = async (provider: ethers.providers.JsonRpcProvider) => {
const network = await provider.getNetwork();
if(network.name !== process.env.REACT_APP_REQUIRED_ETHEREUM_NETWORK) {
// eslint-disable-next-line
console.warn(`User wallet is connected to '${network.name}' instead of '${process.env.REACT_APP_REQUIRED_ETHEREUM_NETWORK}'`);
throw new Error('Wrong network');
}
};
/**
* Will throw an error if the given wallet address is deemed as risky (has a shady history)
* @param walletAddress - the wallet address to check
*/
export const checkForRiskyWallet = async (walletAddress: string) => {
const riskAssessment = await getWalletRiskAssessment(walletAddress);
if(isWalletRisky(riskAssessment)) {
throw new Error('Risky Account Detected');
}
};
/**
* Will make the wallet open a prompt, asking the user to agree to the T&Cs. It will throw an
* error if the user disagrees / closes the window. The signature is stored in localStorage to
* avoid having to sign the terms every time you connect your wallet.
* @param signer - The ethers signer
*/
export const checkForTOSSignature = async (signer: ethers.providers.JsonRpcSigner, walletAddress: string) => {
const signerAddress = await signer.getAddress();
const signatureData = await getSignature(signerAddress);
let termsAccepted = false;
if(signatureData?.signature) {
const existingTOSSignerAddress = ethers.utils.verifyMessage(getTOSText(), signatureData.signature);
if(existingTOSSignerAddress === signerAddress) {
termsAccepted = true;
}
}
if(!termsAccepted) {
try {
const signature = await signer.signMessage(getTOSText());
await saveSignature(signerAddress, signature);
} catch(e) {
throw new Error(' ');
}
}
}
/**
* Retrieves signature data via the signatures API for the given wallet address
* @param walletAddress - the wallet address to retrieve the signature for
*/
export const getSignature = async (walletAddress: string) => {
try {
const resp = await fetch(`https://voltz-rest-api.herokuapp.com/get-signature/${walletAddress}`, {
headers: {
'Content-Type': 'application/json',
}
});
// https://voltz-rest-api.herokuapp.com/get-signature/0x45556408e543158f74403e882E3C8c23eCD9f732
if(resp.ok) {
return await resp.json() as SignatureResponse;
} else if(resp.status === 404) {
return undefined; // API is ok, but the signature wasn't found
} else {
throw await resp.text();
}
} catch(e) {
// eslint-disable-next-line
console.warn('TOS check failed', e);
throw new Error(unavailableText);
}
}
/**
* Returns the terms of service text that users have to agree to to connect their wallet.
* Note - Any changes, including whitespace, will mean a new signature is required.
*/
export const getTOSText = () => {
const text = `
Please sign this message to log in. This won't cost you any ETH!
By signing, you accept Voltz's Terms of Service, which you can find here:
${process.env.REACT_APP_TOS_URL || ''}
If you're connecting a hardware wallet, you'll need to sign the message on your device too.`;
return text.trim();
};
/**
* Attemps to get an ethers-wrapped provider for the given wallet name
* @param name - The wallet name (E.G: metamask)
*/
export const getWalletProvider = async (name: WalletName) => {
switch(name) {
case 'metamask':
return await getWalletProviderMetamask();
case 'walletConnect':
return await getWalletProviderWalletConnect();
}
}
/**
* Returns an ethers Web3Provider, which wraps the Metamask instance
*/
export const getWalletProviderMetamask = async () => {
const externalProvider = await detectEthereumProvider();
if (externalProvider) {
try {
const provider = new ethers.providers.Web3Provider(externalProvider as ethers.providers.ExternalProvider);
// There is a login issue with metamask: https://github.com/MetaMask/metamask-extension/issues/10085
// Triggers metamask login window, but always asks for permissions to allow site to use wallet. However, it handles
// the user closing the login modal correctly (cancels login request so modal pops up next time).
// await provider.send("wallet_requestPermissions", [{ eth_accounts: {} }]);
// Triggers login modal, but if the user closes the login modal, the request isnt cancelled, so the modal
// does not pop up again the next time they choose login with metamask (unless they refresh the page).
await provider.send("eth_requestAccounts", []);
return provider;
}
catch(e) {
return undefined; // Assume user cancelled
}
}
throw new Error('Metamask not installed');
}
/**
* Returns an ethers Web3Provider, which wraps the WalletConnect instance
*/
export const getWalletProviderWalletConnect = async () => {
let provider;
// Try to init WalletConnect - could fail if INFURA_ID is incorrect
try {
provider = new WalletConnectProvider({
infuraId: process.env.REACT_APP_WALLETCONNECT_INFURA_ID,
});
} catch(e) {
throw new Error('WalletConnect not available');
}
// Now try and get the user to log into their wallet
try {
await provider.enable(); // Enable session (triggers QR Code modal)
} catch(e) {
return undefined; // assume user cancelled login
}
if (provider) {
return new ethers.providers.Web3Provider(provider as ethers.providers.ExternalProvider);
}
return undefined;
}
/**
* Makes a request to TRM to get a risk assessment for the given wallet ID
* @param walletId - ID of the wallet to check
*/
export async function getWalletRiskAssessment(walletId: string) {
try {
const result = await fetch('https://api.trmlabs.com/public/v2/screening/addresses', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
Authorization: 'Basic ' + btoa(process.env.REACT_APP_TRM_API_KEY || '')
},
body: JSON.stringify([{
address: walletId,
chain: 'ethereum'
}])
});
if (result.ok) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data:WalletRiskAssessment[] = await result.json();
return data[0];
} else {
throw await result.text();
}
} catch(e) {
// eslint-disable-next-line
console.warn('Wallet screening failed', e);
throw new Error(unavailableText);
}
}
/**
* Returns true or false based upon if the given risk assessment would suggest the wallet is risky
* @param riskAssessment - Risk report foir a wallet obtained from using walletSecurityCheck
*/
export const isWalletRisky = (riskAssessment?: WalletRiskAssessment) => {
const indicators = riskAssessment?.addressRiskIndicators;
const redFlag = Array.isArray(indicators) && indicators.find(i => i.categoryRiskScoreLevel >= 10);
return Boolean(redFlag);
}
/**
* Saves a signature via the signatures API for the given wallet address
* @param walletAddress - the wallet address to save the signature for
* @param signature - thwe signature to save
*/
export const saveSignature = async (walletAddress: string, signature: string) => {
// Build formData object.
const formData = new FormData();
formData.append('signature', signature);
formData.append('walletAddress', walletAddress);
return await fetch(`https://voltz-rest-api.herokuapp.com/add-signature`, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
body: formData
})
}