FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

202
node_modules/@solana/wallet-adapter-react/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

101
node_modules/@solana/wallet-adapter-react/README.md generated vendored Normal file
View File

@@ -0,0 +1,101 @@
# `@solana/wallet-adapter-react`
## Creating a custom connect button
This package exports a series of hooks that you can use to create custom wallet connection buttons. They manage the state of the wallet connection for you, and return helper methods that you can attach to your event handlers.
What follows is the documentation for `useWalletMultiButton()`.
### States
- `no-wallet` \
In this state you are neither connected nor is there a wallet selected. Allow your users to select from the list of `wallets`, then call `onSelectWallet()` with the name of the wallet they chose.
- `has-wallet` \
This state implies that there is a wallet selected, but that your app is not connected to it. Render a connect button that calls `onConnect()` when clicked.
- `disconnecting` \
When in this state, the last-connected wallet is in mid-disconnection.
- `connected` \
In this state, you have access to the connected `publicKey` and an `onDisconnect()` method that you can call to disconnect from the wallet. At any time you can call `onSelectWallet()` to change wallets.
- `connecting` \
When in this state, the wallet is in mid-connection.
### Functions
- `onConnect` \
Connects the currently selected wallet. Available in the `has-wallet` state.
- `onDisconnect` \
Disconnects the currently selected wallet. Available in the `has-wallet`, `connected`, and `connecting` states.
- `onSelectWallet()` \
Calls the `onSelectWallet()` function that you supplied as config to `useWalletMultiButton`. That function receives the list of `wallets` and offers you an `onSelectWallet()` callback that you can call with the name of the wallet to switch to.
### Example
```ts
function CustomConnectButton() {
const [walletModalConfig, setWalletModalConfig] = useState<Readonly<{
onSelectWallet(walletName: WalletName): void;
wallets: Wallet[];
}> | null>(null);
const { buttonState, onConnect, onDisconnect, onSelectWallet } = useWalletMultiButton({
onSelectWallet: setWalletModalConfig,
});
let label;
switch (buttonState) {
case 'connected':
label = 'Disconnect';
break;
case 'connecting':
label = 'Connecting';
break;
case 'disconnecting':
label = 'Disconnecting';
break;
case 'has-wallet':
label = 'Connect';
break;
case 'no-wallet':
label = 'Select Wallet';
break;
}
return (
<>
<button
disabled={buttonState === 'connecting' || buttonState === 'disconnecting'}
onClick={() => {
switch (buttonState) {
case 'connected':
onDisconnect?.();
break;
case 'connecting':
case 'disconnecting':
break;
case 'has-wallet':
onConnect?.();
break;
case 'no-wallet':
onSelectWallet?.();
break;
}
}}
>
{label}
</button>
{walletModalConfig ? (
<Modal>
{walletModalConfig.wallets.map((wallet) => (
<button
key={wallet.adapter.name}
onClick={() => {
walletModalConfig.onSelectWallet(wallet.adapter.name);
setWalletModalConfig(null);
}}
>
{wallet.adapter.name}
</button>
))}
</Modal>
) : null}
</>
);
}
```

View File

@@ -0,0 +1,45 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionProvider = void 0;
const web3_js_1 = require("@solana/web3.js");
const react_1 = __importStar(require("react"));
const useConnection_js_1 = require("./useConnection.js");
const ConnectionProvider = ({ children, endpoint, config = { commitment: 'confirmed' }, }) => {
const connection = (0, react_1.useMemo)(() => new web3_js_1.Connection(endpoint, config), [endpoint, config]);
return react_1.default.createElement(useConnection_js_1.ConnectionContext.Provider, { value: { connection } }, children);
};
exports.ConnectionProvider = ConnectionProvider;
//# sourceMappingURL=ConnectionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/ConnectionProvider.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAoE;AACpE,+CAAgE;AAChE,yDAAuD;AAQhD,MAAM,kBAAkB,GAAgC,CAAC,EAC5D,QAAQ,EACR,QAAQ,EACR,MAAM,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,GACvC,EAAE,EAAE;IACD,MAAM,UAAU,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAI,oBAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAEvF,OAAO,8BAAC,oCAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,UAAU,EAAE,IAAG,QAAQ,CAA8B,CAAC;AACtG,CAAC,CAAC;AARW,QAAA,kBAAkB,sBAQ7B"}

View File

@@ -0,0 +1,179 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletProvider = WalletProvider;
const wallet_adapter_mobile_1 = require("@solana-mobile/wallet-adapter-mobile");
const wallet_standard_wallet_adapter_react_1 = require("@solana/wallet-standard-wallet-adapter-react");
const react_1 = __importStar(require("react"));
const getEnvironment_js_1 = __importStar(require("./getEnvironment.js"));
const getInferredClusterFromEndpoint_js_1 = __importDefault(require("./getInferredClusterFromEndpoint.js"));
const useConnection_js_1 = require("./useConnection.js");
const useLocalStorage_js_1 = require("./useLocalStorage.js");
const WalletProviderBase_js_1 = require("./WalletProviderBase.js");
let _userAgent;
function getUserAgent() {
var _a, _b;
if (_userAgent === undefined) {
_userAgent = (_b = (_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.userAgent) !== null && _b !== void 0 ? _b : null;
}
return _userAgent;
}
function getIsMobile(adapters) {
const userAgentString = getUserAgent();
return (0, getEnvironment_js_1.default)({ adapters, userAgentString }) === getEnvironment_js_1.Environment.MOBILE_WEB;
}
function getUriForAppIdentity() {
const location = globalThis.location;
if (!location)
return;
return `${location.protocol}//${location.host}`;
}
function WalletProvider({ children, wallets: adapters, autoConnect, localStorageKey = 'walletName', onError, }) {
const { connection } = (0, useConnection_js_1.useConnection)();
const adaptersWithStandardAdapters = (0, wallet_standard_wallet_adapter_react_1.useStandardWalletAdapters)(adapters);
const mobileWalletAdapter = (0, react_1.useMemo)(() => {
if (!getIsMobile(adaptersWithStandardAdapters)) {
return null;
}
const existingMobileWalletAdapter = adaptersWithStandardAdapters.find((adapter) => adapter.name === wallet_adapter_mobile_1.SolanaMobileWalletAdapterWalletName);
if (existingMobileWalletAdapter) {
return existingMobileWalletAdapter;
}
return new wallet_adapter_mobile_1.SolanaMobileWalletAdapter({
addressSelector: (0, wallet_adapter_mobile_1.createDefaultAddressSelector)(),
appIdentity: {
uri: getUriForAppIdentity(),
},
authorizationResultCache: (0, wallet_adapter_mobile_1.createDefaultAuthorizationResultCache)(),
cluster: (0, getInferredClusterFromEndpoint_js_1.default)(connection === null || connection === void 0 ? void 0 : connection.rpcEndpoint),
onWalletNotFound: (0, wallet_adapter_mobile_1.createDefaultWalletNotFoundHandler)(),
});
}, [adaptersWithStandardAdapters, connection === null || connection === void 0 ? void 0 : connection.rpcEndpoint]);
const adaptersWithMobileWalletAdapter = (0, react_1.useMemo)(() => {
if (mobileWalletAdapter == null || adaptersWithStandardAdapters.indexOf(mobileWalletAdapter) !== -1) {
return adaptersWithStandardAdapters;
}
return [mobileWalletAdapter, ...adaptersWithStandardAdapters];
}, [adaptersWithStandardAdapters, mobileWalletAdapter]);
const [walletName, setWalletName] = (0, useLocalStorage_js_1.useLocalStorage)(localStorageKey, null);
const adapter = (0, react_1.useMemo)(() => { var _a; return (_a = adaptersWithMobileWalletAdapter.find((a) => a.name === walletName)) !== null && _a !== void 0 ? _a : null; }, [adaptersWithMobileWalletAdapter, walletName]);
const changeWallet = (0, react_1.useCallback)((nextWalletName) => {
if (walletName === nextWalletName)
return;
if (adapter &&
// Selecting a wallet other than the mobile wallet adapter is not
// sufficient reason to call `disconnect` on the mobile wallet adapter.
// Calling `disconnect` on the mobile wallet adapter causes the entire
// authorization store to be wiped.
adapter.name !== wallet_adapter_mobile_1.SolanaMobileWalletAdapterWalletName) {
adapter.disconnect();
}
setWalletName(nextWalletName);
}, [adapter, setWalletName, walletName]);
(0, react_1.useEffect)(() => {
if (!adapter)
return;
function handleDisconnect() {
if (isUnloadingRef.current)
return;
setWalletName(null);
}
adapter.on('disconnect', handleDisconnect);
return () => {
adapter.off('disconnect', handleDisconnect);
};
}, [adapter, adaptersWithStandardAdapters, setWalletName, walletName]);
const hasUserSelectedAWallet = (0, react_1.useRef)(false);
const handleAutoConnectRequest = (0, react_1.useMemo)(() => {
if (!autoConnect || !adapter)
return;
return () => __awaiter(this, void 0, void 0, function* () {
// If autoConnect is true or returns true, use the default autoConnect behavior.
if (autoConnect === true || (yield autoConnect(adapter))) {
if (hasUserSelectedAWallet.current) {
yield adapter.connect();
}
else {
yield adapter.autoConnect();
}
}
});
}, [autoConnect, adapter]);
const isUnloadingRef = (0, react_1.useRef)(false);
(0, react_1.useEffect)(() => {
if (walletName === wallet_adapter_mobile_1.SolanaMobileWalletAdapterWalletName && getIsMobile(adaptersWithStandardAdapters)) {
isUnloadingRef.current = false;
return;
}
function handleBeforeUnload() {
isUnloadingRef.current = true;
}
/**
* Some wallets fire disconnection events when the window unloads. Since there's no way to
* distinguish between a disconnection event received because a user initiated it, and one
* that was received because they've closed the window, we have to track window unload
* events themselves. Downstream components use this information to decide whether to act
* upon or drop wallet events and errors.
*/
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [adaptersWithStandardAdapters, walletName]);
const handleConnectError = (0, react_1.useCallback)(() => {
if (adapter) {
// If any error happens while connecting, unset the adapter.
changeWallet(null);
}
}, [adapter, changeWallet]);
const selectWallet = (0, react_1.useCallback)((walletName) => {
hasUserSelectedAWallet.current = true;
changeWallet(walletName);
}, [changeWallet]);
return (react_1.default.createElement(WalletProviderBase_js_1.WalletProviderBase, { wallets: adaptersWithMobileWalletAdapter, adapter: adapter, isUnloadingRef: isUnloadingRef, onAutoConnectRequest: handleAutoConnectRequest, onConnectError: handleConnectError, onError: onError, onSelectWallet: selectWallet }, children));
}
//# sourceMappingURL=WalletProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WalletProvider.js","sourceRoot":"","sources":["../../src/WalletProvider.tsx"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,286 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletProviderBase = WalletProviderBase;
const wallet_adapter_base_1 = require("@solana/wallet-adapter-base");
const react_1 = __importStar(require("react"));
const errors_js_1 = require("./errors.js");
const useWallet_js_1 = require("./useWallet.js");
function WalletProviderBase({ children, wallets: adapters, adapter, isUnloadingRef, onAutoConnectRequest, onConnectError, onError, onSelectWallet, }) {
const isConnectingRef = (0, react_1.useRef)(false);
const [connecting, setConnecting] = (0, react_1.useState)(false);
const isDisconnectingRef = (0, react_1.useRef)(false);
const [disconnecting, setDisconnecting] = (0, react_1.useState)(false);
const [publicKey, setPublicKey] = (0, react_1.useState)(() => { var _a; return (_a = adapter === null || adapter === void 0 ? void 0 : adapter.publicKey) !== null && _a !== void 0 ? _a : null; });
const [connected, setConnected] = (0, react_1.useState)(() => { var _a; return (_a = adapter === null || adapter === void 0 ? void 0 : adapter.connected) !== null && _a !== void 0 ? _a : false; });
/**
* Store the error handlers as refs so that a change in the
* custom error handler does not recompute other dependencies.
*/
const onErrorRef = (0, react_1.useRef)(onError);
(0, react_1.useEffect)(() => {
onErrorRef.current = onError;
return () => {
onErrorRef.current = undefined;
};
}, [onError]);
const handleErrorRef = (0, react_1.useRef)((error, adapter) => {
if (!isUnloadingRef.current) {
if (onErrorRef.current) {
onErrorRef.current(error, adapter);
}
else {
console.error(error, adapter);
if (error instanceof wallet_adapter_base_1.WalletNotReadyError && typeof window !== 'undefined' && adapter) {
window.open(adapter.url, '_blank');
}
}
}
return error;
});
// Wrap adapters to conform to the `Wallet` interface
const [wallets, setWallets] = (0, react_1.useState)(() => adapters
.map((adapter) => ({
adapter,
readyState: adapter.readyState,
}))
.filter(({ readyState }) => readyState !== wallet_adapter_base_1.WalletReadyState.Unsupported));
// When the adapters change, start to listen for changes to their `readyState`
(0, react_1.useEffect)(() => {
// When the adapters change, wrap them to conform to the `Wallet` interface
setWallets((wallets) => adapters
.map((adapter, index) => {
const wallet = wallets[index];
// If the wallet hasn't changed, return the same instance
return wallet && wallet.adapter === adapter && wallet.readyState === adapter.readyState
? wallet
: {
adapter: adapter,
readyState: adapter.readyState,
};
})
.filter(({ readyState }) => readyState !== wallet_adapter_base_1.WalletReadyState.Unsupported));
function handleReadyStateChange(readyState) {
setWallets((prevWallets) => {
const index = prevWallets.findIndex(({ adapter }) => adapter === this);
if (index === -1)
return prevWallets;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { adapter } = prevWallets[index];
return [
...prevWallets.slice(0, index),
{ adapter, readyState },
...prevWallets.slice(index + 1),
].filter(({ readyState }) => readyState !== wallet_adapter_base_1.WalletReadyState.Unsupported);
});
}
adapters.forEach((adapter) => adapter.on('readyStateChange', handleReadyStateChange, adapter));
return () => {
adapters.forEach((adapter) => adapter.off('readyStateChange', handleReadyStateChange, adapter));
};
}, [adapter, adapters]);
const wallet = (0, react_1.useMemo)(() => { var _a; return (_a = wallets.find((wallet) => wallet.adapter === adapter)) !== null && _a !== void 0 ? _a : null; }, [adapter, wallets]);
// Setup and teardown event listeners when the adapter changes
(0, react_1.useEffect)(() => {
if (!adapter)
return;
const handleConnect = (publicKey) => {
setPublicKey(publicKey);
isConnectingRef.current = false;
setConnecting(false);
setConnected(true);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleDisconnect = () => {
if (isUnloadingRef.current)
return;
setPublicKey(null);
isConnectingRef.current = false;
setConnecting(false);
setConnected(false);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleError = (error) => {
handleErrorRef.current(error, adapter);
};
adapter.on('connect', handleConnect);
adapter.on('disconnect', handleDisconnect);
adapter.on('error', handleError);
return () => {
adapter.off('connect', handleConnect);
adapter.off('disconnect', handleDisconnect);
adapter.off('error', handleError);
handleDisconnect();
};
}, [adapter, isUnloadingRef]);
// When the adapter changes, clear the `autoConnect` tracking flag
const didAttemptAutoConnectRef = (0, react_1.useRef)(false);
(0, react_1.useEffect)(() => {
return () => {
didAttemptAutoConnectRef.current = false;
};
}, [adapter]);
// If auto-connect is enabled, request to connect when the adapter changes and is ready
(0, react_1.useEffect)(() => {
if (didAttemptAutoConnectRef.current ||
isConnectingRef.current ||
connected ||
!onAutoConnectRequest ||
!((wallet === null || wallet === void 0 ? void 0 : wallet.readyState) === wallet_adapter_base_1.WalletReadyState.Installed || (wallet === null || wallet === void 0 ? void 0 : wallet.readyState) === wallet_adapter_base_1.WalletReadyState.Loadable))
return;
isConnectingRef.current = true;
setConnecting(true);
didAttemptAutoConnectRef.current = true;
(function () {
return __awaiter(this, void 0, void 0, function* () {
try {
yield onAutoConnectRequest();
}
catch (_a) {
onConnectError();
// Drop the error. It will be caught by `handleError` anyway.
}
finally {
setConnecting(false);
isConnectingRef.current = false;
}
});
})();
}, [connected, onAutoConnectRequest, onConnectError, wallet]);
// Send a transaction using the provided connection
const sendTransaction = (0, react_1.useCallback)((transaction, connection, options) => __awaiter(this, void 0, void 0, function* () {
if (!adapter)
throw handleErrorRef.current(new errors_js_1.WalletNotSelectedError());
if (!connected)
throw handleErrorRef.current(new wallet_adapter_base_1.WalletNotConnectedError(), adapter);
return yield adapter.sendTransaction(transaction, connection, options);
}), [adapter, connected]);
// Sign a transaction if the wallet supports it
const signTransaction = (0, react_1.useMemo)(() => adapter && 'signTransaction' in adapter
? (transaction) => __awaiter(this, void 0, void 0, function* () {
if (!connected)
throw handleErrorRef.current(new wallet_adapter_base_1.WalletNotConnectedError(), adapter);
return yield adapter.signTransaction(transaction);
})
: undefined, [adapter, connected]);
// Sign multiple transactions if the wallet supports it
const signAllTransactions = (0, react_1.useMemo)(() => adapter && 'signAllTransactions' in adapter
? (transactions) => __awaiter(this, void 0, void 0, function* () {
if (!connected)
throw handleErrorRef.current(new wallet_adapter_base_1.WalletNotConnectedError(), adapter);
return yield adapter.signAllTransactions(transactions);
})
: undefined, [adapter, connected]);
// Sign an arbitrary message if the wallet supports it
const signMessage = (0, react_1.useMemo)(() => adapter && 'signMessage' in adapter
? (message) => __awaiter(this, void 0, void 0, function* () {
if (!connected)
throw handleErrorRef.current(new wallet_adapter_base_1.WalletNotConnectedError(), adapter);
return yield adapter.signMessage(message);
})
: undefined, [adapter, connected]);
// Sign in if the wallet supports it
const signIn = (0, react_1.useMemo)(() => adapter && 'signIn' in adapter
? (input) => __awaiter(this, void 0, void 0, function* () {
return yield adapter.signIn(input);
})
: undefined, [adapter]);
const handleConnect = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
if (isConnectingRef.current || isDisconnectingRef.current || (wallet === null || wallet === void 0 ? void 0 : wallet.adapter.connected))
return;
if (!wallet)
throw handleErrorRef.current(new errors_js_1.WalletNotSelectedError());
const { adapter, readyState } = wallet;
if (!(readyState === wallet_adapter_base_1.WalletReadyState.Installed || readyState === wallet_adapter_base_1.WalletReadyState.Loadable))
throw handleErrorRef.current(new wallet_adapter_base_1.WalletNotReadyError(), adapter);
isConnectingRef.current = true;
setConnecting(true);
try {
yield adapter.connect();
}
catch (e) {
onConnectError();
throw e;
}
finally {
setConnecting(false);
isConnectingRef.current = false;
}
}), [onConnectError, wallet]);
const handleDisconnect = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
if (isDisconnectingRef.current)
return;
if (!adapter)
return;
isDisconnectingRef.current = true;
setDisconnecting(true);
try {
yield adapter.disconnect();
}
finally {
setDisconnecting(false);
isDisconnectingRef.current = false;
}
}), [adapter]);
return (react_1.default.createElement(useWallet_js_1.WalletContext.Provider, { value: {
autoConnect: !!onAutoConnectRequest,
wallets,
wallet,
publicKey,
connected,
connecting,
disconnecting,
select: onSelectWallet,
connect: handleConnect,
disconnect: handleDisconnect,
sendTransaction,
signTransaction,
signAllTransactions,
signMessage,
signIn,
} }, children));
}
//# sourceMappingURL=WalletProviderBase.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = defineProperty;
function defineProperty(obj, prop, val) {
Object.defineProperty(obj, prop, val);
}
//# sourceMappingURL=defineProperty.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"defineProperty.js","sourceRoot":"","sources":["../../src/defineProperty.ts"],"names":[],"mappings":";;AAqBA,iCAMC;AAND,SAAwB,cAAc,CAClC,GAAQ,EACR,IAAS,EACT,GAAU;IAEV,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC"}

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletNotSelectedError = void 0;
const wallet_adapter_base_1 = require("@solana/wallet-adapter-base");
class WalletNotSelectedError extends wallet_adapter_base_1.WalletError {
constructor() {
super(...arguments);
this.name = 'WalletNotSelectedError';
}
}
exports.WalletNotSelectedError = WalletNotSelectedError;
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAAA,qEAA0D;AAE1D,MAAa,sBAAuB,SAAQ,iCAAW;IAAvD;;QACI,SAAI,GAAG,wBAAwB,CAAC;IACpC,CAAC;CAAA;AAFD,wDAEC"}

View File

@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Environment = void 0;
exports.default = getEnvironment;
const wallet_adapter_mobile_1 = require("@solana-mobile/wallet-adapter-mobile");
const wallet_adapter_base_1 = require("@solana/wallet-adapter-base");
var Environment;
(function (Environment) {
Environment[Environment["DESKTOP_WEB"] = 0] = "DESKTOP_WEB";
Environment[Environment["MOBILE_WEB"] = 1] = "MOBILE_WEB";
})(Environment || (exports.Environment = Environment = {}));
function isWebView(userAgentString) {
return /(WebView|Version\/.+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+)|; wv\).+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+))/i.test(userAgentString);
}
function getEnvironment({ adapters, userAgentString }) {
if (adapters.some((adapter) => adapter.name !== wallet_adapter_mobile_1.SolanaMobileWalletAdapterWalletName &&
adapter.readyState === wallet_adapter_base_1.WalletReadyState.Installed)) {
/**
* There are only two ways a browser extension adapter should be able to reach `Installed` status:
*
* 1. Its browser extension is installed.
* 2. The app is running on a mobile wallet's in-app browser.
*
* In either case, we consider the environment to be desktop-like.
*/
return Environment.DESKTOP_WEB;
}
if (userAgentString &&
// Step 1: Check whether we're on a platform that supports MWA at all.
/android/i.test(userAgentString) &&
// Step 2: Determine that we are *not* running in a WebView.
!isWebView(userAgentString)) {
return Environment.MOBILE_WEB;
}
else {
return Environment.DESKTOP_WEB;
}
}
//# sourceMappingURL=getEnvironment.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getEnvironment.js","sourceRoot":"","sources":["../../src/getEnvironment.ts"],"names":[],"mappings":";;;AAmBA,iCA6BC;AAhDD,gFAA2F;AAC3F,qEAA6E;AAE7E,IAAY,WAGX;AAHD,WAAY,WAAW;IACnB,2DAAW,CAAA;IACX,yDAAU,CAAA;AACd,CAAC,EAHW,WAAW,2BAAX,WAAW,QAGtB;AAOD,SAAS,SAAS,CAAC,eAAuB;IACtC,OAAO,yGAAyG,CAAC,IAAI,CACjH,eAAe,CAClB,CAAC;AACN,CAAC;AAED,SAAwB,cAAc,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAU;IACxE,IACI,QAAQ,CAAC,IAAI,CACT,CAAC,OAAO,EAAE,EAAE,CACR,OAAO,CAAC,IAAI,KAAK,2DAAmC;QACpD,OAAO,CAAC,UAAU,KAAK,sCAAgB,CAAC,SAAS,CACxD,EACH,CAAC;QACC;;;;;;;WAOG;QACH,OAAO,WAAW,CAAC,WAAW,CAAC;IACnC,CAAC;IACD,IACI,eAAe;QACf,sEAAsE;QACtE,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC;QAChC,4DAA4D;QAC5D,CAAC,SAAS,CAAC,eAAe,CAAC,EAC7B,CAAC;QACC,OAAO,WAAW,CAAC,UAAU,CAAC;IAClC,CAAC;SAAM,CAAC;QACJ,OAAO,WAAW,CAAC,WAAW,CAAC;IACnC,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = getInferredClusterFromEndpoint;
function getInferredClusterFromEndpoint(endpoint) {
if (!endpoint) {
return 'mainnet-beta';
}
if (/devnet/i.test(endpoint)) {
return 'devnet';
}
else if (/testnet/i.test(endpoint)) {
return 'testnet';
}
else {
return 'mainnet-beta';
}
}
//# sourceMappingURL=getInferredClusterFromEndpoint.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInferredClusterFromEndpoint.js","sourceRoot":"","sources":["../../src/getInferredClusterFromEndpoint.ts"],"names":[],"mappings":";;AAEA,iDAWC;AAXD,SAAwB,8BAA8B,CAAC,QAAiB;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,OAAO,cAAc,CAAC;IAC1B,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,QAAQ,CAAC;IACpB,CAAC;SAAM,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,SAAS,CAAC;IACrB,CAAC;SAAM,CAAC;QACJ,OAAO,cAAc,CAAC;IAC1B,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,24 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./ConnectionProvider.js"), exports);
__exportStar(require("./errors.js"), exports);
__exportStar(require("./useAnchorWallet.js"), exports);
__exportStar(require("./useConnection.js"), exports);
__exportStar(require("./useLocalStorage.js"), exports);
__exportStar(require("./useWallet.js"), exports);
__exportStar(require("./WalletProvider.js"), exports);
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,8CAA4B;AAC5B,uDAAqC;AACrC,qDAAmC;AACnC,uDAAqC;AACrC,iDAA+B;AAC/B,sDAAoC"}

View File

@@ -0,0 +1 @@
{ "type": "commonjs" }

View File

@@ -0,0 +1 @@
{"root":["../../src/ConnectionProvider.tsx","../../src/WalletProvider.tsx","../../src/WalletProviderBase.tsx","../../src/defineProperty.ts","../../src/errors.ts","../../src/getEnvironment.ts","../../src/getInferredClusterFromEndpoint.ts","../../src/index.ts","../../src/useAnchorWallet.ts","../../src/useConnection.ts","../../src/useLocalStorage.native.ts","../../src/useLocalStorage.ts","../../src/useWallet.ts"],"version":"5.8.3"}

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useAnchorWallet = useAnchorWallet;
const react_1 = require("react");
const useWallet_js_1 = require("./useWallet.js");
function useAnchorWallet() {
const { publicKey, signTransaction, signAllTransactions } = (0, useWallet_js_1.useWallet)();
return (0, react_1.useMemo)(() => publicKey && signTransaction && signAllTransactions
? { publicKey, signTransaction, signAllTransactions }
: undefined, [publicKey, signTransaction, signAllTransactions]);
}
//# sourceMappingURL=useAnchorWallet.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useAnchorWallet.js","sourceRoot":"","sources":["../../src/useAnchorWallet.ts"],"names":[],"mappings":";;AAUA,0CASC;AAlBD,iCAAgC;AAChC,iDAA2C;AAQ3C,SAAgB,eAAe;IAC3B,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE,GAAG,IAAA,wBAAS,GAAE,CAAC;IACxE,OAAO,IAAA,eAAO,EACV,GAAG,EAAE,CACD,SAAS,IAAI,eAAe,IAAI,mBAAmB;QAC/C,CAAC,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE;QACrD,CAAC,CAAC,SAAS,EACnB,CAAC,SAAS,EAAE,eAAe,EAAE,mBAAmB,CAAC,CACpD,CAAC;AACN,CAAC"}

View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionContext = void 0;
exports.useConnection = useConnection;
const react_1 = require("react");
exports.ConnectionContext = (0, react_1.createContext)({});
function useConnection() {
return (0, react_1.useContext)(exports.ConnectionContext);
}
//# sourceMappingURL=useConnection.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useConnection.js","sourceRoot":"","sources":["../../src/useConnection.ts"],"names":[],"mappings":";;;AASA,sCAEC;AAVD,iCAAkD;AAMrC,QAAA,iBAAiB,GAAG,IAAA,qBAAa,EAAyB,EAA4B,CAAC,CAAC;AAErG,SAAgB,aAAa;IACzB,OAAO,IAAA,kBAAU,EAAC,yBAAiB,CAAC,CAAC;AACzC,CAAC"}

View File

@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useLocalStorage = useLocalStorage;
const react_1 = require("react");
function useLocalStorage(key, defaultState) {
const state = (0, react_1.useState)(() => {
try {
const value = localStorage.getItem(key);
if (value)
return JSON.parse(value);
}
catch (error) {
if (typeof window !== 'undefined') {
console.error(error);
}
}
return defaultState;
});
const value = state[0];
const isFirstRenderRef = (0, react_1.useRef)(true);
(0, react_1.useEffect)(() => {
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
return;
}
try {
if (value === null) {
localStorage.removeItem(key);
}
else {
localStorage.setItem(key, JSON.stringify(value));
}
}
catch (error) {
if (typeof window !== 'undefined') {
console.error(error);
}
}
}, [value, key]);
return state;
}
//# sourceMappingURL=useLocalStorage.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.js","sourceRoot":"","sources":["../../src/useLocalStorage.ts"],"names":[],"mappings":";;AAEA,0CAmCC;AArCD,iCAAwF;AAExF,SAAgB,eAAe,CAAI,GAAW,EAAE,YAAe;IAC3D,MAAM,KAAK,GAAG,IAAA,gBAAQ,EAAI,GAAG,EAAE;QAC3B,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,KAAK;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACL,CAAC;QAED,OAAO,YAAY,CAAC;IACxB,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEvB,MAAM,gBAAgB,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC;IACtC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC3B,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;YACjC,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACjB,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACJ,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACrD,CAAC;QACL,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAEjB,OAAO,KAAK,CAAC;AACjB,CAAC"}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useLocalStorage = void 0;
const react_1 = require("react");
const useLocalStorage = function useLocalStorage(_key, defaultState) {
/**
* Until such time as we have a strategy for implementing wallet
* memorization on React Native, simply punt and return a no-op.
*/
return (0, react_1.useState)(defaultState);
};
exports.useLocalStorage = useLocalStorage;
//# sourceMappingURL=useLocalStorage.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.native.js","sourceRoot":"","sources":["../../src/useLocalStorage.native.ts"],"names":[],"mappings":";;;AAAA,iCAAiC;AAG1B,MAAM,eAAe,GAA+B,SAAS,eAAe,CAC/E,IAAY,EACZ,YAAe;IAEf;;;OAGG;IACH,OAAO,IAAA,gBAAQ,EAAC,YAAY,CAAC,CAAC;AAClC,CAAC,CAAC;AATW,QAAA,eAAe,mBAS1B"}

View File

@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletContext = void 0;
exports.useWallet = useWallet;
const react_1 = require("react");
const EMPTY_ARRAY = [];
const DEFAULT_CONTEXT = {
autoConnect: false,
connecting: false,
connected: false,
disconnecting: false,
select() {
logMissingProviderError('call', 'select');
},
connect() {
return Promise.reject(logMissingProviderError('call', 'connect'));
},
disconnect() {
return Promise.reject(logMissingProviderError('call', 'disconnect'));
},
sendTransaction() {
return Promise.reject(logMissingProviderError('call', 'sendTransaction'));
},
signTransaction() {
return Promise.reject(logMissingProviderError('call', 'signTransaction'));
},
signAllTransactions() {
return Promise.reject(logMissingProviderError('call', 'signAllTransactions'));
},
signMessage() {
return Promise.reject(logMissingProviderError('call', 'signMessage'));
},
signIn() {
return Promise.reject(logMissingProviderError('call', 'signIn'));
},
};
Object.defineProperty(DEFAULT_CONTEXT, 'wallets', {
get() {
logMissingProviderError('read', 'wallets');
return EMPTY_ARRAY;
},
});
Object.defineProperty(DEFAULT_CONTEXT, 'wallet', {
get() {
logMissingProviderError('read', 'wallet');
return null;
},
});
Object.defineProperty(DEFAULT_CONTEXT, 'publicKey', {
get() {
logMissingProviderError('read', 'publicKey');
return null;
},
});
function logMissingProviderError(action, property) {
const error = new Error(`You have tried to ${action} "${property}" on a WalletContext without providing one. ` +
'Make sure to render a WalletProvider as an ancestor of the component that uses WalletContext.');
console.error(error);
return error;
}
exports.WalletContext = (0, react_1.createContext)(DEFAULT_CONTEXT);
function useWallet() {
return (0, react_1.useContext)(exports.WalletContext);
}
//# sourceMappingURL=useWallet.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useWallet.js","sourceRoot":"","sources":["../../src/useWallet.ts"],"names":[],"mappings":";;;AAmGA,8BAEC;AA3FD,iCAAkD;AA2BlD,MAAM,WAAW,GAAyB,EAAE,CAAC;AAE7C,MAAM,eAAe,GAAgC;IACjD,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,KAAK;IACpB,MAAM;QACF,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,UAAU;QACN,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,eAAe;QACX,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,eAAe;QACX,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,mBAAmB;QACf,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,WAAW;QACP,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM;QACF,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;CACJ,CAAC;AACF,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,SAAS,EAAE;IAC9C,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC3C,OAAO,WAAW,CAAC;IACvB,CAAC;CACJ,CAAC,CAAC;AACH,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,QAAQ,EAAE;IAC7C,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAC,CAAC;AACH,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,WAAW,EAAE;IAChD,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAC,CAAC;AAEH,SAAS,uBAAuB,CAAC,MAAc,EAAE,QAAgB;IAC7D,MAAM,KAAK,GAAG,IAAI,KAAK,CACnB,qBAAqB,MAAM,KAAK,QAAQ,8CAA8C;QAClF,+FAA+F,CACtG,CAAC;IACF,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC;AACjB,CAAC;AAEY,QAAA,aAAa,GAAG,IAAA,qBAAa,EAAqB,eAAqC,CAAC,CAAC;AAEtG,SAAgB,SAAS;IACrB,OAAO,IAAA,kBAAU,EAAC,qBAAa,CAAC,CAAC;AACrC,CAAC"}

View File

@@ -0,0 +1,8 @@
import { Connection } from '@solana/web3.js';
import React, { useMemo } from 'react';
import { ConnectionContext } from './useConnection.js';
export const ConnectionProvider = ({ children, endpoint, config = { commitment: 'confirmed' }, }) => {
const connection = useMemo(() => new Connection(endpoint, config), [endpoint, config]);
return React.createElement(ConnectionContext.Provider, { value: { connection } }, children);
};
//# sourceMappingURL=ConnectionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAyB,MAAM,iBAAiB,CAAC;AACpE,OAAO,KAAK,EAAE,EAA2B,OAAO,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAQvD,MAAM,CAAC,MAAM,kBAAkB,GAAgC,CAAC,EAC5D,QAAQ,EACR,QAAQ,EACR,MAAM,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,GACvC,EAAE,EAAE;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAEvF,OAAO,oBAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,UAAU,EAAE,IAAG,QAAQ,CAA8B,CAAC;AACtG,CAAC,CAAC"}

View File

@@ -0,0 +1,130 @@
import { createDefaultAddressSelector, createDefaultAuthorizationResultCache, createDefaultWalletNotFoundHandler, SolanaMobileWalletAdapter, SolanaMobileWalletAdapterWalletName, } from '@solana-mobile/wallet-adapter-mobile';
import { useStandardWalletAdapters } from '@solana/wallet-standard-wallet-adapter-react';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import getEnvironment, { Environment } from './getEnvironment.js';
import getInferredClusterFromEndpoint from './getInferredClusterFromEndpoint.js';
import { useConnection } from './useConnection.js';
import { useLocalStorage } from './useLocalStorage.js';
import { WalletProviderBase } from './WalletProviderBase.js';
let _userAgent;
function getUserAgent() {
if (_userAgent === undefined) {
_userAgent = globalThis.navigator?.userAgent ?? null;
}
return _userAgent;
}
function getIsMobile(adapters) {
const userAgentString = getUserAgent();
return getEnvironment({ adapters, userAgentString }) === Environment.MOBILE_WEB;
}
function getUriForAppIdentity() {
const location = globalThis.location;
if (!location)
return;
return `${location.protocol}//${location.host}`;
}
export function WalletProvider({ children, wallets: adapters, autoConnect, localStorageKey = 'walletName', onError, }) {
const { connection } = useConnection();
const adaptersWithStandardAdapters = useStandardWalletAdapters(adapters);
const mobileWalletAdapter = useMemo(() => {
if (!getIsMobile(adaptersWithStandardAdapters)) {
return null;
}
const existingMobileWalletAdapter = adaptersWithStandardAdapters.find((adapter) => adapter.name === SolanaMobileWalletAdapterWalletName);
if (existingMobileWalletAdapter) {
return existingMobileWalletAdapter;
}
return new SolanaMobileWalletAdapter({
addressSelector: createDefaultAddressSelector(),
appIdentity: {
uri: getUriForAppIdentity(),
},
authorizationResultCache: createDefaultAuthorizationResultCache(),
cluster: getInferredClusterFromEndpoint(connection?.rpcEndpoint),
onWalletNotFound: createDefaultWalletNotFoundHandler(),
});
}, [adaptersWithStandardAdapters, connection?.rpcEndpoint]);
const adaptersWithMobileWalletAdapter = useMemo(() => {
if (mobileWalletAdapter == null || adaptersWithStandardAdapters.indexOf(mobileWalletAdapter) !== -1) {
return adaptersWithStandardAdapters;
}
return [mobileWalletAdapter, ...adaptersWithStandardAdapters];
}, [adaptersWithStandardAdapters, mobileWalletAdapter]);
const [walletName, setWalletName] = useLocalStorage(localStorageKey, null);
const adapter = useMemo(() => adaptersWithMobileWalletAdapter.find((a) => a.name === walletName) ?? null, [adaptersWithMobileWalletAdapter, walletName]);
const changeWallet = useCallback((nextWalletName) => {
if (walletName === nextWalletName)
return;
if (adapter &&
// Selecting a wallet other than the mobile wallet adapter is not
// sufficient reason to call `disconnect` on the mobile wallet adapter.
// Calling `disconnect` on the mobile wallet adapter causes the entire
// authorization store to be wiped.
adapter.name !== SolanaMobileWalletAdapterWalletName) {
adapter.disconnect();
}
setWalletName(nextWalletName);
}, [adapter, setWalletName, walletName]);
useEffect(() => {
if (!adapter)
return;
function handleDisconnect() {
if (isUnloadingRef.current)
return;
setWalletName(null);
}
adapter.on('disconnect', handleDisconnect);
return () => {
adapter.off('disconnect', handleDisconnect);
};
}, [adapter, adaptersWithStandardAdapters, setWalletName, walletName]);
const hasUserSelectedAWallet = useRef(false);
const handleAutoConnectRequest = useMemo(() => {
if (!autoConnect || !adapter)
return;
return async () => {
// If autoConnect is true or returns true, use the default autoConnect behavior.
if (autoConnect === true || (await autoConnect(adapter))) {
if (hasUserSelectedAWallet.current) {
await adapter.connect();
}
else {
await adapter.autoConnect();
}
}
};
}, [autoConnect, adapter]);
const isUnloadingRef = useRef(false);
useEffect(() => {
if (walletName === SolanaMobileWalletAdapterWalletName && getIsMobile(adaptersWithStandardAdapters)) {
isUnloadingRef.current = false;
return;
}
function handleBeforeUnload() {
isUnloadingRef.current = true;
}
/**
* Some wallets fire disconnection events when the window unloads. Since there's no way to
* distinguish between a disconnection event received because a user initiated it, and one
* that was received because they've closed the window, we have to track window unload
* events themselves. Downstream components use this information to decide whether to act
* upon or drop wallet events and errors.
*/
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [adaptersWithStandardAdapters, walletName]);
const handleConnectError = useCallback(() => {
if (adapter) {
// If any error happens while connecting, unset the adapter.
changeWallet(null);
}
}, [adapter, changeWallet]);
const selectWallet = useCallback((walletName) => {
hasUserSelectedAWallet.current = true;
changeWallet(walletName);
}, [changeWallet]);
return (React.createElement(WalletProviderBase, { wallets: adaptersWithMobileWalletAdapter, adapter: adapter, isUnloadingRef: isUnloadingRef, onAutoConnectRequest: handleAutoConnectRequest, onConnectError: handleConnectError, onError: onError, onSelectWallet: selectWallet }, children));
}
//# sourceMappingURL=WalletProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WalletProvider.js","sourceRoot":"","sources":["../../src/WalletProvider.tsx"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,239 @@
import { WalletNotConnectedError, WalletNotReadyError, WalletReadyState, } from '@solana/wallet-adapter-base';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { WalletNotSelectedError } from './errors.js';
import { WalletContext } from './useWallet.js';
export function WalletProviderBase({ children, wallets: adapters, adapter, isUnloadingRef, onAutoConnectRequest, onConnectError, onError, onSelectWallet, }) {
const isConnectingRef = useRef(false);
const [connecting, setConnecting] = useState(false);
const isDisconnectingRef = useRef(false);
const [disconnecting, setDisconnecting] = useState(false);
const [publicKey, setPublicKey] = useState(() => adapter?.publicKey ?? null);
const [connected, setConnected] = useState(() => adapter?.connected ?? false);
/**
* Store the error handlers as refs so that a change in the
* custom error handler does not recompute other dependencies.
*/
const onErrorRef = useRef(onError);
useEffect(() => {
onErrorRef.current = onError;
return () => {
onErrorRef.current = undefined;
};
}, [onError]);
const handleErrorRef = useRef((error, adapter) => {
if (!isUnloadingRef.current) {
if (onErrorRef.current) {
onErrorRef.current(error, adapter);
}
else {
console.error(error, adapter);
if (error instanceof WalletNotReadyError && typeof window !== 'undefined' && adapter) {
window.open(adapter.url, '_blank');
}
}
}
return error;
});
// Wrap adapters to conform to the `Wallet` interface
const [wallets, setWallets] = useState(() => adapters
.map((adapter) => ({
adapter,
readyState: adapter.readyState,
}))
.filter(({ readyState }) => readyState !== WalletReadyState.Unsupported));
// When the adapters change, start to listen for changes to their `readyState`
useEffect(() => {
// When the adapters change, wrap them to conform to the `Wallet` interface
setWallets((wallets) => adapters
.map((adapter, index) => {
const wallet = wallets[index];
// If the wallet hasn't changed, return the same instance
return wallet && wallet.adapter === adapter && wallet.readyState === adapter.readyState
? wallet
: {
adapter: adapter,
readyState: adapter.readyState,
};
})
.filter(({ readyState }) => readyState !== WalletReadyState.Unsupported));
function handleReadyStateChange(readyState) {
setWallets((prevWallets) => {
const index = prevWallets.findIndex(({ adapter }) => adapter === this);
if (index === -1)
return prevWallets;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { adapter } = prevWallets[index];
return [
...prevWallets.slice(0, index),
{ adapter, readyState },
...prevWallets.slice(index + 1),
].filter(({ readyState }) => readyState !== WalletReadyState.Unsupported);
});
}
adapters.forEach((adapter) => adapter.on('readyStateChange', handleReadyStateChange, adapter));
return () => {
adapters.forEach((adapter) => adapter.off('readyStateChange', handleReadyStateChange, adapter));
};
}, [adapter, adapters]);
const wallet = useMemo(() => wallets.find((wallet) => wallet.adapter === adapter) ?? null, [adapter, wallets]);
// Setup and teardown event listeners when the adapter changes
useEffect(() => {
if (!adapter)
return;
const handleConnect = (publicKey) => {
setPublicKey(publicKey);
isConnectingRef.current = false;
setConnecting(false);
setConnected(true);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleDisconnect = () => {
if (isUnloadingRef.current)
return;
setPublicKey(null);
isConnectingRef.current = false;
setConnecting(false);
setConnected(false);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleError = (error) => {
handleErrorRef.current(error, adapter);
};
adapter.on('connect', handleConnect);
adapter.on('disconnect', handleDisconnect);
adapter.on('error', handleError);
return () => {
adapter.off('connect', handleConnect);
adapter.off('disconnect', handleDisconnect);
adapter.off('error', handleError);
handleDisconnect();
};
}, [adapter, isUnloadingRef]);
// When the adapter changes, clear the `autoConnect` tracking flag
const didAttemptAutoConnectRef = useRef(false);
useEffect(() => {
return () => {
didAttemptAutoConnectRef.current = false;
};
}, [adapter]);
// If auto-connect is enabled, request to connect when the adapter changes and is ready
useEffect(() => {
if (didAttemptAutoConnectRef.current ||
isConnectingRef.current ||
connected ||
!onAutoConnectRequest ||
!(wallet?.readyState === WalletReadyState.Installed || wallet?.readyState === WalletReadyState.Loadable))
return;
isConnectingRef.current = true;
setConnecting(true);
didAttemptAutoConnectRef.current = true;
(async function () {
try {
await onAutoConnectRequest();
}
catch {
onConnectError();
// Drop the error. It will be caught by `handleError` anyway.
}
finally {
setConnecting(false);
isConnectingRef.current = false;
}
})();
}, [connected, onAutoConnectRequest, onConnectError, wallet]);
// Send a transaction using the provided connection
const sendTransaction = useCallback(async (transaction, connection, options) => {
if (!adapter)
throw handleErrorRef.current(new WalletNotSelectedError());
if (!connected)
throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.sendTransaction(transaction, connection, options);
}, [adapter, connected]);
// Sign a transaction if the wallet supports it
const signTransaction = useMemo(() => adapter && 'signTransaction' in adapter
? async (transaction) => {
if (!connected)
throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signTransaction(transaction);
}
: undefined, [adapter, connected]);
// Sign multiple transactions if the wallet supports it
const signAllTransactions = useMemo(() => adapter && 'signAllTransactions' in adapter
? async (transactions) => {
if (!connected)
throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signAllTransactions(transactions);
}
: undefined, [adapter, connected]);
// Sign an arbitrary message if the wallet supports it
const signMessage = useMemo(() => adapter && 'signMessage' in adapter
? async (message) => {
if (!connected)
throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signMessage(message);
}
: undefined, [adapter, connected]);
// Sign in if the wallet supports it
const signIn = useMemo(() => adapter && 'signIn' in adapter
? async (input) => {
return await adapter.signIn(input);
}
: undefined, [adapter]);
const handleConnect = useCallback(async () => {
if (isConnectingRef.current || isDisconnectingRef.current || wallet?.adapter.connected)
return;
if (!wallet)
throw handleErrorRef.current(new WalletNotSelectedError());
const { adapter, readyState } = wallet;
if (!(readyState === WalletReadyState.Installed || readyState === WalletReadyState.Loadable))
throw handleErrorRef.current(new WalletNotReadyError(), adapter);
isConnectingRef.current = true;
setConnecting(true);
try {
await adapter.connect();
}
catch (e) {
onConnectError();
throw e;
}
finally {
setConnecting(false);
isConnectingRef.current = false;
}
}, [onConnectError, wallet]);
const handleDisconnect = useCallback(async () => {
if (isDisconnectingRef.current)
return;
if (!adapter)
return;
isDisconnectingRef.current = true;
setDisconnecting(true);
try {
await adapter.disconnect();
}
finally {
setDisconnecting(false);
isDisconnectingRef.current = false;
}
}, [adapter]);
return (React.createElement(WalletContext.Provider, { value: {
autoConnect: !!onAutoConnectRequest,
wallets,
wallet,
publicKey,
connected,
connecting,
disconnecting,
select: onSelectWallet,
connect: handleConnect,
disconnect: handleDisconnect,
sendTransaction,
signTransaction,
signAllTransactions,
signMessage,
signIn,
} }, children));
}
//# sourceMappingURL=WalletProviderBase.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
export default function defineProperty(obj, prop, val) {
Object.defineProperty(obj, prop, val);
}
//# sourceMappingURL=defineProperty.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"defineProperty.js","sourceRoot":"","sources":["../../src/defineProperty.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,OAAO,UAAU,cAAc,CAClC,GAAQ,EACR,IAAS,EACT,GAAU;IAEV,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC"}

View File

@@ -0,0 +1,8 @@
import { WalletError } from '@solana/wallet-adapter-base';
export class WalletNotSelectedError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletNotSelectedError';
}
}
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,MAAM,OAAO,sBAAuB,SAAQ,WAAW;IAAvD;;QACI,SAAI,GAAG,wBAAwB,CAAC;IACpC,CAAC;CAAA"}

View File

@@ -0,0 +1,35 @@
import { SolanaMobileWalletAdapterWalletName } from '@solana-mobile/wallet-adapter-mobile';
import { WalletReadyState } from '@solana/wallet-adapter-base';
export var Environment;
(function (Environment) {
Environment[Environment["DESKTOP_WEB"] = 0] = "DESKTOP_WEB";
Environment[Environment["MOBILE_WEB"] = 1] = "MOBILE_WEB";
})(Environment || (Environment = {}));
function isWebView(userAgentString) {
return /(WebView|Version\/.+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+)|; wv\).+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+))/i.test(userAgentString);
}
export default function getEnvironment({ adapters, userAgentString }) {
if (adapters.some((adapter) => adapter.name !== SolanaMobileWalletAdapterWalletName &&
adapter.readyState === WalletReadyState.Installed)) {
/**
* There are only two ways a browser extension adapter should be able to reach `Installed` status:
*
* 1. Its browser extension is installed.
* 2. The app is running on a mobile wallet's in-app browser.
*
* In either case, we consider the environment to be desktop-like.
*/
return Environment.DESKTOP_WEB;
}
if (userAgentString &&
// Step 1: Check whether we're on a platform that supports MWA at all.
/android/i.test(userAgentString) &&
// Step 2: Determine that we are *not* running in a WebView.
!isWebView(userAgentString)) {
return Environment.MOBILE_WEB;
}
else {
return Environment.DESKTOP_WEB;
}
}
//# sourceMappingURL=getEnvironment.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getEnvironment.js","sourceRoot":"","sources":["../../src/getEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,sCAAsC,CAAC;AAC3F,OAAO,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE7E,MAAM,CAAN,IAAY,WAGX;AAHD,WAAY,WAAW;IACnB,2DAAW,CAAA;IACX,yDAAU,CAAA;AACd,CAAC,EAHW,WAAW,KAAX,WAAW,QAGtB;AAOD,SAAS,SAAS,CAAC,eAAuB;IACtC,OAAO,yGAAyG,CAAC,IAAI,CACjH,eAAe,CAClB,CAAC;AACN,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAU;IACxE,IACI,QAAQ,CAAC,IAAI,CACT,CAAC,OAAO,EAAE,EAAE,CACR,OAAO,CAAC,IAAI,KAAK,mCAAmC;QACpD,OAAO,CAAC,UAAU,KAAK,gBAAgB,CAAC,SAAS,CACxD,EACH,CAAC;QACC;;;;;;;WAOG;QACH,OAAO,WAAW,CAAC,WAAW,CAAC;IACnC,CAAC;IACD,IACI,eAAe;QACf,sEAAsE;QACtE,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC;QAChC,4DAA4D;QAC5D,CAAC,SAAS,CAAC,eAAe,CAAC,EAC7B,CAAC;QACC,OAAO,WAAW,CAAC,UAAU,CAAC;IAClC,CAAC;SAAM,CAAC;QACJ,OAAO,WAAW,CAAC,WAAW,CAAC;IACnC,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,15 @@
export default function getInferredClusterFromEndpoint(endpoint) {
if (!endpoint) {
return 'mainnet-beta';
}
if (/devnet/i.test(endpoint)) {
return 'devnet';
}
else if (/testnet/i.test(endpoint)) {
return 'testnet';
}
else {
return 'mainnet-beta';
}
}
//# sourceMappingURL=getInferredClusterFromEndpoint.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInferredClusterFromEndpoint.js","sourceRoot":"","sources":["../../src/getInferredClusterFromEndpoint.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,8BAA8B,CAAC,QAAiB;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,OAAO,cAAc,CAAC;IAC1B,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,QAAQ,CAAC;IACpB,CAAC;SAAM,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,SAAS,CAAC;IACrB,CAAC;SAAM,CAAC;QACJ,OAAO,cAAc,CAAC;IAC1B,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,8 @@
export * from './ConnectionProvider.js';
export * from './errors.js';
export * from './useAnchorWallet.js';
export * from './useConnection.js';
export * from './useLocalStorage.js';
export * from './useWallet.js';
export * from './WalletProvider.js';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}

View File

@@ -0,0 +1 @@
{"root":["../../src/ConnectionProvider.tsx","../../src/WalletProvider.tsx","../../src/WalletProviderBase.tsx","../../src/defineProperty.ts","../../src/errors.ts","../../src/getEnvironment.ts","../../src/getInferredClusterFromEndpoint.ts","../../src/index.ts","../../src/useAnchorWallet.ts","../../src/useConnection.ts","../../src/useLocalStorage.native.ts","../../src/useLocalStorage.ts","../../src/useWallet.ts"],"version":"5.8.3"}

View File

@@ -0,0 +1,9 @@
import { useMemo } from 'react';
import { useWallet } from './useWallet.js';
export function useAnchorWallet() {
const { publicKey, signTransaction, signAllTransactions } = useWallet();
return useMemo(() => publicKey && signTransaction && signAllTransactions
? { publicKey, signTransaction, signAllTransactions }
: undefined, [publicKey, signTransaction, signAllTransactions]);
}
//# sourceMappingURL=useAnchorWallet.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useAnchorWallet.js","sourceRoot":"","sources":["../../src/useAnchorWallet.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,MAAM,UAAU,eAAe;IAC3B,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE,GAAG,SAAS,EAAE,CAAC;IACxE,OAAO,OAAO,CACV,GAAG,EAAE,CACD,SAAS,IAAI,eAAe,IAAI,mBAAmB;QAC/C,CAAC,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE;QACrD,CAAC,CAAC,SAAS,EACnB,CAAC,SAAS,EAAE,eAAe,EAAE,mBAAmB,CAAC,CACpD,CAAC;AACN,CAAC"}

View File

@@ -0,0 +1,6 @@
import { createContext, useContext } from 'react';
export const ConnectionContext = createContext({});
export function useConnection() {
return useContext(ConnectionContext);
}
//# sourceMappingURL=useConnection.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useConnection.js","sourceRoot":"","sources":["../../src/useConnection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAMlD,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAyB,EAA4B,CAAC,CAAC;AAErG,MAAM,UAAU,aAAa;IACzB,OAAO,UAAU,CAAC,iBAAiB,CAAC,CAAC;AACzC,CAAC"}

View File

@@ -0,0 +1,39 @@
import { useEffect, useRef, useState } from 'react';
export function useLocalStorage(key, defaultState) {
const state = useState(() => {
try {
const value = localStorage.getItem(key);
if (value)
return JSON.parse(value);
}
catch (error) {
if (typeof window !== 'undefined') {
console.error(error);
}
}
return defaultState;
});
const value = state[0];
const isFirstRenderRef = useRef(true);
useEffect(() => {
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
return;
}
try {
if (value === null) {
localStorage.removeItem(key);
}
else {
localStorage.setItem(key, JSON.stringify(value));
}
}
catch (error) {
if (typeof window !== 'undefined') {
console.error(error);
}
}
}, [value, key]);
return state;
}
//# sourceMappingURL=useLocalStorage.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.js","sourceRoot":"","sources":["../../src/useLocalStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAExF,MAAM,UAAU,eAAe,CAAI,GAAW,EAAE,YAAe;IAC3D,MAAM,KAAK,GAAG,QAAQ,CAAI,GAAG,EAAE;QAC3B,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,KAAK;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACL,CAAC;QAED,OAAO,YAAY,CAAC;IACxB,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEvB,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC3B,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;YACjC,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACjB,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACJ,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACrD,CAAC;QACL,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAEjB,OAAO,KAAK,CAAC;AACjB,CAAC"}

View File

@@ -0,0 +1,9 @@
import { useState } from 'react';
export const useLocalStorage = function useLocalStorage(_key, defaultState) {
/**
* Until such time as we have a strategy for implementing wallet
* memorization on React Native, simply punt and return a no-op.
*/
return useState(defaultState);
};
//# sourceMappingURL=useLocalStorage.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.native.js","sourceRoot":"","sources":["../../src/useLocalStorage.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjC,MAAM,CAAC,MAAM,eAAe,GAA+B,SAAS,eAAe,CAC/E,IAAY,EACZ,YAAe;IAEf;;;OAGG;IACH,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC;AAClC,CAAC,CAAC"}

View File

@@ -0,0 +1,61 @@
import { createContext, useContext } from 'react';
const EMPTY_ARRAY = [];
const DEFAULT_CONTEXT = {
autoConnect: false,
connecting: false,
connected: false,
disconnecting: false,
select() {
logMissingProviderError('call', 'select');
},
connect() {
return Promise.reject(logMissingProviderError('call', 'connect'));
},
disconnect() {
return Promise.reject(logMissingProviderError('call', 'disconnect'));
},
sendTransaction() {
return Promise.reject(logMissingProviderError('call', 'sendTransaction'));
},
signTransaction() {
return Promise.reject(logMissingProviderError('call', 'signTransaction'));
},
signAllTransactions() {
return Promise.reject(logMissingProviderError('call', 'signAllTransactions'));
},
signMessage() {
return Promise.reject(logMissingProviderError('call', 'signMessage'));
},
signIn() {
return Promise.reject(logMissingProviderError('call', 'signIn'));
},
};
Object.defineProperty(DEFAULT_CONTEXT, 'wallets', {
get() {
logMissingProviderError('read', 'wallets');
return EMPTY_ARRAY;
},
});
Object.defineProperty(DEFAULT_CONTEXT, 'wallet', {
get() {
logMissingProviderError('read', 'wallet');
return null;
},
});
Object.defineProperty(DEFAULT_CONTEXT, 'publicKey', {
get() {
logMissingProviderError('read', 'publicKey');
return null;
},
});
function logMissingProviderError(action, property) {
const error = new Error(`You have tried to ${action} "${property}" on a WalletContext without providing one. ` +
'Make sure to render a WalletProvider as an ancestor of the component that uses WalletContext.');
console.error(error);
return error;
}
export const WalletContext = createContext(DEFAULT_CONTEXT);
export function useWallet() {
return useContext(WalletContext);
}
//# sourceMappingURL=useWallet.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useWallet.js","sourceRoot":"","sources":["../../src/useWallet.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA2BlD,MAAM,WAAW,GAAyB,EAAE,CAAC;AAE7C,MAAM,eAAe,GAAgC;IACjD,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,KAAK;IACpB,MAAM;QACF,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,UAAU;QACN,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,eAAe;QACX,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,eAAe;QACX,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,mBAAmB;QACf,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,WAAW;QACP,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM;QACF,OAAO,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;CACJ,CAAC;AACF,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,SAAS,EAAE;IAC9C,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC3C,OAAO,WAAW,CAAC;IACvB,CAAC;CACJ,CAAC,CAAC;AACH,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,QAAQ,EAAE;IAC7C,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAC,CAAC;AACH,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,WAAW,EAAE;IAChD,GAAG;QACC,uBAAuB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAC,CAAC;AAEH,SAAS,uBAAuB,CAAC,MAAc,EAAE,QAAgB;IAC7D,MAAM,KAAK,GAAG,IAAI,KAAK,CACnB,qBAAqB,MAAM,KAAK,QAAQ,8CAA8C;QAClF,+FAA+F,CACtG,CAAC;IACF,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAqB,eAAqC,CAAC,CAAC;AAEtG,MAAM,UAAU,SAAS;IACrB,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;AACrC,CAAC"}

View File

@@ -0,0 +1,9 @@
import { type ConnectionConfig } from '@solana/web3.js';
import { type FC, type ReactNode } from 'react';
export interface ConnectionProviderProps {
children: ReactNode;
endpoint: string;
config?: ConnectionConfig;
}
export declare const ConnectionProvider: FC<ConnectionProviderProps>;
//# sourceMappingURL=ConnectionProvider.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ConnectionProvider.d.ts","sourceRoot":"","sources":["../../src/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAc,EAAE,KAAK,EAAE,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAGhE,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CAQ1D,CAAC"}

View File

@@ -0,0 +1,11 @@
import { type Adapter, type WalletError } from '@solana/wallet-adapter-base';
import React, { type ReactNode } from 'react';
export interface WalletProviderProps {
children: ReactNode;
wallets: Adapter[];
autoConnect?: boolean | ((adapter: Adapter) => Promise<boolean>);
localStorageKey?: string;
onError?: (error: WalletError, adapter?: Adapter) => void;
}
export declare function WalletProvider({ children, wallets: adapters, autoConnect, localStorageKey, onError, }: WalletProviderProps): React.JSX.Element;
//# sourceMappingURL=WalletProvider.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WalletProvider.d.ts","sourceRoot":"","sources":["../../src/WalletProvider.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,6BAA6B,CAAC;AAE9F,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAA2C,MAAM,OAAO,CAAC;AAOvF,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7D;AAqBD,wBAAgB,cAAc,CAAC,EAC3B,QAAQ,EACR,OAAO,EAAE,QAAQ,EACjB,WAAW,EACX,eAA8B,EAC9B,OAAO,GACV,EAAE,mBAAmB,qBA2HrB"}

View File

@@ -0,0 +1,14 @@
import { type Adapter, type WalletError, type WalletName } from '@solana/wallet-adapter-base';
import React, { type ReactNode } from 'react';
export interface WalletProviderBaseProps {
children: ReactNode;
wallets: Adapter[];
adapter: Adapter | null;
isUnloadingRef: React.RefObject<boolean>;
onAutoConnectRequest?: () => Promise<void>;
onConnectError: () => void;
onError?: (error: WalletError, adapter?: Adapter) => void;
onSelectWallet: (walletName: WalletName | null) => void;
}
export declare function WalletProviderBase({ children, wallets: adapters, adapter, isUnloadingRef, onAutoConnectRequest, onConnectError, onError, onSelectWallet, }: WalletProviderBaseProps): React.JSX.Element;
//# sourceMappingURL=WalletProviderBase.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WalletProviderBase.d.ts","sourceRoot":"","sources":["../../src/WalletProviderBase.tsx"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,OAAO,EAKZ,KAAK,WAAW,EAChB,KAAK,UAAU,EAIlB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAIjG,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAEzC,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1D,cAAc,EAAE,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,QAAQ,EACR,OAAO,EAAE,QAAQ,EACjB,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,OAAO,EACP,cAAc,GACjB,EAAE,uBAAuB,qBA8QzB"}

View File

@@ -0,0 +1,22 @@
type InferValue<Prop extends PropertyKey, Desc> = Desc extends {
get(): any;
value: any;
} ? never : Desc extends {
value: infer T;
} ? Record<Prop, T> : Desc extends {
get(): infer T;
} ? Record<Prop, T> : never;
type DefineProperty<Prop extends PropertyKey, Desc extends PropertyDescriptor> = Desc extends {
writable: any;
set(val: any): any;
} ? never : Desc extends {
writable: any;
get(): any;
} ? never : Desc extends {
writable: false;
} ? Readonly<InferValue<Prop, Desc>> : Desc extends {
writable: true;
} ? InferValue<Prop, Desc> : Readonly<InferValue<Prop, Desc>>;
export default function defineProperty<Obj extends object, Key extends PropertyKey, PDesc extends PropertyDescriptor>(obj: Obj, prop: Key, val: PDesc): asserts obj is Obj & DefineProperty<Key, PDesc>;
export {};
//# sourceMappingURL=defineProperty.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"defineProperty.d.ts","sourceRoot":"","sources":["../../src/defineProperty.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,CAAC,IAAI,SAAS,WAAW,EAAE,IAAI,IAAI,IAAI,SAAS;IAAE,GAAG,IAAI,GAAG,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,GACnF,KAAK,GACL,IAAI,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC,CAAA;CAAE,GAC7B,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,GACf,IAAI,SAAS;IAAE,GAAG,IAAI,MAAM,CAAC,CAAA;CAAE,GAC7B,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,GACf,KAAK,CAAC;AAEhB,KAAK,cAAc,CAAC,IAAI,SAAS,WAAW,EAAE,IAAI,SAAS,kBAAkB,IAAI,IAAI,SAAS;IAC1F,QAAQ,EAAE,GAAG,CAAC;IACd,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC;CACtB,GACK,KAAK,GACL,IAAI,SAAS;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,GAAG,IAAI,GAAG,CAAA;CAAE,GACxC,KAAK,GACL,IAAI,SAAS;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAC9B,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAChC,IAAI,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC7B,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,GACtB,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE7C,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,WAAW,EAAE,KAAK,SAAS,kBAAkB,EAChH,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,GAAG,EACT,GAAG,EAAE,KAAK,GACX,OAAO,CAAC,GAAG,IAAI,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAEjD"}

View File

@@ -0,0 +1,5 @@
import { WalletError } from '@solana/wallet-adapter-base';
export declare class WalletNotSelectedError extends WalletError {
name: string;
}
//# sourceMappingURL=errors.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,qBAAa,sBAAuB,SAAQ,WAAW;IACnD,IAAI,SAA4B;CACnC"}

View File

@@ -0,0 +1,12 @@
import { type Adapter } from '@solana/wallet-adapter-base';
export declare enum Environment {
DESKTOP_WEB = 0,
MOBILE_WEB = 1
}
type Config = Readonly<{
adapters: Adapter[];
userAgentString: string | null;
}>;
export default function getEnvironment({ adapters, userAgentString }: Config): Environment;
export {};
//# sourceMappingURL=getEnvironment.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getEnvironment.d.ts","sourceRoot":"","sources":["../../src/getEnvironment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAoB,MAAM,6BAA6B,CAAC;AAE7E,oBAAY,WAAW;IACnB,WAAW,IAAA;IACX,UAAU,IAAA;CACb;AAED,KAAK,MAAM,GAAG,QAAQ,CAAC;IACnB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC,CAAC;AAQH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,MAAM,GAAG,WAAW,CA6BzF"}

View File

@@ -0,0 +1,3 @@
import { type Cluster } from '@solana/web3.js';
export default function getInferredClusterFromEndpoint(endpoint?: string): Cluster;
//# sourceMappingURL=getInferredClusterFromEndpoint.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInferredClusterFromEndpoint.d.ts","sourceRoot":"","sources":["../../src/getInferredClusterFromEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,CAAC,OAAO,UAAU,8BAA8B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAWjF"}

View File

@@ -0,0 +1,8 @@
export * from './ConnectionProvider.js';
export * from './errors.js';
export * from './useAnchorWallet.js';
export * from './useConnection.js';
export * from './useLocalStorage.js';
export * from './useWallet.js';
export * from './WalletProvider.js';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}

View File

@@ -0,0 +1,8 @@
import { type PublicKey, type Transaction, type VersionedTransaction } from '@solana/web3.js';
export interface AnchorWallet {
publicKey: PublicKey;
signTransaction<T extends Transaction | VersionedTransaction>(transaction: T): Promise<T>;
signAllTransactions<T extends Transaction | VersionedTransaction>(transactions: T[]): Promise<T[]>;
}
export declare function useAnchorWallet(): AnchorWallet | undefined;
//# sourceMappingURL=useAnchorWallet.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useAnchorWallet.d.ts","sourceRoot":"","sources":["../../src/useAnchorWallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAI9F,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,oBAAoB,EAAE,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1F,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,oBAAoB,EAAE,YAAY,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACtG;AAED,wBAAgB,eAAe,IAAI,YAAY,GAAG,SAAS,CAS1D"}

View File

@@ -0,0 +1,7 @@
import { type Connection } from '@solana/web3.js';
export interface ConnectionContextState {
connection: Connection;
}
export declare const ConnectionContext: import("react").Context<ConnectionContextState>;
export declare function useConnection(): ConnectionContextState;
//# sourceMappingURL=useConnection.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useConnection.d.ts","sourceRoot":"","sources":["../../src/useConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,MAAM,WAAW,sBAAsB;IACnC,UAAU,EAAE,UAAU,CAAC;CAC1B;AAED,eAAO,MAAM,iBAAiB,iDAAsE,CAAC;AAErG,wBAAgB,aAAa,IAAI,sBAAsB,CAEtD"}

View File

@@ -0,0 +1,3 @@
import { type Dispatch, type SetStateAction } from 'react';
export declare function useLocalStorage<T>(key: string, defaultState: T): [T, Dispatch<SetStateAction<T>>];
//# sourceMappingURL=useLocalStorage.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.d.ts","sourceRoot":"","sources":["../../src/useLocalStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAA+B,MAAM,OAAO,CAAC;AAExF,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAmCjG"}

View File

@@ -0,0 +1,3 @@
import { type useLocalStorage as baseUseLocalStorage } from './useLocalStorage.js';
export declare const useLocalStorage: typeof baseUseLocalStorage;
//# sourceMappingURL=useLocalStorage.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useLocalStorage.native.d.ts","sourceRoot":"","sources":["../../src/useLocalStorage.native.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,IAAI,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEnF,eAAO,MAAM,eAAe,EAAE,OAAO,mBASpC,CAAC"}

View File

@@ -0,0 +1,26 @@
import { type Adapter, type MessageSignerWalletAdapterProps, type SignerWalletAdapterProps, type SignInMessageSignerWalletAdapterProps, type WalletAdapterProps, type WalletName, type WalletReadyState } from '@solana/wallet-adapter-base';
import { type PublicKey } from '@solana/web3.js';
export interface Wallet {
adapter: Adapter;
readyState: WalletReadyState;
}
export interface WalletContextState {
autoConnect: boolean;
wallets: Wallet[];
wallet: Wallet | null;
publicKey: PublicKey | null;
connecting: boolean;
connected: boolean;
disconnecting: boolean;
select(walletName: WalletName | null): void;
connect(): Promise<void>;
disconnect(): Promise<void>;
sendTransaction: WalletAdapterProps['sendTransaction'];
signTransaction: SignerWalletAdapterProps['signTransaction'] | undefined;
signAllTransactions: SignerWalletAdapterProps['signAllTransactions'] | undefined;
signMessage: MessageSignerWalletAdapterProps['signMessage'] | undefined;
signIn: SignInMessageSignerWalletAdapterProps['signIn'] | undefined;
}
export declare const WalletContext: import("react").Context<WalletContextState>;
export declare function useWallet(): WalletContextState;
//# sourceMappingURL=useWallet.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useWallet.d.ts","sourceRoot":"","sources":["../../src/useWallet.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,KAAK,qCAAqC,EAC1C,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACxB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,MAAM;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IAEvB,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B,eAAe,EAAE,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;IACvD,eAAe,EAAE,wBAAwB,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAAC;IACzE,mBAAmB,EAAE,wBAAwB,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAC;IACjF,WAAW,EAAE,+BAA+B,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC;IACxE,MAAM,EAAE,qCAAqC,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;CACvE;AA8DD,eAAO,MAAM,aAAa,6CAA2E,CAAC;AAEtG,wBAAgB,SAAS,IAAI,kBAAkB,CAE9C"}

57
node_modules/@solana/wallet-adapter-react/package.json generated vendored Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "@solana/wallet-adapter-react",
"version": "0.15.39",
"author": "Solana Maintainers <maintainers@solana.foundation>",
"repository": "https://github.com/anza-xyz/wallet-adapter",
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"files": [
"lib",
"src",
"LICENSE"
],
"engines": {
"node": ">=20"
},
"type": "module",
"sideEffects": false,
"main": "./lib/cjs/index.js",
"module": "./lib/esm/index.js",
"types": "./lib/types/index.d.ts",
"exports": {
"require": "./lib/cjs/index.js",
"import": "./lib/esm/index.js",
"types": "./lib/types/index.d.ts"
},
"peerDependencies": {
"@solana/web3.js": "^1.98.0",
"react": "*"
},
"dependencies": {
"@solana-mobile/wallet-adapter-mobile": "^2.2.0",
"@solana/wallet-standard-wallet-adapter-react": "^1.1.4",
"@solana/wallet-adapter-base": "^0.9.27"
},
"devDependencies": {
"@solana/web3.js": "^1.98.2",
"@types/jest": "^29.5.14",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.5",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-localstorage-mock": "^2.4.26",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"shx": "^0.4.0",
"ts-jest": "^29.3.4"
},
"scripts": {
"build": "tsc --build --verbose && pnpm run package",
"clean": "shx mkdir -p lib && shx rm -rf lib",
"lint": "prettier --check 'src/{*,**/*}.{ts,tsx,js,jsx,json}' && eslint",
"package": "shx mkdir -p lib/cjs && shx echo '{ \"type\": \"commonjs\" }' > lib/cjs/package.json",
"test": "jest"
}
}

View File

@@ -0,0 +1,19 @@
import { Connection, type ConnectionConfig } from '@solana/web3.js';
import React, { type FC, type ReactNode, useMemo } from 'react';
import { ConnectionContext } from './useConnection.js';
export interface ConnectionProviderProps {
children: ReactNode;
endpoint: string;
config?: ConnectionConfig;
}
export const ConnectionProvider: FC<ConnectionProviderProps> = ({
children,
endpoint,
config = { commitment: 'confirmed' },
}) => {
const connection = useMemo(() => new Connection(endpoint, config), [endpoint, config]);
return <ConnectionContext.Provider value={{ connection }}>{children}</ConnectionContext.Provider>;
};

View File

@@ -0,0 +1,173 @@
import {
createDefaultAddressSelector,
createDefaultAuthorizationResultCache,
createDefaultWalletNotFoundHandler,
SolanaMobileWalletAdapter,
SolanaMobileWalletAdapterWalletName,
} from '@solana-mobile/wallet-adapter-mobile';
import { type Adapter, type WalletError, type WalletName } from '@solana/wallet-adapter-base';
import { useStandardWalletAdapters } from '@solana/wallet-standard-wallet-adapter-react';
import React, { type ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
import getEnvironment, { Environment } from './getEnvironment.js';
import getInferredClusterFromEndpoint from './getInferredClusterFromEndpoint.js';
import { useConnection } from './useConnection.js';
import { useLocalStorage } from './useLocalStorage.js';
import { WalletProviderBase } from './WalletProviderBase.js';
export interface WalletProviderProps {
children: ReactNode;
wallets: Adapter[];
autoConnect?: boolean | ((adapter: Adapter) => Promise<boolean>);
localStorageKey?: string;
onError?: (error: WalletError, adapter?: Adapter) => void;
}
let _userAgent: string | null;
function getUserAgent() {
if (_userAgent === undefined) {
_userAgent = globalThis.navigator?.userAgent ?? null;
}
return _userAgent;
}
function getIsMobile(adapters: Adapter[]) {
const userAgentString = getUserAgent();
return getEnvironment({ adapters, userAgentString }) === Environment.MOBILE_WEB;
}
function getUriForAppIdentity() {
const location = globalThis.location;
if (!location) return;
return `${location.protocol}//${location.host}`;
}
export function WalletProvider({
children,
wallets: adapters,
autoConnect,
localStorageKey = 'walletName',
onError,
}: WalletProviderProps) {
const { connection } = useConnection();
const adaptersWithStandardAdapters = useStandardWalletAdapters(adapters);
const mobileWalletAdapter = useMemo(() => {
if (!getIsMobile(adaptersWithStandardAdapters)) {
return null;
}
const existingMobileWalletAdapter = adaptersWithStandardAdapters.find(
(adapter) => adapter.name === SolanaMobileWalletAdapterWalletName
);
if (existingMobileWalletAdapter) {
return existingMobileWalletAdapter;
}
return new SolanaMobileWalletAdapter({
addressSelector: createDefaultAddressSelector(),
appIdentity: {
uri: getUriForAppIdentity(),
},
authorizationResultCache: createDefaultAuthorizationResultCache(),
cluster: getInferredClusterFromEndpoint(connection?.rpcEndpoint),
onWalletNotFound: createDefaultWalletNotFoundHandler(),
});
}, [adaptersWithStandardAdapters, connection?.rpcEndpoint]);
const adaptersWithMobileWalletAdapter = useMemo(() => {
if (mobileWalletAdapter == null || adaptersWithStandardAdapters.indexOf(mobileWalletAdapter) !== -1) {
return adaptersWithStandardAdapters;
}
return [mobileWalletAdapter, ...adaptersWithStandardAdapters];
}, [adaptersWithStandardAdapters, mobileWalletAdapter]);
const [walletName, setWalletName] = useLocalStorage<WalletName | null>(localStorageKey, null);
const adapter = useMemo(
() => adaptersWithMobileWalletAdapter.find((a) => a.name === walletName) ?? null,
[adaptersWithMobileWalletAdapter, walletName]
);
const changeWallet = useCallback(
(nextWalletName: WalletName<string> | null) => {
if (walletName === nextWalletName) return;
if (
adapter &&
// Selecting a wallet other than the mobile wallet adapter is not
// sufficient reason to call `disconnect` on the mobile wallet adapter.
// Calling `disconnect` on the mobile wallet adapter causes the entire
// authorization store to be wiped.
adapter.name !== SolanaMobileWalletAdapterWalletName
) {
adapter.disconnect();
}
setWalletName(nextWalletName);
},
[adapter, setWalletName, walletName]
);
useEffect(() => {
if (!adapter) return;
function handleDisconnect() {
if (isUnloadingRef.current) return;
setWalletName(null);
}
adapter.on('disconnect', handleDisconnect);
return () => {
adapter.off('disconnect', handleDisconnect);
};
}, [adapter, adaptersWithStandardAdapters, setWalletName, walletName]);
const hasUserSelectedAWallet = useRef(false);
const handleAutoConnectRequest = useMemo(() => {
if (!autoConnect || !adapter) return;
return async () => {
// If autoConnect is true or returns true, use the default autoConnect behavior.
if (autoConnect === true || (await autoConnect(adapter))) {
if (hasUserSelectedAWallet.current) {
await adapter.connect();
} else {
await adapter.autoConnect();
}
}
};
}, [autoConnect, adapter]);
const isUnloadingRef = useRef(false);
useEffect(() => {
if (walletName === SolanaMobileWalletAdapterWalletName && getIsMobile(adaptersWithStandardAdapters)) {
isUnloadingRef.current = false;
return;
}
function handleBeforeUnload() {
isUnloadingRef.current = true;
}
/**
* Some wallets fire disconnection events when the window unloads. Since there's no way to
* distinguish between a disconnection event received because a user initiated it, and one
* that was received because they've closed the window, we have to track window unload
* events themselves. Downstream components use this information to decide whether to act
* upon or drop wallet events and errors.
*/
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [adaptersWithStandardAdapters, walletName]);
const handleConnectError = useCallback(() => {
if (adapter) {
// If any error happens while connecting, unset the adapter.
changeWallet(null);
}
}, [adapter, changeWallet]);
const selectWallet = useCallback(
(walletName: WalletName | null) => {
hasUserSelectedAWallet.current = true;
changeWallet(walletName);
},
[changeWallet]
);
return (
<WalletProviderBase
wallets={adaptersWithMobileWalletAdapter}
adapter={adapter}
isUnloadingRef={isUnloadingRef}
onAutoConnectRequest={handleAutoConnectRequest}
onConnectError={handleConnectError}
onError={onError}
onSelectWallet={selectWallet}
>
{children}
</WalletProviderBase>
);
}

View File

@@ -0,0 +1,309 @@
import {
type Adapter,
type MessageSignerWalletAdapterProps,
type SignerWalletAdapterProps,
type SignInMessageSignerWalletAdapterProps,
type WalletAdapterProps,
type WalletError,
type WalletName,
WalletNotConnectedError,
WalletNotReadyError,
WalletReadyState,
} from '@solana/wallet-adapter-base';
import { type PublicKey } from '@solana/web3.js';
import React, { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { WalletNotSelectedError } from './errors.js';
import { WalletContext } from './useWallet.js';
export interface WalletProviderBaseProps {
children: ReactNode;
wallets: Adapter[];
adapter: Adapter | null;
isUnloadingRef: React.RefObject<boolean>;
// NOTE: The presence/absence of this handler implies that auto-connect is enabled/disabled.
onAutoConnectRequest?: () => Promise<void>;
onConnectError: () => void;
onError?: (error: WalletError, adapter?: Adapter) => void;
onSelectWallet: (walletName: WalletName | null) => void;
}
export function WalletProviderBase({
children,
wallets: adapters,
adapter,
isUnloadingRef,
onAutoConnectRequest,
onConnectError,
onError,
onSelectWallet,
}: WalletProviderBaseProps) {
const isConnectingRef = useRef(false);
const [connecting, setConnecting] = useState(false);
const isDisconnectingRef = useRef(false);
const [disconnecting, setDisconnecting] = useState(false);
const [publicKey, setPublicKey] = useState(() => adapter?.publicKey ?? null);
const [connected, setConnected] = useState(() => adapter?.connected ?? false);
/**
* Store the error handlers as refs so that a change in the
* custom error handler does not recompute other dependencies.
*/
const onErrorRef = useRef(onError);
useEffect(() => {
onErrorRef.current = onError;
return () => {
onErrorRef.current = undefined;
};
}, [onError]);
const handleErrorRef = useRef((error: WalletError, adapter?: Adapter) => {
if (!isUnloadingRef.current) {
if (onErrorRef.current) {
onErrorRef.current(error, adapter);
} else {
console.error(error, adapter);
if (error instanceof WalletNotReadyError && typeof window !== 'undefined' && adapter) {
window.open(adapter.url, '_blank');
}
}
}
return error;
});
// Wrap adapters to conform to the `Wallet` interface
const [wallets, setWallets] = useState(() =>
adapters
.map((adapter) => ({
adapter,
readyState: adapter.readyState,
}))
.filter(({ readyState }) => readyState !== WalletReadyState.Unsupported)
);
// When the adapters change, start to listen for changes to their `readyState`
useEffect(() => {
// When the adapters change, wrap them to conform to the `Wallet` interface
setWallets((wallets) =>
adapters
.map((adapter, index) => {
const wallet = wallets[index];
// If the wallet hasn't changed, return the same instance
return wallet && wallet.adapter === adapter && wallet.readyState === adapter.readyState
? wallet
: {
adapter: adapter,
readyState: adapter.readyState,
};
})
.filter(({ readyState }) => readyState !== WalletReadyState.Unsupported)
);
function handleReadyStateChange(this: Adapter, readyState: WalletReadyState) {
setWallets((prevWallets) => {
const index = prevWallets.findIndex(({ adapter }) => adapter === this);
if (index === -1) return prevWallets;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { adapter } = prevWallets[index]!;
return [
...prevWallets.slice(0, index),
{ adapter, readyState },
...prevWallets.slice(index + 1),
].filter(({ readyState }) => readyState !== WalletReadyState.Unsupported);
});
}
adapters.forEach((adapter) => adapter.on('readyStateChange', handleReadyStateChange, adapter));
return () => {
adapters.forEach((adapter) => adapter.off('readyStateChange', handleReadyStateChange, adapter));
};
}, [adapter, adapters]);
const wallet = useMemo(() => wallets.find((wallet) => wallet.adapter === adapter) ?? null, [adapter, wallets]);
// Setup and teardown event listeners when the adapter changes
useEffect(() => {
if (!adapter) return;
const handleConnect = (publicKey: PublicKey) => {
setPublicKey(publicKey);
isConnectingRef.current = false;
setConnecting(false);
setConnected(true);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleDisconnect = () => {
if (isUnloadingRef.current) return;
setPublicKey(null);
isConnectingRef.current = false;
setConnecting(false);
setConnected(false);
isDisconnectingRef.current = false;
setDisconnecting(false);
};
const handleError = (error: WalletError) => {
handleErrorRef.current(error, adapter);
};
adapter.on('connect', handleConnect);
adapter.on('disconnect', handleDisconnect);
adapter.on('error', handleError);
return () => {
adapter.off('connect', handleConnect);
adapter.off('disconnect', handleDisconnect);
adapter.off('error', handleError);
handleDisconnect();
};
}, [adapter, isUnloadingRef]);
// When the adapter changes, clear the `autoConnect` tracking flag
const didAttemptAutoConnectRef = useRef(false);
useEffect(() => {
return () => {
didAttemptAutoConnectRef.current = false;
};
}, [adapter]);
// If auto-connect is enabled, request to connect when the adapter changes and is ready
useEffect(() => {
if (
didAttemptAutoConnectRef.current ||
isConnectingRef.current ||
connected ||
!onAutoConnectRequest ||
!(wallet?.readyState === WalletReadyState.Installed || wallet?.readyState === WalletReadyState.Loadable)
)
return;
isConnectingRef.current = true;
setConnecting(true);
didAttemptAutoConnectRef.current = true;
(async function () {
try {
await onAutoConnectRequest();
} catch {
onConnectError();
// Drop the error. It will be caught by `handleError` anyway.
} finally {
setConnecting(false);
isConnectingRef.current = false;
}
})();
}, [connected, onAutoConnectRequest, onConnectError, wallet]);
// Send a transaction using the provided connection
const sendTransaction: WalletAdapterProps['sendTransaction'] = useCallback(
async (transaction, connection, options) => {
if (!adapter) throw handleErrorRef.current(new WalletNotSelectedError());
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.sendTransaction(transaction, connection, options);
},
[adapter, connected]
);
// Sign a transaction if the wallet supports it
const signTransaction: SignerWalletAdapterProps['signTransaction'] | undefined = useMemo(
() =>
adapter && 'signTransaction' in adapter
? async (transaction) => {
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signTransaction(transaction);
}
: undefined,
[adapter, connected]
);
// Sign multiple transactions if the wallet supports it
const signAllTransactions: SignerWalletAdapterProps['signAllTransactions'] | undefined = useMemo(
() =>
adapter && 'signAllTransactions' in adapter
? async (transactions) => {
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signAllTransactions(transactions);
}
: undefined,
[adapter, connected]
);
// Sign an arbitrary message if the wallet supports it
const signMessage: MessageSignerWalletAdapterProps['signMessage'] | undefined = useMemo(
() =>
adapter && 'signMessage' in adapter
? async (message) => {
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signMessage(message);
}
: undefined,
[adapter, connected]
);
// Sign in if the wallet supports it
const signIn: SignInMessageSignerWalletAdapterProps['signIn'] | undefined = useMemo(
() =>
adapter && 'signIn' in adapter
? async (input) => {
return await adapter.signIn(input);
}
: undefined,
[adapter]
);
const handleConnect = useCallback(async () => {
if (isConnectingRef.current || isDisconnectingRef.current || wallet?.adapter.connected) return;
if (!wallet) throw handleErrorRef.current(new WalletNotSelectedError());
const { adapter, readyState } = wallet;
if (!(readyState === WalletReadyState.Installed || readyState === WalletReadyState.Loadable))
throw handleErrorRef.current(new WalletNotReadyError(), adapter);
isConnectingRef.current = true;
setConnecting(true);
try {
await adapter.connect();
} catch (e) {
onConnectError();
throw e;
} finally {
setConnecting(false);
isConnectingRef.current = false;
}
}, [onConnectError, wallet]);
const handleDisconnect = useCallback(async () => {
if (isDisconnectingRef.current) return;
if (!adapter) return;
isDisconnectingRef.current = true;
setDisconnecting(true);
try {
await adapter.disconnect();
} finally {
setDisconnecting(false);
isDisconnectingRef.current = false;
}
}, [adapter]);
return (
<WalletContext.Provider
value={{
autoConnect: !!onAutoConnectRequest,
wallets,
wallet,
publicKey,
connected,
connecting,
disconnecting,
select: onSelectWallet,
connect: handleConnect,
disconnect: handleDisconnect,
sendTransaction,
signTransaction,
signAllTransactions,
signMessage,
signIn,
}}
>
{children}
</WalletContext.Provider>
);
}

View File

@@ -0,0 +1,33 @@
import { BaseWalletAdapter, WalletReadyState } from '@solana/wallet-adapter-base';
import { act } from 'react';
export abstract class MockWalletAdapter extends BaseWalletAdapter {
connectedValue = false;
get connected() {
return this.connectedValue;
}
readyStateValue: WalletReadyState = WalletReadyState.Installed;
get readyState() {
return this.readyStateValue;
}
connecting = false;
connect = jest.fn(async () => {
this.connecting = true;
this.connecting = false;
this.connectedValue = true;
act(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.emit('connect', this.publicKey!);
});
});
disconnect = jest.fn(async () => {
this.connecting = false;
this.connectedValue = false;
act(() => {
this.emit('disconnect');
});
});
sendTransaction = jest.fn();
supportedTransactionVersions = null;
autoConnect = jest.fn();
}

View File

@@ -0,0 +1,462 @@
/**
* @jest-environment jsdom
*/
'use strict';
import {
type Adapter,
BaseWalletAdapter,
WalletError,
type WalletName,
WalletNotReadyError,
WalletReadyState,
} from '@solana/wallet-adapter-base';
import { PublicKey } from '@solana/web3.js';
import React, { act, createRef, forwardRef, useImperativeHandle } from 'react';
import { createRoot } from 'react-dom/client';
import { useWallet, type WalletContextState } from '../useWallet.js';
import { WalletProviderBase, type WalletProviderBaseProps } from '../WalletProviderBase.js';
type TestRefType = {
getWalletContextState(): WalletContextState;
};
const TestComponent = forwardRef(function TestComponentImpl(_props, ref) {
const wallet = useWallet();
useImperativeHandle(
ref,
() => ({
getWalletContextState() {
return wallet;
},
}),
[wallet]
);
return null;
});
describe('WalletProviderBase', () => {
let ref: React.RefObject<TestRefType | null>;
let root: ReturnType<typeof createRoot>;
let container: HTMLElement;
let fooWalletAdapter: MockWalletAdapter;
let barWalletAdapter: MockWalletAdapter;
let bazWalletAdapter: MockWalletAdapter;
let adapters: Adapter[];
let isUnloading: React.MutableRefObject<boolean>;
function renderTest(
props: Omit<
WalletProviderBaseProps,
'children' | 'wallets' | 'isUnloadingRef' | 'onConnectError' | 'onSelectWallet'
>
) {
act(() => {
root.render(
<WalletProviderBase
wallets={adapters}
isUnloadingRef={isUnloading}
onConnectError={jest.fn()}
onSelectWallet={jest.fn()}
{...props}
>
<TestComponent ref={ref} />
</WalletProviderBase>
);
});
}
abstract class MockWalletAdapter extends BaseWalletAdapter {
connectionPromise: null | Promise<void> = null;
disconnectionPromise: null | Promise<void> = null;
connectedValue = false;
get connected() {
return this.connectedValue;
}
readyStateValue: WalletReadyState = WalletReadyState.Installed;
get readyState() {
return this.readyStateValue;
}
connecting = false;
connect = jest.fn(async () => {
this.connecting = true;
if (this.connectionPromise) {
await this.connectionPromise;
}
this.connecting = false;
this.connectedValue = true;
act(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.emit('connect', this.publicKey!);
});
});
disconnect = jest.fn(async () => {
this.connecting = false;
if (this.disconnectionPromise) {
await this.disconnectionPromise;
}
this.connectedValue = false;
act(() => {
this.emit('disconnect');
});
});
sendTransaction = jest.fn();
supportedTransactionVersions = null;
}
class FooWalletAdapter extends MockWalletAdapter {
name = 'FooWallet' as WalletName<'FooWallet'>;
url = 'https://foowallet.com';
icon = 'foo.png';
publicKey = new PublicKey('Foo11111111111111111111111111111111111111111');
}
class BarWalletAdapter extends MockWalletAdapter {
name = 'BarWallet' as WalletName<'BarWallet'>;
url = 'https://barwallet.com';
icon = 'bar.png';
publicKey = new PublicKey('Bar11111111111111111111111111111111111111111');
}
class BazWalletAdapter extends MockWalletAdapter {
name = 'BazWallet' as WalletName<'BazWallet'>;
url = 'https://bazwallet.com';
icon = 'baz.png';
publicKey = new PublicKey('Baz11111111111111111111111111111111111111111');
}
beforeEach(() => {
jest.resetAllMocks();
container = document.createElement('div');
document.body.appendChild(container);
isUnloading = { current: false };
root = createRoot(container);
ref = createRef();
fooWalletAdapter = new FooWalletAdapter();
barWalletAdapter = new BarWalletAdapter();
bazWalletAdapter = new BazWalletAdapter();
adapters = [fooWalletAdapter, barWalletAdapter, bazWalletAdapter];
});
afterEach(() => {
if (root) {
act(() => {
root.unmount();
});
}
});
describe('given a selected wallet', () => {
beforeEach(async () => {
fooWalletAdapter.readyStateValue = WalletReadyState.NotDetected;
renderTest({ adapter: fooWalletAdapter });
expect(ref.current?.getWalletContextState().wallet?.readyState).toBe(WalletReadyState.NotDetected);
});
describe('that then becomes ready', () => {
beforeEach(() => {
act(() => {
fooWalletAdapter.readyStateValue = WalletReadyState.Installed;
fooWalletAdapter.emit('readyStateChange', WalletReadyState.Installed);
});
});
it('sets `ready` to true', () => {
expect(ref.current?.getWalletContextState().wallet?.readyState).toBe(WalletReadyState.Installed);
});
});
describe('when the wallet disconnects of its own accord', () => {
beforeEach(() => {
act(() => {
fooWalletAdapter.disconnect();
});
});
it('clears out the state', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
publicKey: null,
});
});
});
describe('when the wallet disconnects as a consequence of the window unloading', () => {
beforeEach(() => {
act(() => {
isUnloading.current = true;
fooWalletAdapter.disconnect();
});
});
it('should not clear out the state', () => {
expect(ref.current?.getWalletContextState().wallet?.adapter).toBe(fooWalletAdapter);
expect(ref.current?.getWalletContextState().publicKey).not.toBeNull();
});
});
});
describe('given the presence of an unsupported wallet', () => {
beforeEach(() => {
bazWalletAdapter.readyStateValue = WalletReadyState.Unsupported;
renderTest({ adapter: fooWalletAdapter });
});
it('filters out the unsupported wallet', () => {
const adapters = ref.current?.getWalletContextState().wallets.map(({ adapter }) => adapter);
expect(adapters).not.toContain(bazWalletAdapter);
});
});
describe('when auto connect is disabled', () => {
beforeEach(() => {
renderTest({ onAutoConnectRequest: undefined, adapter: fooWalletAdapter });
});
it('`autoConnect` is `false` on state', () => {
expect(ref.current?.getWalletContextState().autoConnect).toBe(false);
});
});
describe('and auto connect is enabled', () => {
let onAutoConnectRequest: jest.Mock;
beforeEach(() => {
onAutoConnectRequest = jest.fn();
fooWalletAdapter.readyStateValue = WalletReadyState.NotDetected;
renderTest({ adapter: fooWalletAdapter, onAutoConnectRequest });
});
it('`autoConnect` is `true` on state', () => {
expect(ref.current?.getWalletContextState().autoConnect).toBe(true);
});
describe('before the adapter is ready', () => {
it('does not call `connect` on the adapter', () => {
expect(fooWalletAdapter.connect).not.toHaveBeenCalled();
});
describe('once the adapter becomes ready', () => {
beforeEach(async () => {
await act(async () => {
fooWalletAdapter.readyStateValue = WalletReadyState.Installed;
fooWalletAdapter.emit('readyStateChange', WalletReadyState.Installed);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('calls `onAutoConnectRequest`', () => {
expect(onAutoConnectRequest).toHaveBeenCalledTimes(1);
});
describe('when switching to another adapter', () => {
beforeEach(async () => {
jest.clearAllMocks();
renderTest({ adapter: barWalletAdapter, onAutoConnectRequest });
});
it('calls `onAutoConnectRequest` despite having called it once before on the old adapter', () => {
expect(onAutoConnectRequest).toHaveBeenCalledTimes(1);
});
});
describe('once the adapter connects', () => {
beforeEach(async () => {
await act(async () => {
await fooWalletAdapter.connect();
});
});
describe('then disconnects', () => {
beforeEach(async () => {
jest.clearAllMocks();
await act(async () => {
await fooWalletAdapter.disconnect();
});
});
it('does not make a second attempt to auto connect', () => {
expect(onAutoConnectRequest).not.toHaveBeenCalled();
});
});
});
});
});
});
describe('custom error handler', () => {
const errorToEmit = new WalletError();
let onError: jest.Mock;
beforeEach(async () => {
onError = jest.fn();
renderTest({ adapter: fooWalletAdapter, onError });
});
it('gets called in response to adapter errors', () => {
act(() => {
fooWalletAdapter.emit('error', errorToEmit);
});
expect(onError).toBeCalledWith(errorToEmit, fooWalletAdapter);
});
it('does not get called if the window is unloading', () => {
const errorToEmit = new WalletError();
act(() => {
isUnloading.current = true;
fooWalletAdapter.emit('error', errorToEmit);
});
expect(onError).not.toBeCalled();
});
describe('when a wallet is connected', () => {
beforeEach(async () => {
await act(() => {
ref.current?.getWalletContextState().connect();
});
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: true,
});
});
describe('then the `onError` function changes', () => {
beforeEach(async () => {
const differentOnError = jest.fn(); /* Some function, different from the one above */
renderTest({ adapter: fooWalletAdapter, onError: differentOnError });
});
it('does not cause state to be cleared when it changes', () => {
// Regression test for https://github.com/anza-xyz/wallet-adapter/issues/636
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: true,
});
});
});
});
});
describe('connect()', () => {
describe('given an adapter that is not ready', () => {
beforeEach(async () => {
window.open = jest.fn();
fooWalletAdapter.readyStateValue = WalletReadyState.NotDetected;
renderTest({ adapter: fooWalletAdapter });
expect(ref.current?.getWalletContextState().wallet?.readyState).toBe(WalletReadyState.NotDetected);
act(() => {
expect(ref.current?.getWalletContextState().connect()).rejects.toThrow();
});
});
it("opens the wallet's URL in a new window", () => {
expect(window.open).toBeCalledWith('https://foowallet.com', '_blank');
});
it('throws a `WalletNotReady` error', () => {
act(() => {
expect(ref.current?.getWalletContextState().connect()).rejects.toThrow(new WalletNotReadyError());
});
});
});
describe('given an adapter that is ready', () => {
let commitConnection: () => void;
beforeEach(async () => {
renderTest({ adapter: fooWalletAdapter });
fooWalletAdapter.connectionPromise = new Promise<void>((resolve) => {
commitConnection = resolve;
});
await act(() => {
ref.current?.getWalletContextState().connect();
});
});
it('calls connect on the adapter', () => {
expect(fooWalletAdapter.connect).toHaveBeenCalled();
});
it('updates state tracking variables appropriately', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: true,
});
});
describe('once connected', () => {
beforeEach(async () => {
await act(() => {
commitConnection();
});
});
it('updates state tracking variables appropriately', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: true,
connecting: false,
});
});
});
});
});
describe('disconnect()', () => {
describe('when there is already an adapter supplied', () => {
let commitDisconnection: () => void;
beforeEach(async () => {
window.open = jest.fn();
renderTest({ adapter: fooWalletAdapter });
await act(() => {
ref.current?.getWalletContextState().connect();
});
fooWalletAdapter.disconnectionPromise = new Promise<void>((resolve) => {
commitDisconnection = resolve;
});
await act(() => {
ref.current?.getWalletContextState().disconnect();
});
});
it('updates state tracking variables appropriately', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: true,
});
});
describe('once disconnected', () => {
beforeEach(async () => {
await act(() => {
commitDisconnection();
});
});
it('clears out the state', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
publicKey: null,
});
});
});
});
});
describe('when there is no adapter supplied', () => {
beforeEach(() => {
renderTest({ adapter: null });
});
describe('and one becomes supplied', () => {
beforeEach(() => {
renderTest({ adapter: fooWalletAdapter });
});
it('sets the state tracking variables', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
wallet: { adapter: fooWalletAdapter, readyState: fooWalletAdapter.readyState },
connected: false,
connecting: false,
publicKey: null,
});
});
});
});
describe('when there is already an adapter supplied', () => {
let commitFooWalletDisconnection: () => void;
beforeEach(async () => {
fooWalletAdapter.disconnectionPromise = new Promise<void>((resolve) => {
commitFooWalletDisconnection = resolve;
});
renderTest({ adapter: fooWalletAdapter });
});
describe('when you null out the adapter', () => {
beforeEach(() => {
renderTest({ adapter: null });
});
it('clears out the state', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
wallet: null,
connected: false,
connecting: false,
publicKey: null,
});
});
});
describe('and a different adapter becomes supplied', () => {
beforeEach(async () => {
renderTest({ adapter: barWalletAdapter });
});
it('the adapter of the new wallet should be set in state', () => {
expect(ref.current?.getWalletContextState().wallet?.adapter).toBe(barWalletAdapter);
});
/**
* Regression test: a race condition in the wallet name setter could result in the
* wallet reverting back to an old value, depending on the cadence of the previous
* wallets' disconnect operation.
*/
describe('then a different one becomes supplied before the first one has disconnected', () => {
beforeEach(async () => {
renderTest({ adapter: bazWalletAdapter });
act(() => {
commitFooWalletDisconnection();
});
});
it('the wallet you selected last should be set in state', () => {
expect(ref.current?.getWalletContextState().wallet?.adapter).toBe(bazWalletAdapter);
});
});
});
});
});

View File

@@ -0,0 +1,322 @@
/**
* @jest-environment jsdom
*/
'use strict';
import {
type AddressSelector,
type AuthorizationResultCache,
SolanaMobileWalletAdapter,
} from '@solana-mobile/wallet-adapter-mobile';
import { type Adapter, WalletError, type WalletName, WalletReadyState } from '@solana/wallet-adapter-base';
import { PublicKey } from '@solana/web3.js';
import 'jest-localstorage-mock';
import React, { act, createRef, forwardRef, useImperativeHandle } from 'react';
import { createRoot } from 'react-dom/client';
import { MockWalletAdapter } from '../__mocks__/MockWalletAdapter.js';
import { useWallet, type WalletContextState } from '../useWallet.js';
import { WalletProvider, type WalletProviderProps } from '../WalletProvider.js';
jest.mock('../getEnvironment.js', () => ({
...jest.requireActual('../getEnvironment.js'),
__esModule: true,
default: () => jest.requireActual('../getEnvironment.js').Environment.DESKTOP_WEB,
}));
type TestRefType = {
getWalletContextState(): WalletContextState;
};
const TestComponent = forwardRef(function TestComponentImpl(_props, ref) {
const wallet = useWallet();
useImperativeHandle(
ref,
() => ({
getWalletContextState() {
return wallet;
},
}),
[wallet]
);
return null;
});
const WALLET_NAME_CACHE_KEY = 'cachedWallet';
/**
* NOTE: If you add a test to this suite, also add it to `WalletProviderMobile-test.tsx`.
*
* You may be wondering why these suites haven't been designed as one suite with a procedurally
* generated `describe` block that mocks `getEnvironment` differently on each pass. The reason has
* to do with the way `jest.resetModules()` plays havoc with the React test renderer. If you have
* a solution, please do send a PR.
*/
describe('WalletProvider when the environment is `DESKTOP_WEB`', () => {
let ref: React.RefObject<TestRefType | null>;
let root: ReturnType<typeof createRoot>;
let container: HTMLElement;
let fooWalletAdapter: MockWalletAdapter;
let adapters: Adapter[];
function renderTest(props: Omit<WalletProviderProps, 'appIdentity' | 'children' | 'cluster' | 'wallets'>) {
act(() => {
root.render(
<WalletProvider {...props} localStorageKey={WALLET_NAME_CACHE_KEY} wallets={adapters}>
<TestComponent ref={ref} />
</WalletProvider>
);
});
}
class FooWalletAdapter extends MockWalletAdapter {
name = 'FooWallet' as WalletName<'FooWallet'>;
url = 'https://foowallet.com';
icon = 'foo.png';
publicKey = new PublicKey('Foo11111111111111111111111111111111111111111');
}
beforeEach(() => {
localStorage.clear();
jest.clearAllMocks().resetModules();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
ref = createRef();
fooWalletAdapter = new FooWalletAdapter();
adapters = [fooWalletAdapter];
});
afterEach(() => {
if (root) {
act(() => {
root.unmount();
});
}
});
describe('given a selected wallet', () => {
beforeEach(async () => {
fooWalletAdapter.readyStateValue = WalletReadyState.NotDetected;
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
expect(ref.current?.getWalletContextState().wallet?.readyState).toBe(WalletReadyState.NotDetected);
});
it('should store the wallet name', () => {
expect(localStorage.setItem).toHaveBeenCalledWith(
WALLET_NAME_CACHE_KEY,
JSON.stringify(fooWalletAdapter.name)
);
});
describe('when the wallet disconnects of its own accord', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
fooWalletAdapter.disconnect();
});
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
});
describe('when the wallet disconnects as a consequence of the window unloading', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
window.dispatchEvent(new Event('beforeunload'));
fooWalletAdapter.disconnect();
});
});
it('should not clear the stored wallet name', () => {
expect(localStorage.removeItem).not.toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
});
});
describe('when there is no mobile wallet adapter in the adapters array', () => {
it('does not create a new mobile wallet adapter', () => {
renderTest({});
expect(jest.mocked(SolanaMobileWalletAdapter).mock.instances).toHaveLength(0);
});
});
describe('when a custom mobile wallet adapter is supplied in the adapters array', () => {
let customAdapter: Adapter;
const CUSTOM_APP_IDENTITY = {
uri: 'https://custom.com',
};
const CUSTOM_CLUSTER = 'devnet';
beforeEach(() => {
customAdapter = new SolanaMobileWalletAdapter({
addressSelector: jest.fn() as unknown as AddressSelector,
appIdentity: CUSTOM_APP_IDENTITY,
authorizationResultCache: jest.fn() as unknown as AuthorizationResultCache,
cluster: CUSTOM_CLUSTER,
onWalletNotFound: jest.fn(),
});
adapters.push(customAdapter);
jest.clearAllMocks();
});
it('does not load the custom mobile wallet adapter into state as the default', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet?.adapter).not.toBe(customAdapter);
});
});
describe('when there exists no stored wallet name', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue(null);
});
it('loads no wallet into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet).toBeNull();
});
it('loads no public key into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().publicKey).toBeNull();
});
});
describe('when there exists a stored wallet name', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue(JSON.stringify('FooWallet'));
});
it('loads the corresponding adapter into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet?.adapter).toBeInstanceOf(FooWalletAdapter);
});
it('loads the corresponding public key into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().publicKey).toBe(fooWalletAdapter.publicKey);
});
it('sets state tracking variables to defaults', () => {
renderTest({});
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
});
});
});
describe('autoConnect', () => {
beforeEach(async () => {
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
describe('when autoConnect is disabled', () => {
beforeEach(() => {
renderTest({ autoConnect: false });
});
it('does not call `autoConnect`', () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adapter = ref.current!.getWalletContextState().wallet!.adapter;
expect(adapter.connect).not.toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
describe('when autoConnect is enabled', () => {
beforeEach(() => {
renderTest({ autoConnect: true });
});
it('calls `connect`', () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adapter = ref.current!.getWalletContextState().wallet!.adapter;
expect(adapter.connect).toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
});
describe('onError', () => {
let onError: jest.Mock;
let errorThrown: WalletError;
beforeEach(() => {
errorThrown = new WalletError('o no');
onError = jest.fn();
renderTest({ onError });
});
describe('when the wallet emits an error', () => {
let adapter: Adapter;
beforeEach(() => {
act(() => {
adapter = ref.current?.getWalletContextState().wallet?.adapter as Adapter;
adapter.emit('error', errorThrown);
});
});
it('should fire the `onError` callback', () => {
expect(onError).toHaveBeenCalledWith(errorThrown, adapter);
});
});
describe('when window `beforeunload` event fires', () => {
beforeEach(() => {
act(() => {
window.dispatchEvent(new Event('beforeunload'));
});
});
describe('then the wallet emits an error', () => {
beforeEach(() => {
act(() => {
const adapter = ref.current?.getWalletContextState().wallet?.adapter as Adapter;
adapter.emit('error', errorThrown);
});
});
it('should not fire the `onError` callback', () => {
expect(onError).not.toHaveBeenCalled();
});
});
});
});
describe('disconnect()', () => {
describe('when there is already a wallet connected', () => {
beforeEach(async () => {
window.open = jest.fn();
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
await act(() => {
ref.current?.getWalletContextState().connect();
});
});
describe('and you select a different wallet', () => {
beforeEach(async () => {
await act(async () => {
ref.current?.getWalletContextState().select('BarWallet' as WalletName<'BarWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('should disconnect the old wallet', () => {
expect(fooWalletAdapter.disconnect).toHaveBeenCalled();
});
});
describe('and you select the same wallet', () => {
beforeEach(async () => {
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('should not disconnect the old wallet', () => {
expect(fooWalletAdapter.disconnect).not.toHaveBeenCalled();
});
});
describe('once disconnected', () => {
beforeEach(async () => {
jest.clearAllMocks();
ref.current?.getWalletContextState().disconnect();
await Promise.resolve(); // Flush all promises in effects after calling `disconnect()`.
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
it('sets state tracking variables to defaults', () => {
renderTest({});
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
publicKey: null,
});
});
});
});
});
});

View File

@@ -0,0 +1,455 @@
/**
* @jest-environment jsdom
*/
'use strict';
import {
type AddressSelector,
type AuthorizationResultCache,
SolanaMobileWalletAdapter,
SolanaMobileWalletAdapterWalletName,
} from '@solana-mobile/wallet-adapter-mobile';
import { type Adapter, WalletError, type WalletName, WalletReadyState } from '@solana/wallet-adapter-base';
import { type Connection, PublicKey } from '@solana/web3.js';
import 'jest-localstorage-mock';
import React, { act, createRef, forwardRef, useImperativeHandle } from 'react';
import { createRoot } from 'react-dom/client';
import { MockWalletAdapter } from '../__mocks__/MockWalletAdapter.js';
import { useConnection } from '../useConnection.js';
import { useWallet, type WalletContextState } from '../useWallet.js';
import { WalletProvider, type WalletProviderProps } from '../WalletProvider.js';
jest.mock('../getEnvironment.js', () => ({
...jest.requireActual('../getEnvironment.js'),
__esModule: true,
default: () => jest.requireActual('../getEnvironment.js').Environment.MOBILE_WEB,
}));
jest.mock('../getInferredClusterFromEndpoint.js', () => ({
...jest.requireActual('../getInferredClusterFromEndpoint.js'),
__esModule: true,
default: (endpoint?: string) => {
switch (endpoint) {
case 'https://fake-endpoint-for-test.com':
return 'fake-cluster-for-test';
default:
return 'mainnet-beta';
}
},
}));
jest.mock('../useConnection.js');
type TestRefType = {
getWalletContextState(): WalletContextState;
};
const TestComponent = forwardRef(function TestComponentImpl(_props, ref) {
const wallet = useWallet();
useImperativeHandle(
ref,
() => ({
getWalletContextState() {
return wallet;
},
}),
[wallet]
);
return null;
});
const WALLET_NAME_CACHE_KEY = 'cachedWallet';
/**
* NOTE: If you add a test to this suite, also add it to `WalletProviderDesktop-test.tsx`.
*
* You may be wondering why these suites haven't been designed as one suite with a procedurally
* generated `describe` block that mocks `getEnvironment` differently on each pass. The reason has
* to do with the way `jest.resetModules()` plays havoc with the React test renderer. If you have
* a solution, please do send a PR.
*/
describe('WalletProvider when the environment is `MOBILE_WEB`', () => {
let ref: React.RefObject<TestRefType | null>;
let root: ReturnType<typeof createRoot>;
let container: HTMLElement;
let fooWalletAdapter: MockWalletAdapter;
let adapters: Adapter[];
function renderTest(props: Omit<WalletProviderProps, 'appIdentity' | 'children' | 'cluster' | 'wallets'>) {
act(() => {
root.render(
<WalletProvider {...props} localStorageKey={WALLET_NAME_CACHE_KEY} wallets={adapters}>
<TestComponent ref={ref} />
</WalletProvider>
);
});
}
class FooWalletAdapter extends MockWalletAdapter {
name = 'FooWallet' as WalletName<'FooWallet'>;
url = 'https://foowallet.com';
icon = 'foo.png';
publicKey = new PublicKey('Foo11111111111111111111111111111111111111111');
}
beforeEach(() => {
localStorage.clear();
jest.clearAllMocks().resetModules();
jest.mocked(useConnection).mockImplementation(() => ({
connection: {
rpcEndpoint: 'https://fake-endpoint-for-test.com',
} as Connection,
}));
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
ref = createRef();
fooWalletAdapter = new FooWalletAdapter();
adapters = [fooWalletAdapter];
});
afterEach(() => {
if (root) {
act(() => {
root.unmount();
});
}
});
describe('given a selected wallet', () => {
beforeEach(async () => {
fooWalletAdapter.readyStateValue = WalletReadyState.NotDetected;
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
expect(ref.current?.getWalletContextState().wallet?.readyState).toBe(WalletReadyState.NotDetected);
});
it('should store the wallet name', () => {
expect(localStorage.setItem).toHaveBeenCalledWith(
WALLET_NAME_CACHE_KEY,
JSON.stringify(fooWalletAdapter.name)
);
});
describe('when the wallet disconnects of its own accord', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
fooWalletAdapter.disconnect();
});
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
});
describe('when the wallet disconnects as a consequence of the window unloading', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
window.dispatchEvent(new Event('beforeunload'));
fooWalletAdapter.disconnect();
});
});
it('should not clear the stored wallet name', () => {
expect(localStorage.removeItem).not.toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
});
});
describe('when there is no mobile wallet adapter in the adapters array', () => {
it("creates a new mobile wallet adapter with the document's host as the uri of the `appIdentity`", () => {
renderTest({});
expect(jest.mocked(SolanaMobileWalletAdapter).mock.instances).toHaveLength(1);
expect(jest.mocked(SolanaMobileWalletAdapter).mock.calls[0][0].appIdentity.uri).toBe(
`${document.location.protocol}//${document.location.host}`
);
});
it('creates a new mobile wallet adapter with the appropriate cluster for the given endpoint', () => {
renderTest({});
expect(jest.mocked(SolanaMobileWalletAdapter).mock.instances).toHaveLength(1);
// @ts-expect-error // HACK: SolanaMobileWalletAdapter has a `cluster` property but it's not declared.
expect(jest.mocked(SolanaMobileWalletAdapter).mock.calls[0][0].cluster).toBe('fake-cluster-for-test');
});
});
describe('when a custom mobile wallet adapter is supplied in the adapters array', () => {
let customAdapter: Adapter;
const CUSTOM_APP_IDENTITY = {
uri: 'https://custom.com',
};
const CUSTOM_CLUSTER = 'devnet';
beforeEach(() => {
customAdapter = new SolanaMobileWalletAdapter({
addressSelector: jest.fn() as unknown as AddressSelector,
appIdentity: CUSTOM_APP_IDENTITY,
authorizationResultCache: jest.fn() as unknown as AuthorizationResultCache,
cluster: CUSTOM_CLUSTER,
onWalletNotFound: jest.fn(),
});
adapters.push(customAdapter);
jest.clearAllMocks();
});
it('does not load the custom mobile wallet adapter into state as the default', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet?.adapter).not.toBe(customAdapter);
});
it('does not construct any further mobile wallet adapters', () => {
renderTest({});
expect(jest.mocked(SolanaMobileWalletAdapter).mock.calls.length).toBe(0);
});
});
describe('when there exists no stored wallet name', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue(null);
});
it('loads no wallet into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet).toBeNull();
});
it('loads no public key into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().publicKey).toBeNull();
});
});
describe('when there exists a stored wallet name', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue(JSON.stringify('FooWallet'));
});
it('loads the corresponding adapter into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().wallet?.adapter).toBeInstanceOf(FooWalletAdapter);
});
it('loads the corresponding public key into state', () => {
renderTest({});
expect(ref.current?.getWalletContextState().publicKey).toBe(fooWalletAdapter.publicKey);
});
it('sets state tracking variables to defaults', () => {
renderTest({});
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
});
});
});
describe('autoConnect', () => {
describe('given a mobile wallet adapter is connected', () => {
beforeEach(async () => {
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select(SolanaMobileWalletAdapterWalletName);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
describe('when autoConnect is disabled', () => {
beforeEach(() => {
renderTest({ autoConnect: false });
});
it('does not call `connect`', () => {
const adapter = ref.current?.getWalletContextState().wallet?.adapter as SolanaMobileWalletAdapter;
expect(adapter.connect).not.toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
describe('when autoConnect is enabled', () => {
beforeEach(() => {
renderTest({ autoConnect: true });
});
it('calls the connect method on the mobile wallet adapter', () => {
const adapter = ref.current?.getWalletContextState().wallet?.adapter as SolanaMobileWalletAdapter;
expect(adapter.connect).toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
});
describe('given a non-mobile wallet adapter is connected', () => {
beforeEach(async () => {
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
describe('when autoConnect is disabled', () => {
beforeEach(() => {
renderTest({ autoConnect: false });
});
it('does not call `autoConnect`', () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adapter = ref.current!.getWalletContextState().wallet!.adapter;
expect(adapter.connect).not.toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
describe('when autoConnect is enabled', () => {
beforeEach(() => {
renderTest({ autoConnect: true });
});
it('calls `connect`', () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adapter = ref.current!.getWalletContextState().wallet!.adapter;
expect(adapter.connect).toHaveBeenCalled();
expect(adapter.autoConnect).not.toHaveBeenCalled();
});
});
});
});
describe('onError', () => {
let onError: jest.Mock;
let errorThrown: WalletError;
beforeEach(() => {
errorThrown = new WalletError('o no');
onError = jest.fn();
renderTest({ onError });
});
describe('when the wallet emits an error', () => {
let adapter: Adapter;
beforeEach(() => {
act(() => {
adapter = ref.current?.getWalletContextState().wallet?.adapter as SolanaMobileWalletAdapter;
adapter.emit('error', errorThrown);
});
});
it('should fire the `onError` callback', () => {
expect(onError).toHaveBeenCalledWith(errorThrown, adapter);
});
});
describe('when window `beforeunload` event fires', () => {
beforeEach(() => {
act(() => {
window.dispatchEvent(new Event('beforeunload'));
});
});
describe('then the wallet emits an error', () => {
let adapter: Adapter;
beforeEach(() => {
act(() => {
adapter = ref.current?.getWalletContextState().wallet?.adapter as SolanaMobileWalletAdapter;
adapter.emit('error', errorThrown);
});
});
it('should not fire the `onError` callback', () => {
expect(onError).not.toHaveBeenCalled();
});
});
});
});
describe('disconnect()', () => {
describe('when there is already a wallet connected', () => {
beforeEach(async () => {
window.open = jest.fn();
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
await act(() => {
ref.current?.getWalletContextState().connect();
});
});
describe('and you select a different wallet', () => {
beforeEach(async () => {
await act(async () => {
ref.current?.getWalletContextState().select('BarWallet' as WalletName<'BarWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('should disconnect the old wallet', () => {
expect(fooWalletAdapter.disconnect).toHaveBeenCalled();
});
});
describe('and you select the same wallet', () => {
beforeEach(async () => {
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('should not disconnect the old wallet', () => {
expect(fooWalletAdapter.disconnect).not.toHaveBeenCalled();
});
});
describe('once disconnected', () => {
beforeEach(async () => {
jest.clearAllMocks();
act(() => {
ref.current?.getWalletContextState().disconnect();
});
await Promise.resolve(); // Flush all promises in effects after calling `disconnect()`.
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
it('sets state tracking variables to defaults', () => {
renderTest({});
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
publicKey: null,
});
});
});
});
describe('given a mobile wallet adapter is connected', () => {
let mobileWalletAdapter: Adapter;
beforeEach(async () => {
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select(SolanaMobileWalletAdapterWalletName);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
mobileWalletAdapter = jest.mocked(SolanaMobileWalletAdapter).mock.results[0].value;
await act(() => {
ref.current?.getWalletContextState().connect();
});
});
describe('then a non-mobile wallet adapter is selected', () => {
beforeEach(async () => {
renderTest({});
await act(async () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
});
it('does not call `disconnect` on the mobile wallet adapter', () => {
expect(mobileWalletAdapter.disconnect).not.toHaveBeenCalled();
});
it('should not clear the stored wallet name', () => {
expect(localStorage.removeItem).not.toHaveBeenCalled();
});
});
describe('when the wallet disconnects of its own accord', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
mobileWalletAdapter.disconnect();
});
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
});
describe('when window beforeunload event fires', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
window.dispatchEvent(new Event('beforeunload'));
});
});
describe('then the wallet disconnects of its own accord', () => {
beforeEach(() => {
jest.clearAllMocks();
act(() => {
mobileWalletAdapter.disconnect();
});
});
it('should clear the stored wallet name', () => {
expect(localStorage.removeItem).toHaveBeenCalledWith(WALLET_NAME_CACHE_KEY);
});
it('should clear out the state', () => {
expect(ref.current?.getWalletContextState()).toMatchObject({
connected: false,
connecting: false,
publicKey: null,
});
});
});
});
});
});
});

View File

@@ -0,0 +1,34 @@
import getInferredClusterFromEndpoint from '../getInferredClusterFromEndpoint.js';
describe('getInferredClusterFromEndpoint()', () => {
describe('when the endpoint is `undefined`', () => {
const endpoint = undefined;
it('creates a new mobile wallet adapter with `mainnet-beta` as the cluster', () => {
expect(getInferredClusterFromEndpoint(endpoint)).toBe('mainnet-beta');
});
});
describe('when the endpoint is the empty string', () => {
const endpoint = '';
it('creates a new mobile wallet adapter with `mainnet-beta` as the cluster', () => {
expect(getInferredClusterFromEndpoint(endpoint)).toBe('mainnet-beta');
});
});
describe("when the endpoint contains the word 'devnet'", () => {
const endpoint = 'https://foo-devnet.com';
it('creates a new mobile wallet adapter with `devnet` as the cluster', () => {
expect(getInferredClusterFromEndpoint(endpoint)).toBe('devnet');
});
});
describe("when the endpoint contains the word 'testnet'", () => {
const endpoint = 'https://foo-testnet.com';
it('creates a new mobile wallet adapter with `testnet` as the cluster', () => {
expect(getInferredClusterFromEndpoint(endpoint)).toBe('testnet');
});
});
describe("when the endpoint contains the word 'mainnet-beta'", () => {
const endpoint = 'https://foo-mainnet-beta.com';
it('creates a new mobile wallet adapter with `mainnet-beta` as the cluster', () => {
expect(getInferredClusterFromEndpoint(endpoint)).toBe('mainnet-beta');
});
});
});

View File

@@ -0,0 +1,67 @@
import { type Adapter, WalletReadyState } from '@solana/wallet-adapter-base';
import getEnvironment, { Environment } from '../getEnvironment.js';
describe('getEnvironment()', () => {
[
{
description: 'on Android',
expectedEnvironmentWithInstalledAdapter: Environment.DESKTOP_WEB,
expectedEnvironmentWithNoInstalledAdapter: Environment.MOBILE_WEB,
userAgentString: 'Android',
},
{
description: 'in a webview',
expectedEnvironmentWithInstalledAdapter: Environment.DESKTOP_WEB,
expectedEnvironmentWithNoInstalledAdapter: Environment.DESKTOP_WEB,
userAgentString: 'WebView',
},
{
description: 'on desktop',
expectedEnvironmentWithInstalledAdapter: Environment.DESKTOP_WEB,
expectedEnvironmentWithNoInstalledAdapter: Environment.DESKTOP_WEB,
userAgentString:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
},
{
description: 'the user agent is null',
expectedEnvironmentWithInstalledAdapter: Environment.DESKTOP_WEB,
expectedEnvironmentWithNoInstalledAdapter: Environment.DESKTOP_WEB,
userAgentString: null,
},
].forEach(
({
description,
expectedEnvironmentWithInstalledAdapter,
expectedEnvironmentWithNoInstalledAdapter,
userAgentString,
}) => {
describe(`when ${description}`, () => {
describe('with no installed adapters', () => {
it(`returns \`${Environment[expectedEnvironmentWithNoInstalledAdapter]}\``, () => {
const adapters = [
{ readyState: WalletReadyState.Loadable } as Adapter,
{ readyState: WalletReadyState.NotDetected } as Adapter,
{ readyState: WalletReadyState.Unsupported } as Adapter,
];
expect(getEnvironment({ adapters, userAgentString })).toBe(
expectedEnvironmentWithNoInstalledAdapter
);
});
});
describe('with at least one installed adapter', () => {
it(`returns \`${Environment[expectedEnvironmentWithInstalledAdapter]}\``, () => {
const adapters = [
{ readyState: WalletReadyState.Loadable } as Adapter,
{ readyState: WalletReadyState.Installed } as Adapter,
{ readyState: WalletReadyState.NotDetected } as Adapter,
{ readyState: WalletReadyState.Unsupported } as Adapter,
];
expect(getEnvironment({ adapters, userAgentString })).toBe(
expectedEnvironmentWithInstalledAdapter
);
});
});
});
}
);
});

View File

@@ -0,0 +1,276 @@
/**
* @jest-environment jsdom
*/
'use strict';
import 'jest-localstorage-mock';
import React, { act, createRef, forwardRef, useImperativeHandle } from 'react';
import { createRoot } from 'react-dom/client';
import { useLocalStorage } from '../useLocalStorage.js';
type TestRefType = {
getPersistedValue(): string;
persistValue(value: string | null): void;
};
const DEFAULT_VALUE = 'default value';
const STORAGE_KEY = 'storageKey';
/**
* Sometimes merely accessing `localStorage` on the `window` object can result
* in a fatal error being thrown - for example in a private window in Firefox.
* Call this method to simulate this in your tests, and don't forget to call
* the cleanup function after you're done, so that other tests can run.
*/
function configureLocalStorageToFatalOnAccess(): () => void {
const savedPropertyDescriptor = Object.getOwnPropertyDescriptor(window, 'localStorage') as PropertyDescriptor;
Object.defineProperty(window, 'localStorage', {
get() {
throw new Error(
'Error: Accessing `localStorage` resulted in a fatal ' +
'(eg. accessing it in a private window in Firefox).'
);
},
});
return function restoreOldLocalStorage() {
Object.defineProperty(window, 'localStorage', savedPropertyDescriptor);
};
}
const TestComponent = forwardRef(function TestComponentImpl(_props, ref) {
const [persistedValue, setPersistedValue] = useLocalStorage<string | null>(STORAGE_KEY, DEFAULT_VALUE);
useImperativeHandle(
ref,
() => ({
getPersistedValue() {
return persistedValue;
},
persistValue(newValue: string | null) {
setPersistedValue(newValue);
},
}),
[persistedValue, setPersistedValue]
);
return null;
});
describe('useLocalStorage', () => {
let container: HTMLDivElement | null;
let root: ReturnType<typeof createRoot>;
let ref: React.RefObject<TestRefType | null>;
function renderTest() {
act(() => {
root.render(<TestComponent ref={ref} />);
});
}
beforeEach(() => {
localStorage.clear();
jest.resetAllMocks();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
ref = createRef();
});
afterEach(() => {
if (root) {
act(() => {
root.unmount();
});
}
});
describe('getting the persisted value', () => {
describe('when local storage has a value for the storage key', () => {
const PERSISTED_VALUE = 'value';
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockImplementation((storageKey) => {
if (storageKey !== STORAGE_KEY) {
return null;
}
return JSON.stringify(PERSISTED_VALUE);
});
expect(renderTest).not.toThrow();
});
it('returns that value', () => {
expect(ref.current?.getPersistedValue()).toBe(PERSISTED_VALUE);
});
});
describe('when local storage has no value for the storage key', () => {
const PERSISTED_VALUE = 'value';
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue(null);
expect(renderTest).not.toThrow();
});
it('returns the default value', () => {
expect(ref.current?.getPersistedValue()).toBe(DEFAULT_VALUE);
});
});
describe('when merely accessing local storage results in a fatal error', () => {
let restoreOldLocalStorage: () => void;
beforeEach(() => {
restoreOldLocalStorage = configureLocalStorageToFatalOnAccess();
expect(renderTest).not.toThrow();
});
afterEach(() => {
restoreOldLocalStorage();
});
it('renders with the default value', () => {
expect(ref.current?.getPersistedValue()).toBe(DEFAULT_VALUE);
});
});
describe('when local storage fatals on read', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockImplementation(() => {
throw new Error('Local storage derped');
});
expect(renderTest).not.toThrow();
});
it('renders with the default value', () => {
expect(ref.current?.getPersistedValue()).toBe(DEFAULT_VALUE);
});
});
describe('when local storage does not exist', () => {
let cachedLocalStorage: Storage;
beforeEach(() => {
cachedLocalStorage = localStorage;
// @ts-ignore - readonly
delete global.localStorage;
expect(renderTest).not.toThrow();
});
afterEach(() => {
// @ts-ignore - readonly
global.localStorage = cachedLocalStorage;
});
it('renders with the default value', () => {
expect(ref.current?.getPersistedValue()).toBe(DEFAULT_VALUE);
});
});
describe('when local storage contains invalid JSON', () => {
beforeEach(() => {
(localStorage.getItem as jest.Mock).mockReturnValue('' /* <- not valid JSON! */);
expect(renderTest).not.toThrow();
});
it('renders with the default value', () => {
expect(ref.current?.getPersistedValue()).toBe(DEFAULT_VALUE);
});
});
});
describe('setting the persisted value', () => {
describe('when setting to a non-null value', () => {
const NEW_VALUE = 'new value';
beforeEach(() => {
expect(renderTest).not.toThrow();
});
it('sets that value in local storage', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
});
expect(localStorage.setItem).toHaveBeenCalledWith(STORAGE_KEY, JSON.stringify(NEW_VALUE));
});
it('re-renders the component with the new value', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
});
expect(ref.current?.getPersistedValue()).toBe(NEW_VALUE);
});
describe('many times in a row', () => {
it('sets the new value in local storage once', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
ref.current?.persistValue(NEW_VALUE);
});
expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
expect(window.localStorage.setItem).toHaveBeenCalledWith(STORAGE_KEY, JSON.stringify(NEW_VALUE));
});
});
describe('multiple times ending with the current value', () => {
it("does not call local storage's setter", () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
ref.current?.persistValue(DEFAULT_VALUE);
});
expect(window.localStorage.setItem).toHaveBeenCalledTimes(0);
});
});
});
describe('when setting to `null`', () => {
beforeEach(() => {
expect(renderTest).not.toThrow();
});
it('removes the key from local storage', () => {
act(() => {
ref.current?.persistValue(null);
});
expect(localStorage.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
});
it('re-renders the component with `null`', () => {
act(() => {
ref.current?.persistValue(null);
});
expect(ref.current?.getPersistedValue()).toBe(null);
});
});
describe('when merely accessing local storage results in a fatal error', () => {
const NEW_VALUE = 'new value';
let restoreOldLocalStorage: () => void;
beforeEach(() => {
restoreOldLocalStorage = configureLocalStorageToFatalOnAccess();
expect(renderTest).not.toThrow();
});
afterEach(() => {
restoreOldLocalStorage();
});
it('re-renders the component with the new value', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
});
expect(ref.current?.getPersistedValue()).toBe(NEW_VALUE);
});
});
describe('when local storage fatals on write', () => {
const NEW_VALUE = 'new value';
beforeEach(() => {
(localStorage.setItem as jest.Mock).mockImplementation(() => {
throw new Error('Local storage derped');
});
expect(renderTest).not.toThrow();
});
it('re-renders the component with the new value', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
});
expect(ref.current?.getPersistedValue()).toBe(NEW_VALUE);
});
});
describe('when local storage does not exist', () => {
let cachedLocalStorage: Storage;
beforeEach(() => {
cachedLocalStorage = localStorage;
// @ts-ignore - readonly
delete global.localStorage;
expect(renderTest).not.toThrow();
});
afterEach(() => {
// @ts-ignore - readonly
global.localStorage = cachedLocalStorage;
});
describe('when setting to a non-null value', () => {
const NEW_VALUE = 'new value';
it('re-renders the component with the new value', () => {
act(() => {
ref.current?.persistValue(NEW_VALUE);
});
expect(ref.current?.getPersistedValue()).toBe(NEW_VALUE);
});
});
describe('when setting to `null`', () => {
it('re-renders the component with `null`', () => {
act(() => {
ref.current?.persistValue(null);
});
expect(ref.current?.getPersistedValue()).toBe(null);
});
});
});
});
});

View File

@@ -0,0 +1,28 @@
type InferValue<Prop extends PropertyKey, Desc> = Desc extends { get(): any; value: any }
? never
: Desc extends { value: infer T }
? Record<Prop, T>
: Desc extends { get(): infer T }
? Record<Prop, T>
: never;
type DefineProperty<Prop extends PropertyKey, Desc extends PropertyDescriptor> = Desc extends {
writable: any;
set(val: any): any;
}
? never
: Desc extends { writable: any; get(): any }
? never
: Desc extends { writable: false }
? Readonly<InferValue<Prop, Desc>>
: Desc extends { writable: true }
? InferValue<Prop, Desc>
: Readonly<InferValue<Prop, Desc>>;
export default function defineProperty<Obj extends object, Key extends PropertyKey, PDesc extends PropertyDescriptor>(
obj: Obj,
prop: Key,
val: PDesc
): asserts obj is Obj & DefineProperty<Key, PDesc> {
Object.defineProperty(obj, prop, val);
}

View File

@@ -0,0 +1,5 @@
import { WalletError } from '@solana/wallet-adapter-base';
export class WalletNotSelectedError extends WalletError {
name = 'WalletNotSelectedError';
}

View File

@@ -0,0 +1,49 @@
import { SolanaMobileWalletAdapterWalletName } from '@solana-mobile/wallet-adapter-mobile';
import { type Adapter, WalletReadyState } from '@solana/wallet-adapter-base';
export enum Environment {
DESKTOP_WEB,
MOBILE_WEB,
}
type Config = Readonly<{
adapters: Adapter[];
userAgentString: string | null;
}>;
function isWebView(userAgentString: string) {
return /(WebView|Version\/.+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+)|; wv\).+(Chrome)\/(\d+)\.(\d+)\.(\d+)\.(\d+))/i.test(
userAgentString
);
}
export default function getEnvironment({ adapters, userAgentString }: Config): Environment {
if (
adapters.some(
(adapter) =>
adapter.name !== SolanaMobileWalletAdapterWalletName &&
adapter.readyState === WalletReadyState.Installed
)
) {
/**
* There are only two ways a browser extension adapter should be able to reach `Installed` status:
*
* 1. Its browser extension is installed.
* 2. The app is running on a mobile wallet's in-app browser.
*
* In either case, we consider the environment to be desktop-like.
*/
return Environment.DESKTOP_WEB;
}
if (
userAgentString &&
// Step 1: Check whether we're on a platform that supports MWA at all.
/android/i.test(userAgentString) &&
// Step 2: Determine that we are *not* running in a WebView.
!isWebView(userAgentString)
) {
return Environment.MOBILE_WEB;
} else {
return Environment.DESKTOP_WEB;
}
}

View File

@@ -0,0 +1,14 @@
import { type Cluster } from '@solana/web3.js';
export default function getInferredClusterFromEndpoint(endpoint?: string): Cluster {
if (!endpoint) {
return 'mainnet-beta';
}
if (/devnet/i.test(endpoint)) {
return 'devnet';
} else if (/testnet/i.test(endpoint)) {
return 'testnet';
} else {
return 'mainnet-beta';
}
}

View File

@@ -0,0 +1,7 @@
export * from './ConnectionProvider.js';
export * from './errors.js';
export * from './useAnchorWallet.js';
export * from './useConnection.js';
export * from './useLocalStorage.js';
export * from './useWallet.js';
export * from './WalletProvider.js';

View File

@@ -0,0 +1,20 @@
import { type PublicKey, type Transaction, type VersionedTransaction } from '@solana/web3.js';
import { useMemo } from 'react';
import { useWallet } from './useWallet.js';
export interface AnchorWallet {
publicKey: PublicKey;
signTransaction<T extends Transaction | VersionedTransaction>(transaction: T): Promise<T>;
signAllTransactions<T extends Transaction | VersionedTransaction>(transactions: T[]): Promise<T[]>;
}
export function useAnchorWallet(): AnchorWallet | undefined {
const { publicKey, signTransaction, signAllTransactions } = useWallet();
return useMemo(
() =>
publicKey && signTransaction && signAllTransactions
? { publicKey, signTransaction, signAllTransactions }
: undefined,
[publicKey, signTransaction, signAllTransactions]
);
}

Some files were not shown because too many files have changed in this diff Show More