@ksangkuk10/wallet-provider 1.7.14

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/README.md ADDED
@@ -0,0 +1,671 @@
1
+ # XPLA Wallet Provider
2
+
3
+ Library to make React dApps easier using XPLA Extension or XPLA Mobile.
4
+
5
+ ## Installation
6
+
7
+ Grab the latest version off [NPM](https://www.npmjs.com/package/@ksangkuk10/wallet-provider):
8
+
9
+ ```sh
10
+ npm i --save @ksangkuk10/wallet-provider
11
+ ```
12
+
13
+ # Basic Usage
14
+
15
+ First, please add `<meta name="xpla-wallet" />` on your html page.
16
+
17
+ Since then, browser extensions (e.g. XPLA chrome extension) will not attempt to connect in a Web app where this `<meta name="xpla-wallet">` tag is not found.
18
+
19
+ ```html
20
+ <html lang="en">
21
+ <head>
22
+ <meta name="xpla-wallet" />
23
+ </head>
24
+ </html>
25
+ ```
26
+
27
+ If you have used `react-router-dom`'s `<BrowserRouter>`, `useLocation()`, you can easily understand it.
28
+
29
+ ```jsx
30
+ import {
31
+ NetworkInfo,
32
+ WalletProvider,
33
+ WalletStatus,
34
+ getChainOptions,
35
+ } from '@ksangkuk10/wallet-provider';
36
+ import React from 'react';
37
+ import ReactDOM from 'react-dom';
38
+
39
+ // getChainOptions(): Promise<{ defaultNetwork, walletConnectChainIds }>
40
+ getChainOptions().then((chainOptions) => {
41
+ ReactDOM.render(
42
+ <WalletProvider {...chainOptions}>
43
+ <YOUR_APP />
44
+ </WalletProvider>,
45
+ document.getElementById('root'),
46
+ );
47
+ });
48
+ ```
49
+
50
+ First, you need to wrap your React App with the `<WalletProvider>` component.
51
+
52
+ ```jsx
53
+ import { useWallet } from '@ksangkuk10/wallet-provider';
54
+ import React from 'react';
55
+
56
+ function Component() {
57
+ const { status, network, wallets } = useWallet();
58
+
59
+ return (
60
+ <div>
61
+ <section>
62
+ <pre>
63
+ {JSON.stringify(
64
+ {
65
+ status,
66
+ network,
67
+ wallets,
68
+ },
69
+ null,
70
+ 2,
71
+ )}
72
+ </pre>
73
+ </section>
74
+ </div>
75
+ );
76
+ }
77
+ ```
78
+
79
+ Afterwards, you can use React Hooks such as `useWallet()`, `useConnectedWallet()` and `useLCDClient()` anywhere in your app.
80
+
81
+ # API
82
+
83
+ <details>
84
+
85
+ <summary><code>&lt;WalletProvider&gt;</code></summary>
86
+
87
+ ```jsx
88
+ import {
89
+ WalletProvider,
90
+ NetworkInfo,
91
+ ReadonlyWalletSession,
92
+ } from '@ksangkuk10/wallet-provider';
93
+
94
+ // network information
95
+ const mainnet: NetworkInfo = {
96
+ name: 'mainnet',
97
+ chainID: 'dimension-1',
98
+ lcd: 'https://lcd.xpla.net',
99
+ };
100
+
101
+ const testnet: NetworkInfo = {
102
+ name: 'testnet',
103
+ chainID: 'cube-1',
104
+ lcd: 'https://lcd.xpla.net',
105
+ };
106
+
107
+ // WalletConnect separates chainId by number.
108
+ // Currently TerraStation Mobile uses 0 as Testnet, 1 as Mainnet.
109
+ const walletConnectChainIds: Record<number, NetworkInfo> = {
110
+ 0: testnet,
111
+ 1: mainnet,
112
+ };
113
+
114
+ // ⚠️ If there is no special reason, use `getChainOptions()` instead of `walletConnectChainIds` above.
115
+
116
+ // Optional
117
+ // If you need to modify the modal, such as changing the design, you can put it in,
118
+ // and if you don't put the value in, there is a default modal.
119
+ async function createReadonlyWalletSession(): Promise<ReadonlyWalletSession> {
120
+ const terraAddress = prompt('YOUR XPLA ADDRESS');
121
+ return {
122
+ network: mainnet,
123
+ terraAddress,
124
+ };
125
+ }
126
+
127
+ // Optional
128
+ // WalletConnect Client option.
129
+ const connectorOpts: IWalletConnectOptions | undefined = undefined;
130
+ const pushServerOpts: IPushServerOptions | undefined = undefined;
131
+
132
+ // Optional
133
+ // Time to wait for the Chrome Extension window.isTerraExtensionAvailable.
134
+ // If not entered, wait for default 1000 * 3 miliseconds.
135
+ // If you reduce excessively, Session recovery of Chrome Extension may fail.
136
+ const waitingChromeExtensionInstallCheck: number | undefined = undefined;
137
+
138
+ ReactDOM.render(
139
+ <WalletProvider
140
+ defaultNetwork={mainnet}
141
+ walletConnectChainIds={walletConnectChainIds}
142
+ createReadonlyWalletSession={createReadonlyWalletSession}
143
+ connectorOpts={connectorOpts}
144
+ pushServerOpts={pushServerOpts}
145
+ waitingChromeExtensionInstallCheck={waitingChromeExtensionInstallCheck}
146
+ >
147
+ <YOUR_APP />
148
+ </WalletProvider>,
149
+ document.getElementById('root'),
150
+ );
151
+ ```
152
+
153
+ </details>
154
+
155
+ <details>
156
+
157
+ <summary><code>useWallet()</code></summary>
158
+
159
+ This is a React Hook that can receive all the information. (Other hooks are functions for the convenience of Wrapping
160
+ this `useWallet()`)
161
+
162
+ <!-- source packages/src/@ksangkuk10/use-wallet/useWallet.ts --pick "Wallet" -->
163
+
164
+ [packages/src/@ksangkuk10/use-wallet/useWallet.ts](packages/src/@ksangkuk10/use-wallet/useWallet.ts)
165
+
166
+ ````ts
167
+ export interface Wallet {
168
+ /**
169
+ * current client status
170
+ *
171
+ * this will be one of WalletStatus.INITIALIZING | WalletStatus.WALLET_NOT_CONNECTED | WalletStatus.WALLET_CONNECTED
172
+ *
173
+ * INITIALIZING = checking that the session and the chrome extension installation. (show the loading to users)
174
+ * WALLET_NOT_CONNECTED = there is no connected wallet (show the connect and install options to users)
175
+ * WALLET_CONNECTED = there is aconnected wallet (show the wallet info and disconnect button to users)
176
+ *
177
+ * @see Wallet#refetchStates
178
+ * @see WalletController#status
179
+ */
180
+ status: WalletStatus;
181
+ /**
182
+ * current selected network
183
+ *
184
+ * - if status is INITIALIZING or WALLET_NOT_CONNECTED = this will be the defaultNetwork
185
+ * - if status is WALLET_CONNECTED = this depends on the connected environment
186
+ *
187
+ * @see WalletProviderProps#defaultNetwork
188
+ * @see WalletController#network
189
+ */
190
+ network: NetworkInfo;
191
+ /**
192
+ * available connect types on the browser
193
+ *
194
+ * @see Wallet#connect
195
+ * @see WalletController#availableConnectTypes
196
+ */
197
+ availableConnectTypes: ConnectType[];
198
+ /**
199
+ * available connections includes identifier, name, icon
200
+ *
201
+ * @example
202
+ * ```
203
+ * const { availableConnections, connect } = useWallet()
204
+ *
205
+ * return (
206
+ * <div>
207
+ * {
208
+ * availableConnections.map(({type, identifier, name, icon}) => (
209
+ * <butotn key={`${type}:${identifier}`} onClick={() => connect(type, identifier)}>
210
+ * <img src={icon} /> {name}
211
+ * </button>
212
+ * ))
213
+ * }
214
+ * </div>
215
+ * )
216
+ * ```
217
+ */
218
+ availableConnections: Connection[];
219
+ /**
220
+ * current connected connection
221
+ */
222
+ connection: Connection | undefined;
223
+ /**
224
+ * connect to wallet
225
+ *
226
+ * @example
227
+ * ```
228
+ * const { status, availableConnectTypes, connect } = useWallet()
229
+ *
230
+ * return status === WalletStatus.WALLET_NOT_CONNECTED &&
231
+ * availableConnectTypes.includs(ConnectType.EXTENSION) &&
232
+ * <button onClick={() => connect(ConnectType.EXTENSION)}>
233
+ * Connct Chrome Extension
234
+ * </button>
235
+ * ```
236
+ *
237
+ * @see Wallet#availableConnectTypes
238
+ * @see WalletController#connect
239
+ */
240
+ connect: (
241
+ type?: ConnectType,
242
+ identifier?: string,
243
+ walletApp?: WalletApp | boolean,
244
+ ) => void;
245
+ /**
246
+ * manual connect to read only session
247
+ *
248
+ * @see Wallet#connectReadonly
249
+ */
250
+ connectReadonly: (xplaAddress: string, network: NetworkInfo) => void;
251
+ /**
252
+ * available install types on the browser
253
+ *
254
+ * in this time, this only contains [ConnectType.EXTENSION]
255
+ *
256
+ * @see Wallet#install
257
+ * @see WalletController#availableInstallTypes
258
+ */
259
+ availableInstallTypes: ConnectType[];
260
+ /**
261
+ * available installations includes identifier, name, icon, url
262
+ *
263
+ * @example
264
+ * ```
265
+ * const { availableInstallations } = useWallet()
266
+ *
267
+ * return (
268
+ * <div>
269
+ * {
270
+ * availableInstallations.map(({type, identifier, name, icon, url}) => (
271
+ * <a key={`${type}:${identifier}`} href={url}>
272
+ * <img src={icon} /> {name}
273
+ * </a>
274
+ * ))
275
+ * }
276
+ * </div>
277
+ * )
278
+ * ```
279
+ *
280
+ * @see Wallet#install
281
+ * @see WalletController#availableInstallations
282
+ */
283
+ availableInstallations: Installation[];
284
+ /**
285
+ * @deprecated Please use availableInstallations
286
+ *
287
+ * install for the connect type
288
+ *
289
+ * @example
290
+ * ```
291
+ * const { status, availableInstallTypes } = useWallet()
292
+ *
293
+ * return status === WalletStatus.WALLET_NOT_CONNECTED &&
294
+ * availableInstallTypes.includes(ConnectType.EXTENSION) &&
295
+ * <button onClick={() => install(ConnectType.EXTENSION)}>
296
+ * Install Extension
297
+ * </button>
298
+ * ```
299
+ *
300
+ * @see Wallet#availableInstallTypes
301
+ * @see WalletController#install
302
+ */
303
+ install: (type: ConnectType) => void;
304
+ /**
305
+ * connected wallets
306
+ *
307
+ * this will be like
308
+ * `[{ connectType: ConnectType.WALLETCONNECT, xplaAddress: 'XXXXXXXXX' }]`
309
+ *
310
+ * in this time, you can get only one wallet. `wallets[0]`
311
+ *
312
+ * @see WalletController#wallets
313
+ */
314
+ wallets: WalletInfo[];
315
+ /**
316
+ * disconnect
317
+ *
318
+ * @example
319
+ * ```
320
+ * const { status, disconnect } = useWallet()
321
+ *
322
+ * return status === WalletStatus.WALLET_CONNECTED &&
323
+ * <button onClick={() => disconnect()}>
324
+ * Disconnect
325
+ * </button>
326
+ * ```
327
+ */
328
+ disconnect: () => void;
329
+ /**
330
+ * reload the connected wallet states
331
+ *
332
+ * in this time, this only work on the ConnectType.EXTENSION
333
+ *
334
+ * @see WalletController#refetchStates
335
+ */
336
+ refetchStates: () => void;
337
+ /**
338
+ * @deprecated please use refetchStates(). this function will remove on next major update
339
+ */
340
+ recheckStatus: () => void;
341
+ /**
342
+ * support features of this connection
343
+ *
344
+ * @example
345
+ * ```
346
+ * const { supportFeatures } = useWallet()
347
+ *
348
+ * return (
349
+ * <div>
350
+ * {
351
+ * supportFeatures.has('post') &&
352
+ * <button onClick={post}>post</button>
353
+ * }
354
+ * {
355
+ * supportFeatures.has('cw20-token') &&
356
+ * <button onClick={addCW20Token}>add cw20 token</button>
357
+ * }
358
+ * </div>
359
+ * )
360
+ * ```
361
+ *
362
+ * This type is same as `import type { XplaWebExtensionFeatures } from '@ksangkuk10/web-extension-interface'`
363
+ */
364
+ supportFeatures: Set<
365
+ 'post' | 'sign' | 'sign-bytes' | 'cw20-token' | 'network'
366
+ >;
367
+ /**
368
+ * post transaction
369
+ *
370
+ * @example
371
+ * ```
372
+ * const { post } = useWallet()
373
+ *
374
+ * const callback = useCallback(async () => {
375
+ * try {
376
+ * const result: TxResult = await post({...CreateTxOptions})
377
+ * // DO SOMETHING...
378
+ * } catch (error) {
379
+ * if (error instanceof UserDenied) {
380
+ * // DO SOMETHING...
381
+ * } else {
382
+ * // DO SOMETHING...
383
+ * }
384
+ * }
385
+ * }, [])
386
+ * ```
387
+ *
388
+ * @param { CreateTxOptions } tx transaction data
389
+ * @param xplaAddress - does not work at this time. for the future extension
390
+ * @param walletApp - wallet type, default is XPLA Vault
391
+ *
392
+ * @return { Promise<TxResult> }
393
+ *
394
+ * @throws { UserDenied } user denied the tx
395
+ * @throws { CreateTxFailed } did not create txhash (error dose not broadcasted)
396
+ * @throws { TxFailed } created txhash (error broadcated)
397
+ * @throws { Timeout } user does not act anything in specific time
398
+ * @throws { TxUnspecifiedError } unknown error
399
+ *
400
+ * @see WalletController#post
401
+ */
402
+ post: (
403
+ tx: CreateTxOptions,
404
+ xplaAddress?: string,
405
+ walletApp?: WalletApp | boolean,
406
+ ) => Promise<TxResult>;
407
+ /**
408
+ * sign transaction
409
+ *
410
+ * @example
411
+ * ```
412
+ * const { sign } = useWallet()
413
+ *
414
+ * const callback = useCallback(async () => {
415
+ * try {
416
+ * const result: SignResult = await sign({...CreateTxOptions})
417
+ *
418
+ * // Broadcast SignResult
419
+ * const tx = result.result
420
+ *
421
+ * const lcd = new LCDClient({
422
+ * chainID: connectedWallet.network.chainID,
423
+ * URL: connectedWallet.network.lcd,
424
+ * })
425
+ *
426
+ * const txResult = await lcd.tx.broadcastSync(tx)
427
+ *
428
+ * // DO SOMETHING...
429
+ * } catch (error) {
430
+ * if (error instanceof UserDenied) {
431
+ * // DO SOMETHING...
432
+ * } else {
433
+ * // DO SOMETHING...
434
+ * }
435
+ * }
436
+ * }, [])
437
+ * ```
438
+ *
439
+ * @param { CreateTxOptions } tx transaction data
440
+ * @param xplaAddress - does not work at this time. for the future extension
441
+ * @param walletApp - wallet type, default is XPLA Vault
442
+ *
443
+ * @return { Promise<SignResult> }
444
+ *
445
+ * @throws { UserDenied } user denied the tx
446
+ * @throws { CreateTxFailed } did not create txhash (error dose not broadcasted)
447
+ * @throws { TxFailed } created txhash (error broadcated)
448
+ * @throws { Timeout } user does not act anything in specific time
449
+ * @throws { TxUnspecifiedError } unknown error
450
+ *
451
+ * @see WalletController#sign
452
+ */
453
+ sign: (
454
+ tx: CreateTxOptions & {
455
+ sequence?: number;
456
+ accountNumber?: number;
457
+ signMode?: SignMode;
458
+ },
459
+ xplaAddress?: string,
460
+ walletApp?: WalletApp | boolean,
461
+ ) => Promise<SignResult>;
462
+ /**
463
+ * sign any bytes
464
+ *
465
+ * @example
466
+ * ```
467
+ * const { signBytes } = useWallet()
468
+ *
469
+ * const BYTES = Buffer.from('hello world')
470
+ *
471
+ * const callback = useCallback(async () => {
472
+ * try {
473
+ * const { result }: SignBytesResult = await signBytes(BYTES)
474
+ *
475
+ * console.log(result.recid)
476
+ * console.log(result.signature)
477
+ * console.log(result.public_key)
478
+ *
479
+ * const verified: boolean = verifyBytes(BYTES, result)
480
+ * } catch (error) {
481
+ * if (error instanceof UserDenied) {
482
+ * // DO SOMETHING...
483
+ * } else {
484
+ * // DO SOMETHING...
485
+ * }
486
+ * }
487
+ * }, [])
488
+ * ```
489
+ *
490
+ * @param bytes
491
+ * @param xplaAddress - does not work at this time. for the future extension
492
+ * @param walletApp - wallet type, default is XPLA Vault
493
+ *
494
+ * @return { Promise<SignBytesResult> }
495
+ */
496
+ signBytes: (
497
+ bytes: Buffer,
498
+ xplaAddress?: string,
499
+ walletApp?: WalletApp | boolean,
500
+ ) => Promise<SignBytesResult>;
501
+ /**
502
+ * check if tokens are added on the extension
503
+ *
504
+ * @param chainID
505
+ * @param tokenAddrs cw20 token addresses
506
+ *
507
+ * @return token exists
508
+ *
509
+ * @see WalletController#hasCW20Tokens
510
+ */
511
+ hasCW20Tokens: (
512
+ chainID: string,
513
+ ...tokenAddrs: string[]
514
+ ) => Promise<{
515
+ [tokenAddr: string]: boolean;
516
+ }>;
517
+ /**
518
+ * request add token addresses to browser extension
519
+ *
520
+ * @param chainID
521
+ * @param tokenAddrs cw20 token addresses
522
+ *
523
+ * @return token exists
524
+ *
525
+ * @see WalletController#addCW20Tokens
526
+ */
527
+ addCW20Tokens: (
528
+ chainID: string,
529
+ ...tokenAddrs: string[]
530
+ ) => Promise<{
531
+ [tokenAddr: string]: boolean;
532
+ }>;
533
+ /**
534
+ * check if network is added on the extension
535
+ *
536
+ * @param network
537
+ *
538
+ * @return network exists
539
+ *
540
+ * @see WalletController#hasNetwork
541
+ */
542
+ hasNetwork: (network: Omit<NetworkInfo, 'name'>) => Promise<boolean>;
543
+ /**
544
+ * request add network to browser extension
545
+ *
546
+ * @param network
547
+ *
548
+ * @return network exists
549
+ *
550
+ * @see WalletController#addNetwork
551
+ */
552
+ addNetwork: (network: NetworkInfo) => Promise<boolean>;
553
+ /**
554
+ * Some mobile wallet emulates the behavior of chrome extension.
555
+ * It confirms that the current connection environment is such a wallet.
556
+ * (If you are running connect() by checking availableConnectType, you do not need to use this API.)
557
+ *
558
+ * @see WalletController#isChromeExtensionCompatibleBrowser
559
+ */
560
+ isChromeExtensionCompatibleBrowser: () => boolean;
561
+ }
562
+ ````
563
+
564
+ <!-- /source -->
565
+
566
+ </details>
567
+
568
+ <details>
569
+
570
+ <summary><code>useConnectedWallet()</code></summary>
571
+
572
+ ```jsx
573
+ import { useConnectedWallet } from '@ksangkuk10/wallet-provider'
574
+
575
+ function Component() {
576
+ const connectedWallet = useConnectedWallet()
577
+
578
+ const postTx = useCallback(async () => {
579
+ if (!connectedWallet) return
580
+
581
+ console.log('walletAddress is', connectedWallet.walletAddress)
582
+ console.log('network is', connectedWallet.network)
583
+ console.log('connectType is', connectedWallet.connectType)
584
+
585
+ const result = await connectedWallet.post({...})
586
+ }, [])
587
+
588
+ return (
589
+ <button disabled={!connectedWallet || !connectedWallet.availablePost} onClick={() => postTx}>
590
+ Post Tx
591
+ </button>
592
+ )
593
+ }
594
+ ```
595
+
596
+ </details>
597
+
598
+ <details>
599
+
600
+ <summary><code>useLCDClient()</code></summary>
601
+
602
+ ```jsx
603
+ import { useLCDClient } from '@ksangkuk10/wallet-provider';
604
+
605
+ function Component() {
606
+ const lcd = useLCDClient();
607
+
608
+ const [result, setResult] = useState('');
609
+
610
+ useEffect(() => {
611
+ lcd.treasury.taxRate().then((taxRate) => {
612
+ setResult(taxRate.toString());
613
+ });
614
+ }, []);
615
+
616
+ return <div>Result: {result}</div>;
617
+ }
618
+ ```
619
+
620
+ </details>
621
+
622
+ # Trouble-shooting guide
623
+
624
+ wallet-provider contains the original source codes in sourcemaps.
625
+
626
+ You can check `src/@ksangkuk10/wallet-provider/` in the Chrome Devtools / Sources Tab, and you can also use breakpoints
627
+ here for debug.
628
+
629
+ (It may not be visible depending on your development settings such as Webpack.)
630
+
631
+ # For Chrome Extension compatible wallet developers
632
+
633
+ <details>
634
+
635
+ <summary><code>Chrome Extension compatible wallet development guide</code></summary>
636
+
637
+ ### 1. Create dApp for test
638
+
639
+ There is the `dangerously__chromeExtensionCompatibleBrowserCheck` option to allow you to create a test environment for
640
+ wallet development.
641
+
642
+ By declaring the `dangerously__chromeExtensionCompatibleBrowserCheck`, you can make your wallet recognized as the chrome
643
+ extension.
644
+
645
+ ```jsx
646
+ <WalletProvider
647
+ dangerously__chromeExtensionCompatibleBrowserCheck={(userAgent) =>
648
+ /YourWallet/.test(userAgent)
649
+ }
650
+ >
651
+ ...
652
+ </WalletProvider>
653
+ ```
654
+
655
+ ### 2. Register your wallet as default allow
656
+
657
+ If your wallet has been developed,
658
+
659
+ Please send me your wallet App link (Testlight version is OK)
660
+
661
+ And send me Pull Request by modifying `DEFAULT_CHROME_EXTENSION_COMPATIBLE_BROWSER_CHECK` in
662
+ the `packages/src/@ksangkuk10/wallet-provider/env.ts` file. (or just make an issue is OK)
663
+
664
+ ```diff
665
+ export const DEFAULT_CHROME_EXTENSION_COMPATIBLE_BROWSER_CHECK = (userAgent: string) => {
666
+ - return /MathWallet\//.test(userAgent);
667
+ + return /MathWallet\//.test(userAgent) || /YourWallet/.test(userAgent);
668
+ }
669
+ ```
670
+
671
+ </details>
@@ -0,0 +1,16 @@
1
+ import { XplaWebExtensionFeatures } from '@ksangkuk10/web-extension-interface';
2
+ import { Connection, ConnectType, Installation, NetworkInfo, WalletInfo, WalletStatus } from '@ksangkuk10/use-wallet';
3
+ import React, { ReactNode } from 'react';
4
+ export interface StaticWalletProviderProps {
5
+ children: ReactNode;
6
+ defaultNetwork: NetworkInfo;
7
+ status?: WalletStatus;
8
+ availableConnectTypes?: ConnectType[];
9
+ availableInstallTypes?: ConnectType[];
10
+ availableConnections?: Connection[];
11
+ availableInstallations?: Installation[];
12
+ wallets?: WalletInfo[];
13
+ supportFeatures?: Set<XplaWebExtensionFeatures>;
14
+ connection?: Connection | undefined;
15
+ }
16
+ export declare function StaticWalletProvider({ children, defaultNetwork, status, availableConnectTypes, availableInstallTypes, availableConnections, availableInstallations, wallets, supportFeatures, connection, }: StaticWalletProviderProps): React.JSX.Element;