@kheopskit/core 0.0.1-alpha.1 → 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.
@@ -1,103 +1,123 @@
1
1
  import { store } from "@/api/store";
2
- import type { PolkadotWallet } from "@/api/types";
3
- import { getWalletId, parseWalletId, type WalletId } from "@/utils/WalletId";
2
+ import type {
3
+ KheopskitConfig,
4
+ PolkadotInjectedWallet,
5
+ PolkadotWallet,
6
+ } from "@/api/types";
7
+ import { type WalletId, getWalletId, parseWalletId } from "@/utils/WalletId";
8
+ import { POLKADOT_EXTENSIONS } from "@/utils/polkadotExtensions";
4
9
  import { isEqual } from "lodash";
5
10
  import {
11
+ type InjectedExtension,
6
12
  connectInjectedExtension,
7
13
  getInjectedExtensions,
8
- type InjectedExtension,
9
14
  } from "polkadot-api/pjs-signer";
10
15
  import {
11
16
  BehaviorSubject,
17
+ Observable,
12
18
  combineLatest,
13
19
  distinctUntilChanged,
14
20
  map,
15
21
  mergeMap,
16
- Observable,
17
22
  of,
18
23
  shareReplay,
19
24
  timer,
20
25
  } from "rxjs";
26
+ import { getAppKitWallets$ } from "../appKit";
21
27
 
22
28
  const getInjectedWalletsIds = () =>
23
29
  getInjectedExtensions().map((name) => getWalletId("polkadot", name));
24
30
 
25
- const polkadotInjectedWallets$ = new Observable<PolkadotWallet[]>(
26
- (subscriber) => {
27
- const enabledExtensions$ = new BehaviorSubject<
28
- Map<WalletId, InjectedExtension>
29
- >(new Map());
31
+ export const polkadotInjectedWallets$ = new Observable<
32
+ PolkadotInjectedWallet[]
33
+ >((subscriber) => {
34
+ const enabledExtensions$ = new BehaviorSubject<
35
+ Map<WalletId, InjectedExtension>
36
+ >(new Map());
30
37
 
31
- const connect = async (walletId: WalletId) => {
32
- if (enabledExtensions$.value.has(walletId))
33
- throw new Error(`Extension ${walletId} already connected`);
34
- const { identifier } = parseWalletId(walletId);
35
- const extension = await connectInjectedExtension(identifier);
38
+ const connect = async (walletId: WalletId) => {
39
+ if (enabledExtensions$.value.has(walletId))
40
+ throw new Error(`Extension ${walletId} already connected`);
41
+ const { identifier } = parseWalletId(walletId);
42
+ const extension = await connectInjectedExtension(identifier);
36
43
 
37
- const newMap = new Map(enabledExtensions$.value);
38
- newMap.set(walletId, extension);
39
- enabledExtensions$.next(newMap);
44
+ const newMap = new Map(enabledExtensions$.value);
45
+ newMap.set(walletId, extension);
46
+ enabledExtensions$.next(newMap);
40
47
 
41
- store.addEnabledWalletId(walletId);
42
- };
48
+ store.addEnabledWalletId(walletId);
49
+ };
43
50
 
44
- const disconnect = (walletId: WalletId) => {
45
- if (!enabledExtensions$.value.has(walletId))
46
- throw new Error(`Extension ${walletId} is not connected`);
51
+ const disconnect = (walletId: WalletId) => {
52
+ if (!enabledExtensions$.value.has(walletId))
53
+ throw new Error(`Extension ${walletId} is not connected`);
47
54
 
48
- const newMap = new Map(enabledExtensions$.value);
49
- newMap.delete(walletId);
50
- enabledExtensions$.next(newMap);
55
+ const newMap = new Map(enabledExtensions$.value);
56
+ newMap.delete(walletId);
57
+ enabledExtensions$.next(newMap);
51
58
 
52
- store.removeEnabledWalletId(walletId);
53
- };
59
+ store.removeEnabledWalletId(walletId);
60
+ };
54
61
 
55
- const walletIds$ = of(0, 200, 500, 1000) // poll for wallets that register after page load
56
- .pipe(
57
- mergeMap((time) => timer(time)),
58
- map(() => getInjectedWalletsIds()),
59
- distinctUntilChanged<WalletId[]>(isEqual)
60
- );
62
+ const walletIds$ = of(0, 200, 500, 1000) // poll for wallets that inject after page load
63
+ .pipe(
64
+ mergeMap((time) => timer(time)),
65
+ map(() => getInjectedWalletsIds()),
66
+ distinctUntilChanged<WalletId[]>(isEqual),
67
+ );
61
68
 
62
- const subscription = combineLatest([walletIds$, enabledExtensions$])
63
- .pipe(
64
- map(([walletIds, enabledExtensions]) => {
65
- return walletIds.map((id): PolkadotWallet => {
66
- const { identifier } = parseWalletId(id);
67
- const extension = enabledExtensions.get(id);
69
+ const subscription = combineLatest([walletIds$, enabledExtensions$])
70
+ .pipe(
71
+ map(([walletIds, enabledExtensions]) => {
72
+ return walletIds.map((id): PolkadotInjectedWallet => {
73
+ const { identifier } = parseWalletId(id);
74
+ const extension = enabledExtensions.get(id);
75
+ const extInfo = POLKADOT_EXTENSIONS[identifier];
76
+
77
+ return {
78
+ id,
79
+ type: "injected",
80
+ platform: "polkadot",
81
+ name: extInfo?.name ?? identifier,
82
+ icon: extInfo?.icon ?? "",
83
+ extensionId: identifier,
84
+ extension,
85
+ isConnected: !!extension,
86
+ connect: () => connect(id),
87
+ disconnect: () => disconnect(id),
88
+ };
89
+ });
90
+ }),
91
+ )
92
+ .subscribe(subscriber);
93
+
94
+ return () => {
95
+ // console.log("Unsubscribing from polkadotInjectedWallets$");
96
+ subscription.unsubscribe();
97
+ };
98
+ }).pipe(
99
+ // logObservable("polkadotInjectedWallets$"),
100
+ shareReplay({ refCount: true, bufferSize: 1 }),
101
+ );
68
102
 
69
- return extension
70
- ? {
71
- id,
72
- platform: "polkadot",
73
- name: identifier,
74
- extensionId: identifier,
75
- isConnected: true,
76
- extension,
77
- disconnect: () => disconnect(id),
78
- }
79
- : {
80
- id,
81
- platform: "polkadot",
82
- name: identifier,
83
- extensionId: identifier,
84
- isConnected: false,
85
- connect: () => connect(id),
86
- };
87
- });
88
- })
103
+ export const getPolkadotWallets$ = (config: KheopskitConfig) => {
104
+ return new Observable<PolkadotWallet[]>((subscriber) => {
105
+ const subscription = combineLatest([
106
+ polkadotInjectedWallets$,
107
+ getAppKitWallets$(config)?.pipe(map((w) => w.polkadot)),
108
+ ])
109
+ .pipe(
110
+ map(([injectedWallets, appKitWallet]) =>
111
+ appKitWallet ? [...injectedWallets, appKitWallet] : injectedWallets,
112
+ ),
89
113
  )
90
114
  .subscribe(subscriber);
91
115
 
92
116
  return () => {
93
117
  subscription.unsubscribe();
94
118
  };
95
- }
96
- ).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
97
-
98
- // TODO merge with wallet connect
99
- export const polkadotWallets$ = polkadotInjectedWallets$;
100
-
101
- polkadotWallets$.subscribe(() => {
102
- console.count("[kheopskit] polkadotWallets$ emit");
103
- });
119
+ }).pipe(
120
+ // logObservable("getPolkadotWallets$"),
121
+ shareReplay({ refCount: true, bufferSize: 1 }),
122
+ );
123
+ };
package/src/api/store.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import { type WalletId, parseWalletId } from "@/utils/WalletId";
1
2
  import { createStore } from "@/utils/createStore";
2
- import type { KheopskitStoreData } from "./types";
3
3
  import { uniq } from "lodash";
4
- import { parseWalletId, type WalletId } from "@/utils/WalletId";
5
4
 
6
5
  const LOCAL_STORAGE_KEY = "kheopskit";
7
6
 
7
+ export type KheopskitStoreData = {
8
+ autoReconnect?: WalletId[];
9
+ };
10
+
8
11
  const DEFAULT_SETTINGS: KheopskitStoreData = {};
9
12
 
10
13
  const storage = createStore(LOCAL_STORAGE_KEY, DEFAULT_SETTINGS);
@@ -21,7 +24,7 @@ export const removeEnabledWalletId = (walletId: WalletId) => {
21
24
  storage.mutate((prev) => ({
22
25
  ...prev,
23
26
  autoReconnect: uniq(
24
- (prev.autoReconnect ?? []).filter((id) => id !== walletId)
27
+ (prev.autoReconnect ?? []).filter((id) => id !== walletId),
25
28
  ),
26
29
  }));
27
30
  };
package/src/api/types.ts CHANGED
@@ -1,47 +1,66 @@
1
+ import type { WalletAccountId } from "@/utils";
1
2
  import type { WalletId } from "@/utils/WalletId";
2
- import type { EIP1193Provider } from "viem";
3
+ import type { AppKit } from "@reown/appkit/core";
4
+ import type { AppKitNetwork } from "@reown/appkit/networks";
5
+ import type { Metadata } from "@walletconnect/universal-provider";
3
6
  import type {
4
7
  InjectedExtension,
5
8
  InjectedPolkadotAccount,
6
9
  } from "polkadot-api/pjs-signer";
7
- import type { WalletAccountId } from "@/utils";
8
-
9
- export type KheopskitStoreData = {
10
- autoReconnect?: WalletId[];
11
- };
10
+ import type {
11
+ Account,
12
+ CustomTransport,
13
+ EIP1193Provider,
14
+ WalletClient,
15
+ } from "viem";
12
16
 
13
17
  export type KheopskitConfig = {
14
- autoReconnect?: boolean;
18
+ autoReconnect: boolean;
15
19
  platforms: WalletPlatform[];
20
+ walletConnect?: {
21
+ projectId: string;
22
+ metadata: Metadata;
23
+ /** Defaults to wss://relay.walletconnect.com */
24
+ relayUrl?: string;
25
+ /**
26
+ * list of CAIP-13 ids of polkadot-sdk chains
27
+ * see https://docs.reown.com/advanced/multichain/polkadot/dapp-integration-guide#walletconnect-code%2Fcomponent-setup
28
+ */
29
+ networks: [AppKitNetwork, ...AppKitNetwork[]];
30
+ };
31
+ debug: boolean;
16
32
  };
17
33
 
18
- export type PolkadotDisabledInjectedWallet = {
34
+ export type PolkadotInjectedWallet = {
19
35
  id: WalletId;
20
36
  platform: "polkadot";
37
+ type: "injected";
21
38
  extensionId: string;
39
+ extension: InjectedExtension | undefined;
22
40
  name: string;
23
- isConnected: false;
41
+ icon: string;
42
+ isConnected: boolean;
24
43
  connect: () => Promise<void>;
44
+ disconnect: () => void;
25
45
  };
26
46
 
27
- export type PolkadotEnabledInjectedWallet = {
47
+ export type PolkadotAppKitWallet = {
28
48
  id: WalletId;
29
49
  platform: "polkadot";
30
- extensionId: string;
31
- extension: InjectedExtension;
50
+ type: "appKit";
51
+ appKit: AppKit;
32
52
  name: string;
33
- isConnected: true;
53
+ icon: string;
54
+ isConnected: boolean;
55
+ connect: () => Promise<void>;
34
56
  disconnect: () => void;
35
57
  };
36
58
 
37
- // TODO export type PolkadotWalletConnectWallet = {}
59
+ export type PolkadotWallet = PolkadotInjectedWallet | PolkadotAppKitWallet;
38
60
 
39
- export type PolkadotWallet =
40
- | PolkadotDisabledInjectedWallet
41
- | PolkadotEnabledInjectedWallet;
42
-
43
- export type EthereumWallet = {
61
+ export type EthereumInjectedWallet = {
44
62
  platform: "ethereum";
63
+ type: "injected";
45
64
  id: WalletId;
46
65
  providerId: string;
47
66
  provider: EIP1193Provider;
@@ -52,6 +71,20 @@ export type EthereumWallet = {
52
71
  disconnect: () => void;
53
72
  };
54
73
 
74
+ export type EthereumAppKitWallet = {
75
+ platform: "ethereum";
76
+ type: "appKit";
77
+ id: WalletId;
78
+ appKit: AppKit;
79
+ name: string;
80
+ icon: string;
81
+ isConnected: boolean;
82
+ connect: () => Promise<void>;
83
+ disconnect: () => void;
84
+ };
85
+
86
+ export type EthereumWallet = EthereumInjectedWallet | EthereumAppKitWallet;
87
+
55
88
  export type Wallet = PolkadotWallet | EthereumWallet;
56
89
 
57
90
  export type WalletPlatform = Wallet["platform"];
@@ -66,7 +99,7 @@ export type PolkadotAccount = InjectedPolkadotAccount & {
66
99
  export type EthereumAccount = {
67
100
  id: WalletAccountId;
68
101
  platform: "ethereum";
69
- provider: EIP1193Provider;
102
+ client: WalletClient<CustomTransport, undefined, Account, undefined>; // let consumer knows chain is unknown
70
103
  address: `0x${string}`;
71
104
  walletName: string;
72
105
  walletId: string;
@@ -1,51 +1,53 @@
1
+ import { sortWallets } from "@/utils/sortWallets";
1
2
  import {
3
+ Observable,
2
4
  combineLatest,
3
5
  distinct,
4
6
  filter,
5
7
  map,
6
8
  mergeMap,
7
- Observable,
8
9
  of,
9
10
  shareReplay,
10
11
  take,
11
12
  } from "rxjs";
12
- import type { ResolvedConfig } from "./config";
13
- import { ethereumWallets$ } from "./ethereum/wallets";
14
- import { polkadotWallets$ } from "./polkadot/wallets";
13
+ import { getEthereumWallets$ } from "./ethereum/wallets";
14
+ import { getPolkadotWallets$ } from "./polkadot/wallets";
15
15
  import { store } from "./store";
16
- import type { Wallet } from "./types";
16
+ import type { KheopskitConfig, Wallet } from "./types";
17
17
 
18
18
  // lock the list of wallets to auto reconnect on first call
19
19
  const autoReconnectWalletIds$ = store.observable.pipe(
20
20
  map((s) => s.autoReconnect ?? []),
21
21
  take(1),
22
- shareReplay(1)
22
+ shareReplay(1),
23
23
  );
24
24
 
25
- export const getWallets$ = (config: ResolvedConfig) => {
25
+ export const getWallets$ = (config: KheopskitConfig) => {
26
26
  return new Observable<Wallet[]>((subscriber) => {
27
27
  const observables = config.platforms.map<Observable<Wallet[]>>(
28
28
  (platform) => {
29
29
  switch (platform) {
30
30
  case "polkadot":
31
- return polkadotWallets$;
31
+ return getPolkadotWallets$(config);
32
32
  case "ethereum":
33
- return ethereumWallets$;
33
+ return getEthereumWallets$(config);
34
34
  }
35
- }
35
+ },
36
36
  );
37
37
 
38
38
  const wallets$ = observables.length
39
- ? combineLatest(observables).pipe(map((wallets) => wallets.flat()))
39
+ ? combineLatest(observables).pipe(
40
+ map((wallets) => wallets.flat().sort(sortWallets)),
41
+ )
40
42
  : of([]);
41
43
 
42
44
  const subAutoReconnect = combineLatest([wallets$, autoReconnectWalletIds$])
43
45
  .pipe(
44
46
  filter(([, walletIds]) => config.autoReconnect && !!walletIds?.length),
45
47
  mergeMap(([wallets, walletIds]) =>
46
- wallets.filter((wallet) => walletIds?.includes(wallet.id))
48
+ wallets.filter((wallet) => walletIds?.includes(wallet.id)),
47
49
  ),
48
- distinct((w) => w.id)
50
+ distinct((w) => w.id),
49
51
  )
50
52
  .subscribe(async (wallet) => {
51
53
  if (wallet.isConnected) {
package/src/index.ts CHANGED
@@ -1,3 +1 @@
1
1
  export * from "./api";
2
-
3
- export type { KheopskitConfig } from "./api/types";
@@ -6,7 +6,7 @@ export type WalletAccountId = string;
6
6
 
7
7
  export const getWalletAccountId = (
8
8
  walletId: string,
9
- address: SS58String
9
+ address: SS58String,
10
10
  ): WalletAccountId => {
11
11
  if (!walletId) throw new Error("Missing walletId");
12
12
  if (!isValidAddress(address)) throw new Error("Invalid address");
@@ -5,7 +5,7 @@ export type WalletId = string;
5
5
 
6
6
  export const getWalletId = (
7
7
  platform: WalletPlatform,
8
- identifier: string
8
+ identifier: string,
9
9
  ): WalletId => {
10
10
  if (!isWalletPlatform(platform)) throw new Error("Invalid platform");
11
11
  if (!identifier) throw new Error("Invalid name");
@@ -7,7 +7,7 @@ export const createStore = <T>(key: string, defaultValue: T) => {
7
7
  fromEvent<StorageEvent>(window, "storage")
8
8
  .pipe(
9
9
  filter((event) => event.key === key),
10
- map((event) => parseData(event.newValue, defaultValue))
10
+ map((event) => parseData(event.newValue, defaultValue)),
11
11
  )
12
12
  .subscribe((newValue) => subject.next(newValue));
13
13
 
@@ -1,6 +1,7 @@
1
1
  import { isEthereumAddress } from "./isEthereumAddress";
2
2
  import { isSs58Address } from "./isSs58Address";
3
- import type { AccountAddressType } from "./types";
3
+
4
+ export type AccountAddressType = "ss58" | "ethereum";
4
5
 
5
6
  export const getAccountAddressType = (address: string): AccountAddressType => {
6
7
  if (address.startsWith("0x")) {
@@ -0,0 +1,12 @@
1
+ import type { Observable } from "rxjs";
2
+
3
+ const CACHE = new Map<string, Observable<unknown>>();
4
+
5
+ export const getCachedObservable$ = <T, Obs = Observable<T>>(
6
+ key: string,
7
+ create: () => Obs,
8
+ ): Obs => {
9
+ if (!CACHE.has(key)) CACHE.set(key, create() as Observable<unknown>);
10
+
11
+ return CACHE.get(key) as Obs;
12
+ };
@@ -0,0 +1,72 @@
1
+ import { isEqual } from "lodash";
2
+ import {
3
+ BehaviorSubject,
4
+ Observable,
5
+ distinctUntilChanged,
6
+ shareReplay,
7
+ } from "rxjs";
8
+
9
+ import { getCachedObservable$ } from "./getCachedObservable";
10
+
11
+ export type QueryStatus = "loading" | "loaded" | "error";
12
+
13
+ export type QueryResult<
14
+ T,
15
+ S extends QueryStatus = "loading" | "loaded" | "error",
16
+ > = S extends "loading"
17
+ ? { status: "loading"; data: T | undefined; error: undefined }
18
+ : S extends "loaded"
19
+ ? { status: "loaded"; data: T; error: undefined }
20
+ : { status: "error"; data: undefined; error: unknown };
21
+
22
+ type QueryOptions<T> = {
23
+ queryKey: string;
24
+ queryFn: () => Promise<T>;
25
+ defaultValue?: T;
26
+ refreshInterval?: number;
27
+ };
28
+
29
+ export const getQuery$ = <T>({
30
+ queryKey,
31
+ queryFn,
32
+ defaultValue,
33
+ refreshInterval,
34
+ }: QueryOptions<T>): Observable<QueryResult<T>> => {
35
+ return getCachedObservable$(queryKey, () =>
36
+ new Observable<QueryResult<T>>((subscriber) => {
37
+ const result = new BehaviorSubject<QueryResult<T>>({
38
+ status: "loading",
39
+ data: defaultValue,
40
+ error: undefined,
41
+ });
42
+
43
+ // result subscription
44
+ const sub = result
45
+ .pipe(distinctUntilChanged<QueryResult<T>>(isEqual))
46
+ .subscribe(subscriber);
47
+
48
+ let timeout: ReturnType<typeof setTimeout> | undefined = undefined;
49
+
50
+ // fetch result subscription
51
+ const run = () => {
52
+ queryFn()
53
+ .then((data) => {
54
+ result.next({ status: "loaded", data, error: undefined });
55
+ })
56
+ .catch((error) => {
57
+ result.next({ status: "error", data: undefined, error });
58
+ })
59
+ .finally(() => {
60
+ if (refreshInterval) timeout = setTimeout(run, refreshInterval);
61
+ });
62
+ };
63
+
64
+ run();
65
+
66
+ return () => {
67
+ sub.unsubscribe();
68
+ if (timeout) clearTimeout(timeout);
69
+ };
70
+ }).pipe(shareReplay({ refCount: true, bufferSize: 1 })),
71
+ );
72
+ };
@@ -1,12 +1,10 @@
1
1
  // export all modules from this folder, except exports.ts
2
2
  export * from "./createStore";
3
3
  export * from "./getAccountAddressType";
4
- export * from "./AccountId";
4
+ export * from "./WalletAccountId";
5
5
  export * from "./isEthereumAddress";
6
6
  export * from "./isSs58Address";
7
- export * from "./isTruthy";
8
7
  export * from "./isValidAddress";
9
8
  export * from "./sleep";
10
9
  export * from "./throwAfter";
11
- export * from "./types";
12
10
  export * from "./isWalletPlatform";
@@ -1,2 +1,4 @@
1
+ import { isAddress } from "viem";
2
+
1
3
  export const isEthereumAddress = (address: string): boolean =>
2
- /^0x[a-fA-F0-9]{40}$/.test(address);
4
+ isAddress(address);
@@ -3,13 +3,13 @@ import { AccountId, type SS58String } from "polkadot-api";
3
3
  const accountIdEncoder = AccountId().enc;
4
4
 
5
5
  export const isSs58Address = (
6
- address: SS58String | string
6
+ address: SS58String | string,
7
7
  ): address is SS58String => {
8
8
  try {
9
9
  if (!address) return false;
10
10
  accountIdEncoder(address);
11
11
  return true;
12
- } catch (_err) {
12
+ } catch {
13
13
  return false;
14
14
  }
15
15
  };
@@ -1,7 +1,7 @@
1
1
  import type { WalletPlatform } from "@/api/types";
2
2
 
3
3
  export const isWalletPlatform = (
4
- platform: unknown
4
+ platform: unknown,
5
5
  ): platform is WalletPlatform =>
6
6
  typeof platform === "string" &&
7
7
  ["polkadot", "ethereum"].includes(platform as WalletPlatform);
@@ -0,0 +1,21 @@
1
+ import { type MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ type Opts = {
4
+ printValue?: boolean;
5
+ enabled?: boolean;
6
+ };
7
+
8
+ export const logObservable = <T>(
9
+ label: string,
10
+ opts?: Opts,
11
+ ): MonoTypeOperatorFunction<T> =>
12
+ tap((value) => {
13
+ const { printValue = false, enabled = true } = opts || {};
14
+
15
+ if (!label || !enabled) return;
16
+
17
+ const text = `[kheopskit] observable ${label} emit`;
18
+
19
+ if (printValue) console.debug(text, value);
20
+ else console.debug(text);
21
+ });
@@ -0,0 +1,21 @@
1
+ export const POLKADOT_EXTENSIONS: Record<
2
+ string,
3
+ { name: string; icon: string }
4
+ > = {
5
+ talisman: {
6
+ name: "Talisman",
7
+ icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgdmlld0JveD0iMCAwIDEyOCAxMjgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjZGRmZTc2IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wIDcwLjI1YzAgMjEuMjU1IDAgMzEuODgzIDQuNDYzIDM5Ljg1MmEzNSAzNSAwIDAgMCAxMy40MzUgMTMuNDM1QzI1Ljg2NyAxMjggMzYuNDk1IDEyOCA1Ny43NSAxMjhoMTIuNWMyMS4yNTUgMCAzMS44ODMgMCAzOS44NTItNC40NjNhMzUgMzUgMCAwIDAgMTMuNDM1LTEzLjQzNUMxMjggMTAyLjEzMyAxMjggOTEuNTA1IDEyOCA3MC4yNXYtMTIuNWMwLTIxLjI1NSAwLTMxLjg4My00LjQ2My0zOS44NTJhMzUgMzUgMCAwIDAtMTMuNDM1LTEzLjQzNUMxMDIuMTMzIDAgOTEuNTA1IDAgNzAuMjUgMGgtMTIuNUMzNi40OTUgMCAyNS44NjcgMCAxNy44OTggNC40NjNBMzUgMzUgMCAwIDAgNC40NjMgMTcuODk4QzAgMjUuODY3IDAgMzYuNDk1IDAgNTcuNzVaIi8+CiAgICA8cGF0aCBmaWxsPSIjZWE1NzUwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0zMy44NzkgMzUuMTE3LS41IDE5LjE2NWM4LjEwNyA0LjE2OCAxNS43NSA0LjA3NSAyNC43NCAyLjA2MyAzLjU2LTEuMzk3IDYuMDU2LTEuNzAyIDkuNTExIDAgOS4wNjcgMi44MTYgMTYuOTY5IDEuOTUgMjUuMTg1LTIuMjQzbC0uNDg1LTE5LjE4N2MwLTEwLjgwNS03LjAwNC0xNC45NjItMTQuNjMyLTEyLjczOS0uNzc5LjIzMi0xLjk0NCAxLjI3NC0xLjk0NCAyLjIwN2wtLjE4MSAxOC43MzNhMS43NyAxLjc3IDAgMSAxLTMuNTM4LS4wMTVWMjAuMDY3YTguODM4IDguODM4IDAgMCAwLTE3LjY3NSAwVjQzLjFhMS43NyAxLjc3IDAgMSAxLTMuNTM4LjAxNWwtLjE3Ni0xOC43NDNjMC0uOTIzLTEuMTA5LTEuOTYtMS44ODItMi4xOTItOC44LTIuNjEtMTQuODggMi41MzgtMTQuODggMTIuOTM2Wm0yLjQ3NSAyMy44NDNhNDguNDMgNDguNDMgMCAwIDEtNS4yMDktMi4yNTRjLTQuNzMtMi4yNjktMTIuMDk1LTEuNTYyLTE3LjA3MiA0LjExMS0yLjI3NCAyLjYtLjUxNSA2LjM2IDIuNzcgNy40NDggMS41ODMuNTI2IDMuMDE3IDEuNDEzIDQuMzUzIDIuNDA4bC40NjQuMzM2YzQuMTMyIDIuOTY1IDYuNzkzIDcuNDA2IDcuMDU2IDEyLjQ4NmwuMjUzIDQuODEyYTMxLjYxNiAzMS42MTYgMCAwIDAgMTkuNDI4IDI1Ljk1OSAzOC41OSAzOC41OSAwIDAgMCAyOS4zMjcgMCAzMS42MTYgMzEuNjE2IDAgMCAwIDE5LjQyOS0yNS45NTljLjA0Ni0uODI1LjA2MS0xLjY1LjA1MS0yLjQ2NWwuMTI0LTIuMzQ3Yy4yNjMtNS4wOCAyLjkyNC05LjUyIDcuMDU2LTEyLjQ4NmwuNDY0LS4zMzZjMS4zNC0uOTk1IDIuNzctMS44ODIgNC4zNTMtMi40MDggMy4yODUtMS4wODkgNS4wNS00Ljg0OSAyLjc3LTcuNDQ4LTQuOTc4LTUuNjczLTEyLjM0My02LjM3NS0xNy4wNzItNC4xMS0xLjcxOC44MjUtMy40MzUgMS42NS01LjIxIDIuMjUzbC0zLjYyIDEuMjM4LS4wMS4wNDFjLTYuNjU0IDEuODQyLTEyLjEyIDEuODQ3LTE4LjM5OC0uNzQyLTMuMTc3LTEuMzEtNi4zOC0xLjU1OC05LjQ4IDAtNS45NjcgMS44NTYtMTIuMDQ4IDIuNjQtMTguMjA2LjcwMWwtMy42MjYtMS4yMzhabTI2LjY2NSA0NC43MzJjMTMuMzkgMCAyNC4yNDEtMTUuNTk2IDI0LjI0MS0xNS41OTZTNzYuNDEgNzIuNDk5IDYzLjAyIDcyLjQ5OWMtMTMuMzg1IDAtMjQuMjM2IDE1LjU5Ny0yNC4yMzYgMTUuNTk3czEwLjg1MSAxNS41OTYgMjQuMjQgMTUuNTk2Wm0xMC44ODMtMTUuNTk2YzAgNi4wMS00Ljg3MiAxMC44ODItMTAuODgzIDEwLjg4Mi02LjAxIDAtMTAuODgyLTQuODcyLTEwLjg4Mi0xMC44ODJzNC44NzItMTAuODgzIDEwLjg4Mi0xMC44ODMgMTAuODgzIDQuODcyIDEwLjg4MyAxMC44ODNabS0xMC44ODMgNC45MzZhNC45MzYgNC45MzYgMCAxIDAgMC05Ljg3MiA0LjkzNiA0LjkzNiAwIDAgMCAwIDkuODcyWiIvPgo8L3N2Zz4K",
8
+ },
9
+ "polkadot-js": {
10
+ name: "Polkadot.js",
11
+ icon: "data:image/svg+xml;base64,ICA8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwNi4yIDEwNi4yIj4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iTGF5ZXJfMS0yIiBkYXRhLW5hbWU9IkxheWVyIDEiPgogICAgICAgIDxjaXJjbGUgY3g9IjUzLjEiIGN5PSI1My4xIiByPSI1My4xIiBmaWxsPSIjZjI5MjM1IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICBmaWxsPSIjZmZmIgogICAgICAgICAgZD0iTTU0LjQ3IDEzLjc2YTI4Ljg1IDI4Ljg1IDAgMDAtMjguNzMgMjguNzMgMjkuMzQgMjkuMzQgMCAwMDEuNTIgOS4zNCA0IDQgMCAxMDcuNDktMi41MkExOC42NyAxOC42NyAwIDAxMzMuNjMgNDJhMjAuNzIgMjAuNzIgMCAxMTIyIDIxLjMxcy00IC4yNS02IC40OWMtLjc0LjExLTEuNDguMjYtMi4yLjQ0YS4yOC4yOCAwIDAxLS4zOCAwIC4yNy4yNyAwIDAxMC0uMzJsLjYzLTMuNDEgMy43OS0xN2EzLjk0IDMuOTQgMCAxMC03LjcxLTEuNjVzLTkgNDEuNy05IDQyLjA4YTMuNzkgMy43OSAwIDAwMi43NCA0LjZoLjI4YTMuNzggMy43OCAwIDAwNC42MS0yLjcxLjQzLjQzIDAgMDAwLS4xMXYtLjE5Yy4xMS0uNDkgMS4yNS02IDEuMjUtNmExMC4yMyAxMC4yMyAwIDAxOC40Ni04Yy44Ny0uMTMgNC41My0uMzggNC41My0uMzhhMjguNzEgMjguNzEgMCAwMC0yLjExLTU3LjI3eiIKICAgICAgICAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICBmaWxsPSIjZmZmIgogICAgICAgICAgZD0iTTU2LjIxIDgwYTQuNzggNC43OCAwIDAwLTUuNjYgMy43MS4yNC4yNCAwIDAxMCAuMDggNC43NyA0Ljc3IDAgMDAzLjY1IDUuNjdoLjE0QTQuNyA0LjcgMCAwMDYwIDg2di0uMzJBNSA1IDAgMDA1Ni4yMSA4MHoiCiAgICAgICAgLz4KICAgICAgPC9nPgogICAgPC9nPgogIDwvc3ZnPg==",
12
+ },
13
+ "subwallet-js": {
14
+ name: "SubWallet",
15
+ icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNODAgNGM1Ny42MyAwIDc2IDE4LjM3IDc2IDc2IDAgNTcuNjMtMTguMzcgNzYtNzYgNzYtNTcuNjMgMC03Ni0xOC4zNy03Ni03NkM0IDIyLjM3IDIyLjM3IDQgODAgNFoiIGZpbGw9InVybCgjYSkiLz48ZyBjbGlwLXBhdGg9InVybCgjYikiPjxwYXRoIGQ9Ik0xMTIuNjE1IDY2LjcyVjUzLjM5OEw1OC43NiAzMiA0OCAzNy40MTJsLjA1NyA0MS40NjQgNDAuMjkyIDE2LjA3LTIxLjUyIDkuMDc1di03LjAxOEw1Ni45NSA5My4wM2wtOC44OTMgNC4xNjN2MjUuMzk1TDU4Ljc2OSAxMjhsNTMuODQ2LTI0LjA2MlY4Ni44NjlMNjQuMTU0IDY3LjY1N1Y1NmwzOC40NDkgMTUuMjE2IDEwLjAxMi00LjQ5NloiIGZpbGw9IiNmZmYiLz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iODAiIHkxPSI0IiB4Mj0iODAiIHkyPSIxNTYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjMDA0QkZGIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNENFQUFDIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImIiPjxwYXRoIGZpbGw9IiNmZmYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ4IDMyKSIgZD0iTTAgMGg2NC42MTV2OTZIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=",
16
+ },
17
+ enkrypt: {
18
+ name: "Enkrypt",
19
+ icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODEiIGhlaWdodD0iODEiIHZpZXdCb3g9IjAgMCA4MSA4MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNy4wMDU3IDE3LjAwNjJDMTguOTMwMyAxNS4wODE2IDIxLjU0MDUgMTQuMDAwNCAyNC4yNjIyIDE0LjAwMDRMNjcuMzI5NiAxNFYyMS44NzQxQzY3LjMyOTYgMjMuODMwNSA2Ni41NTIzIDI1LjcwNjcgNjUuMTY5IDI3LjA5QzYzLjc4NTcgMjguNDczMyA2MS45MDk1IDI5LjI1MDQgNTkuOTUzMiAyOS4yNTA0SDM5LjcwNDVDMzYuOTgyOCAyOS4yNTA0IDM0LjM3MjYgMzAuMzMxNiAzMi40NDggMzIuMjU2MUMzMC41MjM1IDM0LjE4MDcgMjkuNDQyMyAzNi43OTA5IDI5LjQ0MjMgMzkuNTEyNlY0Mi4xMjQyQzI5LjQ0MjMgNDQuODQ1OSAzMC41MjM1IDQ3LjQ1NjEgMzIuNDQ4IDQ5LjM4MDZDMzQuMzcyNiA1MS4zMDUxIDM2Ljk4MjggNTIuMzg2MyAzOS43MDQ1IDUyLjM4NjNINTkuOTUzMkM2MS45MDk1IDUyLjM4NjMgNjMuNzg1NyA1My4xNjM1IDY1LjE2OSA1NC41NDY4QzY2LjU1MjMgNTUuOTMwMSA2Ny4zMjk2IDU3LjgwNjMgNjcuMzI5NiA1OS43NjI2VjY3LjMzSDI0LjI2MjJDMjEuNTQwNSA2Ny4zMyAxOC45MzAzIDY2LjI0ODggMTcuMDA1NyA2NC4zMjQzQzE1LjA4MTIgNjIuMzk5NyAxNCA1OS43ODk1IDE0IDU3LjA2NzhWMjQuMjYyNkMxNCAyMS41NDA5IDE1LjA4MTIgMTguOTMwNyAxNy4wMDU3IDE3LjAwNjJaTTQwLjE0NzkgMzMuNTQyM0g2MC45MTU3QzY0LjQ1OCAzMy41NDIzIDY3LjMyOTUgMzYuNDEzOCA2Ny4zMjk1IDM5Ljk1NjFWNDEuNjgxNkM2Ny4zMjk1IDQ1LjIyMzggNjQuNDU4IDQ4LjA5NTQgNjAuOTE1NyA0OC4wOTU0SDQwLjE0NzlDMzYuNjA1NyA0OC4wOTU0IDMzLjczNDEgNDUuMjIzOCAzMy43MzQxIDQxLjY4MTZWMzkuOTU2MUMzMy43MzQxIDM2LjQxMzggMzYuNjA1NyAzMy41NDIzIDQwLjE0NzkgMzMuNTQyM1oiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8yODdfMjM1OSkiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODdfMjM1OSIgeDE9IjE5LjM2MDIiIHkxPSIxNCIgeDI9IjU2Ljc2OTYiIHkyPSI2OS44MDA1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNDNTQ5RkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjU0QkZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==",
20
+ },
21
+ };