UNPKG

@oasisprotocol/sapphire-viem-v2

Version:
150 lines 5.52 kB
// SPDX-License-Identifier: Apache-2.0 import { KeyFetcher, NETWORKS as SapphireNETWORKS, wrapEthereumProvider, } from "@oasisprotocol/sapphire-paratime"; import { http, defineChain, serializeTransaction } from "viem"; /** * sapphire-localnet chain, a local chain for local people */ export const sapphireLocalnet = defineChain({ id: SapphireNETWORKS.localnet.chainId, name: "Oasis Sapphire Localnet", network: "sapphire-localnet", nativeCurrency: { name: "Sapphire Local Rose", symbol: "TEST", decimals: 18 }, rpcUrls: { default: { http: [SapphireNETWORKS.localnet.defaultGateway], //webSocket: ["ws://localhost:8546/ws"], }, }, testnet: true, }); // Hidden property used to detect if transport is Sapphire-wrapped export const SAPPHIRE_WRAPPED_VIEM_TRANSPORT = Symbol("#SAPPHIRE_WRAPPED_VIEM_TRANSPORT"); /** * Provide a Sapphire encrypted RPC transport for Wagmi or Viem. * * Example: * ``` * import { createConfig } from 'viem'; * import { sapphireHttpTransport } from '@oasisprotocol/sapphire-viem-v2'; * * export const config = createConfig({ * transports: { * [sapphireTestnet.id]: sapphireHttpTransport() * }, * ... * }); * ``` * * Results for every instance of sapphireHttpTransport() are cached to prevent * the wrapper from being instantiated multiple times. * * @returns Same as http() */ export function sapphireHttpTransport(sapphireConfig, overrideUrl, httpConfig) { const cachedProviders = {}; return ((params) => { const defaultUrl = params.chain?.rpcUrls.default.http[0]; const url = overrideUrl || defaultUrl; if (!url) { throw new Error("sapphireHttpTransport() needs a chain.rpcUrls.default.http[0] to be set or explicit url"); } if (!(url in cachedProviders)) { const x = wrapEthereumProvider(http(url, httpConfig)(params), sapphireConfig); Reflect.set(x, SAPPHIRE_WRAPPED_VIEM_TRANSPORT, true); cachedProviders[url] = x; } return cachedProviders[url]; }); } /** * Creates a Viem transaction serializer which encrypts transactions prior to * them being signed. This is compatible with both local wallet clients and * injected wallets. * * Example * ``` * import { defineChain } from 'viem'; * import { createSapphireSerializer } from '@oasisprotocol/sapphire-viem-v2'; * * defineChain({ * serializers: { * transaction: createSapphireSerializer(publicClient) * }, * ... * }); * ``` * * @param client Provides upstream access to Sapphire JSON-RPC via `.request` * @param originalSerializer Optional serializer to wrap, otherwise will use default * @returns Sapphire wrapped transaction encryption serializer */ export async function createSapphireSerializer(client, originalSerializer) { // Don't double-wrap serializer if (originalSerializer && Reflect.has(originalSerializer, SAPPHIRE_WRAPPED_VIEM_SERIALIZER)) { return originalSerializer; } // As the serializer is synchronous, fetching keys while running const fetcher = new KeyFetcher(); const provider = client; await fetcher.fetch(provider); // The fetcher runs in the background, routinely fetching the keys // This means when the serializer requests a calldata public key one will // have been retrieved pre-emptively. const intervalId = setInterval(async () => { await fetcher.fetch(provider); }, fetcher.timeoutMilliseconds); // The interval ID is unreferenced to prevent Node from hanging at exit // See discussion on https://github.com/oasisprotocol/sapphire-paratime/pull/379 // This is only available in NodeJS, and not in browsers if (typeof intervalId.unref === "function") { intervalId.unref(); } const wrappedSerializer = ((tx, sig) => { if (!sig) { const cipher = fetcher.cipherSync(); const encryptedData = cipher.encryptCall(tx.data); tx.data = encryptedData; } if (originalSerializer) { return originalSerializer(tx, sig); } return serializeTransaction(tx, sig); }); Reflect.set(wrappedSerializer, SAPPHIRE_WRAPPED_VIEM_SERIALIZER, true); return wrappedSerializer; } // Hidden property to test if serializer is Sapphire-wrapped export const SAPPHIRE_WRAPPED_VIEM_SERIALIZER = Symbol("#SAPPHIRE_WRAPPED_VIEM_SERIALIZER"); /** * Add the Sapphire transaction encryption wrapper to a wallet client * * Example: * ``` * walletClient = await wrapWalletClient(createWalletClient({ * account, * chain: sapphireLocalnet, * transport: sapphireHttpTransport() * })); * ``` * * @param client Wagmi wallet client * @returns wrapped wallet client */ export async function wrapWalletClient(client) { if (!client.chain) { throw new Error("No chain defined in client"); } const originalSerializer = client.chain?.serializers?.transaction; // Override any existing transaction serializer, or create a new one // With one that auto-encrypts transactions before they're signed if (!originalSerializer || !Reflect.get(originalSerializer, SAPPHIRE_WRAPPED_VIEM_SERIALIZER)) { if (!client.chain.serializers) { client.chain.serializers = {}; } client.chain.serializers.transaction = await createSapphireSerializer(client, originalSerializer); } return client; } //# sourceMappingURL=index.js.map