@kheopskit/core 0.0.1-alpha.0
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 +7 -0
- package/dist/index.d.mts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +458 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +474 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
- package/src/api/accounts.ts +30 -0
- package/src/api/config.ts +12 -0
- package/src/api/ethereum/accounts.ts +86 -0
- package/src/api/ethereum/wallets.ts +103 -0
- package/src/api/index.ts +2 -0
- package/src/api/kheopskit.ts +23 -0
- package/src/api/polkadot/accounts.ts +71 -0
- package/src/api/polkadot/wallets.ts +103 -0
- package/src/api/store.ts +33 -0
- package/src/api/types.ts +92 -0
- package/src/api/wallets.ts +70 -0
- package/src/index.ts +3 -0
- package/src/utils/AccountId.ts +22 -0
- package/src/utils/WalletId.ts +21 -0
- package/src/utils/createStore.ts +45 -0
- package/src/utils/getAccountAddressType.ts +10 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/isEthereumAddress.ts +2 -0
- package/src/utils/isSs58Address.ts +15 -0
- package/src/utils/isTruthy.ts +3 -0
- package/src/utils/isValidAddress.ts +8 -0
- package/src/utils/isWalletPlatform.ts +7 -0
- package/src/utils/sleep.ts +2 -0
- package/src/utils/throwAfter.ts +6 -0
- package/src/utils/types.ts +5 -0
- package/tsconfig.json +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kheopskit/core",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.cjs",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"polkadot-api": ">=1.10.0",
|
|
23
|
+
"rxjs": ">=7.8.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"lodash": "^4.17.21",
|
|
27
|
+
"mipd": "^0.0.7",
|
|
28
|
+
"viem": "^2.29.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/lodash": "^4.17.16",
|
|
32
|
+
"polkadot-api": "^1.10.1",
|
|
33
|
+
"rxjs": "^7.8.2"
|
|
34
|
+
},
|
|
35
|
+
"tsup": {
|
|
36
|
+
"entry": [
|
|
37
|
+
"src/index.ts"
|
|
38
|
+
],
|
|
39
|
+
"splitting": false,
|
|
40
|
+
"sourcemap": true,
|
|
41
|
+
"clean": true,
|
|
42
|
+
"dts": true,
|
|
43
|
+
"format": [
|
|
44
|
+
"esm",
|
|
45
|
+
"cjs"
|
|
46
|
+
],
|
|
47
|
+
"target": "es2020"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
51
|
+
"dev": "tsup --watch",
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"clean": "rm -rf dist"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { combineLatest, map, Observable, of, shareReplay } from "rxjs";
|
|
2
|
+
import type { ResolvedConfig } from "./config";
|
|
3
|
+
import { ethereumAccounts$ } from "./ethereum/accounts";
|
|
4
|
+
import { polkadotAccounts$ } from "./polkadot/accounts";
|
|
5
|
+
import type { WalletAccount } from "./types";
|
|
6
|
+
|
|
7
|
+
export const getAccounts$ = (config: ResolvedConfig) => {
|
|
8
|
+
return new Observable<WalletAccount[]>((subscriber) => {
|
|
9
|
+
const observables = config.platforms.map<Observable<WalletAccount[]>>(
|
|
10
|
+
(platform) => {
|
|
11
|
+
switch (platform) {
|
|
12
|
+
case "polkadot":
|
|
13
|
+
return polkadotAccounts$;
|
|
14
|
+
case "ethereum":
|
|
15
|
+
return ethereumAccounts$;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const accounts$ = observables.length
|
|
21
|
+
? combineLatest(observables).pipe(map((accounts) => accounts.flat()))
|
|
22
|
+
: of([]);
|
|
23
|
+
|
|
24
|
+
const sub = accounts$.subscribe(subscriber);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
sub.unsubscribe();
|
|
28
|
+
};
|
|
29
|
+
}).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
30
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KheopskitConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
export type ResolvedConfig = Required<KheopskitConfig>;
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONFIG: ResolvedConfig = {
|
|
6
|
+
autoReconnect: true,
|
|
7
|
+
platforms: ["polkadot"],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const resolveConfig = (config: KheopskitConfig): ResolvedConfig => {
|
|
11
|
+
return Object.assign({}, DEFAULT_CONFIG, config);
|
|
12
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { EthereumWallet } from "@/api/types";
|
|
2
|
+
import { getAccountId, type AccountId } from "@/utils";
|
|
3
|
+
import {
|
|
4
|
+
combineLatest,
|
|
5
|
+
map,
|
|
6
|
+
Observable,
|
|
7
|
+
of,
|
|
8
|
+
shareReplay,
|
|
9
|
+
switchMap,
|
|
10
|
+
} from "rxjs";
|
|
11
|
+
import { getAddress, type EIP1193Provider } from "viem";
|
|
12
|
+
import { ethereumWallets$ } from "./wallets";
|
|
13
|
+
|
|
14
|
+
export type EthereumAccount = {
|
|
15
|
+
id: AccountId;
|
|
16
|
+
platform: "ethereum";
|
|
17
|
+
provider: EIP1193Provider;
|
|
18
|
+
address: `0x${string}`;
|
|
19
|
+
walletName: string;
|
|
20
|
+
walletId: string;
|
|
21
|
+
isWalletDefault: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getWalletAccounts$ = (
|
|
25
|
+
wallet: EthereumWallet
|
|
26
|
+
): Observable<EthereumAccount[]> => {
|
|
27
|
+
if (!wallet.isEnabled) return of([]);
|
|
28
|
+
|
|
29
|
+
return new Observable<EthereumAccount[]>((subscriber) => {
|
|
30
|
+
const getAccount = (address: string, i: number): EthereumAccount => ({
|
|
31
|
+
id: getAccountId(wallet.id, address),
|
|
32
|
+
platform: "ethereum",
|
|
33
|
+
provider: wallet.provider as EIP1193Provider,
|
|
34
|
+
address: getAddress(address),
|
|
35
|
+
walletName: wallet.name,
|
|
36
|
+
walletId: wallet.id,
|
|
37
|
+
isWalletDefault: i === 0,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const listener = (addresses: string[]) => {
|
|
41
|
+
subscriber.next(addresses.map(getAccount));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// subscribe to changes
|
|
45
|
+
wallet.provider.on("accountsChanged", listener);
|
|
46
|
+
|
|
47
|
+
// initial value
|
|
48
|
+
wallet.provider
|
|
49
|
+
.request({ method: "eth_accounts" })
|
|
50
|
+
.then((addresses: string[]) => {
|
|
51
|
+
subscriber.next(addresses.map(getAccount));
|
|
52
|
+
})
|
|
53
|
+
.catch((err) => {
|
|
54
|
+
console.error("Failed to get accounts", err);
|
|
55
|
+
subscriber.next([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
wallet.provider.removeListener("accountsChanged", listener);
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const ethereumAccounts$ = new Observable<EthereumAccount[]>(
|
|
65
|
+
(subscriber) => {
|
|
66
|
+
const sub = ethereumWallets$
|
|
67
|
+
.pipe(
|
|
68
|
+
map((wallets) => wallets.filter((w) => w.isEnabled)),
|
|
69
|
+
switchMap((wallets) =>
|
|
70
|
+
wallets.length
|
|
71
|
+
? combineLatest(wallets.map(getWalletAccounts$))
|
|
72
|
+
: of([])
|
|
73
|
+
),
|
|
74
|
+
map((accounts) => accounts.flat())
|
|
75
|
+
)
|
|
76
|
+
.subscribe(subscriber);
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
sub.unsubscribe();
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
83
|
+
|
|
84
|
+
ethereumAccounts$.subscribe(() => {
|
|
85
|
+
console.count("[kheopskit] ethereumAccounts$ emit");
|
|
86
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { store } from "@/api/store";
|
|
2
|
+
import type { EthereumWallet } from "@/api/types";
|
|
3
|
+
import { getWalletId, type WalletId } from "@/utils/WalletId";
|
|
4
|
+
import { createStore, type EIP6963ProviderDetail } from "mipd";
|
|
5
|
+
import {
|
|
6
|
+
BehaviorSubject,
|
|
7
|
+
combineLatest,
|
|
8
|
+
map,
|
|
9
|
+
Observable,
|
|
10
|
+
shareReplay,
|
|
11
|
+
} from "rxjs";
|
|
12
|
+
import type { EIP1193Provider } from "viem";
|
|
13
|
+
|
|
14
|
+
const providersDetails$ = new Observable<EIP6963ProviderDetail[]>(
|
|
15
|
+
(subscriber) => {
|
|
16
|
+
const store = createStore();
|
|
17
|
+
|
|
18
|
+
const unsubscribe = store.subscribe((providerDetails) => {
|
|
19
|
+
subscriber.next(providerDetails as EIP6963ProviderDetail[]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const providerDetails = store.getProviders();
|
|
23
|
+
|
|
24
|
+
subscriber.next(providerDetails as EIP6963ProviderDetail[]);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
unsubscribe();
|
|
28
|
+
store.destroy();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
32
|
+
|
|
33
|
+
providersDetails$.subscribe(() => {
|
|
34
|
+
console.count("[kheopskit] providers$ emit");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const ethereumWallets$ = new Observable<EthereumWallet[]>(
|
|
38
|
+
(subscriber) => {
|
|
39
|
+
const enabledWalletIds$ = new BehaviorSubject<Set<WalletId>>(new Set());
|
|
40
|
+
|
|
41
|
+
const connectWallet = async (
|
|
42
|
+
walletId: WalletId,
|
|
43
|
+
provider: EIP1193Provider
|
|
44
|
+
) => {
|
|
45
|
+
if (enabledWalletIds$.value.has(walletId))
|
|
46
|
+
throw new Error(`Extension ${walletId} already connected`);
|
|
47
|
+
|
|
48
|
+
provider.request({
|
|
49
|
+
method: "eth_requestAccounts",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const newSet = new Set(enabledWalletIds$.value);
|
|
53
|
+
newSet.add(walletId);
|
|
54
|
+
enabledWalletIds$.next(newSet);
|
|
55
|
+
|
|
56
|
+
store.addEnabledWalletId(walletId);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const disconnectWallet = async (
|
|
60
|
+
walletId: WalletId,
|
|
61
|
+
_provider: EIP1193Provider
|
|
62
|
+
) => {
|
|
63
|
+
if (!enabledWalletIds$.value.has(walletId))
|
|
64
|
+
throw new Error(`Extension ${walletId} is not connected`);
|
|
65
|
+
const newSet = new Set(enabledWalletIds$.value);
|
|
66
|
+
newSet.delete(walletId);
|
|
67
|
+
enabledWalletIds$.next(newSet);
|
|
68
|
+
|
|
69
|
+
store.removeEnabledWalletId(walletId);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const sub = combineLatest([providersDetails$, enabledWalletIds$])
|
|
73
|
+
.pipe(
|
|
74
|
+
map(([providerDetails, enabledWalletIds]) => {
|
|
75
|
+
return providerDetails.map((pd): EthereumWallet => {
|
|
76
|
+
const walletId = getWalletId("ethereum", pd.info.rdns);
|
|
77
|
+
const provider = pd.provider as EIP1193Provider;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
platform: "ethereum",
|
|
81
|
+
id: walletId,
|
|
82
|
+
name: pd.info.name,
|
|
83
|
+
icon: pd.info.icon,
|
|
84
|
+
provider,
|
|
85
|
+
isEnabled: enabledWalletIds.has(walletId),
|
|
86
|
+
providerId: pd.info.rdns,
|
|
87
|
+
connect: () => connectWallet(walletId, provider),
|
|
88
|
+
disconnect: () => disconnectWallet(walletId, provider),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
})
|
|
92
|
+
)
|
|
93
|
+
.subscribe(subscriber);
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
sub.unsubscribe();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
100
|
+
|
|
101
|
+
ethereumWallets$.subscribe(() => {
|
|
102
|
+
console.count("[kheopskit] ethereumWallets$ emit");
|
|
103
|
+
});
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { combineLatest, map, Observable, shareReplay } from "rxjs";
|
|
2
|
+
import { getAccounts$ } from "./accounts";
|
|
3
|
+
import { resolveConfig } from "./config";
|
|
4
|
+
import type { KheopskitConfig, Wallet, WalletAccount } from "./types";
|
|
5
|
+
import { getWallets$ } from "./wallets";
|
|
6
|
+
|
|
7
|
+
export type { KheopskitConfig } from "./types";
|
|
8
|
+
|
|
9
|
+
export const getKheopskit$ = (config: KheopskitConfig) => {
|
|
10
|
+
const c = resolveConfig(config);
|
|
11
|
+
|
|
12
|
+
return new Observable<{ wallets: Wallet[]; accounts: WalletAccount[] }>(
|
|
13
|
+
(subscriber) => {
|
|
14
|
+
const subscription = combineLatest([getWallets$(c), getAccounts$(c)])
|
|
15
|
+
.pipe(map(([wallets, accounts]) => ({ wallets, accounts })))
|
|
16
|
+
.subscribe(subscriber);
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
subscription.unsubscribe();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
).pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
|
23
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PolkadotWallet } from "@/api/types";
|
|
2
|
+
import { getAccountId, type AccountId } from "@/utils";
|
|
3
|
+
import type { InjectedPolkadotAccount } from "polkadot-api/pjs-signer";
|
|
4
|
+
import {
|
|
5
|
+
combineLatest,
|
|
6
|
+
map,
|
|
7
|
+
Observable,
|
|
8
|
+
of,
|
|
9
|
+
shareReplay,
|
|
10
|
+
switchMap,
|
|
11
|
+
} from "rxjs";
|
|
12
|
+
import { polkadotWallets$ } from "./wallets";
|
|
13
|
+
|
|
14
|
+
export type PolkadotAccount = InjectedPolkadotAccount & {
|
|
15
|
+
id: AccountId;
|
|
16
|
+
platform: "polkadot";
|
|
17
|
+
walletName: string;
|
|
18
|
+
walletId: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getWalletAccounts$ = (
|
|
22
|
+
wallet: PolkadotWallet
|
|
23
|
+
): Observable<PolkadotAccount[]> => {
|
|
24
|
+
if (!wallet.isEnabled) return of([]);
|
|
25
|
+
|
|
26
|
+
return new Observable<PolkadotAccount[]>((subscriber) => {
|
|
27
|
+
const getAccount = (account: InjectedPolkadotAccount): PolkadotAccount => ({
|
|
28
|
+
id: getAccountId(wallet.id, account.address),
|
|
29
|
+
...account,
|
|
30
|
+
platform: "polkadot",
|
|
31
|
+
walletName: wallet.name,
|
|
32
|
+
walletId: wallet.id,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// subscribe to changes
|
|
36
|
+
const unsubscribe = wallet.extension.subscribe((accounts) => {
|
|
37
|
+
subscriber.next(accounts.map(getAccount));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// initial value
|
|
41
|
+
subscriber.next(wallet.extension.getAccounts().map(getAccount));
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
return unsubscribe();
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const polkadotAccounts$ = new Observable<PolkadotAccount[]>(
|
|
50
|
+
(subscriber) => {
|
|
51
|
+
const sub = polkadotWallets$
|
|
52
|
+
.pipe(
|
|
53
|
+
map((wallets) => wallets.filter((w) => w.isEnabled)),
|
|
54
|
+
switchMap((wallets) =>
|
|
55
|
+
wallets.length
|
|
56
|
+
? combineLatest(wallets.map(getWalletAccounts$))
|
|
57
|
+
: of([])
|
|
58
|
+
),
|
|
59
|
+
map((accounts) => accounts.flat())
|
|
60
|
+
)
|
|
61
|
+
.subscribe(subscriber);
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
sub.unsubscribe();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
68
|
+
|
|
69
|
+
polkadotAccounts$.subscribe(() => {
|
|
70
|
+
console.count("[kheopskit] polkadotAccounts$ emit");
|
|
71
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { store } from "@/api/store";
|
|
2
|
+
import type { PolkadotWallet } from "@/api/types";
|
|
3
|
+
import { getWalletId, parseWalletId, type WalletId } from "@/utils/WalletId";
|
|
4
|
+
import { isEqual } from "lodash";
|
|
5
|
+
import {
|
|
6
|
+
connectInjectedExtension,
|
|
7
|
+
getInjectedExtensions,
|
|
8
|
+
type InjectedExtension,
|
|
9
|
+
} from "polkadot-api/pjs-signer";
|
|
10
|
+
import {
|
|
11
|
+
BehaviorSubject,
|
|
12
|
+
combineLatest,
|
|
13
|
+
distinctUntilChanged,
|
|
14
|
+
map,
|
|
15
|
+
mergeMap,
|
|
16
|
+
Observable,
|
|
17
|
+
of,
|
|
18
|
+
shareReplay,
|
|
19
|
+
timer,
|
|
20
|
+
} from "rxjs";
|
|
21
|
+
|
|
22
|
+
const getInjectedWalletsIds = () =>
|
|
23
|
+
getInjectedExtensions().map((name) => getWalletId("polkadot", name));
|
|
24
|
+
|
|
25
|
+
const polkadotInjectedWallets$ = new Observable<PolkadotWallet[]>(
|
|
26
|
+
(subscriber) => {
|
|
27
|
+
const enabledExtensions$ = new BehaviorSubject<
|
|
28
|
+
Map<WalletId, InjectedExtension>
|
|
29
|
+
>(new Map());
|
|
30
|
+
|
|
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);
|
|
36
|
+
|
|
37
|
+
const newMap = new Map(enabledExtensions$.value);
|
|
38
|
+
newMap.set(walletId, extension);
|
|
39
|
+
enabledExtensions$.next(newMap);
|
|
40
|
+
|
|
41
|
+
store.addEnabledWalletId(walletId);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const disconnect = (walletId: WalletId) => {
|
|
45
|
+
if (!enabledExtensions$.value.has(walletId))
|
|
46
|
+
throw new Error(`Extension ${walletId} is not connected`);
|
|
47
|
+
|
|
48
|
+
const newMap = new Map(enabledExtensions$.value);
|
|
49
|
+
newMap.delete(walletId);
|
|
50
|
+
enabledExtensions$.next(newMap);
|
|
51
|
+
|
|
52
|
+
store.removeEnabledWalletId(walletId);
|
|
53
|
+
};
|
|
54
|
+
|
|
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
|
+
);
|
|
61
|
+
|
|
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);
|
|
68
|
+
|
|
69
|
+
return extension
|
|
70
|
+
? {
|
|
71
|
+
id,
|
|
72
|
+
platform: "polkadot",
|
|
73
|
+
name: identifier,
|
|
74
|
+
extensionId: identifier,
|
|
75
|
+
isEnabled: true,
|
|
76
|
+
extension,
|
|
77
|
+
disconnect: () => disconnect(id),
|
|
78
|
+
}
|
|
79
|
+
: {
|
|
80
|
+
id,
|
|
81
|
+
platform: "polkadot",
|
|
82
|
+
name: identifier,
|
|
83
|
+
extensionId: identifier,
|
|
84
|
+
isEnabled: false,
|
|
85
|
+
connect: () => connect(id),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
.subscribe(subscriber);
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
subscription.unsubscribe();
|
|
94
|
+
};
|
|
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
|
+
});
|
package/src/api/store.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createStore } from "@/utils/createStore";
|
|
2
|
+
import type { KheopskitStoreData } from "./types";
|
|
3
|
+
import { uniq } from "lodash";
|
|
4
|
+
import { parseWalletId, type WalletId } from "@/utils/WalletId";
|
|
5
|
+
|
|
6
|
+
const LOCAL_STORAGE_KEY = "kheopskit";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SETTINGS: KheopskitStoreData = {};
|
|
9
|
+
|
|
10
|
+
const storage = createStore(LOCAL_STORAGE_KEY, DEFAULT_SETTINGS);
|
|
11
|
+
|
|
12
|
+
export const addEnabledWalletId = (walletId: WalletId) => {
|
|
13
|
+
parseWalletId(walletId); // validate walletId
|
|
14
|
+
storage.mutate((prev) => ({
|
|
15
|
+
...prev,
|
|
16
|
+
autoReconnect: uniq((prev.autoReconnect ?? []).concat(walletId)),
|
|
17
|
+
}));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const removeEnabledWalletId = (walletId: WalletId) => {
|
|
21
|
+
storage.mutate((prev) => ({
|
|
22
|
+
...prev,
|
|
23
|
+
autoReconnect: uniq(
|
|
24
|
+
(prev.autoReconnect ?? []).filter((id) => id !== walletId)
|
|
25
|
+
),
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const store = {
|
|
30
|
+
observable: storage.observable,
|
|
31
|
+
addEnabledWalletId,
|
|
32
|
+
removeEnabledWalletId,
|
|
33
|
+
};
|
package/src/api/types.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { WalletId } from "@/utils/WalletId";
|
|
2
|
+
import type { EIP1193Provider } from "viem";
|
|
3
|
+
import type {
|
|
4
|
+
InjectedAccount,
|
|
5
|
+
InjectedExtension,
|
|
6
|
+
PolkadotSigner,
|
|
7
|
+
} from "polkadot-api/pjs-signer";
|
|
8
|
+
import type { PolkadotAccount } from "./polkadot/accounts";
|
|
9
|
+
import type { EthereumAccount } from "./ethereum/accounts";
|
|
10
|
+
|
|
11
|
+
type AccountStorageBase = {
|
|
12
|
+
wallet: string;
|
|
13
|
+
address: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type EthereumAccountStorage = AccountStorageBase & {
|
|
17
|
+
platform: "ethereum";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PolkadotAccountStorage = AccountStorageBase & {
|
|
21
|
+
platform: "polkadot";
|
|
22
|
+
name: InjectedAccount["name"];
|
|
23
|
+
type: InjectedAccount["type"]; // required, right?
|
|
24
|
+
genesisHash: InjectedAccount["genesisHash"];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type AccountStorage = PolkadotAccountStorage | EthereumAccountStorage;
|
|
28
|
+
|
|
29
|
+
export type Account<T extends AccountStorage> = T & {
|
|
30
|
+
id: string;
|
|
31
|
+
signer: T extends PolkadotAccountStorage ? PolkadotSigner : null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type PlatformData<T extends AccountStorageBase> = {
|
|
35
|
+
enabledExtensionIds: string[];
|
|
36
|
+
accounts: T[];
|
|
37
|
+
defaultAccountId: string | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type KheopskitStoreData = {
|
|
41
|
+
// polkadot?: PlatformData<PolkadotAccountStorage>;
|
|
42
|
+
// ethereum?: PlatformData<EthereumAccountStorage>;
|
|
43
|
+
autoReconnect?: WalletId[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type KheopskitConfig = {
|
|
47
|
+
autoReconnect?: boolean;
|
|
48
|
+
platforms: WalletPlatform[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type PolkadotDisabledInjectedWallet = {
|
|
52
|
+
id: WalletId;
|
|
53
|
+
platform: "polkadot";
|
|
54
|
+
extensionId: string;
|
|
55
|
+
name: string;
|
|
56
|
+
isEnabled: false;
|
|
57
|
+
connect: () => Promise<void>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type PolkadotEnabledInjectedWallet = {
|
|
61
|
+
id: WalletId;
|
|
62
|
+
platform: "polkadot";
|
|
63
|
+
extensionId: string;
|
|
64
|
+
extension: InjectedExtension;
|
|
65
|
+
name: string;
|
|
66
|
+
isEnabled: true;
|
|
67
|
+
disconnect: () => void;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// TODO export type PolkadotWalletConnectWallet = {}
|
|
71
|
+
|
|
72
|
+
export type PolkadotWallet =
|
|
73
|
+
| PolkadotDisabledInjectedWallet
|
|
74
|
+
| PolkadotEnabledInjectedWallet;
|
|
75
|
+
|
|
76
|
+
export type EthereumWallet = {
|
|
77
|
+
platform: "ethereum";
|
|
78
|
+
id: WalletId;
|
|
79
|
+
providerId: string;
|
|
80
|
+
provider: EIP1193Provider;
|
|
81
|
+
name: string;
|
|
82
|
+
icon: string;
|
|
83
|
+
isEnabled: boolean;
|
|
84
|
+
connect: () => Promise<void>;
|
|
85
|
+
disconnect: () => void;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type Wallet = PolkadotWallet | EthereumWallet;
|
|
89
|
+
|
|
90
|
+
export type WalletPlatform = Wallet["platform"];
|
|
91
|
+
|
|
92
|
+
export type WalletAccount = PolkadotAccount | EthereumAccount;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combineLatest,
|
|
3
|
+
distinct,
|
|
4
|
+
filter,
|
|
5
|
+
map,
|
|
6
|
+
mergeMap,
|
|
7
|
+
Observable,
|
|
8
|
+
of,
|
|
9
|
+
shareReplay,
|
|
10
|
+
take,
|
|
11
|
+
} from "rxjs";
|
|
12
|
+
import type { ResolvedConfig } from "./config";
|
|
13
|
+
import { ethereumWallets$ } from "./ethereum/wallets";
|
|
14
|
+
import { polkadotWallets$ } from "./polkadot/wallets";
|
|
15
|
+
import { store } from "./store";
|
|
16
|
+
import type { Wallet } from "./types";
|
|
17
|
+
|
|
18
|
+
// lock the list of wallets to auto reconnect on first call
|
|
19
|
+
const autoReconnectWalletIds$ = store.observable.pipe(
|
|
20
|
+
map((s) => s.autoReconnect ?? []),
|
|
21
|
+
take(1),
|
|
22
|
+
shareReplay(1)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const getWallets$ = (config: ResolvedConfig) => {
|
|
26
|
+
return new Observable<Wallet[]>((subscriber) => {
|
|
27
|
+
const observables = config.platforms.map<Observable<Wallet[]>>(
|
|
28
|
+
(platform) => {
|
|
29
|
+
switch (platform) {
|
|
30
|
+
case "polkadot":
|
|
31
|
+
return polkadotWallets$;
|
|
32
|
+
case "ethereum":
|
|
33
|
+
return ethereumWallets$;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const wallets$ = observables.length
|
|
39
|
+
? combineLatest(observables).pipe(map((wallets) => wallets.flat()))
|
|
40
|
+
: of([]);
|
|
41
|
+
|
|
42
|
+
const subAutoReconnect = combineLatest([wallets$, autoReconnectWalletIds$])
|
|
43
|
+
.pipe(
|
|
44
|
+
filter(([, walletIds]) => config.autoReconnect && !!walletIds?.length),
|
|
45
|
+
mergeMap(([wallets, walletIds]) =>
|
|
46
|
+
wallets.filter((wallet) => walletIds?.includes(wallet.id))
|
|
47
|
+
),
|
|
48
|
+
distinct((w) => w.id)
|
|
49
|
+
)
|
|
50
|
+
.subscribe(async (wallet) => {
|
|
51
|
+
if (wallet.isEnabled) {
|
|
52
|
+
console.warn("Wallet %s already connected", wallet.id);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await wallet.connect();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error("Failed to reconnect wallet %s", wallet.id, { err });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const subWallets = wallets$.subscribe(subscriber);
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
subAutoReconnect.unsubscribe();
|
|
67
|
+
subWallets.unsubscribe();
|
|
68
|
+
};
|
|
69
|
+
}).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
70
|
+
};
|
package/src/index.ts
ADDED