@hub3js/core 0.59.0 → 0.59.1-next.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.
Files changed (209) hide show
  1. package/dist/builders/action.d.ts +22 -0
  2. package/dist/builders/action.d.ts.map +1 -0
  3. package/dist/builders/mod.d.ts +5 -0
  4. package/dist/builders/mod.d.ts.map +1 -0
  5. package/dist/builders/namespace.d.ts +47 -0
  6. package/dist/builders/namespace.d.ts.map +1 -0
  7. package/dist/builders/provider.d.ts +37 -0
  8. package/dist/builders/provider.d.ts.map +1 -0
  9. package/dist/builders/types.d.ts +23 -0
  10. package/dist/builders/types.d.ts.map +1 -0
  11. package/dist/hub/helpers.d.ts +6 -0
  12. package/dist/hub/helpers.d.ts.map +1 -0
  13. package/dist/hub/hub.d.ts +44 -0
  14. package/dist/hub/hub.d.ts.map +1 -0
  15. package/dist/hub/mod.d.ts +9 -0
  16. package/dist/hub/mod.d.ts.map +1 -0
  17. package/dist/hub/namespaces/errors.d.ts +5 -0
  18. package/dist/hub/namespaces/errors.d.ts.map +1 -0
  19. package/dist/hub/namespaces/mod.d.ts +3 -0
  20. package/dist/hub/namespaces/mod.d.ts.map +1 -0
  21. package/dist/hub/namespaces/namespace.d.ts +148 -0
  22. package/dist/hub/namespaces/namespace.d.ts.map +1 -0
  23. package/dist/hub/namespaces/types.d.ts +36 -0
  24. package/dist/hub/namespaces/types.d.ts.map +1 -0
  25. package/dist/hub/provider/mod.d.ts +3 -0
  26. package/dist/hub/provider/mod.d.ts.map +1 -0
  27. package/dist/hub/provider/provider.d.ts +102 -0
  28. package/dist/hub/provider/provider.d.ts.map +1 -0
  29. package/dist/hub/provider/types.d.ts +28 -0
  30. package/dist/hub/provider/types.d.ts.map +1 -0
  31. package/dist/hub/store/events.d.ts +54 -0
  32. package/dist/hub/store/events.d.ts.map +1 -0
  33. package/dist/hub/store/extend.d.ts +14 -0
  34. package/dist/hub/store/extend.d.ts.map +1 -0
  35. package/dist/hub/store/hub.d.ts +11 -0
  36. package/dist/hub/store/hub.d.ts.map +1 -0
  37. package/dist/hub/store/mod.d.ts +8 -0
  38. package/dist/hub/store/mod.d.ts.map +1 -0
  39. package/dist/hub/store/mod.js +1 -1
  40. package/dist/hub/store/mod.js.map +2 -2
  41. package/dist/hub/store/namespaces.d.ts +38 -0
  42. package/dist/hub/store/namespaces.d.ts.map +1 -0
  43. package/dist/hub/store/providers.d.ts +84 -0
  44. package/dist/hub/store/providers.d.ts.map +1 -0
  45. package/dist/hub/store/selectors.d.ts +18 -0
  46. package/dist/hub/store/selectors.d.ts.map +1 -0
  47. package/dist/hub/store/store.d.ts +14 -0
  48. package/dist/hub/store/store.d.ts.map +1 -0
  49. package/dist/hub/types.d.ts +5 -0
  50. package/dist/hub/types.d.ts.map +1 -0
  51. package/dist/mod.d.ts +5 -0
  52. package/dist/mod.d.ts.map +1 -0
  53. package/dist/mod.js +1 -1
  54. package/dist/mod.js.map +4 -4
  55. package/dist/test-utils/fixtures.d.ts +4 -0
  56. package/dist/test-utils/fixtures.d.ts.map +1 -0
  57. package/dist/test-utils/mod.d.ts +2 -0
  58. package/dist/test-utils/mod.d.ts.map +1 -0
  59. package/dist/test-utils/mod.js +2 -0
  60. package/dist/test-utils/mod.js.map +7 -0
  61. package/dist/utils/mod.d.ts +3 -0
  62. package/dist/utils/mod.d.ts.map +1 -0
  63. package/dist/utils/mod.js +1 -1
  64. package/dist/utils/mod.js.map +4 -4
  65. package/dist/utils/versions.d.ts +50 -0
  66. package/dist/utils/versions.d.ts.map +1 -0
  67. package/package.json +9 -60
  68. package/src/builders/action.test.ts +225 -0
  69. package/src/builders/action.ts +1 -1
  70. package/src/builders/namespace.ts +1 -1
  71. package/src/builders/provider.ts +59 -19
  72. package/src/builders/types.ts +6 -4
  73. package/src/hub/hub.test.ts +108 -0
  74. package/src/hub/hub.ts +26 -9
  75. package/src/hub/mod.ts +2 -1
  76. package/src/hub/namespaces/namespace.ts +2 -2
  77. package/src/hub/namespaces/types.ts +1 -1
  78. package/src/hub/provider/mod.ts +1 -2
  79. package/src/hub/provider/provider.test.ts +64 -19
  80. package/src/hub/provider/provider.ts +43 -92
  81. package/src/hub/provider/types.ts +14 -31
  82. package/src/hub/store/mod.ts +1 -0
  83. package/src/hub/store/providers.ts +32 -18
  84. package/src/mod.ts +2 -21
  85. package/src/test-utils/mod.ts +1 -0
  86. package/src/utils/mod.ts +2 -8
  87. package/src/utils/versions.test.ts +0 -1
  88. package/src/utils/versions.ts +71 -44
  89. package/dist/legacy/mod.js +0 -2
  90. package/dist/legacy/mod.js.map +0 -7
  91. package/dist/namespaces/common/mod.js +0 -2
  92. package/dist/namespaces/common/mod.js.map +0 -7
  93. package/dist/namespaces/cosmos/mod.js +0 -2
  94. package/dist/namespaces/cosmos/mod.js.map +0 -7
  95. package/dist/namespaces/evm/mod.js +0 -2
  96. package/dist/namespaces/evm/mod.js.map +0 -7
  97. package/dist/namespaces/solana/mod.js +0 -2
  98. package/dist/namespaces/solana/mod.js.map +0 -7
  99. package/dist/namespaces/starknet/mod.js +0 -2
  100. package/dist/namespaces/starknet/mod.js.map +0 -7
  101. package/dist/namespaces/sui/mod.js +0 -2
  102. package/dist/namespaces/sui/mod.js.map +0 -7
  103. package/dist/namespaces/tron/mod.js +0 -2
  104. package/dist/namespaces/tron/mod.js.map +0 -7
  105. package/dist/namespaces/utxo/mod.js +0 -2
  106. package/dist/namespaces/utxo/mod.js.map +0 -7
  107. package/dist/namespaces/xrpl/mod.js +0 -2
  108. package/dist/namespaces/xrpl/mod.js.map +0 -7
  109. package/legacy/package.json +0 -8
  110. package/namespaces/common/package.json +0 -8
  111. package/namespaces/cosmos/package.json +0 -8
  112. package/namespaces/evm/package.json +0 -8
  113. package/namespaces/solana/package.json +0 -8
  114. package/namespaces/starknet/package.json +0 -8
  115. package/namespaces/sui/package.json +0 -8
  116. package/namespaces/tron/package.json +0 -8
  117. package/namespaces/utxo/package.json +0 -8
  118. package/namespaces/xrpl/package.json +0 -8
  119. package/src/legacy/helpers.ts +0 -75
  120. package/src/legacy/mod.ts +0 -44
  121. package/src/legacy/persistor.ts +0 -19
  122. package/src/legacy/types.ts +0 -303
  123. package/src/legacy/utils.ts +0 -20
  124. package/src/legacy/wallet.ts +0 -552
  125. package/src/namespaces/common/actions.ts +0 -12
  126. package/src/namespaces/common/after.ts +0 -8
  127. package/src/namespaces/common/and.ts +0 -42
  128. package/src/namespaces/common/before.ts +0 -9
  129. package/src/namespaces/common/builders.ts +0 -14
  130. package/src/namespaces/common/helpers.ts +0 -10
  131. package/src/namespaces/common/hooks/changeAccountSubscriber.test.ts +0 -173
  132. package/src/namespaces/common/hooks/changeAccountSubscriber.ts +0 -236
  133. package/src/namespaces/common/mod.ts +0 -23
  134. package/src/namespaces/common/or.ts +0 -17
  135. package/src/namespaces/common/types.ts +0 -27
  136. package/src/namespaces/common/utils.ts +0 -43
  137. package/src/namespaces/cosmos/actions.ts +0 -3
  138. package/src/namespaces/cosmos/after.ts +0 -3
  139. package/src/namespaces/cosmos/and.ts +0 -5
  140. package/src/namespaces/cosmos/before.ts +0 -3
  141. package/src/namespaces/cosmos/builders.ts +0 -15
  142. package/src/namespaces/cosmos/constants.ts +0 -1
  143. package/src/namespaces/cosmos/mod.ts +0 -15
  144. package/src/namespaces/cosmos/types.ts +0 -31
  145. package/src/namespaces/cosmos/utils.ts +0 -21
  146. package/src/namespaces/evm/actions.ts +0 -134
  147. package/src/namespaces/evm/after.ts +0 -3
  148. package/src/namespaces/evm/and.ts +0 -5
  149. package/src/namespaces/evm/before.ts +0 -3
  150. package/src/namespaces/evm/builders.ts +0 -52
  151. package/src/namespaces/evm/constants.ts +0 -2
  152. package/src/namespaces/evm/eip1193.ts +0 -1415
  153. package/src/namespaces/evm/hooks.ts +0 -43
  154. package/src/namespaces/evm/mod.ts +0 -10
  155. package/src/namespaces/evm/types.ts +0 -36
  156. package/src/namespaces/evm/utils.ts +0 -106
  157. package/src/namespaces/solana/actions.ts +0 -44
  158. package/src/namespaces/solana/after.ts +0 -3
  159. package/src/namespaces/solana/and.ts +0 -5
  160. package/src/namespaces/solana/before.ts +0 -3
  161. package/src/namespaces/solana/builders.ts +0 -40
  162. package/src/namespaces/solana/constants.ts +0 -2
  163. package/src/namespaces/solana/hooks.ts +0 -10
  164. package/src/namespaces/solana/mod.ts +0 -10
  165. package/src/namespaces/solana/types.ts +0 -25
  166. package/src/namespaces/solana/utils.ts +0 -36
  167. package/src/namespaces/starknet/actions.ts +0 -19
  168. package/src/namespaces/starknet/after.ts +0 -3
  169. package/src/namespaces/starknet/and.ts +0 -5
  170. package/src/namespaces/starknet/before.ts +0 -3
  171. package/src/namespaces/starknet/builders.ts +0 -18
  172. package/src/namespaces/starknet/constants.ts +0 -8
  173. package/src/namespaces/starknet/mod.ts +0 -9
  174. package/src/namespaces/starknet/types.ts +0 -18
  175. package/src/namespaces/starknet/utils.ts +0 -35
  176. package/src/namespaces/sui/actions.ts +0 -27
  177. package/src/namespaces/sui/builders.ts +0 -77
  178. package/src/namespaces/sui/constants.ts +0 -8
  179. package/src/namespaces/sui/hooks.ts +0 -10
  180. package/src/namespaces/sui/mod.ts +0 -8
  181. package/src/namespaces/sui/types.ts +0 -25
  182. package/src/namespaces/sui/utils.ts +0 -56
  183. package/src/namespaces/tron/actions.ts +0 -3
  184. package/src/namespaces/tron/after.ts +0 -3
  185. package/src/namespaces/tron/and.ts +0 -5
  186. package/src/namespaces/tron/before.ts +0 -3
  187. package/src/namespaces/tron/builders.ts +0 -15
  188. package/src/namespaces/tron/constants.ts +0 -8
  189. package/src/namespaces/tron/mod.ts +0 -8
  190. package/src/namespaces/tron/types.ts +0 -14
  191. package/src/namespaces/tron/utils.ts +0 -18
  192. package/src/namespaces/utxo/actions.ts +0 -3
  193. package/src/namespaces/utxo/after.ts +0 -3
  194. package/src/namespaces/utxo/and.ts +0 -5
  195. package/src/namespaces/utxo/before.ts +0 -3
  196. package/src/namespaces/utxo/builders.ts +0 -12
  197. package/src/namespaces/utxo/constants.ts +0 -2
  198. package/src/namespaces/utxo/mod.ts +0 -8
  199. package/src/namespaces/utxo/types.ts +0 -14
  200. package/src/namespaces/utxo/utils.ts +0 -18
  201. package/src/namespaces/xrpl/builders.ts +0 -14
  202. package/src/namespaces/xrpl/constants.ts +0 -7
  203. package/src/namespaces/xrpl/mod.ts +0 -5
  204. package/src/namespaces/xrpl/types.ts +0 -16
  205. package/src/namespaces/xrpl/utils.ts +0 -15
  206. package/src/types/accounts.ts +0 -12
  207. package/src/types/mod.ts +0 -1
  208. package/src/types/utils.ts +0 -7
  209. /package/src/{types/actions.ts → hub/types.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hub3js/core",
3
- "version": "0.59.0",
3
+ "version": "0.59.1-next.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "source": "./src/mod.ts",
@@ -11,64 +11,26 @@
11
11
  "types": "./dist/mod.d.ts",
12
12
  "default": "./dist/mod.js"
13
13
  },
14
- "./legacy": {
15
- "types": "./dist/legacy/mod.d.ts",
16
- "default": "./dist/legacy/mod.js"
17
- },
18
- "./utils": {
19
- "types": "./dist/utils/mod.d.ts",
20
- "default": "./dist/utils/mod.js"
21
- },
22
14
  "./store": {
23
15
  "types": "./dist/hub/store/mod.d.ts",
24
16
  "default": "./dist/hub/store/mod.js"
25
17
  },
26
- "./namespaces/common": {
27
- "types": "./dist/namespaces/common/mod.d.ts",
28
- "default": "./dist/namespaces/common/mod.js"
29
- },
30
- "./namespaces/cosmos": {
31
- "types": "./dist/namespaces/cosmos/mod.d.ts",
32
- "default": "./dist/namespaces/cosmos/mod.js"
33
- },
34
- "./namespaces/evm": {
35
- "types": "./dist/namespaces/evm/mod.d.ts",
36
- "default": "./dist/namespaces/evm/mod.js"
37
- },
38
- "./namespaces/solana": {
39
- "types": "./dist/namespaces/solana/mod.d.ts",
40
- "default": "./dist/namespaces/solana/mod.js"
41
- },
42
- "./namespaces/starknet": {
43
- "types": "./dist/namespaces/starknet/mod.d.ts",
44
- "default": "./dist/namespaces/starknet/mod.js"
45
- },
46
- "./namespaces/sui": {
47
- "types": "./dist/namespaces/sui/mod.d.ts",
48
- "default": "./dist/namespaces/sui/mod.js"
49
- },
50
- "./namespaces/utxo": {
51
- "types": "./dist/namespaces/utxo/mod.d.ts",
52
- "default": "./dist/namespaces/utxo/mod.js"
53
- },
54
- "./namespaces/tron": {
55
- "types": "./dist/namespaces/tron/mod.d.ts",
56
- "default": "./dist/namespaces/tron/mod.js"
18
+ "./utils": {
19
+ "types": "./dist/utils/mod.d.ts",
20
+ "default": "./dist/utils/mod.js"
57
21
  },
58
- "./namespaces/xrpl": {
59
- "types": "./dist/namespaces/xrpl/mod.d.ts",
60
- "default": "./dist/namespaces/xrpl/mod.js"
22
+ "./test-utils": {
23
+ "types": "./dist/test-utils/mod.d.ts",
24
+ "default": "./dist/test-utils/mod.js"
61
25
  }
62
26
  },
63
27
  "files": [
64
28
  "dist",
65
29
  "src",
66
- "legacy",
67
- "namespaces",
68
30
  "store"
69
31
  ],
70
32
  "scripts": {
71
- "build": "yarn run rangutopia library build --inputs src/mod.ts,src/utils/mod.ts,src/legacy/mod.ts,src/hub/store/mod.ts,src/namespaces/evm/mod.ts,src/namespaces/solana/mod.ts,src/namespaces/cosmos/mod.ts,src/namespaces/utxo/mod.ts,src/namespaces/sui/mod.ts,src/namespaces/tron/mod.ts,src/namespaces/starknet/mod.ts,src/namespaces/xrpl/mod.ts,src/namespaces/common/mod.ts",
33
+ "build": "yarn run rangutopia library build --inputs src/mod.ts,src/hub/store/mod.ts,src/utils/mod.ts,src/test-utils/mod.ts",
72
34
  "ts-check": "tsc --declaration --emitDeclarationOnly -p tsconfig.json",
73
35
  "clean": "rimraf dist",
74
36
  "format": "prettier --write '{.,src}/**/*.{ts,tsx}'",
@@ -76,24 +38,11 @@
76
38
  "test": "vitest",
77
39
  "coverage": "vitest run --coverage"
78
40
  },
79
- "peerDependencies": {
80
- "@mysten/wallet-standard": "^0.13.26",
81
- "@types/react": "^17.0.0 || ^18.0.0",
82
- "react": "^17.0.0 || ^18.0.0",
83
- "react-dom": "^17.0.0 || ^18.0.0",
84
- "xrpl": "^4.2.0"
85
- },
86
41
  "dependencies": {
87
- "@chain-registry/types": "2.0.110",
88
- "caip": "^1.1.1",
89
42
  "immer": "^10.0.4",
90
- "rango-types": "^0.1.98",
43
+ "rango-types": "^0.5.0",
91
44
  "zustand": "^4.5.2"
92
45
  },
93
- "devDependencies": {
94
- "@mysten/wallet-standard": "^0.13.26",
95
- "xrpl": "^4.2.0"
96
- },
97
46
  "publishConfig": {
98
47
  "access": "public"
99
48
  }
@@ -0,0 +1,225 @@
1
+ import type { Context } from '../hub/namespaces/mod.js';
2
+ import type { FunctionWithContext } from '../hub/types.js';
3
+
4
+ import { describe, expect, test, vi } from 'vitest';
5
+
6
+ import { garbageWalletMetaData } from '../test-utils/fixtures.js';
7
+
8
+ import { ActionBuilder } from './action.js';
9
+ import { NamespaceBuilder } from './namespace.js';
10
+ import { ProviderBuilder } from './provider.js';
11
+
12
+ interface TestEvmActions {
13
+ connect: (chain: string) => Promise<{ accounts: string[]; network: string }>;
14
+ disconnect: () => void;
15
+ }
16
+
17
+ interface TestSolanaActions {
18
+ connect: () => Promise<string[]>;
19
+ }
20
+
21
+ describe('Provider built from multiple namespaces', () => {
22
+ const walletName = 'garbage-wallet';
23
+
24
+ test('connect runs the right action on each registered namespace', async () => {
25
+ const spyOnEvmConnect = vi.fn();
26
+ const evmConnect: FunctionWithContext<
27
+ TestEvmActions['connect'],
28
+ Context
29
+ > = async () => {
30
+ spyOnEvmConnect();
31
+ return {
32
+ accounts: ['eip155:0x1:0x000000000000000000000000000000000000dead'],
33
+ network: 'eth',
34
+ };
35
+ };
36
+
37
+ const spyOnSolanaConnect = vi.fn();
38
+ const solanaConnect: FunctionWithContext<
39
+ TestSolanaActions['connect'],
40
+ Context
41
+ > = async () => {
42
+ spyOnSolanaConnect();
43
+ return ['solana:mainnet:1nc1nerator11111111111111111111111111111111'];
44
+ };
45
+
46
+ const evmNamespace = new NamespaceBuilder<TestEvmActions>('evm', walletName)
47
+ .action('connect', evmConnect)
48
+ .build();
49
+ const solanaNamespace = new NamespaceBuilder<TestSolanaActions>(
50
+ 'solana',
51
+ walletName,
52
+ )
53
+ .action('connect', solanaConnect)
54
+ .build();
55
+
56
+ const wallet = new ProviderBuilder(walletName)
57
+ .add('evm', evmNamespace)
58
+ .add('solana', solanaNamespace)
59
+ .config('metadata', garbageWalletMetaData)
60
+ .build();
61
+
62
+ const evmResult = await wallet.get('evm')?.connect('0x1');
63
+ const solanaResult = await wallet.get('solana')?.connect();
64
+
65
+ expect(evmResult?.accounts).toStrictEqual([
66
+ 'eip155:0x1:0x000000000000000000000000000000000000dead',
67
+ ]);
68
+ expect(solanaResult).toStrictEqual([
69
+ 'solana:mainnet:1nc1nerator11111111111111111111111111111111',
70
+ ]);
71
+ expect(spyOnEvmConnect).toBeCalledTimes(1);
72
+ expect(spyOnSolanaConnect).toBeCalledTimes(1);
73
+ });
74
+ });
75
+
76
+ describe('ActionBuilder integration with namespace', () => {
77
+ const walletName = 'garbage-wallet';
78
+
79
+ test('and-then transforms the action result; after hook runs', async () => {
80
+ const spyOnConnect = vi.fn();
81
+ const evmConnect: FunctionWithContext<
82
+ TestEvmActions['connect'],
83
+ Context
84
+ > = async () => {
85
+ spyOnConnect();
86
+ return {
87
+ accounts: [
88
+ '0x000000000000000000000000000000000000dead',
89
+ '0x0000000000000000000000000000000000000000',
90
+ ],
91
+ network: 'eth',
92
+ };
93
+ };
94
+
95
+ const andConnect = vi.fn(
96
+ (_context, result: { network: string; accounts: string[] }) => ({
97
+ network: result.network,
98
+ accounts: result.accounts.map((account) => `eip155:0x1:${account}`),
99
+ }),
100
+ );
101
+
102
+ const evmDisconnect = vi.fn();
103
+ const afterDisconnect = vi.fn();
104
+
105
+ const connectAction = new ActionBuilder<TestEvmActions, 'connect'>(
106
+ 'connect',
107
+ )
108
+ .action(evmConnect)
109
+ .and(andConnect)
110
+ .build();
111
+
112
+ const disconnectAction = new ActionBuilder<TestEvmActions, 'disconnect'>(
113
+ 'disconnect',
114
+ )
115
+ .action(evmDisconnect)
116
+ .after(afterDisconnect)
117
+ .build();
118
+
119
+ const evmNamespace = new NamespaceBuilder<TestEvmActions>('evm', walletName)
120
+ .action(connectAction)
121
+ .action(disconnectAction)
122
+ .build();
123
+
124
+ const wallet = new ProviderBuilder(walletName)
125
+ .add('evm', evmNamespace)
126
+ .config('metadata', garbageWalletMetaData)
127
+ .build();
128
+
129
+ const evmResult = await wallet.get('evm')?.connect('0x1');
130
+
131
+ expect(evmResult?.accounts).toStrictEqual([
132
+ 'eip155:0x1:0x000000000000000000000000000000000000dead',
133
+ 'eip155:0x1:0x0000000000000000000000000000000000000000',
134
+ ]);
135
+
136
+ await wallet.get('evm')?.connect('0x1');
137
+ wallet.get('evm')?.disconnect();
138
+
139
+ expect(spyOnConnect).toBeCalledTimes(2);
140
+ expect(andConnect).toBeCalledTimes(2);
141
+ expect(evmDisconnect).toBeCalledTimes(1);
142
+ expect(afterDisconnect).toBeCalledTimes(1);
143
+ });
144
+
145
+ test('and runs on success; or runs on throw; throw without or propagates', async () => {
146
+ const spyOnSuccessAndAction = vi.fn((_ctx, value) => value);
147
+ const spyOnThrowAndAction = vi.fn();
148
+ const spyOnThrowAndActionWithOr = vi.fn();
149
+ const spyOnSuccessOrAction = vi.fn();
150
+ const spyOnThrowOrAction = vi.fn();
151
+
152
+ interface GarbageActions {
153
+ successfulAction: () => string;
154
+ throwErrorAction: () => void;
155
+ throwErrorActionWithOr: () => void;
156
+ }
157
+
158
+ const successfulAction = new ActionBuilder<
159
+ GarbageActions,
160
+ 'successfulAction'
161
+ >('successfulAction')
162
+ .action(() => 'yay!')
163
+ .and(spyOnSuccessAndAction)
164
+ .or(spyOnSuccessOrAction)
165
+ .build();
166
+
167
+ const throwErrorAction = new ActionBuilder<
168
+ GarbageActions,
169
+ 'throwErrorAction'
170
+ >('throwErrorAction')
171
+ .action(() => {
172
+ throw new Error('whatever');
173
+ })
174
+ .and(spyOnThrowAndAction)
175
+ .build();
176
+
177
+ const throwErrorActionWithOr = new ActionBuilder<
178
+ GarbageActions,
179
+ 'throwErrorActionWithOr'
180
+ >('throwErrorActionWithOr')
181
+ .action(() => {
182
+ throw new Error('whatever');
183
+ })
184
+ .and(spyOnThrowAndActionWithOr)
185
+ .or(spyOnThrowOrAction)
186
+ .build();
187
+
188
+ const namespace = new NamespaceBuilder<GarbageActions>('evm', walletName)
189
+ .action(successfulAction)
190
+ .action(throwErrorAction)
191
+ .action(throwErrorActionWithOr)
192
+ .build();
193
+
194
+ namespace.successfulAction();
195
+ expect(spyOnSuccessAndAction).toBeCalledTimes(1);
196
+ expect(spyOnSuccessAndAction).toHaveLastReturnedWith('yay!');
197
+ expect(spyOnSuccessOrAction).toBeCalledTimes(0);
198
+
199
+ expect(() => namespace.throwErrorAction()).toThrowError();
200
+ expect(spyOnThrowAndAction).toBeCalledTimes(0);
201
+
202
+ namespace.throwErrorActionWithOr();
203
+ expect(spyOnThrowAndActionWithOr).toBeCalledTimes(0);
204
+ expect(spyOnThrowOrAction).toBeCalledTimes(1);
205
+ });
206
+
207
+ test("'action-name' in namespace returns true for registered actions", () => {
208
+ interface GarbageActions {
209
+ garbageAction: () => string;
210
+ }
211
+
212
+ const garbageAction = new ActionBuilder<GarbageActions, 'garbageAction'>(
213
+ 'garbageAction',
214
+ )
215
+ .action(() => 'yay!')
216
+ .build();
217
+
218
+ const namespace = new NamespaceBuilder<GarbageActions>('evm', walletName)
219
+ .action(garbageAction)
220
+ .build();
221
+
222
+ expect('garbageAction' in namespace).toBe(true);
223
+ expect('init' in namespace).toBe(true);
224
+ });
225
+ });
@@ -1,5 +1,5 @@
1
1
  import type { Actions, Context, Operators } from '../hub/namespaces/types.js';
2
- import type { AnyFunction, FunctionWithContext } from '../types/actions.js';
2
+ import type { AnyFunction, FunctionWithContext } from '../hub/types.js';
3
3
 
4
4
  export interface ActionByBuilder<T, Context> {
5
5
  actionName: keyof T;
@@ -2,7 +2,7 @@ import type { ActionByBuilder } from './action.js';
2
2
  import type { ProxiedNamespace } from './types.js';
3
3
  import type { Actions, ActionsMap, Context } from '../hub/namespaces/mod.js';
4
4
  import type { NamespaceConfig } from '../hub/store/mod.js';
5
- import type { FunctionWithContext } from '../types/actions.js';
5
+ import type { FunctionWithContext } from '../hub/types.js';
6
6
 
7
7
  import { Namespace } from '../hub/mod.js';
8
8
 
@@ -1,18 +1,42 @@
1
- import type { FindProxiedNamespace } from './types.js';
1
+ import type { ProxiedNamespace } from './types.js';
2
+ import type { Actions } from '../hub/namespaces/mod.js';
2
3
  import type {
3
- CommonNamespaces,
4
4
  ExtendableInternalActions,
5
5
  ProviderBuilderOptions,
6
+ RegisteredNamespaces,
6
7
  } from '../hub/provider/mod.js';
7
8
  import type { ProviderConfig } from '../hub/store/mod.js';
8
9
 
9
10
  import { Provider } from '../hub/provider/mod.js';
10
11
 
11
- export class ProviderBuilder {
12
+ /**
13
+ * Fluent builder for `Provider`. Each `.add(key, namespace)` call accumulates
14
+ * the registered namespace map into the `TNS` generic, so the resulting
15
+ * `Provider<TNS>` knows exactly which namespaces it owns.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const phantom = new ProviderBuilder('phantom')
20
+ * .config('metadata', meta)
21
+ * .add('evm', evmNamespace) // adds { evm: EvmActions }
22
+ * .add('solana', solanaNamespace) // adds { solana: SolanaActions }
23
+ * .build();
24
+ * // Provider<{ evm: EvmActions; solana: SolanaActions }>
25
+ * ```
26
+ * `{}` is intentional: identity element under intersection so the first
27
+ * `.add(K, T)` produces `{} & Record<K, T> = Record<K, T>` and subsequent calls
28
+ * accumulate cleanly. Not the unsafe "any non-nullish" usage the rule targets.
29
+ */
30
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
31
+ export class ProviderBuilder<TNS = {}> {
12
32
  #id: string;
13
- #namespaces = new Map();
33
+ /*
34
+ * Internal storage is opaque — types are recovered when `build()` produces
35
+ * the `Provider<TNS>`.
36
+ */
37
+ #namespaces = new Map<string, unknown>();
14
38
  #methods: ExtendableInternalActions = {};
15
- #configs: Partial<ProviderConfig> = {};
39
+ #configs: Partial<ProviderConfig<TNS>> = {};
16
40
  #options: Partial<ProviderBuilderOptions>;
17
41
 
18
42
  constructor(id: string, options?: ProviderBuilderOptions) {
@@ -20,42 +44,58 @@ export class ProviderBuilder {
20
44
  this.#options = options || {};
21
45
  }
22
46
 
23
- public add<K extends keyof CommonNamespaces>(
47
+ /**
48
+ * Register a namespace under `id`. Captures both the key and the namespace's
49
+ * action interface into the builder's `TNS` so subsequent operations are
50
+ * statically typed against the accumulated set.
51
+ */
52
+ public add<const K extends string, T extends Actions<T>>(
24
53
  id: K,
25
- namespace: FindProxiedNamespace<K, CommonNamespaces>,
26
- ) {
54
+ namespace: ProxiedNamespace<T>,
55
+ ): ProviderBuilder<TNS & Record<K, T>> {
27
56
  if (this.#options.store) {
28
57
  namespace.store(this.#options.store);
29
58
  }
30
59
  this.#namespaces.set(id, namespace);
31
- return this;
60
+ return this as unknown as ProviderBuilder<TNS & Record<K, T>>;
32
61
  }
33
62
 
34
- public config<K extends keyof ProviderConfig>(
63
+ public config<K extends keyof ProviderConfig<TNS>>(
35
64
  name: K,
36
- value: ProviderConfig[K],
37
- ) {
65
+ value: ProviderConfig<TNS>[K],
66
+ ): this {
38
67
  this.#configs[name] = value;
39
68
  return this;
40
69
  }
41
70
 
42
- public init(cb: Exclude<ExtendableInternalActions['init'], undefined>) {
71
+ public init(cb: Exclude<ExtendableInternalActions['init'], undefined>): this {
43
72
  this.#methods.init = cb;
44
73
  return this;
45
74
  }
46
75
 
47
- public build(): Provider {
76
+ public build(): Provider<TNS> {
48
77
  if (this.#isConfigsValid(this.#configs)) {
49
- return new Provider(this.#id, this.#namespaces, this.#configs, {
50
- extendInternalActions: this.#methods,
51
- store: this.#options.store,
52
- });
78
+ return new Provider<TNS>(
79
+ this.#id,
80
+ /*
81
+ * The runtime map is keyed by `string`; we re-view it through the
82
+ * builder's accumulated `TNS` for the typed `Provider` API.
83
+ */
84
+ this.#namespaces as unknown as RegisteredNamespaces<TNS>,
85
+ this.#configs,
86
+ {
87
+ extendInternalActions: this.#methods,
88
+ store: this.#options.store,
89
+ },
90
+ );
53
91
  }
54
92
 
55
93
  throw new Error('You need to set all required configs.');
56
94
  }
57
95
 
58
- #isConfigsValid(config: Partial<ProviderConfig>): config is ProviderConfig {
96
+ #isConfigsValid(
97
+ config: Partial<ProviderConfig<TNS>>,
98
+ ): config is ProviderConfig<TNS> {
59
99
  return !!config.metadata;
60
100
  }
61
101
  }
@@ -16,11 +16,13 @@ export type ProxiedNamespace<T extends Actions<T>> = T &
16
16
  NamespacePublicValues;
17
17
 
18
18
  /**
19
- * This is useful when you gave a list of namespaces and want to map a key to corresponding namespace.
19
+ * Map a key into the corresponding proxied namespace inside a namespace set.
20
20
  *
21
- * e.g:
22
- * Type List = { evm: EvmActions, solana: SolanaActions};
23
- * FindProxiedNamespace<"solana", List>
21
+ * @example
22
+ * ```ts
23
+ * type List = { evm: EvmActions; solana: SolanaActions };
24
+ * FindProxiedNamespace<'solana', List>; // ProxiedNamespace<SolanaActions>
25
+ * ```
24
26
  */
25
27
  export type FindProxiedNamespace<K extends keyof T, T> =
26
28
  T[K] extends Actions<T[K]> ? ProxiedNamespace<T[K]> : never;
@@ -0,0 +1,108 @@
1
+ import type { Store } from './store/mod.js';
2
+
3
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
4
+
5
+ import { NamespaceBuilder } from '../builders/namespace.js';
6
+ import { ProviderBuilder } from '../builders/provider.js';
7
+ import { garbageWalletMetaData } from '../test-utils/fixtures.js';
8
+
9
+ import { Hub } from './hub.js';
10
+ import { createStore } from './store/mod.js';
11
+
12
+ interface TestEvmActions {
13
+ connect: (chain: string) => Promise<{ accounts: string[]; network: string }>;
14
+ }
15
+
16
+ const buildEvmProvider = (id: string) => {
17
+ const evmNamespace = new NamespaceBuilder<TestEvmActions>('eip155', id)
18
+ .action('connect', async () => ({
19
+ accounts: ['eip155:0x1:0x000000000000000000000000000000000000dead'],
20
+ network: 'eth',
21
+ }))
22
+ .build();
23
+
24
+ return new ProviderBuilder(id)
25
+ .config('metadata', garbageWalletMetaData)
26
+ .add('evm', evmNamespace)
27
+ .build();
28
+ };
29
+
30
+ describe('Hub', () => {
31
+ let store: Store;
32
+
33
+ beforeEach(() => {
34
+ store = createStore();
35
+ });
36
+
37
+ test('add registers the provider in both the hub and the store', () => {
38
+ const provider = buildEvmProvider('wallet-a');
39
+ const hub = new Hub({ store }).add(provider.id, provider);
40
+
41
+ expect(hub.get('wallet-a')).toBe(provider);
42
+ expect(store.getState().providers.list['wallet-a']).toBeDefined();
43
+ });
44
+
45
+ test('get returns undefined for an unknown provider id', () => {
46
+ const hub = new Hub({ store });
47
+
48
+ expect(hub.get('does-not-exist')).toBeUndefined();
49
+ });
50
+
51
+ test('getAll returns every registered provider', () => {
52
+ const a = buildEvmProvider('wallet-a');
53
+ const b = buildEvmProvider('wallet-b');
54
+ const hub = new Hub({ store }).add(a.id, a).add(b.id, b);
55
+
56
+ const all = hub.getAll();
57
+ expect(all.size).toBe(2);
58
+ expect(all.get('wallet-a')).toBe(a);
59
+ expect(all.get('wallet-b')).toBe(b);
60
+ });
61
+
62
+ test('remove clears the provider from both the hub and the store', () => {
63
+ const provider = buildEvmProvider('wallet-a');
64
+ const hub = new Hub({ store }).add(provider.id, provider);
65
+
66
+ expect(store.getState().providers.list['wallet-a']).toBeDefined();
67
+
68
+ hub.remove('wallet-a');
69
+
70
+ expect(hub.get('wallet-a')).toBeUndefined();
71
+ expect(store.getState().providers.list['wallet-a']).toBeUndefined();
72
+ });
73
+
74
+ test('remove throws when the provider id is unknown', () => {
75
+ const hub = new Hub({ store });
76
+
77
+ expect(() => hub.remove('nope')).toThrowError(/Provider not found/);
78
+ });
79
+
80
+ test('init runs each provider init exactly once', () => {
81
+ const initSpy = vi.fn();
82
+ const provider = new ProviderBuilder('wallet-a')
83
+ .config('metadata', garbageWalletMetaData)
84
+ .init(initSpy)
85
+ .build();
86
+
87
+ const hub = new Hub({ store }).add(provider.id, provider);
88
+
89
+ expect(initSpy).toBeCalledTimes(0);
90
+
91
+ hub.init();
92
+ hub.init();
93
+
94
+ expect(initSpy).toBeCalledTimes(1);
95
+ });
96
+
97
+ test('state aggregates per-provider state and their namespaces', () => {
98
+ const provider = buildEvmProvider('wallet-a');
99
+ const hub = new Hub({ store }).add(provider.id, provider);
100
+
101
+ const state = hub.state();
102
+ expect(state['wallet-a']).toBeDefined();
103
+ expect(state['wallet-a'].installed).toBe(false);
104
+ expect(state['wallet-a'].connected).toBe(false);
105
+ expect(state['wallet-a'].namespaces).toHaveLength(1);
106
+ expect(state['wallet-a'].namespaces[0].connected).toBe(false);
107
+ });
108
+ });