Skip to content

Commit

Permalink
core: add injected wallet discovery hook
Browse files Browse the repository at this point in the history
  • Loading branch information
fracek committed Oct 30, 2023
1 parent 128a926 commit 404747a
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 40 deletions.
124 changes: 124 additions & 0 deletions packages/core/src/connectors/discovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { StarknetWindowObject } from "get-starknet-core";
import { useCallback, useEffect, useMemo, useState } from "react";

import { Connector } from "./base";
import { injected } from "./helpers";

export type UseInjectedConnectorsProps = {
/** List of recommended connectors to display. */
recommended?: Connector[];
/** Whether to include recommended connectors in the list. */
includeRecommended?: "always" | "onlyIfNoConnectors";
/** How to order connectors. */
order?: "random" | "alphabetical";
};

export type UseInjectedConnectorsResult = {
/** Connectors list. */
connectors: Connector[];
};

export function useInjectedConnectors({
recommended,
includeRecommended = "always",
order = "alphabetical",
}: UseInjectedConnectorsProps): UseInjectedConnectorsResult {
const [injectedConnectors, setInjectedConnectors] = useState<Connector[]>([]);

const refreshConnectors = useCallback(() => {
const wallets = scanObjectForWallets(window);
const connectors = wallets.map((wallet) => injected({ id: wallet.id }));
setInjectedConnectors(connectors);
}, [setInjectedConnectors]);

useEffect(() => {
refreshConnectors();
}, [refreshConnectors]);

const connectors = useMemo(() => {
return mergeConnectors(injectedConnectors, recommended ?? [], {
includeRecommended,
order,
});
}, [injectedConnectors, recommended, includeRecommended, order]);

return { connectors };
}

function mergeConnectors(
injected: Connector[],
recommended: Connector[],
{
includeRecommended,
order,
}: Required<Pick<UseInjectedConnectorsProps, "includeRecommended" | "order">>,
): Connector[] {
const injectedIds = new Set(injected.map((connector) => connector.id));
const allConnectors = injected;
const shouldAddRecommended =
includeRecommended === "always" ||
(includeRecommended === "onlyIfNoConnectors" && injected.length === 0);
if (shouldAddRecommended) {
allConnectors.push(
...recommended.filter((connector) => !injectedIds.has(connector.id)),
);
}

if (order === "random") {
return shuffle(allConnectors);
}
return allConnectors.sort((a, b) => a.id.localeCompare(b.id));
}

function shuffle<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
// @ts-ignore: not important
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}

export function scanObjectForWallets(
// biome-ignore lint: window could contain anything
obj: Record<string, any>,
): StarknetWindowObject[] {
return Object.values(
Object.getOwnPropertyNames(obj).reduce<
Record<string, StarknetWindowObject>
>((wallets, key) => {
if (key.startsWith("starknet")) {
const wallet = obj[key];

if (isWalletObject(wallet) && !wallets[wallet.id]) {
wallets[wallet.id] = wallet;
}
}
return wallets;
}, {}),
);
}

// biome-ignore lint: window could contain anything
function isWalletObject(wallet: any): wallet is StarknetWindowObject {
try {
return (
wallet &&
[
// wallet's must have methods/members, see IStarknetWindowObject
"request",
"isConnected",
"provider",
"enable",
"isPreauthorized",
"on",
"off",
"version",
"id",
"name",
"icon",
].every((key) => key in wallet)
);
} catch (err) {}
return false;
}
27 changes: 27 additions & 0 deletions packages/core/src/connectors/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { InjectedConnector } from "./injected";

export function argent(): InjectedConnector {
return new InjectedConnector({
options: {
id: "argentX",
name: "Argent",
},
});
}

export function braavos(): InjectedConnector {
return new InjectedConnector({
options: {
id: "braavos",
name: "Braavos",
},
});
}

export function injected({ id }: { id: string }): InjectedConnector {
return new InjectedConnector({
options: {
id,
},
});
}
35 changes: 6 additions & 29 deletions packages/core/src/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,13 @@
export { Connector } from "./base";
export { InjectedConnector, type InjectedConnectorOptions } from "./injected";

export {
type UseInjectedConnectorsProps,
type UseInjectedConnectorsResult,
useInjectedConnectors,
} from "./discovery";
export {
MockConnector,
type MockConnectorAccounts,
type MockConnectorOptions,
} from "./mock";

import { InjectedConnector } from "./injected";

export function argent(): InjectedConnector {
return new InjectedConnector({
options: {
id: "argentX",
name: "Argent",
},
});
}

export function braavos(): InjectedConnector {
return new InjectedConnector({
options: {
id: "braavos",
name: "Braavos",
},
});
}

export function injected({ id }: { id: string }): InjectedConnector {
return new InjectedConnector({
options: {
id,
},
});
}
export { argent, braavos, injected } from "./helpers";
23 changes: 12 additions & 11 deletions website/components/starknet/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import {
argent,
braavos,
} from "@starknet-react/core";
import { useMemo } from "react";
import { useInjectedConnectors } from "@/../packages/core/dist";

export function StarknetProvider({ children }: { children: React.ReactNode }) {
const chains = [goerli, mainnet];
const providers = [publicProvider()];
const connectors = useMemo(() => shuffle([argent(), braavos()]), []);
const { connectors } = useInjectedConnectors({
// Show these connectors if the user has no connector installed.
recommended: [
argent(),
braavos(),
],
// Hide recommended connectors if the user has any connector installed.
includeRecommended: "onlyIfNoConnectors",
// Randomize the order of the connectors.
order: "random"
});

return (
<StarknetConfig
Expand All @@ -25,12 +35,3 @@ export function StarknetProvider({ children }: { children: React.ReactNode }) {
</StarknetConfig>
);
}

function shuffle<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
// @ts-ignore: not important
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}

0 comments on commit 404747a

Please sign in to comment.