@oasisprotocol/sapphire-wagmi-v2
Version:
Wagmi & Viem support for the Oasis Sapphire ParaTime.
196 lines (174 loc) • 5.3 kB
text/typescript
/**
* @license Apache-2.0
*/
import {
type EIP2696_EthereumProvider,
NETWORKS,
type SapphireWrapConfig,
isWrappedEthereumProvider,
wrapEthereumProvider,
} from "@oasisprotocol/sapphire-paratime";
import { type InjectedParameters, injected } from "@wagmi/core";
import type { EthereumProvider as WCEthereumProvider } from "@walletconnect/ethereum-provider";
import type { EIP1193Provider } from "viem";
type WalletConnectProvider = typeof WCEthereumProvider.prototype;
export * from "@oasisprotocol/sapphire-viem-v2";
type Window = {
ethereum?: EIP1193Provider;
};
const cachedProviders: Map<EIP1193Provider, EIP1193Provider> = new Map();
/**
* Wrap the `window.ethereum` with the Sapphire encryption layer.
* Used to provide encrypted transactions and calldata to Wagmi browser dApps.
*
* ```
* import { injectedWithSapphire } from '@oasisprotocol/sapphire-wagmi-v2';
*
* export const config = createConfig({
* connectors: [
* injectedWithSapphire()
* ],
* ...
* });
* ```
*
* @returns Same as injected()
*/
export function injectedWithSapphire(
options?: SapphireWrapConfig,
): ReturnType<typeof injected> {
return injected({
target: () => {
return {
id: "injected-sapphire",
name: "Injected (Sapphire)",
provider(window?: Window) {
if (window?.ethereum) {
// Note: providers are cached as connectors are frequently retrieved
// it prevents sapphire wrapper being called multiple times
// which spams the RPC with oasis_callDataPublicKey requests
if (!cachedProviders.has(window.ethereum)) {
const wp = wrapEthereumProvider(
window.ethereum as unknown as Parameters<
typeof wrapEthereumProvider
>[0],
options,
) as EIP1193Provider;
cachedProviders.set(window.ethereum, wp);
return wp;
}
return cachedProviders.get(window.ethereum);
}
return undefined;
},
};
},
} as InjectedParameters);
}
interface BaseConnector {
getProvider?(): Promise<unknown> | unknown;
name?: string;
id?: string;
}
type ConnectorFactoryReturn<C extends BaseConnector = BaseConnector> = C;
const SAPPHIRE_CHAIN_IDS = [
NETWORKS.mainnet.chainId,
NETWORKS.testnet.chainId,
NETWORKS.localnet.chainId,
NETWORKS.pontusXTestnet.chainId,
NETWORKS.pontusXDevnet.chainId,
];
/**
* Wrap any Wagmi connector with the Sapphire encryption layer.
* Used to provide encrypted transactions and calldata to any connector type (WalletConnect, MetaMask, etc.).
*
* ```typescript
* import { wrapConnectorWithSapphire } from '@oasisprotocol/sapphire-wagmi-v2';
* import { walletConnect } from '@wagmi/connectors';
*
* export const config = createConfig({
* connectors: [
* wrapConnectorWithSapphire(
* walletConnect({ projectId: 'your-project-id' }),
* {
* id: 'walletconnect-sapphire',
* name: 'WalletConnect (Sapphire)',
* }
* )
* ],
* ...
* });
* ```
*
* @returns A wrapped connector factory that provides Sapphire encryption
*/
export function wrapConnectorWithSapphire<
// biome-ignore lint/suspicious/noExplicitAny: Generic type parameter needs to accept any array type
T extends any[],
C extends BaseConnector = BaseConnector,
>(
connectorFactory: (...args: T) => ConnectorFactoryReturn<C>,
options?: {
customWrapper?: <TConnector extends C>(connector: TConnector) => TConnector;
sapphireWrapConfig?: SapphireWrapConfig;
name?: string;
id?: string;
},
): (...args: T) => ConnectorFactoryReturn<C> {
const cachedWrappedProviders: Map<
EIP2696_EthereumProvider,
EIP2696_EthereumProvider
> = new Map();
return (...args: T) => {
const baseConnector = connectorFactory(...args);
if (options?.name) {
baseConnector.name = options.name;
}
if (options?.id) {
baseConnector.id = options.id;
}
if (options?.customWrapper) {
return options.customWrapper(baseConnector);
}
const originalGetProvider = baseConnector.getProvider?.bind(baseConnector);
if (originalGetProvider) {
baseConnector.getProvider = async () => {
const provider = await originalGetProvider();
if (!provider) {
return provider;
}
let chainId = null;
if ((provider as WalletConnectProvider).isWalletConnect) {
if (!(provider as WalletConnectProvider).connected) {
return provider;
}
chainId = (provider as WalletConnectProvider).chainId;
} else {
const chainIdResponse = await (provider as EIP1193Provider)?.request({
method: "eth_chainId",
});
chainId = Number.parseInt(chainIdResponse, 16);
}
if (!SAPPHIRE_CHAIN_IDS.includes(chainId)) {
return provider;
}
if (isWrappedEthereumProvider(provider as EIP2696_EthereumProvider)) {
return provider;
}
if (!cachedWrappedProviders.has(provider as EIP2696_EthereumProvider)) {
const wrappedProvider = wrapEthereumProvider(
provider as EIP2696_EthereumProvider,
options?.sapphireWrapConfig,
);
cachedWrappedProviders.set(
provider as EIP2696_EthereumProvider,
wrappedProvider,
);
return wrappedProvider;
}
return cachedWrappedProviders.get(provider as EIP2696_EthereumProvider);
};
}
return baseConnector;
};
}