@tomo-inc/inject-providers 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # @tomo-inc/inject-providers
2
+
3
+ ## 0.0.1
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @tomo-inc/inject-providers
2
+
3
+ Supply wallet standard APIs.
4
+
5
+ ## Data flow
6
+
7
+ browser extension/web dapp
8
+
9
+ ```mermaid
10
+ sequenceDiagram
11
+ participant dapp
12
+ participant provider
13
+ provider -->> dapp: inject API
14
+ dapp ->> provider: call API
15
+ provider -->> dapp: result
16
+ Note right of provider: approve for private key
17
+
18
+ provider ->> relay: check API + params, ask for use approve
19
+ relay -->> provider: result
20
+
21
+ relay ->> server API: request
22
+ server API -->> relay: result
23
+
24
+ ```
25
+
26
+ ## supported
27
+
28
+ ```
29
+ //chainType + wallet standard
30
+ doge: "doge",
31
+ evm: "metamask",
32
+ solana: "solana",
33
+ xxxx: "xxxx",
34
+ ```
35
+
36
+ ## how to use?
37
+
38
+ ### 1. namespaces + product info
39
+
40
+ file: /content/inject/setting
41
+
42
+ export providers
43
+
44
+ ### 2. onRequest/onResponse
45
+
46
+ 1. onProviderRequest: receive and deliver dapp request
47
+ 2. onResponse: send result back to dapp
48
+
49
+ ### 3. provider-in-wallet
50
+
51
+ 1. xxxRequest:XxxxRequest(chainType: string, data: any, getUserApprove?: any)
52
+ 2.
53
+
54
+ ```
55
+ import { IXxxxProvider, StateProvider } from "@/providers/xxxx/types";
56
+
57
+ export class XxxxService implements IXxxxProvider
58
+ ```
@@ -0,0 +1,37 @@
1
+ declare module "eth-rpc-errors" {
2
+ export class EthereumRpcError<T = any> extends Error {
3
+ code: number;
4
+ data?: T;
5
+ constructor(code: number, message: string, data?: T);
6
+ }
7
+
8
+ export interface EthereumProviderError<T = any> extends Error {
9
+ message: string;
10
+ code: number;
11
+ data?: T;
12
+ }
13
+
14
+ export const ethErrors: {
15
+ rpc: {
16
+ invalidInput: (opts?: { message?: string; data?: any }) => EthereumRpcError;
17
+ resourceNotFound: (opts?: { message?: string; data?: any }) => EthereumRpcError;
18
+ resourceUnavailable: (opts?: { message?: string; data?: any }) => EthereumRpcError;
19
+ transactionRejected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
20
+ methodNotSupported: (opts?: { message?: string; data?: any }) => EthereumRpcError;
21
+ limitExceeded: (opts?: { message?: string; data?: any }) => EthereumRpcError;
22
+ parse: (opts?: { message?: string; data?: any }) => EthereumRpcError;
23
+ invalidRequest: (opts?: { message?: string; data?: any }) => EthereumRpcError;
24
+ methodNotFound: (opts?: { message?: string; data?: any }) => EthereumRpcError;
25
+ invalidParams: (opts?: { message?: string; data?: any }) => EthereumRpcError;
26
+ internal: (opts?: { message?: string; data?: any }) => EthereumRpcError;
27
+ };
28
+ provider: {
29
+ userRejectedRequest: (opts?: { message?: string; data?: any }) => EthereumRpcError;
30
+ unauthorized: (opts?: { message?: string; data?: any }) => EthereumRpcError;
31
+ unsupportedMethod: (opts?: { message?: string; data?: any }) => EthereumRpcError;
32
+ disconnected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
33
+ chainDisconnected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
34
+ custom: (opts: { code: number; message: string; data?: any }) => EthereumRpcError;
35
+ };
36
+ };
37
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@tomo-inc/inject-providers",
3
+ "version": "0.0.1",
4
+ "author": "tomo.inc",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "@solana/web3.js": "^1.98.4",
20
+ "@tomo-inc/wallet-utils": "^0.0.3",
21
+ "@tronweb3/tronwallet-adapter-tronlink": "^1.1.12",
22
+ "bs58": "^6.0.0",
23
+ "eth-rpc-errors": "^2.1.1",
24
+ "events": "^3.3.0",
25
+ "json-rpc-engine": "^6.1.0",
26
+ "json-rpc-middleware-stream": "^3.0.0",
27
+ "tronweb": "^6.0.3",
28
+ "viem": "^2.38.3"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "@types/supertest": "^2.0.12",
33
+ "@vitest/browser": "^3.2.4",
34
+ "playwright": "^1.44.1",
35
+ "supertest": "^6.3.0",
36
+ "tsup": "^8.0.0",
37
+ "tsx": "^4.19.2",
38
+ "typescript": "^5.0.0",
39
+ "vitest": "^3.2.4"
40
+ }
41
+ }
package/project.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "inject-providers",
3
+ "sourceRoot": "packages/inject-providers/src",
4
+ "projectType": "library",
5
+ "targets": {
6
+ "build": {
7
+ "executor": "nx:run-commands",
8
+ "outputs": ["{projectRoot}/dist"],
9
+ "options": {
10
+ "command": "tsup src/index.ts --format esm,cjs --dts --treeshake",
11
+ "cwd": "packages/inject-providers"
12
+ }
13
+ },
14
+ "dev": {
15
+ "executor": "nx:run-commands",
16
+ "options": {
17
+ "command": "tsup src/index.ts --format esm,cjs --watch --dts",
18
+ "cwd": "packages/inject-providers"
19
+ }
20
+ },
21
+ "lint": {
22
+ "executor": "nx:run-commands",
23
+ "options": {
24
+ "command": "eslint src/**/*.ts",
25
+ "cwd": "packages/inject-providers"
26
+ }
27
+ },
28
+ "lint:fix": {
29
+ "executor": "nx:run-commands",
30
+ "options": {
31
+ "command": "eslint src/**/*.ts --fix",
32
+ "cwd": "packages/inject-providers"
33
+ }
34
+ },
35
+ "format": {
36
+ "executor": "nx:run-commands",
37
+ "options": {
38
+ "command": "prettier --write \"src/**/*.{ts,tsx}\"",
39
+ "cwd": "packages/inject-providers"
40
+ }
41
+ },
42
+ "test": {
43
+ "executor": "nx:run-commands",
44
+ "outputs": ["{projectRoot}/coverage"],
45
+ "options": {
46
+ "command": "vitest run",
47
+ "cwd": "packages/inject-providers"
48
+ }
49
+ },
50
+ "test:watch": {
51
+ "executor": "nx:run-commands",
52
+ "options": {
53
+ "command": "vitest",
54
+ "cwd": "packages/inject-providers"
55
+ }
56
+ }
57
+ },
58
+ "tags": ["npm:private", "scope:inject-providers", "type:library"]
59
+ }
@@ -0,0 +1,3 @@
1
+ import { BtcProvider } from "./unisat";
2
+
3
+ export { BtcProvider };
@@ -0,0 +1,38 @@
1
+ export enum TxType {
2
+ SIGN_TX,
3
+ SEND_BITCOIN,
4
+ SEND_INSCRIPTION,
5
+ SEND_DOGECOIN,
6
+ }
7
+
8
+ export type Network = "livenet" | "testnet";
9
+ export type ChainId = "BITCOIN_MAINNET" | "BITCOIN_TESTNET" | "FRACTAL_BITCOIN_MAINNET";
10
+
11
+ export type Balance = {
12
+ confirmed: number;
13
+ unconfirmed: number;
14
+ total: number;
15
+ };
16
+
17
+ export type IBtcProvider = {
18
+ connect: () => Promise<any>;
19
+ disconnect: () => Promise<any>;
20
+ requestAccounts: () => Promise<any>;
21
+ getAccounts: () => Promise<any>;
22
+ getPublicKey: () => Promise<any>;
23
+ getBalance: () => Promise<any>;
24
+ getTransactionStatus: (params: { txId: string }) => Promise<any>;
25
+ signMessage: (params: { text: string; type: string }) => Promise<any>;
26
+ getNetwork: () => Promise<any>;
27
+ switchNetwork: (network: Network) => Promise<any>;
28
+ getChain: () => Promise<any>;
29
+ switchChain: (chainId: any) => Promise<any>;
30
+ networkChanged?: (network: Network) => Promise<any>;
31
+ chainChanged?: (chainId: ChainId) => Promise<any>;
32
+ signPsbt: (psbtHex: string, options?: any) => Promise<any>;
33
+ signPsbts?: (psbtHexs: string[], options?: any[]) => Promise<any>;
34
+ signTx: (rawtx: string) => Promise<any>;
35
+ pushTx: (rawtx: string) => Promise<any>;
36
+ sendTransaction: (params: any) => Promise<any>;
37
+ sendBitcoin: (rawtx: string) => Promise<any>;
38
+ };
@@ -0,0 +1,332 @@
1
+ import { ethErrors } from "eth-rpc-errors";
2
+ import { EventEmitter } from "events";
3
+
4
+ import { ChainTypes, IConnectors, IProductInfo } from "../types";
5
+ import { ReadyPromise, domReadyCall, getDappInfo } from "../utils/index";
6
+ import { Balance, ChainId, Network } from "./types";
7
+
8
+ const chainType = ChainTypes.BTC;
9
+
10
+ interface StateProvider {
11
+ accounts: string[] | null;
12
+ isConnected: boolean;
13
+ isUnlocked: boolean;
14
+ initialized: boolean;
15
+ isPermanentlyDisconnected: boolean;
16
+ }
17
+
18
+ export class BtcProvider extends EventEmitter {
19
+ _selectedAddress: string | null = null;
20
+ _isConnected = false;
21
+ _initialized = false;
22
+ _isUnlocked = false;
23
+ rdns = "";
24
+ name = "";
25
+ chainId = "BITCOIN_MAINNET";
26
+ sendRequest: (chainType: string, data: any) => void;
27
+ onResponse: any;
28
+
29
+ _state: StateProvider = {
30
+ accounts: null,
31
+ isConnected: false,
32
+ isUnlocked: false,
33
+ initialized: false,
34
+ isPermanentlyDisconnected: false,
35
+ };
36
+
37
+ private _requestPromise = new ReadyPromise(0);
38
+
39
+ constructor(productInfo: IProductInfo, { sendRequest, onResponse }: IConnectors) {
40
+ super();
41
+ this.name = productInfo.name;
42
+ this.rdns = productInfo.rdns;
43
+ this._handleAccountsChanged = this._handleAccountsChanged.bind(this);
44
+ this.setMaxListeners(100);
45
+ this.sendRequest = sendRequest;
46
+ this.onResponse = onResponse;
47
+ this.initialize();
48
+ }
49
+
50
+ initialize = async () => {
51
+ // dapp tab switch send data;
52
+ document.addEventListener("visibilitychange", async () => {
53
+ if (document.visibilityState === "visible") {
54
+ this._request({
55
+ method: "wallet_sendDomainMetadata",
56
+ params: await getDappInfo(),
57
+ });
58
+ }
59
+ });
60
+
61
+ domReadyCall(async () => {
62
+ this._request({
63
+ method: "wallet_sendDomainMetadata",
64
+ params: await getDappInfo(),
65
+ });
66
+ });
67
+
68
+ window.addEventListener("message", (event: MessageEvent<any>) => {
69
+ const { data, type, method } = event.data;
70
+ if (type === "subscribeWalletEvents" && method) {
71
+ this.subscribeWalletEventsCallback({ data, method });
72
+ }
73
+ });
74
+
75
+ this._state.initialized = true;
76
+ this._state.isConnected = true;
77
+
78
+ this.keepAlive();
79
+ };
80
+
81
+ subscribeWalletEventsCallback = ({ method, data }: { method: string; data: any }) => {
82
+ if (method === "accountsChanged") {
83
+ const addresses = data?.[chainType] || [];
84
+ // addresses = addresses.filter((item) => item.subType === "default");
85
+ const accounts = addresses.map(({ address }: { address: string }) => address);
86
+ this._handleAccountsChanged(accounts);
87
+ }
88
+ if (method === "chainChanged") {
89
+ const { chainId, type }: { chainId: string; type: string } = data;
90
+ if (type.indexOf(`${chainType}:`) === 0) {
91
+ this._handleChainChanged({ chainId, isConnected: true });
92
+ }
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Sending a message to the extension to receive will keep the service worker alive.
98
+ */
99
+ private keepAlive = () => {
100
+ this._request({
101
+ method: "keepAlive",
102
+ params: {},
103
+ }).then((v) => {
104
+ setTimeout(() => {
105
+ this.keepAlive();
106
+ }, 1000);
107
+ });
108
+ };
109
+
110
+ private _handleChainChanged({ chainId, isConnected }: { chainId?: string; isConnected?: boolean } = {}) {
111
+ if (!chainId || typeof chainId !== "string" || !chainId.startsWith("0x")) {
112
+ return;
113
+ }
114
+
115
+ if (!isConnected || chainId === this.chainId || !this._state.initialized) {
116
+ return;
117
+ }
118
+
119
+ this.chainId = chainId;
120
+ this.emit("chainChanged", this.chainId);
121
+ }
122
+
123
+ private _handleAccountsChanged(accounts: string[]): void {
124
+ let _accounts = accounts;
125
+
126
+ if (!Array.isArray(accounts)) {
127
+ console.error("BTC: Received invalid accounts parameter.", accounts);
128
+ _accounts = [];
129
+ }
130
+
131
+ if (this._state.accounts !== _accounts) {
132
+ this._state.accounts = _accounts;
133
+ this._selectedAddress = _accounts[0];
134
+
135
+ // finally, after all state has been updated, emit the event
136
+ if (this._state.initialized) {
137
+ this.emit("accountsChanged", accounts);
138
+ }
139
+ }
140
+ }
141
+
142
+ //adapter data by reuqest function
143
+ _request = async ({ method, params }: { method: string; params?: any }, adapter?: any) => {
144
+ if (!method) {
145
+ throw ethErrors.rpc.invalidRequest();
146
+ }
147
+
148
+ //api call -> ("0: dapp -> injector -> content");
149
+ const dappInfo = await getDappInfo();
150
+ this.sendRequest(chainType, { method, params, dappInfo });
151
+
152
+ const connectFuns: Record<string, boolean> = {
153
+ getAccounts: true,
154
+ requestAccounts: true,
155
+ };
156
+ //check result by method
157
+ return this.onResponse({ method }).then((res: any) => {
158
+ const { data } = res || {};
159
+
160
+ if (method === "wallet_switchEthereumChain") {
161
+ this._handleChainChanged({ chainId: data, isConnected: true });
162
+ }
163
+ if (method === "connect" && data?.address) {
164
+ this._handleAccountsChanged([data?.address]);
165
+ }
166
+ if (method in connectFuns && connectFuns[method]) {
167
+ this._handleAccountsChanged(data || []);
168
+ }
169
+
170
+ //maybe need some adaptor for doge api
171
+ return adapter ? adapter(data) : data;
172
+ });
173
+ };
174
+
175
+ request = async ({ method, params }: { method: string; params: any }) => {
176
+ if (!method) {
177
+ throw ethErrors.rpc.invalidRequest();
178
+ }
179
+ return this._request({
180
+ method,
181
+ params,
182
+ });
183
+ };
184
+
185
+ //res = { address, approved, balance, publicKey }
186
+ connect = async () => {
187
+ return this._request({
188
+ method: "connect",
189
+ });
190
+ };
191
+
192
+ //res = { disconnected }
193
+ disconnect = async () => {
194
+ return this._request({
195
+ method: "disconnect",
196
+ });
197
+ };
198
+
199
+ //unisat public methods
200
+ requestAccounts = async (): Promise<string[]> => {
201
+ return this._request({
202
+ method: "requestAccounts",
203
+ });
204
+ };
205
+
206
+ getAccounts = async (): Promise<string[]> => {
207
+ return this._request({
208
+ method: "getAccounts",
209
+ });
210
+ };
211
+
212
+ getPublicKey = async () => {
213
+ return this._request({
214
+ method: "getPublicKey",
215
+ });
216
+ };
217
+
218
+ //res = { address, balance }
219
+ getBalance = async (): Promise<Balance> => {
220
+ return this._request({
221
+ method: "getBalance",
222
+ });
223
+ };
224
+
225
+ //res = {status === 'confirmed', confirmations > 1}
226
+ getTransactionStatus = async ({ txId }: { txId: string }) => {
227
+ return this._request({
228
+ method: "getTransactionStatus",
229
+ params: { txId },
230
+ });
231
+ };
232
+
233
+ signMessage = async (text: string, type?: "ecdsa" | "bip322-simple"): Promise<string> => {
234
+ return this._request({
235
+ method: "signMessage",
236
+ params: {
237
+ text,
238
+ type,
239
+ },
240
+ });
241
+ };
242
+
243
+ getNetwork = async () => {
244
+ return this._request({
245
+ method: "getNetwork",
246
+ });
247
+ };
248
+
249
+ switchNetwork = async (network: Network) => {
250
+ return this._request({
251
+ method: "switchNetwork",
252
+ params: {
253
+ network,
254
+ },
255
+ });
256
+ };
257
+
258
+ getChain = async () => {
259
+ return this._request({
260
+ method: "getChain",
261
+ });
262
+ };
263
+
264
+ switchChain = async (chainId: ChainId) => {
265
+ return this._request({
266
+ method: "switchChain",
267
+ params: {
268
+ chainId,
269
+ },
270
+ });
271
+ };
272
+
273
+ signPsbt = async (psbtHex: string, options?: any): Promise<string> => {
274
+ return this._request({
275
+ method: "signPsbt",
276
+ params: {
277
+ psbtHex,
278
+ options: {
279
+ autoFinalized: options?.autoFinalized,
280
+ },
281
+ },
282
+ });
283
+ };
284
+
285
+ signPsbts = async (psbtHexs: string[], options?: any[]): Promise<string[]> => {
286
+ return this._request({
287
+ method: "signPsbts",
288
+ params: {
289
+ psbtHexs,
290
+ options,
291
+ },
292
+ });
293
+ };
294
+
295
+ pushPsbt = async (psbtHex: string): Promise<string> => {
296
+ return this._request({
297
+ method: "pushPsbt",
298
+ params: {
299
+ psbtHex,
300
+ },
301
+ });
302
+ };
303
+
304
+ sendBitcoin = async (toAddress: string, satoshis?: number, options?: { feeRate: number }): Promise<string> => {
305
+ return this._request({
306
+ method: "sendBitcoin",
307
+ params: {
308
+ to: toAddress,
309
+ amount: satoshis,
310
+ feeRate: options?.feeRate,
311
+ },
312
+ });
313
+ };
314
+
315
+ signTx = async (rawtx: string) => {
316
+ return this._request({
317
+ method: "signTx",
318
+ params: {
319
+ rawtx,
320
+ },
321
+ });
322
+ };
323
+
324
+ pushTx = async (rawtx: string): Promise<string> => {
325
+ return this._request({
326
+ method: "pushTx",
327
+ params: {
328
+ rawtx,
329
+ },
330
+ });
331
+ };
332
+ }
package/src/const.ts ADDED
File without changes