Skip to content

Commit 5e1d8c8

Browse files
committed
add wiresaw transport
1 parent 3ec8374 commit 5e1d8c8

File tree

3 files changed

+173
-3
lines changed

3 files changed

+173
-3
lines changed

packages/entrykit/src/createWagmiConfig.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Chain, Transport } from "viem";
22
import { connectorsForWallets } from "@rainbow-me/rainbowkit";
33
import { Config, CreateConfigParameters, createConfig } from "wagmi";
44
import { getWallets } from "./getWallets";
5+
import { mapObject } from "@latticexyz/common/utils";
6+
import { wiresaw } from "./quarry/transports/wiresaw";
57

68
export type CreateWagmiConfigOptions<
79
chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]],
@@ -27,11 +29,12 @@ export function createWagmiConfig<
2729
appName: config.appName,
2830
projectId: config.walletConnectProjectId,
2931
});
32+
const transports = mapObject(config.transports, (transport) => wiresaw(transport));
3033

3134
return createConfig({
3235
connectors,
3336
chains: config.chains,
34-
transports: config.transports,
37+
transports,
3538
pollingInterval: config.pollingInterval,
3639
}) as never;
3740
}

packages/entrykit/src/getBundlerTransport.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ import { transactionQueue } from "@latticexyz/common/actions";
22
import { Chain, Client, Transport, createClient, http, keccak256, stringToHex } from "viem";
33
import { privateKeyToAccount } from "viem/accounts";
44
import { userOpExecutor } from "./quarry/transports/userOpExecutor";
5+
import { wiresaw } from "./quarry/transports/wiresaw";
56

67
export function getBundlerTransport(client: Client<Transport, Chain>) {
78
const bundlerHttpUrl = client.chain.rpcUrls.bundler?.http[0];
89
// TODO: bundler websocket
10+
// TODO: do chain checks/conditionals inside the transports
911
const bundlerTransport = bundlerHttpUrl
10-
? alto(http(bundlerHttpUrl))
12+
? client.chain.id === 17420
13+
? wiresaw(http(bundlerHttpUrl))
14+
: client.chain.id === 690 || client.chain.id === 17069
15+
? alto(http(bundlerHttpUrl))
16+
: http(bundlerHttpUrl)
1117
: client.chain.id === 31337
1218
? userOpExecutor({
1319
executor: createClient({
@@ -24,7 +30,7 @@ export function getBundlerTransport(client: Client<Transport, Chain>) {
2430
return bundlerTransport;
2531
}
2632

27-
export function alto<transport extends Transport>(createTransport: transport): transport {
33+
function alto<transport extends Transport>(createTransport: transport): transport {
2834
return ((opts) => {
2935
const transport = createTransport(opts);
3036
const request: typeof transport.request = async (req) => {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import {
2+
BundlerRpcSchema,
3+
Hash,
4+
Hex,
5+
PublicRpcSchema,
6+
RpcTransactionReceipt,
7+
RpcUserOperationReceipt,
8+
Transport,
9+
fallback,
10+
http,
11+
webSocket,
12+
} from "viem";
13+
import { getRpcMethod, getRpcSchema, TransportRequestFn, TransportRequestFnMapped } from "./common";
14+
15+
export type WiresawRpcSchema = [
16+
{
17+
Method: "wiresaw_call";
18+
Parameters: getRpcMethod<PublicRpcSchema, "eth_call">["Parameters"];
19+
ReturnType: getRpcMethod<PublicRpcSchema, "eth_call">["ReturnType"];
20+
},
21+
{
22+
Method: "wiresaw_getTransactionReceipt";
23+
Parameters: getRpcMethod<PublicRpcSchema, "eth_getTransactionReceipt">["Parameters"];
24+
ReturnType: getRpcMethod<PublicRpcSchema, "eth_getTransactionReceipt">["ReturnType"];
25+
},
26+
{
27+
Method: "wiresaw_sendUserOperation";
28+
Parameters: getRpcMethod<BundlerRpcSchema, "eth_sendUserOperation">["Parameters"];
29+
ReturnType: {
30+
userOpHash: Hex;
31+
txHash: Hex;
32+
};
33+
},
34+
];
35+
36+
export type OverriddenMethods = [
37+
...getRpcSchema<PublicRpcSchema, "eth_chainId" | "eth_call" | "eth_getTransactionReceipt">,
38+
...getRpcSchema<BundlerRpcSchema, "eth_sendUserOperation" | "eth_getUserOperationReceipt">,
39+
];
40+
41+
type ChainCache = {
42+
receipts: Map<Hash, RpcTransactionReceipt>;
43+
/**
44+
* user op hash to transaction hash
45+
*/
46+
userOps: Map<Hash, Hash>;
47+
};
48+
49+
const cache = new Map<Hex, ChainCache>();
50+
51+
function getCache(chainId: Hex) {
52+
const cached = cache.get(chainId) ?? {
53+
receipts: new Map<Hash, RpcTransactionReceipt>(),
54+
userOps: new Map<Hash, Hash>(),
55+
};
56+
if (!cache.has(chainId)) cache.set(chainId, cached);
57+
return cached;
58+
}
59+
60+
export function wiresaw<const transport extends Transport>(getTransport: transport): transport {
61+
return ((args) => {
62+
const getWiresawTransport =
63+
args.chain?.rpcUrls && "wiresaw" in args.chain.rpcUrls
64+
? args.chain.rpcUrls.wiresaw.webSocket
65+
? fallback([webSocket(args.chain.rpcUrls.wiresaw.webSocket[0]), http(args.chain.rpcUrls.wiresaw.http[0])])
66+
: http(args.chain.rpcUrls.wiresaw.http[0])
67+
: undefined;
68+
if (!getWiresawTransport) return getTransport(args);
69+
70+
const transport = getTransport(args) as {
71+
request: TransportRequestFn<OverriddenMethods>;
72+
};
73+
const wiresawTransport = getWiresawTransport(args) as {
74+
request: TransportRequestFn<WiresawRpcSchema>;
75+
};
76+
77+
let chainId: Hex | null = null;
78+
async function getChainId() {
79+
return (chainId ??= await transport.request({ method: "eth_chainId" }));
80+
}
81+
82+
async function getReceipt(hash: Hex, receipts: ChainCache["receipts"]) {
83+
if (receipts.has(hash)) return receipts.get(hash)!;
84+
const [wiresawReceipt, chainReceipt] = await Promise.all([
85+
wiresawTransport.request({
86+
method: "wiresaw_getTransactionReceipt",
87+
params: [hash],
88+
}),
89+
transport.request({
90+
method: "eth_getTransactionReceipt",
91+
params: [hash],
92+
}),
93+
]);
94+
const receipt = wiresawReceipt ?? chainReceipt;
95+
if (receipt != null) receipts.set(hash, receipt);
96+
return receipt;
97+
}
98+
99+
const request: TransportRequestFnMapped<OverriddenMethods> = async ({ method, params }, opts) => {
100+
if (method === "eth_chainId") {
101+
return await getChainId();
102+
}
103+
104+
// TODO: only route to wiresaw if using pending block tag
105+
if (method === "eth_call") {
106+
return wiresawTransport.request({ method: "wiresaw_call", params });
107+
}
108+
109+
// We intentionally don't reroute `eth_sendRawTransaction` because Wiresaw
110+
// already handles this method within the RPC spec, where it returns the
111+
// tx hash, which can be fetched immediately with `eth_getTransactionReceipt`.
112+
113+
if (method === "eth_getTransactionReceipt") {
114+
const { receipts } = getCache(await getChainId());
115+
const [hash] = params;
116+
return await getReceipt(hash, receipts);
117+
}
118+
119+
if (method === "eth_sendUserOperation") {
120+
const { userOps } = getCache(await getChainId());
121+
const result = await wiresawTransport.request({
122+
method: "wiresaw_sendUserOperation",
123+
params,
124+
});
125+
userOps.set(result.userOpHash, result.txHash);
126+
return result.userOpHash;
127+
}
128+
129+
if (method === "eth_getUserOperationReceipt") {
130+
const { userOps } = getCache(await getChainId());
131+
const [userOpHash] = params;
132+
const transactionHash = userOps.get(userOpHash);
133+
134+
if (!transactionHash) {
135+
// TODO: look up user op logs
136+
// https://github.com/latticexyz/alto/blob/206dd8fc0d672a3d49e06d4bdd2eff4d519bdea3/src/executor/executorManager.ts#L617-L625
137+
throw new Error(`Could not find transaction hash for user op hash "${userOpHash}".`);
138+
}
139+
140+
// return instant/partial receipt for now until we can find a good way
141+
// to opt-in to this when using things like permissionless actions, which
142+
// call out to `waitForUserOperationReceipt`
143+
return {
144+
success: true,
145+
userOpHash,
146+
receipt: {
147+
transactionHash,
148+
},
149+
} as RpcUserOperationReceipt;
150+
151+
// const receipt = await getReceipt(transactionHash, receipts);
152+
// if (!receipt) return null;
153+
// return getUserOperationReceipt(userOpHash, receipt);
154+
}
155+
156+
return await transport.request({ method, params }, opts);
157+
};
158+
159+
return { ...transport, request };
160+
}) as transport;
161+
}

0 commit comments

Comments
 (0)