@asseteragmbh/wallet-connector 0.1.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/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @asseteragmbh/wallet-connector
2
+
3
+ The partner side of the **Assetera Wallet** pop-up: an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)
4
+ provider that talks to the wallet at `wallet.{env}.assetera.com`, plus a wagmi v2 connector and a RainbowKit
5
+ wallet entry. It has no dependency on Dynamic or on `@asseteragmbh/wallet` (ADR-0054 D3), so a partner site can
6
+ add it without pulling in the wallet app.
7
+
8
+ The pop-up protocol — message types, the `ready` handshake, `SUPPORTED_METHODS`, error codes and `connectUrl` —
9
+ is the contract in [`src/protocol.ts`](./src/protocol.ts). This package is the opener side; the wallet's
10
+ `/connect` page is the pop-up side and imports the same types from
11
+ `@asseteragmbh/wallet-connector/protocol`.
12
+
13
+ ## Before you start: allow-list the partner origin
14
+
15
+ The wallet only answers a pop-up whose `origin` query parameter is on its allow-list (ADR-0054 D4). **Ask
16
+ Assetera to list your origin** (for example `https://app.partner.example`) before shipping. An unlisted origin
17
+ gets no `ready` message, so the connection simply never completes.
18
+
19
+ Also note:
20
+
21
+ - **`connect` must be triggered by a click.** `eth_requestAccounts` calls `window.open` synchronously inside
22
+ `request`, and browsers only allow that during user activation. Call `connect()` / `eth_requestAccounts` from a
23
+ button handler, never on page load. If the pop-up is blocked, the request rejects with code `4900`.
24
+ - **Mobile opens a tab.** A pop-up (`window.open`) becomes a normal browser tab on mobile; the user switches
25
+ back to the partner page when done.
26
+
27
+ ## 1. Raw EIP-1193 provider
28
+
29
+ ```ts
30
+ import { createAsseteraWalletProvider } from "@asseteragmbh/wallet-connector";
31
+
32
+ const provider = createAsseteraWalletProvider({
33
+ walletUrl: "https://wallet.playground.assetera.com",
34
+ defaultChainId: 1,
35
+ });
36
+
37
+ button.addEventListener("click", async () => {
38
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
39
+ console.log(accounts);
40
+ });
41
+
42
+ provider.on("accountsChanged", (accounts) => console.log(accounts));
43
+ provider.on("chainChanged", (chainId) => console.log(chainId));
44
+ provider.on("disconnect", (error) => console.log(error));
45
+ ```
46
+
47
+ `request` supports `eth_requestAccounts`, `eth_accounts`, `eth_chainId`, `wallet_switchEthereumChain`,
48
+ `personal_sign`, `eth_signTypedData_v4` and `eth_sendTransaction`. `eth_accounts` and `eth_chainId` are answered
49
+ locally without opening the pop-up. Anything else rejects with `4200`; read-only chain calls go through your own
50
+ RPC transports. Errors are thrown as `ProviderRpcError` with an EIP-1193 `code` (`4001`, `4100`, `4200`,
51
+ `4900`, …).
52
+
53
+ ## 2. wagmi v2 connector
54
+
55
+ ```ts
56
+ import { createConfig, http } from "wagmi";
57
+ import { mainnet } from "wagmi/chains";
58
+ import { asseteraWallet } from "@asseteragmbh/wallet-connector/wagmi";
59
+
60
+ const config = createConfig({
61
+ chains: [mainnet],
62
+ connectors: [asseteraWallet({ walletUrl: "https://wallet.playground.assetera.com" })],
63
+ transports: { [mainnet.id]: http() },
64
+ });
65
+ ```
66
+
67
+ `@wagmi/core` and `viem` are optional peer dependencies. The connector's `isAuthorized()` always returns `false`
68
+ on purpose: a pop-up cannot be opened without a click, so wagmi must not try to auto-reconnect on load.
69
+
70
+ ## 3. RainbowKit
71
+
72
+ ```ts
73
+ import { connectorsForWallets } from "@rainbow-me/rainbowkit";
74
+ import { asseteraRainbowWallet } from "@asseteragmbh/wallet-connector/rainbowkit";
75
+
76
+ const connectors = connectorsForWallets(
77
+ [
78
+ {
79
+ groupName: "Assetera",
80
+ wallets: [asseteraRainbowWallet({ walletUrl: "https://wallet.playground.assetera.com" })],
81
+ },
82
+ // …other groups
83
+ ],
84
+ { appName: "My Partner App", projectId: "…" },
85
+ );
86
+ ```
87
+
88
+ `@rainbow-me/rainbowkit` is an optional peer dependency. The wallet entry has id `assetera-wallet`, name
89
+ `Assetera Wallet` and renders the same inline icon as the wagmi connector.
90
+
91
+ ## Protocol summary
92
+
93
+ 1. On a click, the partner opens `${walletUrl}/connect?origin=<partner origin>&v=1`.
94
+ 2. The pop-up checks the claimed origin against its allow-list, then posts `ready` to `window.opener` with
95
+ `targetOrigin` = that origin.
96
+ 3. The partner waits for `ready` from the wallet origin, then posts
97
+ `{ type: "assetera-wallet:request", v: 1, id, method, params }` with `targetOrigin` = the wallet origin. The
98
+ pop-up answers with `assetera-wallet:response` (matched by `id`) and forwards `accountsChanged`,
99
+ `chainChanged` and `disconnect` events.
100
+ 4. Messages are accepted only when `event.origin` is the wallet origin **and** `event.source` is the current
101
+ pop-up. The pop-up stays open while the partner uses the wallet.
102
+
103
+ See [`src/protocol.ts`](./src/protocol.ts) for the exact types, guards and error codes.
@@ -0,0 +1,129 @@
1
+ import { createAsseteraWalletProvider } from './chunk-QPNIGEP4.js';
2
+ import { createConnector, ChainNotConfiguredError } from '@wagmi/core';
3
+ import { getAddress, numberToHex, UserRejectedRequestError } from 'viem';
4
+
5
+ // src/icon.ts
6
+ var ICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="28" fill="#1a1a2e"/><path fill="#ffffff" fill-rule="evenodd" d="M64 24 100 104H80.5L73.6 86H54.4L47.5 104H28L64 24Zm-4.5 44.5h9L64 54.4 59.5 68.5Z"/></svg>';
7
+ var ASSETERA_WALLET_ICON = `data:image/svg+xml,${encodeURIComponent(ICON_SVG)}`;
8
+
9
+ // src/wagmi.ts
10
+ var ASSETERA_WALLET_ID = "assetera-wallet";
11
+ var ASSETERA_WALLET_NAME = "Assetera Wallet";
12
+ var ASSETERA_WALLET_TYPE = "asseteraWallet";
13
+ function asseteraWallet(parameters) {
14
+ let provider;
15
+ let accountsChanged;
16
+ let chainChanged;
17
+ let disconnectHandler;
18
+ function removeProviderListeners(activeProvider) {
19
+ if (accountsChanged) {
20
+ activeProvider.removeListener("accountsChanged", accountsChanged);
21
+ accountsChanged = void 0;
22
+ }
23
+ if (chainChanged) {
24
+ activeProvider.removeListener("chainChanged", chainChanged);
25
+ chainChanged = void 0;
26
+ }
27
+ if (disconnectHandler) {
28
+ activeProvider.removeListener("disconnect", disconnectHandler);
29
+ disconnectHandler = void 0;
30
+ }
31
+ }
32
+ return createConnector((config) => ({
33
+ id: ASSETERA_WALLET_ID,
34
+ name: ASSETERA_WALLET_NAME,
35
+ type: ASSETERA_WALLET_TYPE,
36
+ icon: ASSETERA_WALLET_ICON,
37
+ async connect({ chainId, withCapabilities } = {}) {
38
+ const activeProvider = await this.getProvider();
39
+ const requested = await activeProvider.request({
40
+ method: "eth_requestAccounts"
41
+ });
42
+ const addresses = requested.map((account) => getAddress(account));
43
+ if (!accountsChanged) {
44
+ accountsChanged = this.onAccountsChanged.bind(this);
45
+ activeProvider.on("accountsChanged", accountsChanged);
46
+ }
47
+ if (!chainChanged) {
48
+ chainChanged = this.onChainChanged.bind(this);
49
+ activeProvider.on("chainChanged", chainChanged);
50
+ }
51
+ if (!disconnectHandler) {
52
+ disconnectHandler = this.onDisconnect.bind(this);
53
+ activeProvider.on("disconnect", disconnectHandler);
54
+ }
55
+ let currentChainId = await this.getChainId();
56
+ if (chainId && currentChainId !== chainId) {
57
+ const chain = await this.switchChain({ chainId }).catch((error) => {
58
+ if (error?.code === UserRejectedRequestError.code) throw error;
59
+ return { id: currentChainId };
60
+ });
61
+ currentChainId = chain?.id ?? currentChainId;
62
+ }
63
+ return {
64
+ accounts: withCapabilities ? addresses.map((address) => ({ address, capabilities: {} })) : addresses,
65
+ chainId: currentChainId
66
+ };
67
+ },
68
+ async disconnect() {
69
+ if (!provider) return;
70
+ removeProviderListeners(provider);
71
+ provider.disconnect();
72
+ },
73
+ async getAccounts() {
74
+ const activeProvider = await this.getProvider();
75
+ const requested = await activeProvider.request({ method: "eth_accounts" });
76
+ return requested.map((account) => getAddress(account));
77
+ },
78
+ async getChainId() {
79
+ const activeProvider = await this.getProvider();
80
+ return Number(await activeProvider.request({ method: "eth_chainId" }));
81
+ },
82
+ async getProvider() {
83
+ if (!provider) {
84
+ provider = createAsseteraWalletProvider({
85
+ walletUrl: parameters.walletUrl,
86
+ defaultChainId: config.chains[0].id
87
+ });
88
+ }
89
+ return provider;
90
+ },
91
+ /**
92
+ * Always `false`: opening the pop-up requires a user click, so there is nothing to reconnect to on page
93
+ * load. Returning `true` would make wagmi attempt an automatic reconnect that the browser blocks; the
94
+ * partner app must drive `connect()` from a click instead.
95
+ */
96
+ async isAuthorized() {
97
+ return false;
98
+ },
99
+ async switchChain({ chainId }) {
100
+ const chain = config.chains.find((candidate) => candidate.id === chainId);
101
+ if (!chain) throw new ChainNotConfiguredError();
102
+ const activeProvider = await this.getProvider();
103
+ await activeProvider.request({
104
+ method: "wallet_switchEthereumChain",
105
+ params: [{ chainId: numberToHex(chainId) }]
106
+ });
107
+ this.onChainChanged(numberToHex(chainId));
108
+ return chain;
109
+ },
110
+ onAccountsChanged(accounts) {
111
+ if (accounts.length === 0) this.onDisconnect();
112
+ else
113
+ config.emitter.emit("change", {
114
+ accounts: accounts.map((account) => getAddress(account))
115
+ });
116
+ },
117
+ onChainChanged(chain) {
118
+ config.emitter.emit("change", { chainId: Number(chain) });
119
+ },
120
+ onDisconnect() {
121
+ config.emitter.emit("disconnect");
122
+ if (provider) removeProviderListeners(provider);
123
+ }
124
+ }));
125
+ }
126
+
127
+ export { ASSETERA_WALLET_ICON, ASSETERA_WALLET_ID, ASSETERA_WALLET_NAME, ASSETERA_WALLET_TYPE, asseteraWallet };
128
+ //# sourceMappingURL=chunk-L7EBYABA.js.map
129
+ //# sourceMappingURL=chunk-L7EBYABA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/icon.ts","../src/wagmi.ts"],"names":[],"mappings":"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","file":"chunk-L7EBYABA.js","sourcesContent":["/**\n * The wallet's mark as an inline data-URI, shared by the wagmi connector's `icon` and RainbowKit's `iconUrl`.\n *\n * It is intentionally tiny and dependency-free: a rounded dark square with a stylised \"A\". Keeping it as a\n * data URI means a partner app renders the wallet entry before its own bundle loads the wallet origin.\n */\nconst ICON_SVG =\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"128\" height=\"128\" viewBox=\"0 0 128 128\" fill=\"none\">' +\n '<rect width=\"128\" height=\"128\" rx=\"28\" fill=\"#1a1a2e\"/>' +\n '<path fill=\"#ffffff\" fill-rule=\"evenodd\" d=\"M64 24 100 104H80.5L73.6 86H54.4L47.5 104H28L64 24Z' +\n 'm-4.5 44.5h9L64 54.4 59.5 68.5Z\"/>' +\n \"</svg>\";\n\n/** `data:image/svg+xml,...` representation of the Assetera Wallet mark. */\nexport const ASSETERA_WALLET_ICON = `data:image/svg+xml,${encodeURIComponent(ICON_SVG)}`;\n","/**\n * wagmi v2 connector for the Assetera Wallet pop-up (ADR-0054 D3).\n *\n * Built with `createConnector` from `@wagmi/core`, following the structure of wagmi's own `injected` connector.\n * `@wagmi/core`, `viem` and wagmi are peer dependencies so a partner app keeps a single copy of them on the\n * page. The connector is intentionally thin: all protocol work lives in {@link createAsseteraWalletProvider}.\n */\nimport { ChainNotConfiguredError, createConnector, type Connector } from \"@wagmi/core\";\nimport { getAddress, numberToHex, UserRejectedRequestError } from \"viem\";\nimport { ASSETERA_WALLET_ICON } from \"./icon\";\nimport { createAsseteraWalletProvider, type AsseteraWalletProvider } from \"./provider\";\n\n/** Stable connector id, used by wagmi for storage keys and RainbowKit for the wallet entry. */\nexport const ASSETERA_WALLET_ID = \"assetera-wallet\";\n/** Product name, exactly \"Assetera Wallet\". */\nexport const ASSETERA_WALLET_NAME = \"Assetera Wallet\";\n/** Connector `type` reported to wagmi. */\nexport const ASSETERA_WALLET_TYPE = \"asseteraWallet\";\n\n/** Parameters accepted by {@link asseteraWallet}. */\nexport interface AsseteraWalletParameters {\n /** Base URL of the wallet origin, e.g. `https://wallet.playground.assetera.com`. */\n walletUrl: string;\n}\n\n/**\n * Create the wagmi connector for the Assetera Wallet pop-up.\n *\n * `connect()` must be called from a user click: `eth_requestAccounts` opens the pop-up, and a pop-up cannot be\n * opened without transient user activation.\n */\nexport function asseteraWallet(parameters: AsseteraWalletParameters) {\n type Provider = AsseteraWalletProvider;\n\n let provider: Provider | undefined;\n let accountsChanged: Connector[\"onAccountsChanged\"] | undefined;\n let chainChanged: Connector[\"onChainChanged\"] | undefined;\n let disconnectHandler: Connector[\"onDisconnect\"] | undefined;\n\n function removeProviderListeners(activeProvider: Provider): void {\n if (accountsChanged) {\n activeProvider.removeListener(\"accountsChanged\", accountsChanged);\n accountsChanged = undefined;\n }\n if (chainChanged) {\n activeProvider.removeListener(\"chainChanged\", chainChanged);\n chainChanged = undefined;\n }\n if (disconnectHandler) {\n activeProvider.removeListener(\"disconnect\", disconnectHandler);\n disconnectHandler = undefined;\n }\n }\n\n return createConnector<Provider>((config) => ({\n id: ASSETERA_WALLET_ID,\n name: ASSETERA_WALLET_NAME,\n type: ASSETERA_WALLET_TYPE,\n icon: ASSETERA_WALLET_ICON,\n\n async connect({ chainId, withCapabilities } = {}) {\n const activeProvider = await this.getProvider();\n const requested = (await activeProvider.request({\n method: \"eth_requestAccounts\",\n })) as string[];\n const addresses = requested.map((account) => getAddress(account));\n\n // Forward the pop-up's own events into wagmi's emitter.\n if (!accountsChanged) {\n accountsChanged = this.onAccountsChanged.bind(this);\n activeProvider.on(\"accountsChanged\", accountsChanged);\n }\n if (!chainChanged) {\n chainChanged = this.onChainChanged.bind(this);\n activeProvider.on(\"chainChanged\", chainChanged);\n }\n if (!disconnectHandler) {\n disconnectHandler = this.onDisconnect.bind(this);\n activeProvider.on(\"disconnect\", disconnectHandler);\n }\n\n // Switch to the requested chain when the caller asked for one.\n let currentChainId = await this.getChainId();\n if (chainId && currentChainId !== chainId) {\n const chain = await this.switchChain!({ chainId }).catch((error) => {\n if (error?.code === UserRejectedRequestError.code) throw error;\n return { id: currentChainId };\n });\n currentChainId = chain?.id ?? currentChainId;\n }\n\n return {\n accounts: (withCapabilities\n ? addresses.map((address) => ({ address, capabilities: {} }))\n : addresses) as never,\n chainId: currentChainId,\n };\n },\n\n async disconnect() {\n if (!provider) return;\n removeProviderListeners(provider);\n provider.disconnect();\n },\n\n async getAccounts() {\n const activeProvider = await this.getProvider();\n const requested = (await activeProvider.request({ method: \"eth_accounts\" })) as string[];\n return requested.map((account) => getAddress(account));\n },\n\n async getChainId() {\n const activeProvider = await this.getProvider();\n return Number(await activeProvider.request({ method: \"eth_chainId\" }));\n },\n\n async getProvider() {\n // One provider per connector, created lazily so a partner that never connects pays nothing.\n if (!provider) {\n provider = createAsseteraWalletProvider({\n walletUrl: parameters.walletUrl,\n defaultChainId: config.chains[0].id,\n });\n }\n return provider;\n },\n\n /**\n * Always `false`: opening the pop-up requires a user click, so there is nothing to reconnect to on page\n * load. Returning `true` would make wagmi attempt an automatic reconnect that the browser blocks; the\n * partner app must drive `connect()` from a click instead.\n */\n async isAuthorized() {\n return false;\n },\n\n async switchChain({ chainId }) {\n const chain = config.chains.find((candidate) => candidate.id === chainId);\n if (!chain) throw new ChainNotConfiguredError();\n const activeProvider = await this.getProvider();\n await activeProvider.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: numberToHex(chainId) }],\n });\n this.onChainChanged(numberToHex(chainId));\n return chain;\n },\n\n onAccountsChanged(accounts) {\n if (accounts.length === 0) this.onDisconnect();\n else\n config.emitter.emit(\"change\", {\n accounts: accounts.map((account) => getAddress(account)),\n });\n },\n\n onChainChanged(chain) {\n config.emitter.emit(\"change\", { chainId: Number(chain) });\n },\n\n onDisconnect() {\n config.emitter.emit(\"disconnect\");\n if (provider) removeProviderListeners(provider);\n },\n }));\n}\n"]}
@@ -0,0 +1,63 @@
1
+ // src/protocol.ts
2
+ var PROTOCOL_VERSION = 1;
3
+ var CONNECT_PATH = "/connect";
4
+ var MESSAGE_PREFIX = "assetera-wallet:";
5
+ var SUPPORTED_METHODS = [
6
+ "eth_requestAccounts",
7
+ "eth_accounts",
8
+ "eth_chainId",
9
+ "wallet_switchEthereumChain",
10
+ "personal_sign",
11
+ "eth_signTypedData_v4",
12
+ "eth_sendTransaction"
13
+ ];
14
+ function isSupportedMethod(method) {
15
+ return SUPPORTED_METHODS.includes(method);
16
+ }
17
+ var RpcErrorCode = {
18
+ /** The client rejected the request in the pop-up. */
19
+ UserRejected: 4001,
20
+ /** Not connected yet (no `eth_requestAccounts` approved in this pop-up). */
21
+ Unauthorized: 4100,
22
+ /** The method is not in SUPPORTED_METHODS. */
23
+ UnsupportedMethod: 4200,
24
+ /** The pop-up closed, or the wallet session ended. */
25
+ Disconnected: 4900,
26
+ /** `wallet_switchEthereumChain` to a chain the wallet does not offer. */
27
+ UnrecognizedChain: 4902,
28
+ /** Malformed params. */
29
+ InvalidParams: -32602,
30
+ /** Anything unexpected inside the wallet. */
31
+ Internal: -32603
32
+ };
33
+ function isRecord(value) {
34
+ return typeof value === "object" && value !== null;
35
+ }
36
+ function hasType(value, suffix) {
37
+ return isRecord(value) && value.type === `${MESSAGE_PREFIX}${suffix}` && value.v === PROTOCOL_VERSION;
38
+ }
39
+ function isReadyMessage(value) {
40
+ return hasType(value, "ready");
41
+ }
42
+ function isResponseMessage(value) {
43
+ return hasType(value, "response") && typeof value.id === "string" && ("result" in value || isRecord(value.error) && typeof value.error.code === "number");
44
+ }
45
+ function isEventMessage(value) {
46
+ return hasType(value, "event") && (value.event === "accountsChanged" || value.event === "chainChanged" || value.event === "disconnect");
47
+ }
48
+ function isRequestMessage(value) {
49
+ return hasType(value, "request") && typeof value.id === "string" && typeof value.method === "string";
50
+ }
51
+ function isDisconnectMessage(value) {
52
+ return hasType(value, "disconnect");
53
+ }
54
+ function connectUrl(walletUrl, partnerOrigin) {
55
+ const url = new URL(CONNECT_PATH, walletUrl);
56
+ url.searchParams.set("origin", partnerOrigin);
57
+ url.searchParams.set("v", String(PROTOCOL_VERSION));
58
+ return url.toString();
59
+ }
60
+
61
+ export { CONNECT_PATH, MESSAGE_PREFIX, PROTOCOL_VERSION, RpcErrorCode, SUPPORTED_METHODS, connectUrl, isDisconnectMessage, isEventMessage, isReadyMessage, isRequestMessage, isResponseMessage, isSupportedMethod };
62
+ //# sourceMappingURL=chunk-N6D53D3G.js.map
63
+ //# sourceMappingURL=chunk-N6D53D3G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol.ts"],"names":[],"mappings":";AAwBO,IAAM,gBAAA,GAAmB;AAGzB,IAAM,YAAA,GAAe;AAGrB,IAAM,cAAA,GAAiB;AAMvB,IAAM,iBAAA,GAAoB;AAAA,EAC/B,qBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,4BAAA;AAAA,EACA,eAAA;AAAA,EACA,sBAAA;AAAA,EACA;AACF;AAIO,SAAS,kBAAkB,MAAA,EAA2C;AAC3E,EAAA,OAAQ,iBAAA,CAAwC,SAAS,MAAM,CAAA;AACjE;AAGO,IAAM,YAAA,GAAe;AAAA;AAAA,EAE1B,YAAA,EAAc,IAAA;AAAA;AAAA,EAEd,YAAA,EAAc,IAAA;AAAA;AAAA,EAEd,iBAAA,EAAmB,IAAA;AAAA;AAAA,EAEnB,YAAA,EAAc,IAAA;AAAA;AAAA,EAEd,iBAAA,EAAmB,IAAA;AAAA;AAAA,EAEnB,aAAA,EAAe,MAAA;AAAA;AAAA,EAEf,QAAA,EAAU;AACZ;AAqEA,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAEA,SAAS,OAAA,CAAQ,OAAgB,MAAA,EAAkD;AACjF,EAAA,OAAO,QAAA,CAAS,KAAK,CAAA,IAAK,KAAA,CAAM,IAAA,KAAS,CAAA,EAAG,cAAc,CAAA,EAAG,MAAM,CAAA,CAAA,IAAM,KAAA,CAAM,CAAA,KAAM,gBAAA;AACvF;AAEO,SAAS,eAAe,KAAA,EAAuC;AACpE,EAAA,OAAO,OAAA,CAAQ,OAAO,OAAO,CAAA;AAC/B;AAEO,SAAS,kBAAkB,KAAA,EAA0C;AAC1E,EAAA,OACE,QAAQ,KAAA,EAAO,UAAU,CAAA,IACzB,OAAO,MAAM,EAAA,KAAO,QAAA,KACnB,QAAA,IAAY,KAAA,IAAU,SAAS,KAAA,CAAM,KAAK,KAAK,OAAO,KAAA,CAAM,MAAM,IAAA,KAAS,QAAA,CAAA;AAEhF;AAEO,SAAS,eAAe,KAAA,EAAuC;AACpE,EAAA,OACE,OAAA,CAAQ,KAAA,EAAO,OAAO,CAAA,KACrB,KAAA,CAAM,KAAA,KAAU,iBAAA,IAAqB,KAAA,CAAM,KAAA,KAAU,cAAA,IAAkB,KAAA,CAAM,KAAA,KAAU,YAAA,CAAA;AAE5F;AAEO,SAAS,iBAAiB,KAAA,EAAyC;AACxE,EAAA,OAAO,OAAA,CAAQ,KAAA,EAAO,SAAS,CAAA,IAAK,OAAO,MAAM,EAAA,KAAO,QAAA,IAAY,OAAO,KAAA,CAAM,MAAA,KAAW,QAAA;AAC9F;AAEO,SAAS,oBAAoB,KAAA,EAA4C;AAC9E,EAAA,OAAO,OAAA,CAAQ,OAAO,YAAY,CAAA;AACpC;AAGO,SAAS,UAAA,CAAW,WAAmB,aAAA,EAA+B;AAC3E,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,YAAA,EAAc,SAAS,CAAA;AAC3C,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,aAAa,CAAA;AAC5C,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,GAAA,EAAK,MAAA,CAAO,gBAAgB,CAAC,CAAA;AAClD,EAAA,OAAO,IAAI,QAAA,EAAS;AACtB","file":"chunk-N6D53D3G.js","sourcesContent":["/**\n * The pop-up protocol between a partner page (the opener) and the Assetera Wallet (the pop-up) — ADR-0054 D3/D4.\n *\n * This file is the CONTRACT. The connector in this package speaks the opener side; AsseteraWalletFront's\n * `/connect` page speaks the pop-up side and imports these types from `@asseteragmbh/wallet-connector/protocol`.\n * Change it only in a backwards-compatible way, or bump `PROTOCOL_VERSION` and support both on the wallet side:\n * partner sites upgrade on their own schedule.\n *\n * ## The handshake\n *\n * 1. The partner opens `${walletUrl}${CONNECT_PATH}?origin=<partner origin>&v=<version>` from a user click.\n * 2. The pop-up checks the claimed origin against its allowlist (D4). If listed, it posts `ready` to\n * `window.opener` with `targetOrigin` = that origin. The browser delivers it ONLY if the opener really is\n * that origin, so a page cannot claim someone else's origin and still hear the answer.\n * 3. The opener waits for `ready` from the wallet origin, then sends `request`s with `targetOrigin` = the wallet\n * origin. The pop-up accepts a message only when `event.origin` is the allowed origin from step 2 AND\n * `event.source === window.opener`, and answers with `targetOrigin` = that origin. Nothing else is accepted.\n *\n * ## Lifetime\n *\n * The pop-up stays open while the partner uses the wallet (ADR-0054, \"the wallet tab must stay open\"). When it is\n * closed, the opener sees `disconnect` (or notices `popup.closed`) and must reconnect from a user click.\n */\n\nexport const PROTOCOL_VERSION = 1 as const;\n\n/** The pop-up route on the wallet origin. */\nexport const CONNECT_PATH = \"/connect\";\n\n/** Every message carries this prefix in `type`, so both sides can ignore unrelated `postMessage` traffic. */\nexport const MESSAGE_PREFIX = \"assetera-wallet:\" as const;\n\n/**\n * The EIP-1193 methods the pop-up answers (ADR-0054 D3). Anything else is refused with 4200. Read-only chain\n * calls (`eth_call`, `eth_getBalance`, …) do not go through the pop-up; wagmi sends them to its own transports.\n */\nexport const SUPPORTED_METHODS = [\n \"eth_requestAccounts\",\n \"eth_accounts\",\n \"eth_chainId\",\n \"wallet_switchEthereumChain\",\n \"personal_sign\",\n \"eth_signTypedData_v4\",\n \"eth_sendTransaction\",\n] as const;\n\nexport type SupportedMethod = (typeof SUPPORTED_METHODS)[number];\n\nexport function isSupportedMethod(method: string): method is SupportedMethod {\n return (SUPPORTED_METHODS as readonly string[]).includes(method);\n}\n\n/** EIP-1193 / EIP-1474 error codes the pop-up uses. */\nexport const RpcErrorCode = {\n /** The client rejected the request in the pop-up. */\n UserRejected: 4001,\n /** Not connected yet (no `eth_requestAccounts` approved in this pop-up). */\n Unauthorized: 4100,\n /** The method is not in SUPPORTED_METHODS. */\n UnsupportedMethod: 4200,\n /** The pop-up closed, or the wallet session ended. */\n Disconnected: 4900,\n /** `wallet_switchEthereumChain` to a chain the wallet does not offer. */\n UnrecognizedChain: 4902,\n /** Malformed params. */\n InvalidParams: -32602,\n /** Anything unexpected inside the wallet. */\n Internal: -32603,\n} as const;\n\nexport type RpcErrorCode = (typeof RpcErrorCode)[keyof typeof RpcErrorCode];\n\nexport interface RpcError {\n code: number;\n message: string;\n data?: unknown;\n}\n\n// --- pop-up → opener ---------------------------------------------------------------------------------\n\n/** The pop-up is loaded and accepts requests from this opener. */\nexport interface ReadyMessage {\n type: `${typeof MESSAGE_PREFIX}ready`;\n v: typeof PROTOCOL_VERSION;\n /** Shown to the partner for diagnostics only; the partner must not rely on it for anything. */\n walletVersion?: string;\n}\n\n/** The answer to one `request`, matched by `id`. Exactly one of `result` / `error`. */\nexport type ResponseMessage =\n | { type: `${typeof MESSAGE_PREFIX}response`; v: typeof PROTOCOL_VERSION; id: string; result: unknown }\n | { type: `${typeof MESSAGE_PREFIX}response`; v: typeof PROTOCOL_VERSION; id: string; error: RpcError };\n\n/** EIP-1193 provider events, forwarded to the partner. */\nexport type EventMessage =\n | {\n type: `${typeof MESSAGE_PREFIX}event`;\n v: typeof PROTOCOL_VERSION;\n event: \"accountsChanged\";\n data: `0x${string}`[];\n }\n | {\n type: `${typeof MESSAGE_PREFIX}event`;\n v: typeof PROTOCOL_VERSION;\n event: \"chainChanged\";\n data: `0x${string}`;\n }\n | {\n type: `${typeof MESSAGE_PREFIX}event`;\n v: typeof PROTOCOL_VERSION;\n event: \"disconnect\";\n data: RpcError;\n };\n\nexport type WalletToPartnerMessage = ReadyMessage | ResponseMessage | EventMessage;\n\n// --- opener → pop-up ---------------------------------------------------------------------------------\n\nexport interface RequestMessage {\n type: `${typeof MESSAGE_PREFIX}request`;\n v: typeof PROTOCOL_VERSION;\n /** Unique per request within one connection; the response carries it back. */\n id: string;\n method: string;\n params?: unknown[] | Record<string, unknown>;\n}\n\n/** The partner disconnects (for example the user clicked \"Disconnect\" in the partner page). */\nexport interface DisconnectMessage {\n type: `${typeof MESSAGE_PREFIX}disconnect`;\n v: typeof PROTOCOL_VERSION;\n}\n\nexport type PartnerToWalletMessage = RequestMessage | DisconnectMessage;\n\n// --- guards ------------------------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction hasType(value: unknown, suffix: string): value is Record<string, unknown> {\n return isRecord(value) && value.type === `${MESSAGE_PREFIX}${suffix}` && value.v === PROTOCOL_VERSION;\n}\n\nexport function isReadyMessage(value: unknown): value is ReadyMessage {\n return hasType(value, \"ready\");\n}\n\nexport function isResponseMessage(value: unknown): value is ResponseMessage {\n return (\n hasType(value, \"response\") &&\n typeof value.id === \"string\" &&\n (\"result\" in value || (isRecord(value.error) && typeof value.error.code === \"number\"))\n );\n}\n\nexport function isEventMessage(value: unknown): value is EventMessage {\n return (\n hasType(value, \"event\") &&\n (value.event === \"accountsChanged\" || value.event === \"chainChanged\" || value.event === \"disconnect\")\n );\n}\n\nexport function isRequestMessage(value: unknown): value is RequestMessage {\n return hasType(value, \"request\") && typeof value.id === \"string\" && typeof value.method === \"string\";\n}\n\nexport function isDisconnectMessage(value: unknown): value is DisconnectMessage {\n return hasType(value, \"disconnect\");\n}\n\n/** The URL the partner opens. `partnerOrigin` is `window.location.origin` of the partner page. */\nexport function connectUrl(walletUrl: string, partnerOrigin: string): string {\n const url = new URL(CONNECT_PATH, walletUrl);\n url.searchParams.set(\"origin\", partnerOrigin);\n url.searchParams.set(\"v\", String(PROTOCOL_VERSION));\n return url.toString();\n}\n"]}
@@ -0,0 +1,259 @@
1
+ import { RpcErrorCode, isSupportedMethod, isReadyMessage, isResponseMessage, isEventMessage, PROTOCOL_VERSION, MESSAGE_PREFIX, connectUrl } from './chunk-N6D53D3G.js';
2
+
3
+ // src/provider.ts
4
+ var ProviderRpcError = class extends Error {
5
+ code;
6
+ data;
7
+ constructor(code, message, data) {
8
+ super(message);
9
+ this.name = "ProviderRpcError";
10
+ this.code = code;
11
+ this.data = data;
12
+ }
13
+ };
14
+ var DEFAULT_POPUP_FEATURES = "popup,width=420,height=720";
15
+ var POPUP_NAME = "assetera-wallet";
16
+ var CLOSE_POLL_MS = 500;
17
+ var SIGNING_METHODS = /* @__PURE__ */ new Set([
18
+ "personal_sign",
19
+ "eth_signTypedData_v4",
20
+ "eth_sendTransaction"
21
+ ]);
22
+ function createDeferred() {
23
+ let resolve;
24
+ let reject;
25
+ const promise = new Promise((res, rej) => {
26
+ resolve = res;
27
+ reject = rej;
28
+ });
29
+ void promise.catch(() => {
30
+ });
31
+ return { promise, resolve, reject };
32
+ }
33
+ function toHexChainId(chainId) {
34
+ return `0x${chainId.toString(16)}`;
35
+ }
36
+ function createAsseteraWalletProvider(options) {
37
+ const { defaultChainId } = options;
38
+ const walletOrigin = new URL(options.walletUrl).origin;
39
+ const popupFeatures = options.popupFeatures ?? DEFAULT_POPUP_FEATURES;
40
+ let popup = null;
41
+ let ready = createDeferred();
42
+ let accounts = [];
43
+ let chainIdHex = null;
44
+ let walletDisconnected = false;
45
+ const pending = /* @__PURE__ */ new Map();
46
+ const listeners = /* @__PURE__ */ new Map();
47
+ let closeTimer = null;
48
+ function currentChainId() {
49
+ return chainIdHex ?? toHexChainId(defaultChainId);
50
+ }
51
+ function blockedError() {
52
+ return new ProviderRpcError(
53
+ RpcErrorCode.Disconnected,
54
+ "The Assetera Wallet pop-up was blocked by the browser. The pop-up must be opened from a user click (for example a button press) and then the request retried."
55
+ );
56
+ }
57
+ function disconnectedError(message = "The Assetera Wallet pop-up was closed.") {
58
+ return new ProviderRpcError(RpcErrorCode.Disconnected, message);
59
+ }
60
+ function emit(event, ...args) {
61
+ const set = listeners.get(event);
62
+ if (!set) return;
63
+ for (const listener of set) {
64
+ try {
65
+ listener(...args);
66
+ } catch (error) {
67
+ console.error(`[assetera-wallet] listener for "${event}" threw`, error);
68
+ }
69
+ }
70
+ }
71
+ function addListener(event, listener) {
72
+ let set = listeners.get(event);
73
+ if (!set) {
74
+ set = /* @__PURE__ */ new Set();
75
+ listeners.set(event, set);
76
+ }
77
+ set.add(listener);
78
+ }
79
+ function removeEventListener(event, listener) {
80
+ listeners.get(event)?.delete(listener);
81
+ }
82
+ function stopClosePolling() {
83
+ if (closeTimer !== null) {
84
+ clearInterval(closeTimer);
85
+ closeTimer = null;
86
+ }
87
+ }
88
+ function startClosePolling() {
89
+ if (closeTimer !== null) return;
90
+ closeTimer = setInterval(() => {
91
+ if (popup?.closed) handlePopupClosed();
92
+ }, CLOSE_POLL_MS);
93
+ }
94
+ function rejectAllPending(error) {
95
+ for (const request2 of pending.values()) request2.reject(error);
96
+ pending.clear();
97
+ }
98
+ function handlePopupClosed() {
99
+ const hadPending = pending.size > 0;
100
+ stopClosePolling();
101
+ const closedPopup = popup;
102
+ popup = null;
103
+ const closedReady = ready;
104
+ ready = createDeferred();
105
+ const error = disconnectedError();
106
+ if (closedPopup) closedReady.reject(error);
107
+ rejectAllPending(error);
108
+ const shouldEmit = hadPending || walletDisconnected;
109
+ walletDisconnected = false;
110
+ if (shouldEmit) emit("disconnect", error);
111
+ }
112
+ function openPopup() {
113
+ if (popup && !popup.closed) {
114
+ popup.focus();
115
+ return popup;
116
+ }
117
+ if (typeof window === "undefined") return null;
118
+ const opened = window.open(
119
+ connectUrl(options.walletUrl, window.location.origin),
120
+ POPUP_NAME,
121
+ popupFeatures
122
+ );
123
+ if (!opened) return null;
124
+ popup = opened;
125
+ ready = createDeferred();
126
+ walletDisconnected = false;
127
+ startClosePolling();
128
+ return opened;
129
+ }
130
+ function sendRequest(method, params) {
131
+ const active = popup;
132
+ if (!active || active.closed) return Promise.reject(disconnectedError());
133
+ const id = crypto.randomUUID();
134
+ const message = {
135
+ type: `${MESSAGE_PREFIX}request`,
136
+ v: PROTOCOL_VERSION,
137
+ id,
138
+ method,
139
+ ...params === void 0 ? {} : { params }
140
+ };
141
+ return new Promise((resolve, reject) => {
142
+ pending.set(id, { resolve, reject });
143
+ active.postMessage(message, walletOrigin);
144
+ });
145
+ }
146
+ function handleResponse(message) {
147
+ const request2 = pending.get(message.id);
148
+ if (!request2) return;
149
+ pending.delete(message.id);
150
+ if ("error" in message) {
151
+ request2.reject(new ProviderRpcError(message.error.code, message.error.message, message.error.data));
152
+ } else {
153
+ request2.resolve(message.result);
154
+ }
155
+ }
156
+ function handleEventMessage(message) {
157
+ if (message.event === "accountsChanged") {
158
+ accounts = [...message.data];
159
+ emit("accountsChanged", [...accounts]);
160
+ return;
161
+ }
162
+ if (message.event === "chainChanged") {
163
+ chainIdHex = message.data;
164
+ emit("chainChanged", chainIdHex);
165
+ return;
166
+ }
167
+ walletDisconnected = true;
168
+ emit("disconnect", new ProviderRpcError(message.data.code, message.data.message, message.data.data));
169
+ }
170
+ function onWindowMessage(event) {
171
+ if (event.origin !== walletOrigin) return;
172
+ if (popup === null || event.source !== popup) return;
173
+ const data = event.data;
174
+ if (isReadyMessage(data)) {
175
+ ready.resolve(void 0);
176
+ return;
177
+ }
178
+ if (isResponseMessage(data)) {
179
+ handleResponse(data);
180
+ return;
181
+ }
182
+ if (isEventMessage(data)) handleEventMessage(data);
183
+ }
184
+ if (typeof window !== "undefined") window.addEventListener("message", onWindowMessage);
185
+ function request({ method, params }) {
186
+ if (typeof window === "undefined") {
187
+ return Promise.reject(
188
+ new ProviderRpcError(
189
+ RpcErrorCode.Disconnected,
190
+ "The Assetera Wallet provider can only run in a browser."
191
+ )
192
+ );
193
+ }
194
+ if (!isSupportedMethod(method)) {
195
+ return Promise.reject(
196
+ new ProviderRpcError(
197
+ RpcErrorCode.UnsupportedMethod,
198
+ `The Assetera Wallet pop-up does not support the method "${method}".`
199
+ )
200
+ );
201
+ }
202
+ if (method === "eth_accounts") return Promise.resolve([...accounts]);
203
+ if (method === "eth_chainId") return Promise.resolve(currentChainId());
204
+ if (method === "eth_requestAccounts") {
205
+ const active2 = openPopup();
206
+ if (!active2) return Promise.reject(blockedError());
207
+ return ready.promise.then(() => sendRequest("eth_requestAccounts", params)).then((result) => {
208
+ accounts = Array.isArray(result) ? result : [];
209
+ emit("accountsChanged", [...accounts]);
210
+ emit("connect", { chainId: currentChainId() });
211
+ return [...accounts];
212
+ });
213
+ }
214
+ if (accounts.length === 0) {
215
+ return Promise.reject(
216
+ new ProviderRpcError(
217
+ RpcErrorCode.Unauthorized,
218
+ "Connect to the Assetera Wallet first (eth_requestAccounts)."
219
+ )
220
+ );
221
+ }
222
+ const active = openPopup();
223
+ if (!active) return Promise.reject(blockedError());
224
+ if (SIGNING_METHODS.has(method)) active.focus();
225
+ return ready.promise.then(() => sendRequest(method, params));
226
+ }
227
+ function disconnect() {
228
+ const active = popup;
229
+ if (active && !active.closed) {
230
+ const message = {
231
+ type: `${MESSAGE_PREFIX}disconnect`,
232
+ v: PROTOCOL_VERSION
233
+ };
234
+ active.postMessage(message, walletOrigin);
235
+ active.close();
236
+ }
237
+ stopClosePolling();
238
+ popup = null;
239
+ const closedReady = ready;
240
+ ready = createDeferred();
241
+ closedReady.reject(disconnectedError("Disconnected from the Assetera Wallet."));
242
+ rejectAllPending(disconnectedError("Disconnected from the Assetera Wallet."));
243
+ accounts = [];
244
+ chainIdHex = null;
245
+ walletDisconnected = false;
246
+ emit("disconnect", disconnectedError("Disconnected from the Assetera Wallet."));
247
+ }
248
+ function on(event, listener) {
249
+ addListener(event, listener);
250
+ }
251
+ function removeListener(event, listener) {
252
+ removeEventListener(event, listener);
253
+ }
254
+ return { request, on, removeListener, disconnect };
255
+ }
256
+
257
+ export { ProviderRpcError, createAsseteraWalletProvider };
258
+ //# sourceMappingURL=chunk-QPNIGEP4.js.map
259
+ //# sourceMappingURL=chunk-QPNIGEP4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.ts"],"names":["request","active"],"mappings":"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","file":"chunk-QPNIGEP4.js","sourcesContent":["/**\n * EIP-1193 provider for the Assetera Wallet pop-up — the opener side of the ADR-0054 D3/D4 protocol.\n *\n * The provider owns a single pop-up (`window.open(connectUrl(...), \"assetera-wallet\", ...)`) and speaks the\n * wire format defined in `./protocol`. It never touches Dynamic, React, wagmi or viem, so a partner can use it\n * on its own or through the wagmi / RainbowKit adapters in this package.\n */\nimport {\n MESSAGE_PREFIX,\n PROTOCOL_VERSION,\n RpcErrorCode,\n connectUrl,\n isEventMessage,\n isReadyMessage,\n isResponseMessage,\n isSupportedMethod,\n type DisconnectMessage,\n type EventMessage,\n type RequestMessage,\n type ResponseMessage,\n} from \"./protocol\";\n\n/** Arguments accepted by `request`, mirroring EIP-1193. */\nexport interface RequestArguments {\n method: string;\n params?: unknown[] | Record<string, unknown>;\n}\n\n/** EIP-1193 provider events emitted by the pop-up. */\nexport type ProviderEvent = \"accountsChanged\" | \"chainChanged\" | \"connect\" | \"disconnect\";\n\n/**\n * Listener shape for EIP-1193 provider events. `never[]` accepts a listener with any concrete parameter list\n * (for example wagmi's `(accounts: string[]) => void`), while internal emission stays type-safe via a cast.\n */\nexport type ProviderListener = (...args: never[]) => void;\n\n/** The EIP-1193 surface returned by {@link createAsseteraWalletProvider}. */\nexport interface AsseteraWalletProvider {\n request(args: RequestArguments): Promise<unknown>;\n on(event: ProviderEvent, listener: ProviderListener): void;\n removeListener(event: ProviderEvent, listener: ProviderListener): void;\n disconnect(): void;\n}\n\n/** Options for {@link createAsseteraWalletProvider}. */\nexport interface AsseteraWalletProviderOptions {\n /** Base URL of the wallet origin, e.g. `https://wallet.playground.assetera.com`. */\n walletUrl: string;\n /** Chain id used before the wallet reports one, and as the local `eth_chainId` fallback. */\n defaultChainId: number;\n /** `window.open` feature string. Defaults to `\"popup,width=420,height=720\"`. */\n popupFeatures?: string;\n}\n\n/**\n * EIP-1193 error carrying a JSON-RPC `code` (and optional `data`). This is what every rejected `request`\n * throws, so wagmi maps the wallet's `4001` / `4100` / `4200` / `4900` correctly.\n */\nexport class ProviderRpcError extends Error {\n readonly code: number;\n readonly data?: unknown;\n\n constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.name = \"ProviderRpcError\";\n this.code = code;\n this.data = data;\n }\n}\n\nconst DEFAULT_POPUP_FEATURES = \"popup,width=420,height=720\";\nconst POPUP_NAME = \"assetera-wallet\";\nconst CLOSE_POLL_MS = 500;\n\n/** Methods that show the user a signature / transaction prompt, so the pop-up should be focused first. */\nconst SIGNING_METHODS: ReadonlySet<string> = new Set([\n \"personal_sign\",\n \"eth_signTypedData_v4\",\n \"eth_sendTransaction\",\n]);\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n}\n\ninterface Deferred<T> {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (reason: unknown) => void;\n}\n\nfunction createDeferred<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n // The `ready` deferred is rejected when the pop-up closes even if no request is waiting on it; mark the\n // promise as handled so that rejection is not reported as an unhandled promise rejection.\n void promise.catch(() => {});\n return { promise, resolve, reject };\n}\n\nfunction toHexChainId(chainId: number): string {\n return `0x${chainId.toString(16)}`;\n}\n\n/**\n * Create the EIP-1193 provider that talks to the Assetera Wallet pop-up.\n *\n * Call `request` from a user gesture: `eth_requestAccounts` opens the pop-up synchronously (before any\n * `await`) so the browser treats it as part of the click and does not block it.\n */\nexport function createAsseteraWalletProvider(options: AsseteraWalletProviderOptions): AsseteraWalletProvider {\n const { defaultChainId } = options;\n const walletOrigin = new URL(options.walletUrl).origin;\n const popupFeatures = options.popupFeatures ?? DEFAULT_POPUP_FEATURES;\n\n let popup: Window | null = null;\n /** Resolves when the *current* pop-up posts `ready`. Replaced when a new pop-up is opened. */\n let ready = createDeferred<void>();\n let accounts: string[] = [];\n let chainIdHex: string | null = null;\n /** Set when the wallet itself told us the session ended (as opposed to the user merely closing the tab). */\n let walletDisconnected = false;\n const pending = new Map<string, PendingRequest>();\n const listeners = new Map<ProviderEvent, Set<(...args: unknown[]) => void>>();\n let closeTimer: ReturnType<typeof setInterval> | null = null;\n\n function currentChainId(): string {\n return chainIdHex ?? toHexChainId(defaultChainId);\n }\n\n function blockedError(): ProviderRpcError {\n return new ProviderRpcError(\n RpcErrorCode.Disconnected,\n \"The Assetera Wallet pop-up was blocked by the browser. The pop-up must be opened from a user click \" +\n \"(for example a button press) and then the request retried.\",\n );\n }\n\n function disconnectedError(message = \"The Assetera Wallet pop-up was closed.\"): ProviderRpcError {\n return new ProviderRpcError(RpcErrorCode.Disconnected, message);\n }\n\n function emit(event: ProviderEvent, ...args: unknown[]): void {\n const set = listeners.get(event);\n if (!set) return;\n for (const listener of set) {\n try {\n listener(...args);\n } catch (error) {\n console.error(`[assetera-wallet] listener for \"${event}\" threw`, error);\n }\n }\n }\n\n function addListener(event: ProviderEvent, listener: ProviderListener): void {\n let set = listeners.get(event);\n if (!set) {\n set = new Set<(...args: unknown[]) => void>();\n listeners.set(event, set);\n }\n set.add(listener as (...args: unknown[]) => void);\n }\n\n function removeEventListener(event: ProviderEvent, listener: ProviderListener): void {\n listeners.get(event)?.delete(listener as (...args: unknown[]) => void);\n }\n\n function stopClosePolling(): void {\n if (closeTimer !== null) {\n clearInterval(closeTimer);\n closeTimer = null;\n }\n }\n\n function startClosePolling(): void {\n if (closeTimer !== null) return;\n closeTimer = setInterval(() => {\n if (popup?.closed) handlePopupClosed();\n }, CLOSE_POLL_MS);\n }\n\n function rejectAllPending(error: ProviderRpcError): void {\n for (const request of pending.values()) request.reject(error);\n pending.clear();\n }\n\n /**\n * The pop-up was closed by the user (or the wallet app).\n *\n * We deliberately KEEP `accounts` / `chainIdHex` cached: the dApp stays \"connected\" in React state and does\n * not flicker back to a disconnected UI. Because a pop-up cannot be reopened without a user gesture, the next\n * signing request reopens it from the caller's click. `disconnect` is only emitted when the close actually\n * interrupted work (a request was pending) or when the wallet itself announced the disconnect.\n */\n function handlePopupClosed(): void {\n const hadPending = pending.size > 0;\n stopClosePolling();\n const closedPopup = popup;\n popup = null;\n const closedReady = ready;\n ready = createDeferred<void>();\n const error = disconnectedError();\n if (closedPopup) closedReady.reject(error);\n rejectAllPending(error);\n const shouldEmit = hadPending || walletDisconnected;\n walletDisconnected = false;\n if (shouldEmit) emit(\"disconnect\", error);\n }\n\n /**\n * Open the pop-up, or focus and reuse it when it is still around. Must stay synchronous (it is called before\n * any `await` in `request`) so it runs inside the caller's user activation.\n */\n function openPopup(): Window | null {\n if (popup && !popup.closed) {\n popup.focus();\n return popup;\n }\n if (typeof window === \"undefined\") return null;\n const opened = window.open(\n connectUrl(options.walletUrl, window.location.origin),\n POPUP_NAME,\n popupFeatures,\n );\n if (!opened) return null;\n popup = opened;\n ready = createDeferred<void>();\n walletDisconnected = false;\n startClosePolling();\n return opened;\n }\n\n function sendRequest(method: string, params?: RequestArguments[\"params\"]): Promise<unknown> {\n const active = popup;\n if (!active || active.closed) return Promise.reject(disconnectedError());\n const id = crypto.randomUUID();\n const message: RequestMessage = {\n type: `${MESSAGE_PREFIX}request`,\n v: PROTOCOL_VERSION,\n id,\n method,\n ...(params === undefined ? {} : { params }),\n };\n return new Promise<unknown>((resolve, reject) => {\n pending.set(id, { resolve, reject });\n active.postMessage(message, walletOrigin);\n });\n }\n\n function handleResponse(message: ResponseMessage): void {\n const request = pending.get(message.id);\n if (!request) return;\n pending.delete(message.id);\n if (\"error\" in message) {\n request.reject(new ProviderRpcError(message.error.code, message.error.message, message.error.data));\n } else {\n request.resolve(message.result);\n }\n }\n\n function handleEventMessage(message: EventMessage): void {\n if (message.event === \"accountsChanged\") {\n accounts = [...message.data];\n emit(\"accountsChanged\", [...accounts]);\n return;\n }\n if (message.event === \"chainChanged\") {\n chainIdHex = message.data;\n emit(\"chainChanged\", chainIdHex);\n return;\n }\n walletDisconnected = true;\n emit(\"disconnect\", new ProviderRpcError(message.data.code, message.data.message, message.data.data));\n }\n\n function onWindowMessage(event: MessageEvent): void {\n if (event.origin !== walletOrigin) return;\n if (popup === null || (event.source as unknown) !== popup) return;\n const data: unknown = event.data;\n if (isReadyMessage(data)) {\n ready.resolve(undefined);\n return;\n }\n if (isResponseMessage(data)) {\n handleResponse(data);\n return;\n }\n if (isEventMessage(data)) handleEventMessage(data);\n }\n\n if (typeof window !== \"undefined\") window.addEventListener(\"message\", onWindowMessage);\n\n function request({ method, params }: RequestArguments): Promise<unknown> {\n if (typeof window === \"undefined\") {\n return Promise.reject(\n new ProviderRpcError(\n RpcErrorCode.Disconnected,\n \"The Assetera Wallet provider can only run in a browser.\",\n ),\n );\n }\n // Read-only chain calls go through wagmi's own transports; the pop-up refuses everything else.\n if (!isSupportedMethod(method)) {\n return Promise.reject(\n new ProviderRpcError(\n RpcErrorCode.UnsupportedMethod,\n `The Assetera Wallet pop-up does not support the method \"${method}\".`,\n ),\n );\n }\n // Answered locally, without a pop-up, so wagmi can read the connection before the user clicks.\n if (method === \"eth_accounts\") return Promise.resolve([...accounts]);\n if (method === \"eth_chainId\") return Promise.resolve(currentChainId());\n\n // Opening the pop-up MUST happen synchronously, before the first `await`, to stay in the click.\n if (method === \"eth_requestAccounts\") {\n const active = openPopup();\n if (!active) return Promise.reject(blockedError());\n return ready.promise\n .then(() => sendRequest(\"eth_requestAccounts\", params))\n .then((result) => {\n accounts = Array.isArray(result) ? (result as string[]) : [];\n emit(\"accountsChanged\", [...accounts]);\n emit(\"connect\", { chainId: currentChainId() });\n return [...accounts];\n });\n }\n\n // Every other supported method needs an approved connection.\n if (accounts.length === 0) {\n return Promise.reject(\n new ProviderRpcError(\n RpcErrorCode.Unauthorized,\n \"Connect to the Assetera Wallet first (eth_requestAccounts).\",\n ),\n );\n }\n const active = openPopup();\n if (!active) return Promise.reject(blockedError());\n if (SIGNING_METHODS.has(method)) active.focus();\n return ready.promise.then(() => sendRequest(method, params));\n }\n\n function disconnect(): void {\n const active = popup;\n if (active && !active.closed) {\n const message: DisconnectMessage = {\n type: `${MESSAGE_PREFIX}disconnect`,\n v: PROTOCOL_VERSION,\n };\n active.postMessage(message, walletOrigin);\n active.close();\n }\n stopClosePolling();\n popup = null;\n const closedReady = ready;\n ready = createDeferred<void>();\n closedReady.reject(disconnectedError(\"Disconnected from the Assetera Wallet.\"));\n rejectAllPending(disconnectedError(\"Disconnected from the Assetera Wallet.\"));\n accounts = [];\n chainIdHex = null;\n walletDisconnected = false;\n emit(\"disconnect\", disconnectedError(\"Disconnected from the Assetera Wallet.\"));\n }\n\n function on(event: ProviderEvent, listener: ProviderListener): void {\n addListener(event, listener);\n }\n\n function removeListener(event: ProviderEvent, listener: ProviderListener): void {\n removeEventListener(event, listener);\n }\n\n return { request, on, removeListener, disconnect };\n}\n"]}