@usebutr/evm 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.
@@ -0,0 +1,247 @@
1
+ import { ChainBase, PlatformDiscoverer, WalletAdapter, WalletCapabilities } from "@usebutr/core";
2
+
3
+ //#region src/eip1193.d.ts
4
+ /**
5
+ * Minimal EIP-1193 / EIP-6963 type surface — declared inline rather
6
+ * than imported from `@types/ethereum` (which doesn't exist as a
7
+ * cohesive package) or `viem` (which would expand butr's footprint).
8
+ *
9
+ * These types describe only the methods butr's auto-discovery layer
10
+ * actually calls on a provider. They're intentionally loose
11
+ * (`request(...)` returns `unknown`) so individual call sites can
12
+ * narrow the result via a cast.
13
+ */
14
+ type Eip1193RequestArgs = {
15
+ method: string;
16
+ params?: ReadonlyArray<unknown> | object;
17
+ };
18
+ type Eip1193Listener = (...args: ReadonlyArray<unknown>) => void;
19
+ type Eip1193Provider = {
20
+ on(event: string, listener: Eip1193Listener): void;
21
+ removeListener(event: string, listener: Eip1193Listener): void;
22
+ request(args: Eip1193RequestArgs): Promise<unknown>;
23
+ };
24
+ type Eip6963ProviderInfo = {
25
+ /** Data URI or absolute URL pointing at the wallet's icon. */icon: string; /** Human-readable wallet name shown in pickers. */
26
+ name: string;
27
+ /** Reverse-DNS identifier — `io.metamask`, `io.rabby`, `app.phantom`.
28
+ * Stable across versions, unique per wallet, used as the pool key. */
29
+ rdns: string; /** Per-page UUID generated by the wallet. Not stable across reloads. */
30
+ uuid: string;
31
+ };
32
+ type Eip6963ProviderDetail = {
33
+ info: Eip6963ProviderInfo;
34
+ provider: Eip1193Provider;
35
+ };
36
+ type Eip6963AnnounceEvent = CustomEvent<Eip6963ProviderDetail>;
37
+ //#endregion
38
+ //#region src/eip6963.d.ts
39
+ declare const ANNOUNCE_EVENT = "eip6963:announceProvider";
40
+ declare const REQUEST_EVENT = "eip6963:requestProvider";
41
+ type AdapterCallback = (adapter: WalletAdapter, info: Eip6963ProviderInfo) => void;
42
+ type DiscoverOptions = {
43
+ /** Event target to listen on. Defaults to `window` in browser/RN/
44
+ * SSR'd environments; tests can pass a fresh `new EventTarget()` to
45
+ * exercise the discovery loop without a DOM. */
46
+ target?: EventTarget;
47
+ };
48
+ /**
49
+ * Subscribe to EIP-6963 wallet announcements. Spec:
50
+ * https://eips.ethereum.org/EIPS/eip-6963
51
+ *
52
+ * Flow:
53
+ * 1. Page registers a listener for `eip6963:announceProvider`.
54
+ * 2. Page dispatches `eip6963:requestProvider` to kick wallets that
55
+ * have already loaded.
56
+ * 3. Wallets that load later still dispatch their own announcement
57
+ * when they boot, so the listener stays attached for the lifetime
58
+ * of the discovery session.
59
+ * 4. Dedup by `info.rdns` — wallets occasionally re-announce, and
60
+ * `info.uuid` is regenerated per page load so it's not stable.
61
+ *
62
+ * Returns an unsubscribe function that removes the listener.
63
+ * In non-browser environments (SSR, tests with no window) returns
64
+ * a noop unsubscribe and never fires the callback.
65
+ */
66
+ declare const discoverEvmAdapters: (onAdapter: AdapterCallback, options?: DiscoverOptions) => (() => void);
67
+ //#endregion
68
+ //#region src/eip6963-adapter.d.ts
69
+ /**
70
+ * Convert a hex string (with or without `0x` prefix) into a Uint8Array.
71
+ * Odd-length strings are not supported; EIP-1193 returns canonical
72
+ * even-length hex.
73
+ */
74
+ declare const hexToBytes: (hex: string) => Uint8Array;
75
+ declare const bytesToHex: (bytes: Uint8Array) => string;
76
+ declare const chainIdHexToDecimal: (hex: string) => string;
77
+ declare const chainIdDecimalToHex: (dec: string) => string;
78
+ /** Pure 18-decimal wei → ether formatter. Trims trailing zeros so
79
+ * `1.0` stays `1` and `1.5` stays `1.5`. */
80
+ declare const formatEther: (wei: bigint) => string;
81
+ /**
82
+ * Adapt an EIP-1193 provider (announced via EIP-6963) into a butr
83
+ * `WalletAdapter`. The returned adapter covers the full `Connector +
84
+ * Wallet` surface; consumers can pass it through `createConnector` like
85
+ * any hand-written adapter.
86
+ *
87
+ * **Caveats**
88
+ *
89
+ * - `disconnect` calls `wallet_revokePermissions`. Many wallets don't
90
+ * implement that method and silently ignore the call. The reducer
91
+ * will still mark the wallet as disconnected on butr's side; the
92
+ * wallet's own auto-reconnect heuristic may or may not honour it.
93
+ * - `switchAccount` is intentionally unimplemented because EIP-1193 has
94
+ * no silent "use address X" RPC. The active account is whichever one
95
+ * the wallet exposes first; the user changes it through the wallet's
96
+ * own UI. butr's `subscribe` bridge auto-updates the pool entry when
97
+ * `accountsChanged` fires. Call `requestAccounts` if you want the
98
+ * wallet to expose more accounts.
99
+ * - `getBalance` returns the native ETH balance with symbol `"ETH"`,
100
+ * regardless of which EVM chain the wallet is currently on. Consumers
101
+ * that target multiple EVM chains should overlay the symbol via
102
+ * their own logic.
103
+ * - `getSigner` returns the raw EIP-1193 provider. Wrap it in viem's
104
+ * `createWalletClient` or ethers' `BrowserProvider` at the call site.
105
+ */
106
+ declare const buildEvmAdapter: (info: Eip6963ProviderInfo, provider: Eip1193Provider) => WalletAdapter;
107
+ //#endregion
108
+ //#region src/injected.d.ts
109
+ /**
110
+ * Generic wallet icon (a stylised purse SVG, ~600 bytes). Used as
111
+ * the fallback icon when an injected provider has no EIP-6963 info.
112
+ * Inline rather than a network fetch so the picker doesn't flicker.
113
+ */
114
+ declare const GENERIC_INJECTED_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjEgMTJWN0g1YTIgMiAwIDAgMSAwLTQgaDE0djQiLz48cGF0aCBkPSJNMyA1djE0YTIgMiAwIDAgMCAyIDJoMTZ2LTVNMjEgMTIgaC0zYTIgMiAwIDAgMC0yIDJ2MGEyIDIgMCAwIDAgMiAyaDN6Ii8+PC9zdmc+";
115
+ type InjectedDiscoveryOptions = {
116
+ /**
117
+ * Predicate the fallback consults before emitting — if any EIP-6963
118
+ * adapter has been registered by the time the settle timer fires,
119
+ * the injected fallback is skipped (the wallet is already covered
120
+ * via the standards path; we don't want a duplicate).
121
+ *
122
+ * `discoverWalletAdapters` wires this to the shared `seen` set so
123
+ * the dedupe is automatic. Callers using `discoverInjectedAdapter`
124
+ * directly should pass their own check or omit it (always emit).
125
+ */
126
+ hasAnyEip6963Adapter?: () => boolean;
127
+ /**
128
+ * How long to wait for EIP-6963 announcements before falling back
129
+ * to `window.ethereum`. Most wallets announce within the first
130
+ * frame; 150ms is a conservative settle window that keeps the
131
+ * picker responsive while giving slow announcements time to land.
132
+ */
133
+ settleMs?: number; /** Override the global `window` reference (tests, iframes). */
134
+ target?: {
135
+ ethereum?: unknown;
136
+ } | null;
137
+ };
138
+ /**
139
+ * Last-resort discovery path for EVM wallets that DON'T announce via
140
+ * EIP-6963. Most modern wallets (MetaMask, Rabby, Coinbase, Phantom,
141
+ * Brave, etc.) do announce, so the standards-based path
142
+ * (`discoverEvmAdapters`) covers them. This module catches the long
143
+ * tail: regional wallets (KuCoin, Bitget, BitKeep, Okto, Frontier)
144
+ * and older builds that only expose `window.ethereum`.
145
+ *
146
+ * **Coordination**: the fallback waits `settleMs` (default 150ms)
147
+ * after subscription, then consults `hasAnyEip6963Adapter` if
148
+ * provided. If any EIP-6963 adapter has already been registered, we
149
+ * skip emission — assume the same wallet is also speaking standards
150
+ * and we'd just be duplicating. If no EIP-6963 adapter showed up but
151
+ * `window.ethereum` does, we emit a generic adapter.
152
+ *
153
+ * The emitted adapter has `rdns: "injected:legacy"` so consumers can
154
+ * distinguish it from EIP-6963 adapters in the picker UI.
155
+ */
156
+ declare const discoverInjectedAdapter: (onAdapter: (adapter: WalletAdapter) => void, options?: InjectedDiscoveryOptions) => (() => void);
157
+ //#endregion
158
+ //#region src/chains.d.ts
159
+ /**
160
+ * Common EVM chain registry keyed by CAIP-2 id. Useful for chain-
161
+ * switcher UIs. butr itself doesn't read this — `Connector.switchChain`
162
+ * accepts any `ChainBase`.
163
+ */
164
+ declare const EVM_CHAINS: {
165
+ readonly arbitrum: {
166
+ readonly id: "eip155:42161";
167
+ readonly name: "Arbitrum One";
168
+ readonly namespace: "eip155";
169
+ readonly reference: "42161";
170
+ };
171
+ readonly base: {
172
+ readonly id: "eip155:8453";
173
+ readonly name: "Base";
174
+ readonly namespace: "eip155";
175
+ readonly reference: "8453";
176
+ };
177
+ readonly bsc: {
178
+ readonly id: "eip155:56";
179
+ readonly name: "BNB Smart Chain";
180
+ readonly namespace: "eip155";
181
+ readonly reference: "56";
182
+ };
183
+ readonly ethereum: {
184
+ readonly id: "eip155:1";
185
+ readonly name: "Ethereum";
186
+ readonly namespace: "eip155";
187
+ readonly reference: "1";
188
+ };
189
+ readonly optimism: {
190
+ readonly id: "eip155:10";
191
+ readonly name: "Optimism";
192
+ readonly namespace: "eip155";
193
+ readonly reference: "10";
194
+ };
195
+ readonly polygon: {
196
+ readonly id: "eip155:137";
197
+ readonly name: "Polygon";
198
+ readonly namespace: "eip155";
199
+ readonly reference: "137";
200
+ };
201
+ readonly sepolia: {
202
+ readonly id: "eip155:11155111";
203
+ readonly name: "Sepolia";
204
+ readonly namespace: "eip155";
205
+ readonly reference: "11155111";
206
+ };
207
+ };
208
+ declare const EVM_CHAINS_LIST: ReadonlyArray<ChainBase>;
209
+ //#endregion
210
+ //#region src/capabilities.d.ts
211
+ declare const EIP6963_RDNS_WITH_REQUEST_ACCOUNTS: Set<string>;
212
+ type Eip6963CapabilityInput = {
213
+ rdns: string;
214
+ };
215
+ declare const resolveEip6963Capabilities: (input: Eip6963CapabilityInput) => WalletCapabilities;
216
+ //#endregion
217
+ //#region src/discoverer.d.ts
218
+ /**
219
+ * EVM's `PlatformDiscoverer`: EIP-6963 announcements primary, with the
220
+ * legacy `window.ethereum` injected fallback wired to defer until
221
+ * EIP-6963 has had a chance to fire.
222
+ *
223
+ * Consumers using `@usebutr/wallets`'s `autoDiscovery()` get this
224
+ * wired in automatically. Direct consumers who don't want the
225
+ * injected fallback build their own discovery source from
226
+ * `discoverEvmAdapters` alone.
227
+ */
228
+ declare const evmDiscoverer: PlatformDiscoverer;
229
+ //#endregion
230
+ //#region src/signer-augmentation.d.ts
231
+ /**
232
+ * Augment `@usebutr/core`'s `SignerForPlatform` registry with the EVM
233
+ * entry. Consumers that import anything from `@usebutr/evm` get the
234
+ * `evm` key typed as `Eip1193Provider` transitively.
235
+ *
236
+ * `getSigner()` itself still returns `Promise<unknown>` (see
237
+ * `packages/core/src/types/signer.ts` for why); this declaration only
238
+ * affects what `SignerForPlatform["evm"]` evaluates to.
239
+ */
240
+ declare module "@usebutr/core" {
241
+ interface SignerForPlatform {
242
+ evm: Eip1193Provider;
243
+ }
244
+ } //# sourceMappingURL=signer-augmentation.d.ts.map
245
+ //#endregion
246
+ export { ANNOUNCE_EVENT, EIP6963_RDNS_WITH_REQUEST_ACCOUNTS, EVM_CHAINS, EVM_CHAINS_LIST, type Eip1193Listener, type Eip1193Provider, type Eip1193RequestArgs, type Eip6963AnnounceEvent, type Eip6963CapabilityInput, type Eip6963ProviderDetail, type Eip6963ProviderInfo, GENERIC_INJECTED_ICON, type InjectedDiscoveryOptions, REQUEST_EVENT, buildEvmAdapter, bytesToHex, chainIdDecimalToHex, chainIdHexToDecimal, discoverEvmAdapters, discoverInjectedAdapter, evmDiscoverer, formatEther, hexToBytes, resolveEip6963Capabilities };
247
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/eip1193.ts","../src/eip6963.ts","../src/eip6963-adapter.ts","../src/injected.ts","../src/chains.ts","../src/capabilities.ts","../src/discoverer.ts","../src/signer-augmentation.ts"],"mappings":";;;;;;;;;;;;;KAWK,kBAAA;EACH,MAAA;EACA,MAAA,GAAS,aAAa;AAAA;AAAA,KAGnB,eAAA,OAAsB,IAAA,EAAM,aAAa;AAAA,KAEzC,eAAA;EACH,EAAA,CAAG,KAAA,UAAe,QAAA,EAAU,eAAA;EAC5B,cAAA,CAAe,KAAA,UAAe,QAAA,EAAU,eAAA;EACxC,OAAA,CAAQ,IAAA,EAAM,kBAAA,GAAqB,OAAA;AAAA;AAAA,KAGhC,mBAAA;EAJqC,8DAMxC,IAAA,UALmC;EAOnC,IAAA;EAP0C;;EAU1C,IAAA,UAZ4B;EAc5B,IAAA;AAAA;AAAA,KAGG,qBAAA;EACH,IAAA,EAAM,mBAAA;EACN,QAAA,EAAU,eAAe;AAAA;AAAA,KAGtB,oBAAA,GAAuB,WAAW,CAAC,qBAAA;;;cCpClC,cAAA;AAAA,cACA,aAAA;AAAA,KAED,eAAA,IAAmB,OAAA,EAAS,aAAA,EAAe,IAAA,EAAM,mBAAmB;AAAA,KAEpE,eAAA;EDGmB;;;ECCtB,MAAA,GAAS,WAAW;AAAA;ADDE;AAAA;;;;AAGsB;AAAA;;;;;;;;;;;;AAHtB,cCwClB,mBAAA,GACJ,SAAA,EAAW,eAAA,EACX,OAAA,GAAS,eAAoB;;;;;;;;cCzCzB,UAAA,GAAc,GAAA,aAAc,UAOjC;AAAA,cAEK,UAAA,GAAc,KAAiB,EAAV,UAAU;AAAA,cAQ/B,mBAAA,GAAuB,GAAW;AAAA,cAClC,mBAAA,GAAuB,GAAW;AFnBhB;AAAA;AAAA,cEuBlB,WAAA,GAAe,GAAW;;;AFpBc;AAAA;;;;;;;;;;;;;;;;;;;;;;cE0ExC,eAAA,GAAmB,IAAA,EAAM,mBAAA,EAAqB,QAAA,EAAU,eAAA,KAAkB,aAAA;;;;;;;;cChF1E,qBAAA;AAAA,KAKD,wBAAA;EHFH;;;AAAsB;AAAA;;;;AAGsB;AAAA;EGU5C,oBAAA;;;;;;;EAOA,QAAA,WHdA;EGgBA,MAAA;IAAW,QAAA;EAAA;AAAA;;;;;;;;;AHd+B;AAAA;;;;;;;;;cGmDtC,uBAAA,GACJ,SAAA,GAAY,OAAA,EAAS,aAAA,WACrB,OAAA,GAAS,wBAA6B;;;;;;;;cCnElC,UAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAeA,eAAA,EAAiB,aAAa,CAAC,SAAA;;;cCT/B,kCAAA,EAAkC,GAAA;AAAA,KAInC,sBAAA;EAA2B,IAAI;AAAA;AAAA,cAE9B,0BAAA,GAA8B,KAAA,EAAO,sBAAA,KAAyB,kBAelE;;;;;;;;;;;;;cCnBI,aAAA,EAAe,kBASpB;;;;;;;;;;;;;YCXW,iBAAA;IACR,GAAA,EAAK,eAAe;EAAA;AAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ const e=new Set([`io.metamask`]),t=t=>({getBalance:!0,getTransactionReceipt:!0,requestAccounts:e.has(t.rdns),sendTransaction:!0,signIn:!1,signMessage:!0,signTransaction:!1,subscribe:!0,switchAccount:!1,switchChain:!0}),n=18n,r=10n**n,i=e=>{let t=e.startsWith(`0x`)?e.slice(2):e,n=new Uint8Array(t.length/2);for(let e=0;e<n.length;e+=1)n[e]=Number.parseInt(t.slice(e*2,e*2+2),16);return n},a=e=>{let t=`0x`;for(let n of e)t+=n.toString(16).padStart(2,`0`);return t},o=e=>BigInt(e).toString(10),s=e=>`0x${BigInt(e).toString(16)}`,c=e=>{let t=e/r,i=e%r;if(i===0n)return t.toString();let a=i.toString().padStart(Number(n),`0`).replace(RegExp(`0+$`,`v`),``);return a.length>0?`${t}.${a}`:t.toString()},l=(e,t)=>{let n=o(e);return{id:`eip155:${n}`,name:t,namespace:`eip155`,reference:n}},u=(e,t)=>({chain:t,id:`${t.id}:${e.toLowerCase()}`,walletAddress:e}),d=(e,r)=>({capabilities:t({rdns:e.rdns}),chainPlatform:`evm`,async connect(e){if(e?.silent){if((await r.request({method:`eth_accounts`})).length===0)throw Error(`No authorized accounts for silent reconnect`);return}await r.request({method:`eth_requestAccounts`})},async disconnect(){try{await r.request({method:`wallet_revokePermissions`,params:[{eth_accounts:{}}]})}catch{}},async getAccount(){let t=await r.request({method:`eth_accounts`});if(t.length===0)return null;let n=l(await r.request({method:`eth_chainId`}),e.name),i=t[0];return i?u(i,n):null},async getAccounts(){let t=await r.request({method:`eth_accounts`});if(t.length===0)return[];let n=l(await r.request({method:`eth_chainId`}),e.name);return t.map(e=>u(e,n))},async getBalance(){let e=(await r.request({method:`eth_accounts`}))[0];if(!e)throw Error(`No connected account`);let t=await r.request({method:`eth_getBalance`,params:[e,`latest`]}),i=BigInt(t);return{decimals:Number(n),formatted:c(i),symbol:`ETH`,value:i}},getSigner(){return Promise.resolve(r)},async getTransactionReceipt(e){let t=await r.request({method:`eth_getTransactionReceipt`,params:[e]});return t?{status:t.status===`0x1`?`Success`:`Error`}:{status:`Pending`}},icon:e.icon,id:e.rdns,name:e.name,async requestAccounts(){try{await r.request({method:`wallet_requestPermissions`,params:[{eth_accounts:{}}]})}catch(e){let t=e,n=t?.code,i=t?.data?.originalError?.code;if(n===4200||n===-32601||n===-32603||i===4200||i===-32601||i===-32604){await r.request({method:`eth_requestAccounts`});return}throw e}},async sendTx(e,t){let n=t?{...e,from:t.walletAddress}:e;return await r.request({method:`eth_sendTransaction`,params:[n]})},async sendTxToChain(e,t,n,i){let a=await r.request({method:`eth_chainId`}),o=s(t);a.toLowerCase()!==o.toLowerCase()&&(await r.request({method:`wallet_switchEthereumChain`,params:[{chainId:o}]}),i?.());let c=n?{...e,from:n.walletAddress}:e;return await r.request({method:`eth_sendTransaction`,params:[c]})},async signMessage(e,t){let n=t?.walletAddress;if(n||=(await r.request({method:`eth_accounts`}))[0],!n)throw Error(`No connected account`);return{signature:i(await r.request({method:`personal_sign`,params:[a(e),n]})),signedMessage:e}},subscribe(t){let n=(...n)=>{let i=n[0];if(i.length===0){t({type:`disconnected`});return}r.request({method:`eth_chainId`}).then(n=>{let r=l(n,e.name),a=i.map(e=>u(e,r)),o=a[0];o&&t({account:o,accounts:a,type:`accountChanged`})}).catch(()=>{})},i=(...n)=>{let i=n[0];r.request({method:`eth_accounts`}).then(n=>{let r=n;if(r.length===0)return;let a=l(i,e.name),o=r.map(e=>u(e,a)),s=o[0];s&&t({account:s,accounts:o,type:`accountChanged`})}).catch(()=>{})},a=()=>{t({type:`disconnected`})},o=()=>{Promise.all([r.request({method:`eth_accounts`}),r.request({method:`eth_chainId`})]).then(([n,r])=>{let i=n;if(i.length===0)return;let a=l(r,e.name),o=i.map(e=>u(e,a)),s=o[0];s&&t({account:s,accounts:o,type:`accountChanged`})}).catch(()=>{})};return r.on(`accountsChanged`,n),r.on(`chainChanged`,i),r.on(`connect`,o),r.on(`disconnect`,a),()=>{r.removeListener(`accountsChanged`,n),r.removeListener(`chainChanged`,i),r.removeListener(`connect`,o),r.removeListener(`disconnect`,a)}},async switchChain(e){await r.request({method:`wallet_switchEthereumChain`,params:[{chainId:s(e.reference)}]})}}),f=`eip6963:announceProvider`,p=`eip6963:requestProvider`,m=e=>{if(e)return e;let t=globalThis.window;return!t||typeof t.addEventListener!=`function`?null:t},h=(e,t={})=>{let n=m(t.target);if(!n)return()=>{};let r=new Set,i=t=>{let n=t.detail;if(!n||!n.info||!n.provider)return;let{info:i,provider:a}=n;typeof i.rdns!=`string`||i.rdns.length===0||r.has(i.rdns)||(r.add(i.rdns),e(d(i,a),i))};return n.addEventListener(f,i),n.dispatchEvent(new Event(p)),()=>{n.removeEventListener(f,i)}},g=`data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjEgMTJWN0g1YTIgMiAwIDAgMSAwLTQgaDE0djQiLz48cGF0aCBkPSJNMyA1djE0YTIgMiAwIDAgMCAyIDJoMTZ2LTVNMjEgMTIgaC0zYTIgMiAwIDAgMC0yIDJ2MGEyIDIgMCAwIDAgMiAyaDN6Ii8+PC9zdmc+`,_=e=>{let t=e===void 0?typeof window>`u`?null:window:e;if(!t)return null;let n=t.ethereum;return!n||typeof n.request!=`function`?null:n},v=(e,t={})=>{let n=t.settleMs??150,r=!1,i=setTimeout(()=>{if(r||t.hasAnyEip6963Adapter?.())return;let n=_(t.target);n&&e(d({icon:g,name:`Browser wallet`,rdns:`injected:legacy`,uuid:`injected:legacy`},n))},n);return()=>{r=!0,clearTimeout(i)}},y={arbitrum:{id:`eip155:42161`,name:`Arbitrum One`,namespace:`eip155`,reference:`42161`},base:{id:`eip155:8453`,name:`Base`,namespace:`eip155`,reference:`8453`},bsc:{id:`eip155:56`,name:`BNB Smart Chain`,namespace:`eip155`,reference:`56`},ethereum:{id:`eip155:1`,name:`Ethereum`,namespace:`eip155`,reference:`1`},optimism:{id:`eip155:10`,name:`Optimism`,namespace:`eip155`,reference:`10`},polygon:{id:`eip155:137`,name:`Polygon`,namespace:`eip155`,reference:`137`},sepolia:{id:`eip155:11155111`,name:`Sepolia`,namespace:`eip155`,reference:`11155111`}},b=Object.values(y),x={fallback:{subscribe:(e,t)=>v(e,{hasAnyEip6963Adapter:t.hasAnyPrimaryAdapter})},platform:`evm`,subscribe:h};export{f as ANNOUNCE_EVENT,e as EIP6963_RDNS_WITH_REQUEST_ACCOUNTS,y as EVM_CHAINS,b as EVM_CHAINS_LIST,g as GENERIC_INJECTED_ICON,p as REQUEST_EVENT,d as buildEvmAdapter,a as bytesToHex,s as chainIdDecimalToHex,o as chainIdHexToDecimal,h as discoverEvmAdapters,v as discoverInjectedAdapter,x as evmDiscoverer,c as formatEther,i as hexToBytes,t as resolveEip6963Capabilities};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["announce"],"sources":["../src/capabilities.ts","../src/eip6963-adapter.ts","../src/eip6963.ts","../src/injected.ts","../src/chains.ts","../src/discoverer.ts"],"sourcesContent":["import type { WalletCapabilities } from \"@usebutr/core\";\n\n// Allow-list of EIP-6963 wallets whose `wallet_requestPermissions` call\n// actually surfaces a fresh account-picker UI (the spec intent).\n// Every other tested wallet either rejects the method (Phantom EVM,\n// Coinbase Wallet) or silently returns the existing accounts (Rabby,\n// OKX, Binance, Backpack EVM), making the affordance a no-op.\n//\n// Listed by EIP-6963 `rdns`. Add a wallet here only after verifying\n// against the real install that calling `requestAccounts` surfaces a\n// picker. The capability flag gates UI affordances — `requestAccounts`\n// stays callable on every adapter for consumers with wallet-specific\n// flows.\nconst EIP6963_RDNS_WITH_REQUEST_ACCOUNTS = new Set<string>([\n \"io.metamask\", // MetaMask — verified May 2026\n]);\n\ntype Eip6963CapabilityInput = { rdns: string };\n\nconst resolveEip6963Capabilities = (input: Eip6963CapabilityInput): WalletCapabilities => ({\n getBalance: true,\n getTransactionReceipt: true,\n requestAccounts: EIP6963_RDNS_WITH_REQUEST_ACCOUNTS.has(input.rdns),\n sendTransaction: true,\n // SIWS is Solana-only.\n signIn: false,\n signMessage: true,\n // EVM adapters don't expose a standalone signTransaction (most\n // wallets reject eth_signTransaction); use sendTx.\n signTransaction: false,\n subscribe: true,\n // EIP-1193 has no silent \"use address X\" RPC.\n switchAccount: false,\n switchChain: true,\n});\n\nexport type { Eip6963CapabilityInput };\nexport { EIP6963_RDNS_WITH_REQUEST_ACCOUNTS, resolveEip6963Capabilities };\n","import type { Account, ChainBase, WalletAdapter } from \"@usebutr/core\";\n\nimport { resolveEip6963Capabilities } from \"./capabilities\";\nimport type { Eip1193Listener, Eip1193Provider, Eip6963ProviderInfo } from \"./eip1193\";\n\nconst HEX_PREFIX = \"0x\";\nconst ETH_DECIMALS = 18n;\nconst ETH_UNIT = 10n ** ETH_DECIMALS;\n\n/**\n * Convert a hex string (with or without `0x` prefix) into a Uint8Array.\n * Odd-length strings are not supported; EIP-1193 returns canonical\n * even-length hex.\n */\nconst hexToBytes = (hex: string): Uint8Array => {\n const clean = hex.startsWith(HEX_PREFIX) ? hex.slice(2) : hex;\n const out = new Uint8Array(clean.length / 2);\n for (let i = 0; i < out.length; i += 1) {\n out[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n};\n\nconst bytesToHex = (bytes: Uint8Array): string => {\n let hex = HEX_PREFIX;\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, \"0\");\n }\n return hex;\n};\n\nconst chainIdHexToDecimal = (hex: string): string => BigInt(hex).toString(10);\nconst chainIdDecimalToHex = (dec: string): string => `${HEX_PREFIX}${BigInt(dec).toString(16)}`;\n\n/** Pure 18-decimal wei → ether formatter. Trims trailing zeros so\n * `1.0` stays `1` and `1.5` stays `1.5`. */\nconst formatEther = (wei: bigint): string => {\n const integer = wei / ETH_UNIT;\n const remainder = wei % ETH_UNIT;\n if (remainder === 0n) {\n return integer.toString();\n }\n const fraction = remainder.toString().padStart(Number(ETH_DECIMALS), \"0\").replace(/0+$/v, \"\");\n return fraction.length > 0 ? `${integer}.${fraction}` : integer.toString();\n};\n\nconst buildEvmChain = (chainIdHex: string, walletName: string): ChainBase => {\n const reference = chainIdHexToDecimal(chainIdHex);\n return {\n id: `eip155:${reference}`,\n // We don't ship a chain-id → name table because that's a\n // domain decision (mainnet, sepolia, base, polygon, …). Consumers\n // overlay their own name via structural typing if needed.\n name: walletName,\n namespace: \"eip155\",\n reference,\n };\n};\n\nconst buildEvmAccount = (address: string, chain: ChainBase): Account => ({\n chain,\n id: `${chain.id}:${address.toLowerCase()}`,\n walletAddress: address,\n});\n\n/**\n * Adapt an EIP-1193 provider (announced via EIP-6963) into a butr\n * `WalletAdapter`. The returned adapter covers the full `Connector +\n * Wallet` surface; consumers can pass it through `createConnector` like\n * any hand-written adapter.\n *\n * **Caveats**\n *\n * - `disconnect` calls `wallet_revokePermissions`. Many wallets don't\n * implement that method and silently ignore the call. The reducer\n * will still mark the wallet as disconnected on butr's side; the\n * wallet's own auto-reconnect heuristic may or may not honour it.\n * - `switchAccount` is intentionally unimplemented because EIP-1193 has\n * no silent \"use address X\" RPC. The active account is whichever one\n * the wallet exposes first; the user changes it through the wallet's\n * own UI. butr's `subscribe` bridge auto-updates the pool entry when\n * `accountsChanged` fires. Call `requestAccounts` if you want the\n * wallet to expose more accounts.\n * - `getBalance` returns the native ETH balance with symbol `\"ETH\"`,\n * regardless of which EVM chain the wallet is currently on. Consumers\n * that target multiple EVM chains should overlay the symbol via\n * their own logic.\n * - `getSigner` returns the raw EIP-1193 provider. Wrap it in viem's\n * `createWalletClient` or ethers' `BrowserProvider` at the call site.\n */\nconst buildEvmAdapter = (info: Eip6963ProviderInfo, provider: Eip1193Provider): WalletAdapter => {\n return {\n capabilities: resolveEip6963Capabilities({ rdns: info.rdns }),\n chainPlatform: \"evm\",\n\n async connect(opts) {\n if (opts?.silent) {\n // EIP-1193 has no silent variant of `eth_requestAccounts`. The\n // closest non-interactive probe is `eth_accounts`, which returns\n // the already-authorized set without a prompt. Empty → reject so\n // hydration drops the stale entry instead of restoring nothing.\n const accounts = (await provider.request({\n method: \"eth_accounts\",\n })) as Array<string>;\n if (accounts.length === 0) {\n throw new Error(\"No authorized accounts for silent reconnect\");\n }\n return;\n }\n await provider.request({ method: \"eth_requestAccounts\" });\n },\n\n async disconnect() {\n try {\n await provider.request({\n method: \"wallet_revokePermissions\",\n params: [{ eth_accounts: {} }],\n });\n } catch {\n // Many wallets don't implement wallet_revokePermissions yet —\n // not a failure.\n }\n },\n\n async getAccount() {\n const accounts = (await provider.request({ method: \"eth_accounts\" })) as Array<string>;\n if (accounts.length === 0) {\n return null;\n }\n const chainIdHex = (await provider.request({ method: \"eth_chainId\" })) as string;\n const chain = buildEvmChain(chainIdHex, info.name);\n const first = accounts[0];\n if (!first) {\n return null;\n }\n return buildEvmAccount(first, chain);\n },\n\n async getAccounts() {\n const accounts = (await provider.request({ method: \"eth_accounts\" })) as Array<string>;\n if (accounts.length === 0) {\n return [];\n }\n const chainIdHex = (await provider.request({ method: \"eth_chainId\" })) as string;\n const chain = buildEvmChain(chainIdHex, info.name);\n return accounts.map((addr) => buildEvmAccount(addr, chain));\n },\n\n async getBalance() {\n const accounts = (await provider.request({ method: \"eth_accounts\" })) as Array<string>;\n const first = accounts[0];\n if (!first) {\n throw new Error(\"No connected account\");\n }\n const balanceHex = (await provider.request({\n method: \"eth_getBalance\",\n params: [first, \"latest\"],\n })) as string;\n const value = BigInt(balanceHex);\n return {\n decimals: Number(ETH_DECIMALS),\n formatted: formatEther(value),\n symbol: \"ETH\",\n value,\n };\n },\n\n getSigner() {\n // Consumers cast this to a viem WalletClient / ethers\n // BrowserProvider / their preferred wrapper.\n return Promise.resolve(provider);\n },\n\n async getTransactionReceipt(tx) {\n const receipt = (await provider.request({\n method: \"eth_getTransactionReceipt\",\n params: [tx],\n })) as { status: string } | null;\n if (!receipt) {\n return { status: \"Pending\" };\n }\n return { status: receipt.status === \"0x1\" ? \"Success\" : \"Error\" };\n },\n\n icon: info.icon,\n id: info.rdns,\n name: info.name,\n\n async requestAccounts() {\n // Preferred path: EIP-2255 `wallet_requestPermissions` reopens\n // the wallet's account-picker UI so the user can grant new\n // accounts. In practice, MetaMask is the only major EVM wallet\n // that actually surfaces a fresh picker — Rabby / OKX / Binance\n // / Backpack silently return existing accounts; Phantom EVM and\n // Coinbase Wallet reject with `method not supported`. The\n // `capabilities` layer (`EIP6963_RDNS_WITH_REQUEST_ACCOUNTS` in\n // `capabilities.ts`) gates the\n // demo button so users only see it where it'll do something\n // visible; this method stays callable for consumers with\n // wallet-specific flows.\n //\n // Fallback: rejecting wallets get routed through\n // `eth_requestAccounts`, which at least doesn't throw. It won't\n // reopen the picker but surfaces newly-added accounts on the\n // next read.\n try {\n await provider.request({\n method: \"wallet_requestPermissions\",\n params: [{ eth_accounts: {} }],\n });\n } catch (error: unknown) {\n const err = error as { code?: number; data?: { originalError?: { code?: number } } } | null;\n const outerCode = err?.code;\n const innerCode = err?.data?.originalError?.code;\n const isMethodNotSupported =\n outerCode === 4200 || // EIP-1474 \"method not supported\"\n outerCode === -32_601 || // JSON-RPC \"method not found\"\n outerCode === -32_603 || // JSON-RPC \"internal error\" (Coinbase wraps -32604 here)\n innerCode === 4200 ||\n innerCode === -32_601 ||\n innerCode === -32_604; // Coinbase's custom \"method not supported\"\n if (isMethodNotSupported) {\n await provider.request({ method: \"eth_requestAccounts\" });\n return;\n }\n throw error;\n }\n },\n\n async sendTx(tx, account) {\n const txWithFrom = account\n ? { ...(tx as Record<string, unknown>), from: account.walletAddress }\n : tx;\n const hash = (await provider.request({\n method: \"eth_sendTransaction\",\n params: [txWithFrom],\n })) as string;\n return hash;\n },\n\n async sendTxToChain(tx, targetChainIdDecimal, account, cb) {\n const current = (await provider.request({ method: \"eth_chainId\" })) as string;\n const targetHex = chainIdDecimalToHex(targetChainIdDecimal);\n if (current.toLowerCase() !== targetHex.toLowerCase()) {\n await provider.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: targetHex }],\n });\n cb?.();\n }\n const txWithFrom = account\n ? { ...(tx as Record<string, unknown>), from: account.walletAddress }\n : tx;\n const hash = (await provider.request({\n method: \"eth_sendTransaction\",\n params: [txWithFrom],\n })) as string;\n return hash;\n },\n\n async signMessage(msg, account) {\n // Default to the first exposed account when the caller doesn't\n // specify one. MetaMask requires the address to be one currently\n // exposed via `eth_requestAccounts`; pass any address from\n // `ConnectedWallet.accounts` to sign with a non-active one.\n let signer = account?.walletAddress;\n if (!signer) {\n const accounts = (await provider.request({ method: \"eth_accounts\" })) as Array<string>;\n signer = accounts[0];\n }\n if (!signer) {\n throw new Error(\"No connected account\");\n }\n const signatureHex = (await provider.request({\n method: \"personal_sign\",\n params: [bytesToHex(msg), signer],\n })) as string;\n return { signature: hexToBytes(signatureHex), signedMessage: msg };\n },\n\n subscribe(listener) {\n const onAccountsChanged: Eip1193Listener = (...args) => {\n const accs = args[0] as Array<string>;\n if (accs.length === 0) {\n listener({ type: \"disconnected\" });\n return;\n }\n // Forward the FULL accounts array — `accountsChanged` reflects\n // the wallet's current exposure set, not an incremental add.\n // The runtime mirrors this list verbatim into the pool entry,\n // so single-account-exposure wallets (Phantom EVM) don't end\n // up with stale, non-signable addresses lingering in the array.\n void provider\n .request({ method: \"eth_chainId\" })\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .then((chainIdHex) => {\n const chain = buildEvmChain(chainIdHex as string, info.name);\n const accounts = accs.map((addr) => buildEvmAccount(addr, chain));\n const first = accounts[0];\n if (!first) {\n return undefined;\n }\n listener({ account: first, accounts, type: \"accountChanged\" });\n return undefined;\n })\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .catch(() => {\n // Drop silently — next event will retry the read.\n });\n };\n\n const onChainChanged: Eip1193Listener = (...args) => {\n const chainIdHex = args[0] as string;\n void provider\n .request({ method: \"eth_accounts\" })\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .then((accounts) => {\n const accs = accounts as Array<string>;\n if (accs.length === 0) {\n return undefined;\n }\n const chain = buildEvmChain(chainIdHex, info.name);\n const built = accs.map((addr) => buildEvmAccount(addr, chain));\n const first = built[0];\n if (!first) {\n return undefined;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n return undefined;\n })\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .catch(() => {\n // Drop silently.\n });\n };\n\n const onDisconnect: Eip1193Listener = () => {\n listener({ type: \"disconnected\" });\n };\n\n // EIP-1193 `connect` fires when the provider (re)connects to a\n // chain — e.g. the wallet comes back after being unreachable.\n // Re-read accounts + chain and resync the pool entry; state is\n // otherwise only derived from `accountsChanged`/`chainChanged`.\n const onConnect: Eip1193Listener = () => {\n void Promise.all([\n provider.request({ method: \"eth_accounts\" }),\n provider.request({ method: \"eth_chainId\" }),\n ])\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .then(([accountsRaw, chainIdHex]) => {\n const accs = accountsRaw as Array<string>;\n if (accs.length === 0) {\n return undefined;\n }\n const chain = buildEvmChain(chainIdHex as string, info.name);\n const built = accs.map((addr) => buildEvmAccount(addr, chain));\n const first = built[0];\n if (!first) {\n return undefined;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n return undefined;\n })\n // oxlint-disable-next-line promise/prefer-await-to-then -- callback context, not async\n .catch(() => {\n // Drop silently — a later event will resync.\n });\n };\n\n provider.on(\"accountsChanged\", onAccountsChanged);\n provider.on(\"chainChanged\", onChainChanged);\n provider.on(\"connect\", onConnect);\n provider.on(\"disconnect\", onDisconnect);\n\n return () => {\n provider.removeListener(\"accountsChanged\", onAccountsChanged);\n provider.removeListener(\"chainChanged\", onChainChanged);\n provider.removeListener(\"connect\", onConnect);\n provider.removeListener(\"disconnect\", onDisconnect);\n };\n },\n\n async switchChain(chain) {\n await provider.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: chainIdDecimalToHex(chain.reference) }],\n });\n },\n };\n};\n\nexport {\n buildEvmAdapter,\n bytesToHex,\n chainIdDecimalToHex,\n chainIdHexToDecimal,\n formatEther,\n hexToBytes,\n};\n","import type { WalletAdapter } from \"@usebutr/core\";\n\nimport type { Eip6963AnnounceEvent, Eip6963ProviderInfo } from \"./eip1193\";\nimport { buildEvmAdapter } from \"./eip6963-adapter\";\n\nconst ANNOUNCE_EVENT = \"eip6963:announceProvider\";\nconst REQUEST_EVENT = \"eip6963:requestProvider\";\n\ntype AdapterCallback = (adapter: WalletAdapter, info: Eip6963ProviderInfo) => void;\n\ntype DiscoverOptions = {\n /** Event target to listen on. Defaults to `window` in browser/RN/\n * SSR'd environments; tests can pass a fresh `new EventTarget()` to\n * exercise the discovery loop without a DOM. */\n target?: EventTarget;\n};\n\nconst resolveTarget = (target?: EventTarget): EventTarget | null => {\n if (target) {\n return target;\n }\n // Use globalThis.window when present and EventTarget-shaped. Some\n // test environments inject a `window` stub that isn't a real\n // EventTarget; in that case fall back to null so callers short-circuit\n // rather than throw on `.addEventListener`.\n const candidate = (globalThis as { window?: unknown }).window;\n if (\n !candidate ||\n typeof (candidate as { addEventListener?: unknown }).addEventListener !== \"function\"\n ) {\n return null;\n }\n return candidate as EventTarget;\n};\n\n/**\n * Subscribe to EIP-6963 wallet announcements. Spec:\n * https://eips.ethereum.org/EIPS/eip-6963\n *\n * Flow:\n * 1. Page registers a listener for `eip6963:announceProvider`.\n * 2. Page dispatches `eip6963:requestProvider` to kick wallets that\n * have already loaded.\n * 3. Wallets that load later still dispatch their own announcement\n * when they boot, so the listener stays attached for the lifetime\n * of the discovery session.\n * 4. Dedup by `info.rdns` — wallets occasionally re-announce, and\n * `info.uuid` is regenerated per page load so it's not stable.\n *\n * Returns an unsubscribe function that removes the listener.\n * In non-browser environments (SSR, tests with no window) returns\n * a noop unsubscribe and never fires the callback.\n */\nconst discoverEvmAdapters = (\n onAdapter: AdapterCallback,\n options: DiscoverOptions = {},\n): (() => void) => {\n const target = resolveTarget(options.target);\n if (!target) {\n return () => {};\n }\n\n const seen = new Set<string>();\n const handler = (event: Event) => {\n const announce = event as Eip6963AnnounceEvent;\n const detail = announce.detail;\n if (!detail || !detail.info || !detail.provider) {\n return;\n }\n const { info, provider } = detail;\n if (typeof info.rdns !== \"string\" || info.rdns.length === 0) {\n return;\n }\n if (seen.has(info.rdns)) {\n return;\n }\n seen.add(info.rdns);\n onAdapter(buildEvmAdapter(info, provider), info);\n };\n\n target.addEventListener(ANNOUNCE_EVENT, handler);\n target.dispatchEvent(new Event(REQUEST_EVENT));\n\n return () => {\n target.removeEventListener(ANNOUNCE_EVENT, handler);\n };\n};\n\nexport { ANNOUNCE_EVENT, REQUEST_EVENT, discoverEvmAdapters };\n","import type { WalletAdapter } from \"@usebutr/core\";\n\nimport type { Eip1193Provider, Eip6963ProviderInfo } from \"./eip1193\";\nimport { buildEvmAdapter } from \"./eip6963-adapter\";\n\n/**\n * Generic wallet icon (a stylised purse SVG, ~600 bytes). Used as\n * the fallback icon when an injected provider has no EIP-6963 info.\n * Inline rather than a network fetch so the picker doesn't flicker.\n */\nconst GENERIC_INJECTED_ICON =\n \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjEgMTJWN0g1YTIgMiAwIDAgMSAwLTQgaDE0djQiLz48cGF0aCBkPSJNMyA1djE0YTIgMiAwIDAgMCAyIDJoMTZ2LTVNMjEgMTIgaC0zYTIgMiAwIDAgMC0yIDJ2MGEyIDIgMCAwIDAgMiAyaDN6Ii8+PC9zdmc+\";\n\nconst DEFAULT_SETTLE_MS = 150;\n\ntype InjectedDiscoveryOptions = {\n /**\n * Predicate the fallback consults before emitting — if any EIP-6963\n * adapter has been registered by the time the settle timer fires,\n * the injected fallback is skipped (the wallet is already covered\n * via the standards path; we don't want a duplicate).\n *\n * `discoverWalletAdapters` wires this to the shared `seen` set so\n * the dedupe is automatic. Callers using `discoverInjectedAdapter`\n * directly should pass their own check or omit it (always emit).\n */\n hasAnyEip6963Adapter?: () => boolean;\n /**\n * How long to wait for EIP-6963 announcements before falling back\n * to `window.ethereum`. Most wallets announce within the first\n * frame; 150ms is a conservative settle window that keeps the\n * picker responsive while giving slow announcements time to land.\n */\n settleMs?: number;\n /** Override the global `window` reference (tests, iframes). */\n target?: { ethereum?: unknown } | null;\n};\n\nconst readEthereum = (target: InjectedDiscoveryOptions[\"target\"]): Eip1193Provider | null => {\n // Resolve the host object: use the caller's override if given, otherwise\n // fall back to the global window (or null in SSR environments).\n const globalWindow =\n typeof window === \"undefined\" ? null : (window as unknown as { ethereum?: unknown });\n const host = target === undefined ? globalWindow : target;\n if (!host) {\n return null;\n }\n const eth = (host as { ethereum?: unknown }).ethereum;\n if (!eth || typeof (eth as { request?: unknown }).request !== \"function\") {\n return null;\n }\n return eth as Eip1193Provider;\n};\n\n/**\n * Last-resort discovery path for EVM wallets that DON'T announce via\n * EIP-6963. Most modern wallets (MetaMask, Rabby, Coinbase, Phantom,\n * Brave, etc.) do announce, so the standards-based path\n * (`discoverEvmAdapters`) covers them. This module catches the long\n * tail: regional wallets (KuCoin, Bitget, BitKeep, Okto, Frontier)\n * and older builds that only expose `window.ethereum`.\n *\n * **Coordination**: the fallback waits `settleMs` (default 150ms)\n * after subscription, then consults `hasAnyEip6963Adapter` if\n * provided. If any EIP-6963 adapter has already been registered, we\n * skip emission — assume the same wallet is also speaking standards\n * and we'd just be duplicating. If no EIP-6963 adapter showed up but\n * `window.ethereum` does, we emit a generic adapter.\n *\n * The emitted adapter has `rdns: \"injected:legacy\"` so consumers can\n * distinguish it from EIP-6963 adapters in the picker UI.\n */\nconst discoverInjectedAdapter = (\n onAdapter: (adapter: WalletAdapter) => void,\n options: InjectedDiscoveryOptions = {},\n): (() => void) => {\n const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;\n let cancelled = false;\n const timer = setTimeout(() => {\n if (cancelled) {\n return;\n }\n if (options.hasAnyEip6963Adapter?.()) {\n return;\n }\n const provider = readEthereum(options.target);\n if (!provider) {\n return;\n }\n const info: Eip6963ProviderInfo = {\n icon: GENERIC_INJECTED_ICON,\n name: \"Browser wallet\",\n rdns: \"injected:legacy\",\n uuid: \"injected:legacy\",\n };\n onAdapter(buildEvmAdapter(info, provider));\n }, settleMs);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n};\n\nexport type { InjectedDiscoveryOptions };\nexport { GENERIC_INJECTED_ICON, discoverInjectedAdapter };\n","import type { ChainBase } from \"@usebutr/core\";\n\n/**\n * Common EVM chain registry keyed by CAIP-2 id. Useful for chain-\n * switcher UIs. butr itself doesn't read this — `Connector.switchChain`\n * accepts any `ChainBase`.\n */\nconst EVM_CHAINS = {\n arbitrum: {\n id: \"eip155:42161\",\n name: \"Arbitrum One\",\n namespace: \"eip155\",\n reference: \"42161\",\n },\n base: { id: \"eip155:8453\", name: \"Base\", namespace: \"eip155\", reference: \"8453\" },\n bsc: { id: \"eip155:56\", name: \"BNB Smart Chain\", namespace: \"eip155\", reference: \"56\" },\n ethereum: { id: \"eip155:1\", name: \"Ethereum\", namespace: \"eip155\", reference: \"1\" },\n optimism: { id: \"eip155:10\", name: \"Optimism\", namespace: \"eip155\", reference: \"10\" },\n polygon: { id: \"eip155:137\", name: \"Polygon\", namespace: \"eip155\", reference: \"137\" },\n sepolia: { id: \"eip155:11155111\", name: \"Sepolia\", namespace: \"eip155\", reference: \"11155111\" },\n} as const satisfies Record<string, ChainBase>;\n\nconst EVM_CHAINS_LIST: ReadonlyArray<ChainBase> = Object.values(EVM_CHAINS);\n\nexport { EVM_CHAINS, EVM_CHAINS_LIST };\n","import type { PlatformDiscoverer } from \"@usebutr/core\";\n\nimport { discoverEvmAdapters } from \"./eip6963\";\nimport { discoverInjectedAdapter } from \"./injected\";\n\n/**\n * EVM's `PlatformDiscoverer`: EIP-6963 announcements primary, with the\n * legacy `window.ethereum` injected fallback wired to defer until\n * EIP-6963 has had a chance to fire.\n *\n * Consumers using `@usebutr/wallets`'s `autoDiscovery()` get this\n * wired in automatically. Direct consumers who don't want the\n * injected fallback build their own discovery source from\n * `discoverEvmAdapters` alone.\n */\nconst evmDiscoverer: PlatformDiscoverer = {\n fallback: {\n subscribe: (onAdapter, opts) =>\n discoverInjectedAdapter(onAdapter, {\n hasAnyEip6963Adapter: opts.hasAnyPrimaryAdapter,\n }),\n },\n platform: \"evm\",\n subscribe: discoverEvmAdapters,\n};\n\nexport { evmDiscoverer };\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@usebutr/evm",
3
+ "version": "0.1.0",
4
+ "description": "EIP-1193 / EIP-6963 / injected wallet discovery + adapters for butr.",
5
+ "license": "MIT",
6
+ "author": "Pedro Filho <pedro@filho.me>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/pedroapfilho/usebutr.git",
10
+ "directory": "packages/evm"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@usebutr/core": "0.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^6.0.3",
32
+ "vitest": "^4.1.6",
33
+ "@repo/config-vitest": "0.0.0",
34
+ "@repo/typescript-config": "0.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsdown",
38
+ "clean": "rm -rf dist",
39
+ "dev": "tsdown --watch",
40
+ "lint": "oxlint src",
41
+ "test": "vitest run --passWithNoTests",
42
+ "test:coverage": "vitest run --coverage --passWithNoTests",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit"
44
+ }
45
+ }