@etherplay/wallet-connector-ethereum 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +85 -0
- package/dist/provider.js.map +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +264 -0
- package/src/provider.ts +111 -0
- package/src/utils.ts +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { WalletConnector, ChainInfo, WalletProvider, WalletHandle, AlwaysOnProviderWrapper, AccountGenerator, PrivateKeyAccount } from '@etherplay/wallet-connector';
|
|
2
|
+
import type { EIP1193WindowWalletProvider, Methods } from 'eip-1193';
|
|
3
|
+
import { CurriedRPC } from 'remote-procedure-call';
|
|
4
|
+
import { HDKey } from '@scure/bip32';
|
|
5
|
+
export declare function strip0x(hex: string): string;
|
|
6
|
+
export declare function add0x(hex: string): string;
|
|
7
|
+
export declare function astr(str: unknown): void;
|
|
8
|
+
export declare function parse(address: string): {
|
|
9
|
+
hasPrefix: boolean;
|
|
10
|
+
data: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function addChecksum(nonChecksummedAddress: string): string;
|
|
13
|
+
export declare function fromPublicKey(key: string | Uint8Array): string;
|
|
14
|
+
export declare function fromPrivateKey(key: string | Uint8Array): string;
|
|
15
|
+
export type UnderlyingEthereumProvider = CurriedRPC<Methods>;
|
|
16
|
+
interface EIP6963ProviderInfo {
|
|
17
|
+
uuid: string;
|
|
18
|
+
name: string;
|
|
19
|
+
icon: string;
|
|
20
|
+
rdns: string;
|
|
21
|
+
}
|
|
22
|
+
interface EIP6963ProviderDetail {
|
|
23
|
+
info: EIP6963ProviderInfo;
|
|
24
|
+
provider: EIP1193WindowWalletProvider;
|
|
25
|
+
}
|
|
26
|
+
export interface EIP6963AnnounceProviderEvent extends CustomEvent {
|
|
27
|
+
type: 'eip6963:announceProvider';
|
|
28
|
+
detail: EIP6963ProviderDetail;
|
|
29
|
+
}
|
|
30
|
+
export declare class EthereumWalletConnector implements WalletConnector<CurriedRPC<Methods>> {
|
|
31
|
+
accountGenerator: AccountGenerator;
|
|
32
|
+
fetchWallets(walletAnnounced: (walletInfo: WalletHandle<CurriedRPC<Methods>>) => void): void;
|
|
33
|
+
createAlwaysOnProvider(params: {
|
|
34
|
+
endpoint: string;
|
|
35
|
+
chainId: string;
|
|
36
|
+
prioritizeWalletProvider?: boolean;
|
|
37
|
+
requestsPerSecond?: number;
|
|
38
|
+
}): AlwaysOnProviderWrapper<CurriedRPC<Methods>>;
|
|
39
|
+
}
|
|
40
|
+
export declare function concatUint8Arrays(values: readonly Uint8Array[]): Uint8Array;
|
|
41
|
+
export declare function hashTextMessage(str: string): string;
|
|
42
|
+
export declare function fromMnemonicToHDKey(mnemonic: string, index: number): HDKey;
|
|
43
|
+
export declare class EthereumAccountGenerator implements AccountGenerator {
|
|
44
|
+
type: string;
|
|
45
|
+
fromMnemonicToAccount(mnemonic: string, index: number): PrivateKeyAccount;
|
|
46
|
+
signTextMessage(message: string, privateKey: `0x${string}`): `0x${string}`;
|
|
47
|
+
}
|
|
48
|
+
export declare class EthereumWalletProvider implements WalletProvider<CurriedRPC<Methods>> {
|
|
49
|
+
protected windowProvider: EIP1193WindowWalletProvider;
|
|
50
|
+
readonly underlyingProvider: CurriedRPC<Methods>;
|
|
51
|
+
constructor(windowProvider: EIP1193WindowWalletProvider);
|
|
52
|
+
signMessage(message: string, account: `0x${string}`): Promise<`0x${string}`>;
|
|
53
|
+
getChainId(): Promise<`0x${string}`>;
|
|
54
|
+
getAccounts(): Promise<`0x${string}`[]>;
|
|
55
|
+
requestAccounts(): Promise<`0x${string}`[]>;
|
|
56
|
+
listenForAccountsChanged(handler: (accounts: `0x${string}`[]) => void): void;
|
|
57
|
+
stopListenForAccountsChanged(handler: (accounts: `0x${string}`[]) => void): void;
|
|
58
|
+
listenForChainChanged(handler: (chainId: `0x${string}`) => void): void;
|
|
59
|
+
stopListenForChainChanged(handler: (chainId: `0x${string}`) => void): void;
|
|
60
|
+
switchChain(chainId: string): Promise<null | any>;
|
|
61
|
+
addChain(chainInfo: ChainInfo): Promise<null | any>;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { hashMessage } from './utils.js';
|
|
2
|
+
import { createProvider } from './provider.js';
|
|
3
|
+
import { createCurriedJSONRPC } from 'remote-procedure-call';
|
|
4
|
+
import { HDKey } from '@scure/bip32';
|
|
5
|
+
import { mnemonicToSeedSync } from '@scure/bip39';
|
|
6
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
7
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
8
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
9
|
+
import { getPublicKey } from '@noble/secp256k1';
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// TAKEN FROM https://github.com/paulmillr/micro-eth-signer/
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
const ethHexStartRe = /^0[xX]/;
|
|
14
|
+
export function strip0x(hex) {
|
|
15
|
+
return hex.replace(ethHexStartRe, '');
|
|
16
|
+
}
|
|
17
|
+
export function add0x(hex) {
|
|
18
|
+
return ethHexStartRe.test(hex) ? hex : `0x${hex}`;
|
|
19
|
+
}
|
|
20
|
+
export function astr(str) {
|
|
21
|
+
if (typeof str !== 'string')
|
|
22
|
+
throw new Error('string expected');
|
|
23
|
+
}
|
|
24
|
+
const RE = /^(0[xX])?([0-9a-fA-F]{40})?$/;
|
|
25
|
+
export function parse(address) {
|
|
26
|
+
astr(address);
|
|
27
|
+
const res = address.match(RE) || [];
|
|
28
|
+
const hasPrefix = res[1] != null;
|
|
29
|
+
const data = res[2];
|
|
30
|
+
if (!data) {
|
|
31
|
+
const len = hasPrefix ? 42 : 40;
|
|
32
|
+
throw new Error(`address must be ${len}-char hex, got ${address.length}-char ${address}`);
|
|
33
|
+
}
|
|
34
|
+
return { hasPrefix, data };
|
|
35
|
+
}
|
|
36
|
+
export function addChecksum(nonChecksummedAddress) {
|
|
37
|
+
const low = parse(nonChecksummedAddress).data.toLowerCase();
|
|
38
|
+
const hash = bytesToHex(keccak_256(low));
|
|
39
|
+
let checksummed = '';
|
|
40
|
+
for (let i = 0; i < low.length; i++) {
|
|
41
|
+
const hi = Number.parseInt(hash[i], 16);
|
|
42
|
+
const li = low[i];
|
|
43
|
+
checksummed += hi <= 7 ? li : li.toUpperCase(); // if char is 9-f, upcase it
|
|
44
|
+
}
|
|
45
|
+
return add0x(checksummed);
|
|
46
|
+
}
|
|
47
|
+
export function fromPublicKey(key) {
|
|
48
|
+
if (!key)
|
|
49
|
+
throw new Error('invalid public key: ' + key);
|
|
50
|
+
const pub65b = secp256k1.ProjectivePoint.fromHex(key).toRawBytes(false);
|
|
51
|
+
const hashed = keccak_256(pub65b.subarray(1, 65));
|
|
52
|
+
return addChecksum(bytesToHex(hashed).slice(24)); // slice 24..64
|
|
53
|
+
}
|
|
54
|
+
export function fromPrivateKey(key) {
|
|
55
|
+
if (typeof key === 'string')
|
|
56
|
+
key = strip0x(key);
|
|
57
|
+
return fromPublicKey(secp256k1.getPublicKey(key, false));
|
|
58
|
+
}
|
|
59
|
+
export class EthereumWalletConnector {
|
|
60
|
+
accountGenerator = new EthereumAccountGenerator();
|
|
61
|
+
fetchWallets(walletAnnounced) {
|
|
62
|
+
if (typeof window !== 'undefined') {
|
|
63
|
+
// const defaultProvider = (window as any).ethereum;
|
|
64
|
+
// console.log(defaultProvider);
|
|
65
|
+
// TODO ?
|
|
66
|
+
window.addEventListener('eip6963:announceProvider', (event) => {
|
|
67
|
+
const { detail } = event;
|
|
68
|
+
// const { info, provider } = detail;
|
|
69
|
+
// const { uuid, name, icon, rdns } = info;
|
|
70
|
+
// console.log('provider', provider);
|
|
71
|
+
// console.log(`isDefault: ${provider === defaultProvider}`);
|
|
72
|
+
// console.log('info', info);
|
|
73
|
+
walletAnnounced({
|
|
74
|
+
walletProvider: new EthereumWalletProvider(detail.provider),
|
|
75
|
+
info: detail.info,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
createAlwaysOnProvider(params) {
|
|
82
|
+
return createProvider(params);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function toHex(arr) {
|
|
86
|
+
let str = `0x`;
|
|
87
|
+
for (const element of arr) {
|
|
88
|
+
str += element.toString(16).padStart(2, '0');
|
|
89
|
+
}
|
|
90
|
+
return str;
|
|
91
|
+
}
|
|
92
|
+
export function concatUint8Arrays(values) {
|
|
93
|
+
let length = 0;
|
|
94
|
+
for (const arr of values) {
|
|
95
|
+
length += arr.length;
|
|
96
|
+
}
|
|
97
|
+
const result = new Uint8Array(length);
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const arr of values) {
|
|
100
|
+
result.set(arr, offset);
|
|
101
|
+
offset += arr.length;
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
const EIP191MessagePrefix = '\x19Ethereum Signed Message:\n';
|
|
106
|
+
const encoder = new TextEncoder();
|
|
107
|
+
export function hashTextMessage(str) {
|
|
108
|
+
const bytes = encoder.encode(str);
|
|
109
|
+
const prefixBytes = encoder.encode(`${EIP191MessagePrefix}${bytes.length}`);
|
|
110
|
+
const fullBytes = concatUint8Arrays([prefixBytes, bytes]);
|
|
111
|
+
return bytesToHex(keccak_256(fullBytes));
|
|
112
|
+
}
|
|
113
|
+
export function fromMnemonicToHDKey(mnemonic, index) {
|
|
114
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
115
|
+
const hd = HDKey.fromMasterSeed(seed);
|
|
116
|
+
return hd.derive(`m/44'/60'/0'/0/${index}`);
|
|
117
|
+
}
|
|
118
|
+
export class EthereumAccountGenerator {
|
|
119
|
+
type = 'ethereum';
|
|
120
|
+
fromMnemonicToAccount(mnemonic, index) {
|
|
121
|
+
const hdkey = fromMnemonicToHDKey(mnemonic, index);
|
|
122
|
+
if (!hdkey.privateKey) {
|
|
123
|
+
throw new Error(`invalid key`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
address: fromPrivateKey(hdkey.privateKey).toLowerCase(),
|
|
127
|
+
privateKey: `0x${bytesToHex(hdkey.privateKey)}`,
|
|
128
|
+
publicKey: toHex(getPublicKey(hdkey.privateKey)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
signTextMessage(message, privateKey) {
|
|
132
|
+
const hash = hashTextMessage(message);
|
|
133
|
+
const signature = secp256k1.sign(hash, privateKey.slice(2));
|
|
134
|
+
const r = signature.r;
|
|
135
|
+
const s = signature.s;
|
|
136
|
+
const v = signature.recovery ? 28n : 27n;
|
|
137
|
+
const yParity = signature.recovery;
|
|
138
|
+
let postfix = '';
|
|
139
|
+
if (v === 27n || yParity === 0) {
|
|
140
|
+
postfix = '1b';
|
|
141
|
+
}
|
|
142
|
+
else if (v === 28n || yParity === 1) {
|
|
143
|
+
postfix = '1c';
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
throw new Error('Invalid v value');
|
|
147
|
+
}
|
|
148
|
+
return `0x${new secp256k1.Signature(r, s).toCompactHex()}${postfix}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export class EthereumWalletProvider {
|
|
152
|
+
windowProvider;
|
|
153
|
+
underlyingProvider;
|
|
154
|
+
constructor(windowProvider) {
|
|
155
|
+
this.windowProvider = windowProvider;
|
|
156
|
+
this.underlyingProvider = createCurriedJSONRPC(windowProvider);
|
|
157
|
+
}
|
|
158
|
+
async signMessage(message, account) {
|
|
159
|
+
return this.underlyingProvider.request({
|
|
160
|
+
method: 'personal_sign',
|
|
161
|
+
params: [hashMessage(message), account],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async getChainId() {
|
|
165
|
+
return this.underlyingProvider.request({
|
|
166
|
+
method: 'eth_chainId',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async getAccounts() {
|
|
170
|
+
return this.underlyingProvider.request({
|
|
171
|
+
method: 'eth_accounts',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async requestAccounts() {
|
|
175
|
+
return this.underlyingProvider.request({
|
|
176
|
+
method: 'eth_requestAccounts',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
listenForAccountsChanged(handler) {
|
|
180
|
+
this.windowProvider.on('accountsChanged', handler);
|
|
181
|
+
}
|
|
182
|
+
stopListenForAccountsChanged(handler) {
|
|
183
|
+
this.windowProvider.removeListener('accountsChanged', handler);
|
|
184
|
+
}
|
|
185
|
+
listenForChainChanged(handler) {
|
|
186
|
+
this.windowProvider.on('chainChanged', handler);
|
|
187
|
+
}
|
|
188
|
+
stopListenForChainChanged(handler) {
|
|
189
|
+
this.windowProvider.removeListener('chainChanged', handler);
|
|
190
|
+
}
|
|
191
|
+
async switchChain(chainId) {
|
|
192
|
+
const result = await this.underlyingProvider.request({
|
|
193
|
+
method: 'wallet_switchEthereumChain',
|
|
194
|
+
params: [
|
|
195
|
+
{
|
|
196
|
+
chainId: ('0x' + parseInt(chainId).toString(16)),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
async addChain(chainInfo) {
|
|
203
|
+
const result = await this.underlyingProvider.request({
|
|
204
|
+
method: 'wallet_addEthereumChain',
|
|
205
|
+
params: [
|
|
206
|
+
{
|
|
207
|
+
chainId: chainInfo.chainId,
|
|
208
|
+
rpcUrls: chainInfo.rpcUrls,
|
|
209
|
+
chainName: chainInfo.chainName,
|
|
210
|
+
blockExplorerUrls: chainInfo.blockExplorerUrls,
|
|
211
|
+
iconUrls: chainInfo.iconUrls,
|
|
212
|
+
nativeCurrency: chainInfo.nativeCurrency,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
});
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Methods } from 'eip-1193';
|
|
2
|
+
import { CurriedRPC } from 'remote-procedure-call';
|
|
3
|
+
import { AlwaysOnProviderWrapper } from '@etherplay/wallet-connector';
|
|
4
|
+
export declare function createProvider(params: {
|
|
5
|
+
endpoint: string;
|
|
6
|
+
chainId: string;
|
|
7
|
+
prioritizeWalletProvider?: boolean;
|
|
8
|
+
requestsPerSecond?: number;
|
|
9
|
+
}): AlwaysOnProviderWrapper<CurriedRPC<Methods>>;
|
|
10
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsE,OAAO,EAAC,MAAM,UAAU,CAAC;AAC3G,OAAO,EAAuB,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAEvE,OAAO,EAAC,uBAAuB,EAAC,MAAM,6BAA6B,CAAC;AAoGpE,wBAAgB,cAAc,CAAC,MAAM,EAAE;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,uBAAuB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAE/C"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createCurriedJSONRPC } from 'remote-procedure-call';
|
|
2
|
+
import { withTimeout } from './utils.js';
|
|
3
|
+
const signerMethods = [
|
|
4
|
+
'eth_accounts',
|
|
5
|
+
'eth_sign',
|
|
6
|
+
'eth_signTransaction',
|
|
7
|
+
'personal_sign',
|
|
8
|
+
'eth_signTypedData_v4',
|
|
9
|
+
'eth_signTypedData',
|
|
10
|
+
];
|
|
11
|
+
const connectedAccountMethods = ['eth_sendTransaction'];
|
|
12
|
+
const walletOnlyMethods = ['eth_requestAccounts', 'wallet_switchEthereumChain', 'wallet_addEthereumChain'];
|
|
13
|
+
class AlwaysOnEthereumProviderWrapper {
|
|
14
|
+
chainId;
|
|
15
|
+
provider;
|
|
16
|
+
walletProvider;
|
|
17
|
+
jsonRPC;
|
|
18
|
+
status = 'disconnected';
|
|
19
|
+
constructor(params) {
|
|
20
|
+
const self = this;
|
|
21
|
+
this.chainId = params.chainId;
|
|
22
|
+
this.jsonRPC = createCurriedJSONRPC(params.endpoint);
|
|
23
|
+
const provider = {
|
|
24
|
+
async request(req) {
|
|
25
|
+
const signingMethod = signerMethods.includes(req.method) ||
|
|
26
|
+
connectedAccountMethods.includes(req.method) ||
|
|
27
|
+
walletOnlyMethods.includes(req.method) ||
|
|
28
|
+
req.method.indexOf('sign') != -1;
|
|
29
|
+
if (self.walletProvider) {
|
|
30
|
+
if (params.prioritizeWalletProvider || signingMethod) {
|
|
31
|
+
if (signingMethod) {
|
|
32
|
+
if (self.status !== 'connected') {
|
|
33
|
+
return Promise.reject({ message: 'wallet provider is not connected', code: 4001 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
let currentChainIdAsHex;
|
|
37
|
+
try {
|
|
38
|
+
currentChainIdAsHex = await withTimeout(self.walletProvider.request({
|
|
39
|
+
method: 'eth_chainId',
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (signingMethod) {
|
|
44
|
+
return Promise.reject(err);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// we fallback on jsonRPc if error while getting chain and not a signing method
|
|
48
|
+
return self.jsonRPC.request(req);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const currentChainId = Number(currentChainIdAsHex).toString();
|
|
52
|
+
if (self.chainId !== currentChainId) {
|
|
53
|
+
if (signingMethod) {
|
|
54
|
+
return Promise.reject({
|
|
55
|
+
message: `wallet provider is connected to a different chain, expected ${self.chainId} but got ${currentChainId}`,
|
|
56
|
+
code: 4001,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// we fallback on jsonRPc if invalid chain and not a signing method
|
|
61
|
+
return self.jsonRPC.request(req);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return self.walletProvider.request(req);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (signingMethod) {
|
|
68
|
+
return Promise.reject(new Error('wallet provider is not connected'));
|
|
69
|
+
}
|
|
70
|
+
return self.jsonRPC.request(req);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
this.provider = createCurriedJSONRPC(provider, { requestsPerSecond: params.requestsPerSecond });
|
|
74
|
+
}
|
|
75
|
+
setWalletProvider(walletProvider) {
|
|
76
|
+
this.walletProvider = walletProvider;
|
|
77
|
+
}
|
|
78
|
+
setWalletStatus(newStatus) {
|
|
79
|
+
this.status = newStatus;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function createProvider(params) {
|
|
83
|
+
return new AlwaysOnEthereumProviderWrapper(params);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps any promise with a timeout
|
|
3
|
+
* @param promise The promise to wrap with a timeout
|
|
4
|
+
* @param timeoutMs Timeout in milliseconds
|
|
5
|
+
* @param timeoutMessage Optional custom error message for timeout
|
|
6
|
+
* @returns A new promise that resolves/rejects with the original promise result or times out
|
|
7
|
+
*/
|
|
8
|
+
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs?: number, timeoutMessage?: string): Promise<T>;
|
|
9
|
+
export declare function hashMessage(message: string): `0x${string}`;
|
|
10
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,GAAE,MAAa,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CA0BjH;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,CAI1D"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps any promise with a timeout
|
|
4
|
+
* @param promise The promise to wrap with a timeout
|
|
5
|
+
* @param timeoutMs Timeout in milliseconds
|
|
6
|
+
* @param timeoutMessage Optional custom error message for timeout
|
|
7
|
+
* @returns A new promise that resolves/rejects with the original promise result or times out
|
|
8
|
+
*/
|
|
9
|
+
export function withTimeout(promise, timeoutMs = 5000, timeoutMessage) {
|
|
10
|
+
// Create a promise that rejects after the specified timeout
|
|
11
|
+
let id;
|
|
12
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
13
|
+
id = setTimeout(() => {
|
|
14
|
+
if (id) {
|
|
15
|
+
// console.log(`time out reached`);
|
|
16
|
+
clearTimeout(id);
|
|
17
|
+
id = undefined;
|
|
18
|
+
reject(new Error(timeoutMessage || `Promise timed out after ${timeoutMs}ms`));
|
|
19
|
+
}
|
|
20
|
+
}, timeoutMs);
|
|
21
|
+
});
|
|
22
|
+
promise.then((result) => {
|
|
23
|
+
if (id) {
|
|
24
|
+
clearTimeout(id);
|
|
25
|
+
id = undefined;
|
|
26
|
+
// console.log(`promise resolved in time`, result);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// console.log(`promise resolved too late`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
// Race the original promise against the timeout
|
|
33
|
+
return Promise.race([promise, timeoutPromise]);
|
|
34
|
+
}
|
|
35
|
+
const encoder = new TextEncoder();
|
|
36
|
+
export function hashMessage(message) {
|
|
37
|
+
const messageAsBytes = encoder.encode(message);
|
|
38
|
+
const msg = `0x${bytesToHex(messageAsBytes)}`;
|
|
39
|
+
return msg;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,YAAoB,IAAI,EAAE,cAAuB;IACpG,4DAA4D;IAC5D,IAAI,EAAsB,CAAC;IAC3B,MAAM,cAAc,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QACvD,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE;YACpB,IAAI,EAAE,EAAE,CAAC;gBACR,mCAAmC;gBACnC,YAAY,CAAC,EAAE,CAAC,CAAC;gBACjB,EAAE,GAAG,SAAS,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,IAAI,2BAA2B,SAAS,IAAI,CAAC,CAAC,CAAC;YAC/E,CAAC;QACF,CAAC,EAAE,SAAS,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACvB,IAAI,EAAE,EAAE,CAAC;YACR,YAAY,CAAC,EAAE,CAAC,CAAC;YACjB,EAAE,GAAG,SAAS,CAAC;YACf,mDAAmD;QACpD,CAAC;aAAM,CAAC;YACP,4CAA4C;QAC7C,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,MAAM,UAAU,WAAW,CAAC,OAAe;IAC1C,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,KAAK,UAAU,CAAC,cAAc,CAAC,EAAmB,CAAC;IAC/D,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@etherplay/wallet-connector-ethereum",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"as-soon": "^0.0.11",
|
|
21
|
+
"eip-1193": "^0.6.3",
|
|
22
|
+
"ldenv": "^0.3.12",
|
|
23
|
+
"prettier": "^3.5.3",
|
|
24
|
+
"typescript": "^5.8.3"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@noble/hashes": "^1.7.1",
|
|
28
|
+
"@scure/bip32": "^1.6.2",
|
|
29
|
+
"@scure/bip39": "^1.5.4",
|
|
30
|
+
"@noble/curves": "1.8.1",
|
|
31
|
+
"@noble/secp256k1": "^2.2.3",
|
|
32
|
+
"remote-procedure-call": "^0.1.1",
|
|
33
|
+
"@etherplay/wallet-connector": "0.0.2"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc --project tsconfig.json",
|
|
37
|
+
"dev": "as-soon -w src pnpm build",
|
|
38
|
+
"format:check": "prettier --check .",
|
|
39
|
+
"format:write": "prettier --write ."
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WalletConnector,
|
|
3
|
+
ChainInfo,
|
|
4
|
+
WalletProvider,
|
|
5
|
+
WalletHandle,
|
|
6
|
+
AlwaysOnProviderWrapper,
|
|
7
|
+
AccountGenerator,
|
|
8
|
+
PrivateKeyAccount,
|
|
9
|
+
} from '@etherplay/wallet-connector';
|
|
10
|
+
import type {EIP1193ChainId, EIP1193WindowWalletProvider, Methods} from 'eip-1193';
|
|
11
|
+
import {hashMessage} from './utils.js';
|
|
12
|
+
import {createProvider} from './provider.js';
|
|
13
|
+
import {createCurriedJSONRPC, CurriedRPC} from 'remote-procedure-call';
|
|
14
|
+
import {HDKey} from '@scure/bip32';
|
|
15
|
+
import {mnemonicToSeedSync} from '@scure/bip39';
|
|
16
|
+
import {bytesToHex} from '@noble/hashes/utils';
|
|
17
|
+
import {secp256k1} from '@noble/curves/secp256k1';
|
|
18
|
+
import {keccak_256} from '@noble/hashes/sha3';
|
|
19
|
+
import {getPublicKey} from '@noble/secp256k1';
|
|
20
|
+
|
|
21
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
22
|
+
// TAKEN FROM https://github.com/paulmillr/micro-eth-signer/
|
|
23
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
24
|
+
const ethHexStartRe = /^0[xX]/;
|
|
25
|
+
export function strip0x(hex: string): string {
|
|
26
|
+
return hex.replace(ethHexStartRe, '');
|
|
27
|
+
}
|
|
28
|
+
export function add0x(hex: string): string {
|
|
29
|
+
return ethHexStartRe.test(hex) ? hex : `0x${hex}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function astr(str: unknown) {
|
|
33
|
+
if (typeof str !== 'string') throw new Error('string expected');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const RE = /^(0[xX])?([0-9a-fA-F]{40})?$/;
|
|
37
|
+
export function parse(address: string) {
|
|
38
|
+
astr(address);
|
|
39
|
+
const res = address.match(RE) || [];
|
|
40
|
+
const hasPrefix = res[1] != null;
|
|
41
|
+
const data = res[2];
|
|
42
|
+
if (!data) {
|
|
43
|
+
const len = hasPrefix ? 42 : 40;
|
|
44
|
+
throw new Error(`address must be ${len}-char hex, got ${address.length}-char ${address}`);
|
|
45
|
+
}
|
|
46
|
+
return {hasPrefix, data};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function addChecksum(nonChecksummedAddress: string): string {
|
|
50
|
+
const low = parse(nonChecksummedAddress).data.toLowerCase();
|
|
51
|
+
const hash = bytesToHex(keccak_256(low));
|
|
52
|
+
let checksummed = '';
|
|
53
|
+
for (let i = 0; i < low.length; i++) {
|
|
54
|
+
const hi = Number.parseInt(hash[i], 16);
|
|
55
|
+
const li = low[i];
|
|
56
|
+
checksummed += hi <= 7 ? li : li.toUpperCase(); // if char is 9-f, upcase it
|
|
57
|
+
}
|
|
58
|
+
return add0x(checksummed);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function fromPublicKey(key: string | Uint8Array): string {
|
|
62
|
+
if (!key) throw new Error('invalid public key: ' + key);
|
|
63
|
+
const pub65b = secp256k1.ProjectivePoint.fromHex(key).toRawBytes(false);
|
|
64
|
+
const hashed = keccak_256(pub65b.subarray(1, 65));
|
|
65
|
+
return addChecksum(bytesToHex(hashed).slice(24)); // slice 24..64
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function fromPrivateKey(key: string | Uint8Array): string {
|
|
69
|
+
if (typeof key === 'string') key = strip0x(key);
|
|
70
|
+
return fromPublicKey(secp256k1.getPublicKey(key, false));
|
|
71
|
+
}
|
|
72
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
73
|
+
|
|
74
|
+
export type UnderlyingEthereumProvider = CurriedRPC<Methods>;
|
|
75
|
+
|
|
76
|
+
interface EIP6963ProviderInfo {
|
|
77
|
+
uuid: string;
|
|
78
|
+
name: string;
|
|
79
|
+
icon: string;
|
|
80
|
+
rdns: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface EIP6963ProviderDetail {
|
|
84
|
+
info: EIP6963ProviderInfo;
|
|
85
|
+
provider: EIP1193WindowWalletProvider;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface EIP6963AnnounceProviderEvent extends CustomEvent {
|
|
89
|
+
type: 'eip6963:announceProvider';
|
|
90
|
+
detail: EIP6963ProviderDetail;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class EthereumWalletConnector implements WalletConnector<CurriedRPC<Methods>> {
|
|
94
|
+
accountGenerator: AccountGenerator = new EthereumAccountGenerator();
|
|
95
|
+
fetchWallets(walletAnnounced: (walletInfo: WalletHandle<CurriedRPC<Methods>>) => void): void {
|
|
96
|
+
if (typeof window !== 'undefined') {
|
|
97
|
+
// const defaultProvider = (window as any).ethereum;
|
|
98
|
+
// console.log(defaultProvider);
|
|
99
|
+
// TODO ?
|
|
100
|
+
(window as any).addEventListener('eip6963:announceProvider', (event: EIP6963AnnounceProviderEvent) => {
|
|
101
|
+
const {detail} = event;
|
|
102
|
+
// const { info, provider } = detail;
|
|
103
|
+
// const { uuid, name, icon, rdns } = info;
|
|
104
|
+
// console.log('provider', provider);
|
|
105
|
+
// console.log(`isDefault: ${provider === defaultProvider}`);
|
|
106
|
+
// console.log('info', info);
|
|
107
|
+
walletAnnounced({
|
|
108
|
+
walletProvider: new EthereumWalletProvider(detail.provider),
|
|
109
|
+
info: detail.info,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
createAlwaysOnProvider(params: {
|
|
117
|
+
endpoint: string;
|
|
118
|
+
chainId: string;
|
|
119
|
+
prioritizeWalletProvider?: boolean;
|
|
120
|
+
requestsPerSecond?: number;
|
|
121
|
+
}): AlwaysOnProviderWrapper<CurriedRPC<Methods>> {
|
|
122
|
+
return createProvider(params);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function toHex(arr: Uint8Array): `0x${string}` {
|
|
127
|
+
let str = `0x`;
|
|
128
|
+
for (const element of arr) {
|
|
129
|
+
str += element.toString(16).padStart(2, '0');
|
|
130
|
+
}
|
|
131
|
+
return str as `0x${string}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function concatUint8Arrays(values: readonly Uint8Array[]): Uint8Array {
|
|
135
|
+
let length = 0;
|
|
136
|
+
for (const arr of values) {
|
|
137
|
+
length += arr.length;
|
|
138
|
+
}
|
|
139
|
+
const result = new Uint8Array(length);
|
|
140
|
+
let offset = 0;
|
|
141
|
+
for (const arr of values) {
|
|
142
|
+
result.set(arr, offset);
|
|
143
|
+
offset += arr.length;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const EIP191MessagePrefix = '\x19Ethereum Signed Message:\n';
|
|
149
|
+
const encoder = new TextEncoder();
|
|
150
|
+
|
|
151
|
+
export function hashTextMessage(str: string): string {
|
|
152
|
+
const bytes = encoder.encode(str);
|
|
153
|
+
const prefixBytes = encoder.encode(`${EIP191MessagePrefix}${bytes.length}`);
|
|
154
|
+
const fullBytes = concatUint8Arrays([prefixBytes, bytes]);
|
|
155
|
+
return bytesToHex(keccak_256(fullBytes));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function fromMnemonicToHDKey(mnemonic: string, index: number): HDKey {
|
|
159
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
160
|
+
const hd = HDKey.fromMasterSeed(seed);
|
|
161
|
+
return hd.derive(`m/44'/60'/0'/0/${index}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class EthereumAccountGenerator implements AccountGenerator {
|
|
165
|
+
type = 'ethereum';
|
|
166
|
+
fromMnemonicToAccount(mnemonic: string, index: number): PrivateKeyAccount {
|
|
167
|
+
const hdkey = fromMnemonicToHDKey(mnemonic, index);
|
|
168
|
+
if (!hdkey.privateKey) {
|
|
169
|
+
throw new Error(`invalid key`);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
address: fromPrivateKey(hdkey.privateKey).toLowerCase() as `0x${string}`,
|
|
173
|
+
privateKey: `0x${bytesToHex(hdkey.privateKey)}` as `0x${string}`,
|
|
174
|
+
publicKey: toHex(getPublicKey(hdkey.privateKey)),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
signTextMessage(message: string, privateKey: `0x${string}`): `0x${string}` {
|
|
178
|
+
const hash = hashTextMessage(message);
|
|
179
|
+
const signature = secp256k1.sign(hash, privateKey.slice(2));
|
|
180
|
+
const r = signature.r;
|
|
181
|
+
const s = signature.s;
|
|
182
|
+
const v = signature.recovery ? 28n : 27n;
|
|
183
|
+
const yParity = signature.recovery;
|
|
184
|
+
let postfix = '';
|
|
185
|
+
if (v === 27n || yParity === 0) {
|
|
186
|
+
postfix = '1b';
|
|
187
|
+
} else if (v === 28n || yParity === 1) {
|
|
188
|
+
postfix = '1c';
|
|
189
|
+
} else {
|
|
190
|
+
throw new Error('Invalid v value');
|
|
191
|
+
}
|
|
192
|
+
return `0x${new secp256k1.Signature(r, s).toCompactHex()}${postfix}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class EthereumWalletProvider implements WalletProvider<CurriedRPC<Methods>> {
|
|
197
|
+
public readonly underlyingProvider: CurriedRPC<Methods>;
|
|
198
|
+
constructor(protected windowProvider: EIP1193WindowWalletProvider) {
|
|
199
|
+
this.underlyingProvider = createCurriedJSONRPC<Methods>(windowProvider as any);
|
|
200
|
+
}
|
|
201
|
+
async signMessage(message: string, account: `0x${string}`): Promise<`0x${string}`> {
|
|
202
|
+
return this.underlyingProvider.request({
|
|
203
|
+
method: 'personal_sign',
|
|
204
|
+
params: [hashMessage(message), account],
|
|
205
|
+
}) as Promise<`0x${string}`>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getChainId(): Promise<`0x${string}`> {
|
|
209
|
+
return this.underlyingProvider.request({
|
|
210
|
+
method: 'eth_chainId',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getAccounts(): Promise<`0x${string}`[]> {
|
|
215
|
+
return this.underlyingProvider.request({
|
|
216
|
+
method: 'eth_accounts',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async requestAccounts(): Promise<`0x${string}`[]> {
|
|
220
|
+
return this.underlyingProvider.request({
|
|
221
|
+
method: 'eth_requestAccounts',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
listenForAccountsChanged(handler: (accounts: `0x${string}`[]) => void) {
|
|
226
|
+
this.windowProvider.on('accountsChanged', handler);
|
|
227
|
+
}
|
|
228
|
+
stopListenForAccountsChanged(handler: (accounts: `0x${string}`[]) => void) {
|
|
229
|
+
this.windowProvider.removeListener('accountsChanged', handler);
|
|
230
|
+
}
|
|
231
|
+
listenForChainChanged(handler: (chainId: `0x${string}`) => void) {
|
|
232
|
+
this.windowProvider.on('chainChanged', handler);
|
|
233
|
+
}
|
|
234
|
+
stopListenForChainChanged(handler: (chainId: `0x${string}`) => void) {
|
|
235
|
+
this.windowProvider.removeListener('chainChanged', handler);
|
|
236
|
+
}
|
|
237
|
+
async switchChain(chainId: string): Promise<null | any> {
|
|
238
|
+
const result = await this.underlyingProvider.request({
|
|
239
|
+
method: 'wallet_switchEthereumChain',
|
|
240
|
+
params: [
|
|
241
|
+
{
|
|
242
|
+
chainId: ('0x' + parseInt(chainId).toString(16)) as EIP1193ChainId,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
async addChain(chainInfo: ChainInfo): Promise<null | any> {
|
|
249
|
+
const result = await this.underlyingProvider.request({
|
|
250
|
+
method: 'wallet_addEthereumChain',
|
|
251
|
+
params: [
|
|
252
|
+
{
|
|
253
|
+
chainId: chainInfo.chainId,
|
|
254
|
+
rpcUrls: chainInfo.rpcUrls,
|
|
255
|
+
chainName: chainInfo.chainName,
|
|
256
|
+
blockExplorerUrls: chainInfo.blockExplorerUrls,
|
|
257
|
+
iconUrls: chainInfo.iconUrls,
|
|
258
|
+
nativeCurrency: chainInfo.nativeCurrency,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
}
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {EIP1193Provider, EIP1193WalletProvider, EIP1193WindowWalletProvider, Methods} from 'eip-1193';
|
|
2
|
+
import {createCurriedJSONRPC, CurriedRPC} from 'remote-procedure-call';
|
|
3
|
+
import {withTimeout} from './utils.js';
|
|
4
|
+
import {AlwaysOnProviderWrapper} from '@etherplay/wallet-connector';
|
|
5
|
+
|
|
6
|
+
const signerMethods = [
|
|
7
|
+
'eth_accounts',
|
|
8
|
+
'eth_sign',
|
|
9
|
+
'eth_signTransaction',
|
|
10
|
+
'personal_sign',
|
|
11
|
+
'eth_signTypedData_v4',
|
|
12
|
+
'eth_signTypedData',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const connectedAccountMethods = ['eth_sendTransaction'];
|
|
16
|
+
|
|
17
|
+
const walletOnlyMethods = ['eth_requestAccounts', 'wallet_switchEthereumChain', 'wallet_addEthereumChain'];
|
|
18
|
+
|
|
19
|
+
class AlwaysOnEthereumProviderWrapper implements AlwaysOnProviderWrapper<CurriedRPC<Methods>> {
|
|
20
|
+
public readonly chainId: string;
|
|
21
|
+
public readonly provider: CurriedRPC<Methods>;
|
|
22
|
+
private walletProvider?: CurriedRPC<Methods>;
|
|
23
|
+
private jsonRPC: CurriedRPC<Methods>;
|
|
24
|
+
private status: 'connected' | 'locked' | 'disconnected' = 'disconnected';
|
|
25
|
+
|
|
26
|
+
constructor(params: {
|
|
27
|
+
endpoint: string;
|
|
28
|
+
chainId: string;
|
|
29
|
+
prioritizeWalletProvider?: boolean;
|
|
30
|
+
requestsPerSecond?: number;
|
|
31
|
+
}) {
|
|
32
|
+
const self = this;
|
|
33
|
+
this.chainId = params.chainId;
|
|
34
|
+
this.jsonRPC = createCurriedJSONRPC<Methods>(params.endpoint);
|
|
35
|
+
|
|
36
|
+
const provider = {
|
|
37
|
+
async request(req: {method: string; params?: any[]}) {
|
|
38
|
+
const signingMethod =
|
|
39
|
+
signerMethods.includes(req.method) ||
|
|
40
|
+
connectedAccountMethods.includes(req.method) ||
|
|
41
|
+
walletOnlyMethods.includes(req.method) ||
|
|
42
|
+
req.method.indexOf('sign') != -1;
|
|
43
|
+
|
|
44
|
+
if (self.walletProvider) {
|
|
45
|
+
if (params.prioritizeWalletProvider || signingMethod) {
|
|
46
|
+
if (signingMethod) {
|
|
47
|
+
if (self.status !== 'connected') {
|
|
48
|
+
return Promise.reject({message: 'wallet provider is not connected', code: 4001});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let currentChainIdAsHex: string;
|
|
53
|
+
try {
|
|
54
|
+
currentChainIdAsHex = await withTimeout(
|
|
55
|
+
self.walletProvider.request({
|
|
56
|
+
method: 'eth_chainId',
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (signingMethod) {
|
|
61
|
+
return Promise.reject(err);
|
|
62
|
+
} else {
|
|
63
|
+
// we fallback on jsonRPc if error while getting chain and not a signing method
|
|
64
|
+
return self.jsonRPC.request(req as any);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const currentChainId = Number(currentChainIdAsHex).toString();
|
|
69
|
+
if (self.chainId !== currentChainId) {
|
|
70
|
+
if (signingMethod) {
|
|
71
|
+
return Promise.reject({
|
|
72
|
+
message: `wallet provider is connected to a different chain, expected ${self.chainId} but got ${currentChainId}`,
|
|
73
|
+
code: 4001,
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
// we fallback on jsonRPc if invalid chain and not a signing method
|
|
77
|
+
return self.jsonRPC.request(req as any);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return self.walletProvider.request(req as any);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (signingMethod) {
|
|
85
|
+
return Promise.reject(new Error('wallet provider is not connected'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return self.jsonRPC.request(req as any);
|
|
89
|
+
},
|
|
90
|
+
} as unknown as EIP1193Provider;
|
|
91
|
+
|
|
92
|
+
this.provider = createCurriedJSONRPC<Methods>(provider, {requestsPerSecond: params.requestsPerSecond});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setWalletProvider(walletProvider: CurriedRPC<Methods> | undefined) {
|
|
96
|
+
this.walletProvider = walletProvider;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setWalletStatus(newStatus: 'connected' | 'locked' | 'disconnected') {
|
|
100
|
+
this.status = newStatus;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createProvider(params: {
|
|
105
|
+
endpoint: string;
|
|
106
|
+
chainId: string;
|
|
107
|
+
prioritizeWalletProvider?: boolean;
|
|
108
|
+
requestsPerSecond?: number;
|
|
109
|
+
}): AlwaysOnProviderWrapper<CurriedRPC<Methods>> {
|
|
110
|
+
return new AlwaysOnEthereumProviderWrapper(params);
|
|
111
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {bytesToHex} from '@noble/hashes/utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps any promise with a timeout
|
|
5
|
+
* @param promise The promise to wrap with a timeout
|
|
6
|
+
* @param timeoutMs Timeout in milliseconds
|
|
7
|
+
* @param timeoutMessage Optional custom error message for timeout
|
|
8
|
+
* @returns A new promise that resolves/rejects with the original promise result or times out
|
|
9
|
+
*/
|
|
10
|
+
export function withTimeout<T>(promise: Promise<T>, timeoutMs: number = 5000, timeoutMessage?: string): Promise<T> {
|
|
11
|
+
// Create a promise that rejects after the specified timeout
|
|
12
|
+
let id: number | undefined;
|
|
13
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
14
|
+
id = setTimeout(() => {
|
|
15
|
+
if (id) {
|
|
16
|
+
// console.log(`time out reached`);
|
|
17
|
+
clearTimeout(id);
|
|
18
|
+
id = undefined;
|
|
19
|
+
reject(new Error(timeoutMessage || `Promise timed out after ${timeoutMs}ms`));
|
|
20
|
+
}
|
|
21
|
+
}, timeoutMs);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
promise.then((result) => {
|
|
25
|
+
if (id) {
|
|
26
|
+
clearTimeout(id);
|
|
27
|
+
id = undefined;
|
|
28
|
+
// console.log(`promise resolved in time`, result);
|
|
29
|
+
} else {
|
|
30
|
+
// console.log(`promise resolved too late`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Race the original promise against the timeout
|
|
35
|
+
return Promise.race([promise, timeoutPromise]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
|
|
40
|
+
export function hashMessage(message: string): `0x${string}` {
|
|
41
|
+
const messageAsBytes = encoder.encode(message);
|
|
42
|
+
const msg = `0x${bytesToHex(messageAsBytes)}` as `0x${string}`;
|
|
43
|
+
return msg;
|
|
44
|
+
}
|