Skip to content

[WIP] 1.0.0 #1097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/core/base/src/signer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SolanaSignInInput, SolanaSignInOutput } from '@solana/wallet-standard-features';
import type { SolanaSignInInput, SolanaSignInOutput, SolanaSignMessageOutput } from '@solana/wallet-standard-features';
import type { Connection, TransactionSignature } from '@solana/web3.js';
import {
BaseWalletAdapter,
Expand Down Expand Up @@ -115,8 +115,10 @@ export abstract class BaseSignerWalletAdapter<Name extends string = string>
}
}

export type SignMessageOutput = SolanaSignMessageOutput;

export interface MessageSignerWalletAdapterProps<Name extends string = string> extends WalletAdapterProps<Name> {
signMessage(message: Uint8Array): Promise<Uint8Array>;
signMessage(message: Uint8Array): Promise<SignMessageOutput>;
}

export type MessageSignerWalletAdapter<Name extends string = string> = WalletAdapter<Name> &
Expand All @@ -126,11 +128,14 @@ export abstract class BaseMessageSignerWalletAdapter<Name extends string = strin
extends BaseSignerWalletAdapter<Name>
implements MessageSignerWalletAdapter<Name>
{
abstract signMessage(message: Uint8Array): Promise<Uint8Array>;
abstract signMessage(message: Uint8Array): Promise<SignMessageOutput>;
}

export type SignInInput = SolanaSignInInput;
export type SignInOutput = SolanaSignInOutput;

export interface SignInMessageSignerWalletAdapterProps<Name extends string = string> extends WalletAdapterProps<Name> {
signIn(input?: SolanaSignInInput): Promise<SolanaSignInOutput>;
signIn(input?: SignInInput): Promise<SignInOutput>;
}

export type SignInMessageSignerWalletAdapter<Name extends string = string> = WalletAdapter<Name> &
Expand All @@ -140,5 +145,5 @@ export abstract class BaseSignInMessageSignerWalletAdapter<Name extends string =
extends BaseMessageSignerWalletAdapter<Name>
implements SignInMessageSignerWalletAdapter<Name>
{
abstract signIn(input?: SolanaSignInInput): Promise<SolanaSignInOutput>;
abstract signIn(input?: SignInInput): Promise<SignInOutput>;
}
1 change: 0 additions & 1 deletion packages/core/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"react": "*"
},
"dependencies": {
"@solana-mobile/wallet-adapter-mobile": "^2.2.0",
"@solana/wallet-adapter-base": "workspace:^",
"@solana/wallet-standard-wallet-adapter-react": "^1.1.4"
},
Expand Down
52 changes: 5 additions & 47 deletions packages/core/react/src/WalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
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';

Expand All @@ -35,51 +26,18 @@ function getIsMobile(adapters: Adapter[]) {
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]
() => adaptersWithStandardAdapters.find((a) => a.name === walletName) ?? null,
[adaptersWithStandardAdapters, walletName]
);
const changeWallet = useCallback(
(nextWalletName: WalletName<string> | null) => {
Expand All @@ -90,7 +48,7 @@ export function WalletProvider({
// 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.name !== 'Solana Mobile Wallet'
) {
adapter.disconnect();
}
Expand Down Expand Up @@ -125,7 +83,7 @@ export function WalletProvider({
}, [autoConnect, adapter]);
const isUnloadingRef = useRef(false);
useEffect(() => {
if (walletName === SolanaMobileWalletAdapterWalletName && getIsMobile(adaptersWithStandardAdapters)) {
if (walletName === 'Solana Mobile Wallet' && getIsMobile(adaptersWithStandardAdapters)) {
isUnloadingRef.current = false;
return;
}
Expand Down Expand Up @@ -159,7 +117,7 @@ export function WalletProvider({
);
return (
<WalletProviderBase
wallets={adaptersWithMobileWalletAdapter}
wallets={adaptersWithStandardAdapters}
adapter={adapter}
isUnloadingRef={isUnloadingRef}
onAutoConnectRequest={handleAutoConnectRequest}
Expand Down
18 changes: 9 additions & 9 deletions packages/core/react/src/__tests__/WalletProviderBase-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ describe('WalletProviderBase', () => {
expect(onError).not.toBeCalled();
});
describe('when a wallet is connected', () => {
beforeEach(async () => {
await act(() => {
beforeEach(() => {
act(() => {
ref.current?.getWalletContextState().connect();
});
expect(ref.current?.getWalletContextState()).toMatchObject({
Expand Down Expand Up @@ -330,7 +330,7 @@ describe('WalletProviderBase', () => {
fooWalletAdapter.connectionPromise = new Promise<void>((resolve) => {
commitConnection = resolve;
});
await act(() => {
act(() => {
ref.current?.getWalletContextState().connect();
});
});
Expand All @@ -344,8 +344,8 @@ describe('WalletProviderBase', () => {
});
});
describe('once connected', () => {
beforeEach(async () => {
await act(() => {
beforeEach(() => {
act(() => {
commitConnection();
});
});
Expand All @@ -364,13 +364,13 @@ describe('WalletProviderBase', () => {
beforeEach(async () => {
window.open = jest.fn();
renderTest({ adapter: fooWalletAdapter });
await act(() => {
act(() => {
ref.current?.getWalletContextState().connect();
});
fooWalletAdapter.disconnectionPromise = new Promise<void>((resolve) => {
commitDisconnection = resolve;
});
await act(() => {
act(() => {
ref.current?.getWalletContextState().disconnect();
});
});
Expand All @@ -380,8 +380,8 @@ describe('WalletProviderBase', () => {
});
});
describe('once disconnected', () => {
beforeEach(async () => {
await act(() => {
beforeEach(() => {
act(() => {
commitDisconnection();
});
});
Expand Down
35 changes: 1 addition & 34 deletions packages/core/react/src/__tests__/WalletProviderDesktop-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@

'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';
Expand Down Expand Up @@ -133,34 +128,6 @@ describe('WalletProvider when the environment is `DESKTOP_WEB`', () => {
});
});
});
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);
Expand Down Expand Up @@ -273,7 +240,7 @@ describe('WalletProvider when the environment is `DESKTOP_WEB`', () => {
ref.current?.getWalletContextState().select('FooWallet' as WalletName<'FooWallet'>);
await Promise.resolve(); // Flush all promises in effects after calling `select()`.
});
await act(() => {
act(() => {
ref.current?.getWalletContextState().connect();
});
});
Expand Down
Loading