@usebutr/wallet-standard-shared 0.2.3 → 0.2.5
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/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +136 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -32,21 +32,21 @@ type WalletStandardWallet = {
|
|
|
32
32
|
version: string;
|
|
33
33
|
};
|
|
34
34
|
type WalletsApp = {
|
|
35
|
-
get()
|
|
36
|
-
on(event: "register" | "unregister", listener: (...wallets: ReadonlyArray<WalletStandardWallet>) => void)
|
|
35
|
+
get: () => ReadonlyArray<WalletStandardWallet>;
|
|
36
|
+
on: (event: "register" | "unregister", listener: (...wallets: ReadonlyArray<WalletStandardWallet>) => void) => () => void;
|
|
37
37
|
};
|
|
38
38
|
type WalletStandardAppModule = {
|
|
39
|
-
getWallets()
|
|
39
|
+
getWallets: () => WalletsApp;
|
|
40
40
|
};
|
|
41
41
|
type StandardConnectFeature = {
|
|
42
|
-
connect(input?: {
|
|
42
|
+
connect: (input?: {
|
|
43
43
|
silent?: boolean;
|
|
44
|
-
})
|
|
44
|
+
}) => Promise<{
|
|
45
45
|
accounts: ReadonlyArray<WalletStandardWalletAccount>;
|
|
46
46
|
}>;
|
|
47
47
|
};
|
|
48
48
|
type StandardDisconnectFeature = {
|
|
49
|
-
disconnect()
|
|
49
|
+
disconnect: () => Promise<void>;
|
|
50
50
|
};
|
|
51
51
|
type StandardEventsListener = (changes: {
|
|
52
52
|
accounts?: ReadonlyArray<WalletStandardWalletAccount>;
|
|
@@ -54,7 +54,7 @@ type StandardEventsListener = (changes: {
|
|
|
54
54
|
features?: ReadonlyArray<string>;
|
|
55
55
|
}) => void;
|
|
56
56
|
type StandardEventsFeature = {
|
|
57
|
-
on(event: "change", listener: StandardEventsListener)
|
|
57
|
+
on: (event: "change", listener: StandardEventsListener) => () => void;
|
|
58
58
|
};
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region src/primitives.d.ts
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/primitives.ts","../src/discovery.ts"],"mappings":";;;;;;;;;;;;;;;;;KAeK,2BAAA;EACH,OAAA;EACA,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,aAAA,UAEY;EAAtB,SAAA,GAAY,UAAA;AAAA;AAAA,KAGT,oBAAA;EACH,QAAA,EAAU,aAAA,CAAc,2BAAA;EACxB,MAAA,EAAQ,aAAA;EADE;;EAIV,QAAA,EAAU,QAAA,CAAS,MAAA;EACnB,IAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA,KAGG,UAAA;EACH,GAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/primitives.ts","../src/discovery.ts"],"mappings":";;;;;;;;;;;;;;;;;KAeK,2BAAA;EACH,OAAA;EACA,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,aAAA,UAEY;EAAtB,SAAA,GAAY,UAAA;AAAA;AAAA,KAGT,oBAAA;EACH,QAAA,EAAU,aAAA,CAAc,2BAAA;EACxB,MAAA,EAAQ,aAAA;EADE;;EAIV,QAAA,EAAU,QAAA,CAAS,MAAA;EACnB,IAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA,KAGG,UAAA;EACH,GAAA,QAAW,aAAA,CAAc,oBAAA;EACzB,EAAA,GACE,KAAA,6BACA,QAAA,MAAc,OAAA,EAAS,aAAA,CAAc,oBAAA;AAAA;AAAA,KAIpC,uBAAA;EACH,UAAA,QAAkB,UAAU;AAAA;AAAA,KAGzB,sBAAA;EACH,OAAA,GAAU,KAAA;IAAU,MAAA;EAAA,MAAuB,OAAA;IACzC,QAAA,EAAU,aAAA,CAAc,2BAAA;EAAA;AAAA;AAAA,KAIvB,yBAAA;EACH,UAAA,QAAkB,OAAO;AAAA;AAAA,KAGtB,sBAAA,IAA0B,OAAA;EAC7B,QAAA,GAAW,aAAA,CAAc,2BAAA;EACzB,MAAA,GAAS,aAAA;EACT,QAAA,GAAW,aAAA;AAAA;AAAA,KAGR,qBAAA;EACH,EAAA,GAAK,KAAA,YAAiB,QAAA,EAAU,sBAAsB;AAAA;;;;;;;;;;;;;;cChDlD,OAAA,GAAW,cAAA,UAAwB,IAAY;;;;;;cAa/C,UAAA,MAAiB,MAAA,EAAQ,oBAAA,EAAsB,IAAA,aAAe,CAAC;ADR7C;;;;;;AAAA,cCmBlB,YAAA,GAAgB,OAAA,UAAiB,KAAA,EAAO,SAAA,KAAY,OAIxD;;;;;cAMI,gBAAA,GAAoB,QAAA,EAAU,aAAa,CAAC,2BAAA;;;;;;;;;cAa5C,oBAAA,GACJ,QAAA,EAAU,aAAA,CAAc,2BAAA,GACxB,OAAA,aACC,2BAAA;;;;;;;;;;;;;;;;;KC1CE,4BAAA,IACH,MAAA,EAAQ,oBAAA,EACR,oBAAA,GAAuB,IAAA,0BACpB,aAAa;;;AFNM;AAAA;;;;;;;;;;;;;;;;;;;;;;cEiClB,sBAAA,GACJ,SAAA,GAAY,OAAA,EAAS,aAAA,WACrB,KAAA,EAAO,4BAA4B"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,137 @@
|
|
|
1
|
-
|
|
1
|
+
import { logWarn } from "@usebutr/core";
|
|
2
|
+
|
|
3
|
+
//#region src/primitives.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert a wallet name into a stable kebab-case adapter id, scoped by
|
|
6
|
+
* a platform prefix so adapters from different platforms (Phantom SVM,
|
|
7
|
+
* Phantom Sui, Phantom BTC) coexist in the pool without colliding on
|
|
8
|
+
* the shared `getWallets()` bus.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* slugify("svm", "Phantom") // "wallet-standard:svm-phantom"
|
|
12
|
+
* slugify("sui", "Sui Wallet") // "wallet-standard:sui-sui-wallet"
|
|
13
|
+
* slugify("btc", "Phantom") // "wallet-standard:btc-phantom"
|
|
14
|
+
*/
|
|
15
|
+
const slugify = (platformPrefix, name) => {
|
|
16
|
+
return `wallet-standard:${platformPrefix}-${name.trim().toLowerCase().replaceAll(/* @__PURE__ */ new RegExp("[^a-z0-9]+", "gv"), "-")}`;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Read a Wallet Standard feature off a wallet, returning `undefined`
|
|
20
|
+
* when absent. The caller narrows the unknown payload to the
|
|
21
|
+
* platform-specific feature shape at use sites.
|
|
22
|
+
*/
|
|
23
|
+
const getFeature = (wallet, name) => {
|
|
24
|
+
const feature = wallet.features[name];
|
|
25
|
+
return feature ? feature : void 0;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Build butr's `Account` shape from a Wallet Standard account address
|
|
29
|
+
* and a resolved `ChainBase`. The composite id (`<chain>:<address>`)
|
|
30
|
+
* is what butr's reducer uses to compare accounts across refreshes,
|
|
31
|
+
* so keep this format identical across platforms.
|
|
32
|
+
*/
|
|
33
|
+
const buildAccount = (address, chain) => ({
|
|
34
|
+
chain,
|
|
35
|
+
id: `${chain.id}:${address}`,
|
|
36
|
+
walletAddress: address
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Pick the first address out of a wallet's accounts array, returning
|
|
40
|
+
* `null` when the wallet currently exposes none.
|
|
41
|
+
*/
|
|
42
|
+
const pickFirstAddress = (accounts) => {
|
|
43
|
+
const first = accounts[0];
|
|
44
|
+
return first ? first.address : null;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Look up a wallet account by address, falling back to the first
|
|
48
|
+
* exposed account when the requested address isn't present.
|
|
49
|
+
*
|
|
50
|
+
* Used by per-call routing in `sendTx` / `signMessage`: callers pass
|
|
51
|
+
* an `account` from `ConnectedWallet.accounts` to sign with a non-
|
|
52
|
+
* active address; the wallet's `accounts[]` is the source of truth.
|
|
53
|
+
*/
|
|
54
|
+
const pickAccountByAddress = (accounts, address) => accounts.find((a) => a.address === address) ?? accounts[0];
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/discovery.ts
|
|
58
|
+
let warnedMissingApp = false;
|
|
59
|
+
/**
|
|
60
|
+
* Subscribe to Wallet Standard announcements and turn matching wallets
|
|
61
|
+
* into butr `WalletAdapter`s using a per-platform `build` callback.
|
|
62
|
+
*
|
|
63
|
+
* **Shared machinery** every platform package needs:
|
|
64
|
+
* - Dynamic import of `@wallet-standard/app` (optional peer dep —
|
|
65
|
+
* failure to resolve quietly disables discovery so consumers who
|
|
66
|
+
* don't install it pay nothing).
|
|
67
|
+
* - Cancellation: the returned unsubscribe is safe to call before the
|
|
68
|
+
* dynamic import has resolved.
|
|
69
|
+
* - Dedupe by adapter id, so re-announcements don't double-emit.
|
|
70
|
+
* - `unregister` integration: when an extension is removed mid-session,
|
|
71
|
+
* fire the disconnector the builder registered so connected pool
|
|
72
|
+
* entries on the consumer side tear down cleanly.
|
|
73
|
+
*
|
|
74
|
+
* **What's platform-specific** lives entirely in `build`:
|
|
75
|
+
* - Which `wallet.chains` to accept.
|
|
76
|
+
* - Which features to gate on.
|
|
77
|
+
* - The `id` slug prefix (use `slugify(prefix, wallet.name)`).
|
|
78
|
+
* - The capability resolver, the feature decoding, the chain coercion.
|
|
79
|
+
*
|
|
80
|
+
* Disconnectors are keyed by the wallet object itself (identity equality),
|
|
81
|
+
* not by adapter id — Wallet Standard's `register` / `unregister` emit
|
|
82
|
+
* the same wallet object, so identity is the cheapest stable key.
|
|
83
|
+
*/
|
|
84
|
+
const discoverWalletStandard = (onAdapter, build) => {
|
|
85
|
+
let cancelled = false;
|
|
86
|
+
let internalUnsub = null;
|
|
87
|
+
(async () => {
|
|
88
|
+
let mod;
|
|
89
|
+
try {
|
|
90
|
+
mod = await import("@wallet-standard/app");
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!warnedMissingApp) {
|
|
93
|
+
warnedMissingApp = true;
|
|
94
|
+
logWarn("[butr] Wallet Standard wallet discovery is disabled: `@wallet-standard/app` could not be loaded. Install it (`npm i @wallet-standard/app`) to detect Wallet Standard wallets.", error);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (cancelled) return;
|
|
99
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
100
|
+
const disconnectors = /* @__PURE__ */ new Map();
|
|
101
|
+
const tryAdd = (wallet) => {
|
|
102
|
+
const adapter = build(wallet, (emit) => {
|
|
103
|
+
disconnectors.set(wallet, emit);
|
|
104
|
+
});
|
|
105
|
+
if (!adapter || seenIds.has(adapter.id)) return;
|
|
106
|
+
seenIds.add(adapter.id);
|
|
107
|
+
onAdapter(adapter);
|
|
108
|
+
};
|
|
109
|
+
const app = mod.getWallets();
|
|
110
|
+
for (const wallet of app.get()) tryAdd(wallet);
|
|
111
|
+
const offRegister = app.on("register", (...wallets) => {
|
|
112
|
+
for (const wallet of wallets) tryAdd(wallet);
|
|
113
|
+
});
|
|
114
|
+
const offUnregister = app.on("unregister", (...wallets) => {
|
|
115
|
+
for (const wallet of wallets) {
|
|
116
|
+
const emit = disconnectors.get(wallet);
|
|
117
|
+
if (emit) {
|
|
118
|
+
emit();
|
|
119
|
+
disconnectors.delete(wallet);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
internalUnsub = () => {
|
|
124
|
+
offRegister();
|
|
125
|
+
offUnregister();
|
|
126
|
+
};
|
|
127
|
+
})();
|
|
128
|
+
return () => {
|
|
129
|
+
cancelled = true;
|
|
130
|
+
internalUnsub?.();
|
|
131
|
+
internalUnsub = null;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
export { buildAccount, discoverWalletStandard, getFeature, pickAccountByAddress, pickFirstAddress, slugify };
|
|
2
137
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/primitives.ts","../src/discovery.ts"],"sourcesContent":["import type { Account, ChainBase } from \"@usebutr/core\";\n\nimport type { WalletStandardWallet, WalletStandardWalletAccount } from \"./types\";\n\n/**\n * Convert a wallet name into a stable kebab-case adapter id, scoped by\n * a platform prefix so adapters from different platforms (Phantom SVM,\n * Phantom Sui, Phantom BTC) coexist in the pool without colliding on\n * the shared `getWallets()` bus.\n *\n * @example\n * slugify(\"svm\", \"Phantom\") // \"wallet-standard:svm-phantom\"\n * slugify(\"sui\", \"Sui Wallet\") // \"wallet-standard:sui-sui-wallet\"\n * slugify(\"btc\", \"Phantom\") // \"wallet-standard:btc-phantom\"\n */\nconst slugify = (platformPrefix: string, name: string): string => {\n const slug = name\n .trim()\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gv, \"-\");\n return `wallet-standard:${platformPrefix}-${slug}`;\n};\n\n/**\n * Read a Wallet Standard feature off a wallet, returning `undefined`\n * when absent. The caller narrows the unknown payload to the\n * platform-specific feature shape at use sites.\n */\nconst getFeature = <T>(wallet: WalletStandardWallet, name: string): T | undefined => {\n const feature = wallet.features[name];\n return feature ? (feature as T) : undefined;\n};\n\n/**\n * Build butr's `Account` shape from a Wallet Standard account address\n * and a resolved `ChainBase`. The composite id (`<chain>:<address>`)\n * is what butr's reducer uses to compare accounts across refreshes,\n * so keep this format identical across platforms.\n */\nconst buildAccount = (address: string, chain: ChainBase): Account => ({\n chain,\n id: `${chain.id}:${address}`,\n walletAddress: address,\n});\n\n/**\n * Pick the first address out of a wallet's accounts array, returning\n * `null` when the wallet currently exposes none.\n */\nconst pickFirstAddress = (accounts: ReadonlyArray<WalletStandardWalletAccount>): string | null => {\n const first = accounts[0];\n return first ? first.address : null;\n};\n\n/**\n * Look up a wallet account by address, falling back to the first\n * exposed account when the requested address isn't present.\n *\n * Used by per-call routing in `sendTx` / `signMessage`: callers pass\n * an `account` from `ConnectedWallet.accounts` to sign with a non-\n * active address; the wallet's `accounts[]` is the source of truth.\n */\nconst pickAccountByAddress = (\n accounts: ReadonlyArray<WalletStandardWalletAccount>,\n address: string,\n): WalletStandardWalletAccount | undefined =>\n accounts.find((a) => a.address === address) ?? accounts[0];\n\nexport { buildAccount, getFeature, pickAccountByAddress, pickFirstAddress, slugify };\n","import type { WalletAdapter } from \"@usebutr/core\";\n\nimport type { WalletStandardAppModule, WalletStandardWallet } from \"./types\";\n\n/**\n * Callback supplied by per-platform discovery to build a `WalletAdapter`\n * out of a Wallet Standard wallet announcement.\n *\n * Returns `null` when the wallet doesn't advertise this platform — that\n * lets a multi-chain wallet (e.g. Phantom: SVM + EVM + Sui + Bitcoin)\n * produce one adapter per platform without each builder needing to know\n * about the others.\n *\n * The second argument is a hook the builder calls during construction\n * to wire a synthetic `disconnected` event into its subscriber set.\n * The discovery loop fires the registered emitter when Wallet Standard\n * announces `unregister` for that wallet.\n */\ntype WalletStandardAdapterBuilder = (\n wallet: WalletStandardWallet,\n registerDisconnector: (emit: () => void) => void,\n) => WalletAdapter | null;\n\n/**\n * Subscribe to Wallet Standard announcements and turn matching wallets\n * into butr `WalletAdapter`s using a per-platform `build` callback.\n *\n * **Shared machinery** every platform package needs:\n * - Dynamic import of `@wallet-standard/app` (optional peer dep —\n * failure to resolve quietly disables discovery so consumers who\n * don't install it pay nothing).\n * - Cancellation: the returned unsubscribe is safe to call before the\n * dynamic import has resolved.\n * - Dedupe by adapter id, so re-announcements don't double-emit.\n * - `unregister` integration: when an extension is removed mid-session,\n * fire the disconnector the builder registered so connected pool\n * entries on the consumer side tear down cleanly.\n *\n * **What's platform-specific** lives entirely in `build`:\n * - Which `wallet.chains` to accept.\n * - Which features to gate on.\n * - The `id` slug prefix (use `slugify(prefix, wallet.name)`).\n * - The capability resolver, the feature decoding, the chain coercion.\n *\n * Disconnectors are keyed by the wallet object itself (identity equality),\n * not by adapter id — Wallet Standard's `register` / `unregister` emit\n * the same wallet object, so identity is the cheapest stable key.\n */\nconst discoverWalletStandard = (\n onAdapter: (adapter: WalletAdapter) => void,\n build: WalletStandardAdapterBuilder,\n): (() => void) => {\n let cancelled = false;\n let internalUnsub: (() => void) | null = null;\n\n void (async () => {\n let mod: WalletStandardAppModule;\n try {\n mod = (await import(\"@wallet-standard/app\")) as unknown as WalletStandardAppModule;\n } catch {\n return;\n }\n if (cancelled) {\n return;\n }\n\n const seenIds = new Set<string>();\n // wallet object → emitter that pushes synthetic `disconnected` to\n // this wallet's adapter's subscribers. Identity-keyed so the\n // unregister handler can find the right emitter cheaply.\n const disconnectors = new Map<WalletStandardWallet, () => void>();\n\n const tryAdd = (wallet: WalletStandardWallet) => {\n const adapter = build(wallet, (emit) => {\n disconnectors.set(wallet, emit);\n });\n if (!adapter || seenIds.has(adapter.id)) {\n return;\n }\n seenIds.add(adapter.id);\n onAdapter(adapter);\n };\n\n const app = mod.getWallets();\n for (const wallet of app.get()) {\n tryAdd(wallet);\n }\n\n const offRegister = app.on(\"register\", (...wallets) => {\n for (const wallet of wallets) {\n tryAdd(wallet);\n }\n });\n const offUnregister = app.on(\"unregister\", (...wallets) => {\n for (const wallet of wallets) {\n const emit = disconnectors.get(wallet);\n if (emit) {\n emit();\n disconnectors.delete(wallet);\n }\n }\n });\n internalUnsub = () => {\n offRegister();\n offUnregister();\n };\n })();\n\n return () => {\n cancelled = true;\n internalUnsub?.();\n internalUnsub = null;\n };\n};\n\nexport type { WalletStandardAdapterBuilder };\nexport { discoverWalletStandard };\n"],"mappings":"AAeA,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/primitives.ts","../src/discovery.ts"],"sourcesContent":["import type { Account, ChainBase } from \"@usebutr/core\";\n\nimport type { WalletStandardWallet, WalletStandardWalletAccount } from \"./types\";\n\n/**\n * Convert a wallet name into a stable kebab-case adapter id, scoped by\n * a platform prefix so adapters from different platforms (Phantom SVM,\n * Phantom Sui, Phantom BTC) coexist in the pool without colliding on\n * the shared `getWallets()` bus.\n *\n * @example\n * slugify(\"svm\", \"Phantom\") // \"wallet-standard:svm-phantom\"\n * slugify(\"sui\", \"Sui Wallet\") // \"wallet-standard:sui-sui-wallet\"\n * slugify(\"btc\", \"Phantom\") // \"wallet-standard:btc-phantom\"\n */\nconst slugify = (platformPrefix: string, name: string): string => {\n const slug = name\n .trim()\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gv, \"-\");\n return `wallet-standard:${platformPrefix}-${slug}`;\n};\n\n/**\n * Read a Wallet Standard feature off a wallet, returning `undefined`\n * when absent. The caller narrows the unknown payload to the\n * platform-specific feature shape at use sites.\n */\nconst getFeature = <T>(wallet: WalletStandardWallet, name: string): T | undefined => {\n const feature = wallet.features[name];\n return feature ? (feature as T) : undefined;\n};\n\n/**\n * Build butr's `Account` shape from a Wallet Standard account address\n * and a resolved `ChainBase`. The composite id (`<chain>:<address>`)\n * is what butr's reducer uses to compare accounts across refreshes,\n * so keep this format identical across platforms.\n */\nconst buildAccount = (address: string, chain: ChainBase): Account => ({\n chain,\n id: `${chain.id}:${address}`,\n walletAddress: address,\n});\n\n/**\n * Pick the first address out of a wallet's accounts array, returning\n * `null` when the wallet currently exposes none.\n */\nconst pickFirstAddress = (accounts: ReadonlyArray<WalletStandardWalletAccount>): string | null => {\n const first = accounts[0];\n return first ? first.address : null;\n};\n\n/**\n * Look up a wallet account by address, falling back to the first\n * exposed account when the requested address isn't present.\n *\n * Used by per-call routing in `sendTx` / `signMessage`: callers pass\n * an `account` from `ConnectedWallet.accounts` to sign with a non-\n * active address; the wallet's `accounts[]` is the source of truth.\n */\nconst pickAccountByAddress = (\n accounts: ReadonlyArray<WalletStandardWalletAccount>,\n address: string,\n): WalletStandardWalletAccount | undefined =>\n accounts.find((a) => a.address === address) ?? accounts[0];\n\nexport { buildAccount, getFeature, pickAccountByAddress, pickFirstAddress, slugify };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport { logWarn } from \"@usebutr/core\";\n\nimport type { WalletStandardAppModule, WalletStandardWallet } from \"./types\";\n\n// Warn once per process: every platform calls this, so a missing optional peer\n// dep would otherwise log one identical line per chain.\nlet warnedMissingApp = false;\n\n/**\n * Callback supplied by per-platform discovery to build a `WalletAdapter`\n * out of a Wallet Standard wallet announcement.\n *\n * Returns `null` when the wallet doesn't advertise this platform — that\n * lets a multi-chain wallet (e.g. Phantom: SVM + EVM + Sui + Bitcoin)\n * produce one adapter per platform without each builder needing to know\n * about the others.\n *\n * The second argument is a hook the builder calls during construction\n * to wire a synthetic `disconnected` event into its subscriber set.\n * The discovery loop fires the registered emitter when Wallet Standard\n * announces `unregister` for that wallet.\n */\ntype WalletStandardAdapterBuilder = (\n wallet: WalletStandardWallet,\n registerDisconnector: (emit: () => void) => void,\n) => WalletAdapter | null;\n\n/**\n * Subscribe to Wallet Standard announcements and turn matching wallets\n * into butr `WalletAdapter`s using a per-platform `build` callback.\n *\n * **Shared machinery** every platform package needs:\n * - Dynamic import of `@wallet-standard/app` (optional peer dep —\n * failure to resolve quietly disables discovery so consumers who\n * don't install it pay nothing).\n * - Cancellation: the returned unsubscribe is safe to call before the\n * dynamic import has resolved.\n * - Dedupe by adapter id, so re-announcements don't double-emit.\n * - `unregister` integration: when an extension is removed mid-session,\n * fire the disconnector the builder registered so connected pool\n * entries on the consumer side tear down cleanly.\n *\n * **What's platform-specific** lives entirely in `build`:\n * - Which `wallet.chains` to accept.\n * - Which features to gate on.\n * - The `id` slug prefix (use `slugify(prefix, wallet.name)`).\n * - The capability resolver, the feature decoding, the chain coercion.\n *\n * Disconnectors are keyed by the wallet object itself (identity equality),\n * not by adapter id — Wallet Standard's `register` / `unregister` emit\n * the same wallet object, so identity is the cheapest stable key.\n */\nconst discoverWalletStandard = (\n onAdapter: (adapter: WalletAdapter) => void,\n build: WalletStandardAdapterBuilder,\n): (() => void) => {\n let cancelled = false;\n let internalUnsub: (() => void) | null = null;\n\n void (async () => {\n let mod: WalletStandardAppModule;\n try {\n mod = (await import(\"@wallet-standard/app\")) as unknown as WalletStandardAppModule;\n } catch (error) {\n if (!warnedMissingApp) {\n warnedMissingApp = true;\n logWarn(\n \"[butr] Wallet Standard wallet discovery is disabled: `@wallet-standard/app` could not be loaded. Install it (`npm i @wallet-standard/app`) to detect Wallet Standard wallets.\",\n error,\n );\n }\n return;\n }\n if (cancelled) {\n return;\n }\n\n const seenIds = new Set<string>();\n // wallet object → emitter that pushes synthetic `disconnected` to\n // this wallet's adapter's subscribers. Identity-keyed so the\n // unregister handler can find the right emitter cheaply.\n const disconnectors = new Map<WalletStandardWallet, () => void>();\n\n const tryAdd = (wallet: WalletStandardWallet) => {\n const adapter = build(wallet, (emit) => {\n disconnectors.set(wallet, emit);\n });\n if (!adapter || seenIds.has(adapter.id)) {\n return;\n }\n seenIds.add(adapter.id);\n onAdapter(adapter);\n };\n\n const app = mod.getWallets();\n for (const wallet of app.get()) {\n tryAdd(wallet);\n }\n\n const offRegister = app.on(\"register\", (...wallets) => {\n for (const wallet of wallets) {\n tryAdd(wallet);\n }\n });\n const offUnregister = app.on(\"unregister\", (...wallets) => {\n for (const wallet of wallets) {\n const emit = disconnectors.get(wallet);\n if (emit) {\n emit();\n disconnectors.delete(wallet);\n }\n }\n });\n internalUnsub = () => {\n offRegister();\n offUnregister();\n };\n })();\n\n return () => {\n cancelled = true;\n internalUnsub?.();\n internalUnsub = null;\n };\n};\n\nexport type { WalletStandardAdapterBuilder };\nexport { discoverWalletStandard };\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAM,WAAW,gBAAwB,SAAyB;CAKhE,OAAO,mBAAmB,eAAe,GAJ5B,KACV,KAAK,CAAC,CACN,YAAY,CAAC,CACb,2BAAW,6BAAa,GAAG,GACiB;AACjD;;;;;;AAOA,MAAM,cAAiB,QAA8B,SAAgC;CACnF,MAAM,UAAU,OAAO,SAAS;CAChC,OAAO,UAAW,UAAgB;AACpC;;;;;;;AAQA,MAAM,gBAAgB,SAAiB,WAA+B;CACpE;CACA,IAAI,GAAG,MAAM,GAAG,GAAG;CACnB,eAAe;AACjB;;;;;AAMA,MAAM,oBAAoB,aAAwE;CAChG,MAAM,QAAQ,SAAS;CACvB,OAAO,QAAQ,MAAM,UAAU;AACjC;;;;;;;;;AAUA,MAAM,wBACJ,UACA,YAEA,SAAS,MAAM,MAAM,EAAE,YAAY,OAAO,KAAK,SAAS;;;;AC3D1D,IAAI,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CvB,MAAM,0BACJ,WACA,UACiB;CACjB,IAAI,YAAY;CAChB,IAAI,gBAAqC;CAEzC,CAAM,YAAY;EAChB,IAAI;EACJ,IAAI;GACF,MAAO,MAAM,OAAO;EACtB,SAAS,OAAO;GACd,IAAI,CAAC,kBAAkB;IACrB,mBAAmB;IACnB,QACE,iLACA,KACF;GACF;GACA;EACF;EACA,IAAI,WACF;EAGF,MAAM,0BAAU,IAAI,IAAY;EAIhC,MAAM,gCAAgB,IAAI,IAAsC;EAEhE,MAAM,UAAU,WAAiC;GAC/C,MAAM,UAAU,MAAM,SAAS,SAAS;IACtC,cAAc,IAAI,QAAQ,IAAI;GAChC,CAAC;GACD,IAAI,CAAC,WAAW,QAAQ,IAAI,QAAQ,EAAE,GACpC;GAEF,QAAQ,IAAI,QAAQ,EAAE;GACtB,UAAU,OAAO;EACnB;EAEA,MAAM,MAAM,IAAI,WAAW;EAC3B,KAAK,MAAM,UAAU,IAAI,IAAI,GAC3B,OAAO,MAAM;EAGf,MAAM,cAAc,IAAI,GAAG,aAAa,GAAG,YAAY;GACrD,KAAK,MAAM,UAAU,SACnB,OAAO,MAAM;EAEjB,CAAC;EACD,MAAM,gBAAgB,IAAI,GAAG,eAAe,GAAG,YAAY;GACzD,KAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,OAAO,cAAc,IAAI,MAAM;IACrC,IAAI,MAAM;KACR,KAAK;KACL,cAAc,OAAO,MAAM;IAC7B;GACF;EACF,CAAC;EACD,sBAAsB;GACpB,YAAY;GACZ,cAAc;EAChB;CACF,EAAC,CAAE;CAEH,aAAa;EACX,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;CAClB;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebutr/wallet-standard-shared",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Shared Wallet Standard discovery primitives used by @usebutr/svm, @usebutr/sui, @usebutr/bitcoin.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Pedro Filho <pedro@filho.me>",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@usebutr/core": "0.
|
|
25
|
+
"@usebutr/core": "0.4.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@wallet-standard/app": "^1.1.
|
|
28
|
+
"@wallet-standard/app": "^1.1.1",
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
|
-
"vitest": "^4.1.
|
|
30
|
+
"vitest": "^4.1.9",
|
|
31
31
|
"@repo/config-vitest": "0.0.0",
|
|
32
32
|
"@repo/typescript-config": "0.0.0"
|
|
33
33
|
},
|