Skip to content

Commit 38e2fdc

Browse files
committed
refactor: address comments and fix issues
1 parent 4ea4250 commit 38e2fdc

File tree

6 files changed

+228
-241
lines changed

6 files changed

+228
-241
lines changed

pnpm-lock.yaml

Lines changed: 44 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

target_chains/ethereum/sdk/js/README.md

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,53 @@ The `fillPythUpdate` function helps you automatically determine what Pyth price
2525
This function uses the `trace_callMany` method by default but can be used with `debug_traceCall` and a bundler as well. See the example below for more information.
2626

2727
```typescript
28-
import { fillPythUpdate, multicall3Bundler } from "@pythnetwork/pyth-evm-js";
28+
import { fillPythUpdate, multicall3Bundler, CallRequest } from "@pythnetwork/pyth-evm-js";
2929
import { createPublicClient, http } from "viem";
3030
import { optimismSepolia } from "viem/chains";
3131

32-
async function example() {
33-
const client = createPublicClient({
34-
chain: optimismSepolia,
35-
transport: http("YOUR_RPC_ENDPOINT"),
36-
});
37-
38-
// Fill Pyth update data using "trace_callMany"
39-
const pythUpdate = await fillPythUpdate(
40-
client,
41-
{
42-
to: "0x3252c2F7962689fA17f892C52555613f36056f22",
43-
data: "0xd09de08a", // Your transaction calldata
44-
from: "0x78357316239040e19fC823372cC179ca75e64b81",
45-
},
46-
"0x0708325268df9f66270f1401206434524814508b", // Pyth contract address
47-
"https://hermes.pyth.network", // Hermes endpoint
48-
{
49-
method: "trace_callMany"
50-
maxIter: 5,
51-
},
52-
);
53-
54-
// Fill Pyth update data using "debug_traceCall"
55-
const pythUpdateWithDebugTrace = await fillPythUpdate(
56-
client,
57-
{
58-
to: "0x3252c2F7962689fA17f892C52555613f36056f22",
59-
data: "0xd09de08a", // Your transaction calldata
60-
from: "0x78357316239040e19fC823372cC179ca75e64b81",
61-
},
62-
"0x0708325268df9f66270f1401206434524814508b", // Pyth contract address
63-
"https://hermes.pyth.network", // Hermes endpoint
64-
{
65-
method: "debug_traceCall",
66-
bundler: multicall3Bundler // or any function that takes a PythUpdate and a CallRequest and produces a CallRequest
67-
maxIter: 5,
68-
},
69-
);
70-
71-
if (pythUpdate) {
72-
console.log("Pyth update needed:", pythUpdate);
73-
// Bundle the calls together, or pass the pythUpdate.updateData to your contract.
74-
} else {
75-
console.log("No Pyth data needed for this transaction");
76-
}
32+
const PYTH_CONTRACT_OP_SEPOLIA = "0x0708325268df9f66270f1401206434524814508b"
33+
const HERMES_ENDPOINT = "https://hermes.pyth.network"
34+
35+
const client = createPublicClient({
36+
chain: optimismSepolia,
37+
transport: http("YOUR_RPC_ENDPOINT"),
38+
});
39+
40+
const call: CallRequest = {
41+
to: "0x3252c2F7962689fA17f892C52555613f36056f22",
42+
data: "0xd09de08a", // Your transaction calldata
43+
from: "0x78357316239040e19fC823372cC179ca75e64b81",
44+
};
45+
46+
// Fill Pyth update data using "trace_callMany"
47+
const pythUpdate = await fillPythUpdate(
48+
client,
49+
call,
50+
PYTH_CONTRACT_OP_SEPOLIA,
51+
HERMES_ENDPOINT,
52+
{
53+
method: "trace_callMany",
54+
maxIter: 5,
55+
},
56+
);
57+
58+
// Fill Pyth update data using "debug_traceCall"
59+
const _pythUpdateWithDebugTraceCall = await fillPythUpdate(
60+
client,
61+
call,
62+
PYTH_CONTRACT_OP_SEPOLIA,
63+
HERMES_ENDPOINT,
64+
{
65+
method: "debug_traceCall",
66+
bundler: multicall3Bundler, // or any function that takes a PythUpdate and a CallRequest and produces a CallRequest
67+
maxIter: 5,
68+
},
69+
);
70+
71+
if (pythUpdate) {
72+
console.log("Pyth update needed:", pythUpdate);
73+
// Bundle the calls together, or pass the pythUpdate.updateData to your contract.
74+
} else {
75+
console.log("No Pyth data needed for this transaction");
7776
}
78-
```
77+
```

target_chains/ethereum/sdk/js/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"jest": "^29.4.1",
4848
"prettier": "catalog:",
4949
"ts-jest": "^29.0.5",
50-
"ts-node": "catalog:",
5150
"typescript": "catalog:"
5251
},
5352
"dependencies": {

target_chains/ethereum/sdk/js/src/filler.ts

Lines changed: 126 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
Chain,
99
} from "viem";
1010

11-
import { multicall3Bundler } from "./multicall3-bundler";
1211
import { IPythAbi } from "./pyth-abi";
1312
import {
1413
debugTraceCallAction,
@@ -28,7 +27,7 @@ export type CallRequest = {
2827
/** The target contract address */
2928
to: Address;
3029
/** The encoded function call data (optional) */
31-
data?: `0x${string}`;
30+
data?: Hex;
3231
/** The amount of ETH to send with the call (optional) */
3332
value?: bigint;
3433
};
@@ -67,43 +66,36 @@ export type Bundler = (
6766
) => CallRequest;
6867

6968
/**
70-
* Configuration for debug_traceCall method.
71-
* Use this when you want to trace a single bundled transaction that combines the Pyth update with the original call.
72-
* The bundler function is responsible for creating a single transaction that executes both operations.
73-
*
74-
* The bundler is crucial because debug_traceCall can only trace one transaction at a time. The bundler
75-
* must create a single call that includes both the Pyth price update and the original transaction logic.
76-
* This allows the tracer to see all the Pyth price feed calls that would be made in the actual execution.
77-
*/
78-
export type DebugTraceCallConfig = {
79-
/** Must be "debug_traceCall" */
80-
method: "debug_traceCall";
81-
/** Function that takes a Pyth update and original call, returns a single bundled call request.
82-
* Common bundlers include multicall3Bundler for combining calls via Multicall3 contract.
83-
* The bundler must create a single transaction that executes both the Pyth update and the original call. */
84-
bundler: Bundler;
85-
/** Maximum number of iterations to find all required price feeds. Default is 5.
86-
* Each iteration traces the current transaction to find new Pyth price feed calls. */
87-
maxIter: number;
88-
};
89-
90-
/**
91-
* Configuration for trace_callMany method.
92-
* Use this when you want to trace multiple separate transactions (Pyth update + original call).
93-
* This method traces each call independently, which may be more accurate but requires more RPC calls.
94-
*/
95-
export type TraceCallManyConfig = {
96-
/** Must be "trace_callMany" */
97-
method: "trace_callMany";
98-
/** Maximum number of iterations to find all required price feeds. Default is 5.
99-
* Each iteration traces the current set of transactions to find new Pyth price feed calls. */
100-
maxIter: number;
101-
};
102-
103-
/**
104-
* Union type for tracing configuration options
69+
* Tracing configuration options
10570
*/
106-
export type Config = DebugTraceCallConfig | TraceCallManyConfig;
71+
export type Config = {
72+
/** Maximum number of iterations to find all required price feeds. Default is 5. */
73+
maxIter?: number;
74+
} & (
75+
| {
76+
/**
77+
* Use this when you want to trace multiple separate transactions (Pyth update + original call).
78+
* This method traces each call independently, which may be more accurate but requires more RPC calls.
79+
*/
80+
method: "trace_callMany";
81+
}
82+
| {
83+
/**
84+
* Use this when you want to trace a single bundled transaction that combines the Pyth update with the original call.
85+
* The bundler function is responsible for creating a single transaction that executes both operations.
86+
*
87+
* The bundler is crucial because debug_traceCall can only trace one transaction at a time.
88+
* The bundler must create a single call that includes both the Pyth price update and the original transaction logic.
89+
* This allows the tracer to see all the Pyth price feed calls that would be made in the actual execution.
90+
*/
91+
method: "debug_traceCall";
92+
/**
93+
* Function that takes a Pyth update and original call, returns a single bundled call request.
94+
* Common bundlers include multicall3Bundler for combining calls via Multicall3 contract.
95+
*/
96+
bundler: Bundler;
97+
}
98+
);
10799

108100
/**
109101
* Represents a Pyth price update transaction
@@ -120,16 +112,23 @@ export type PythUpdate = {
120112
/**
121113
* Fill the Pyth data for a given call request.
122114
* Requires a client that supports trace_callMany or debug_traceCall with a bundler.
115+
* This function will trace the call and find all the Pyth price feeds that are needed to fill the call in multiple
116+
* iterations because a single call might revert if it requires a price feed that is not available and we need to
117+
* trace the call again with the new price feeds until we have all the price feeds.
123118
*
124119
* @param client - The public client instance
125120
* @param call - The call request to fill with Pyth data
126121
* @param pythContractAddress - The Pyth contract address
127122
* @param hermesEndpoint - The Hermes endpoint URL for fetching price updates
128-
* @param config - Configuration options for tracing and bundling. Can be either:
129-
* - `DebugTraceCallConfig`: For debug_traceCall method with a bundler function to combine Pyth update with original call.
130-
* The bundler creates a single transaction that executes both the Pyth update and the original call.
131-
* - `TraceCallManyConfig`: For trace_callMany method which traces multiple calls separately.
123+
* @param config - Configuration options for tracing and bundling. Default is `{ method: "trace_callMany" }`.
124+
* - `Config` with `method: "trace_callMany"`: For trace_callMany method which traces multiple calls separately.
132125
* This method traces the Pyth update and original call as separate transactions.
126+
* - `Config` with `method: "debug_traceCall"` and `bundler`: For debug_traceCall method with a bundler function to
127+
* combine Pyth update with the original call. The bundler creates a single transaction that executes both the
128+
* Pyth update and the original call.
129+
* - `maxIter`: Maximum number of iterations to find all required price feeds. Each iteration traces the current
130+
* transaction(s) to find new Pyth price feed calls. The process stops when no new price feeds are found
131+
* or when maxIter is reached. Default is 5.
133132
* @returns Promise resolving to Pyth update object or undefined if no Pyth data needed
134133
*/
135134
export async function fillPythUpdate<
@@ -142,77 +141,102 @@ export async function fillPythUpdate<
142141
hermesEndpoint: string,
143142
config?: Config,
144143
): Promise<PythUpdate | undefined> {
145-
const defaultConfig: Config = {
146-
method: "debug_traceCall",
147-
bundler: multicall3Bundler,
148-
maxIter: 5,
144+
config = {
145+
method: "trace_callMany",
146+
...config,
149147
};
150-
const finalConfig = config ?? defaultConfig;
151-
const traceActionsClient = client
152-
.extend(debugTraceCallAction)
153-
.extend(traceCallManyAction);
154-
const hermesClient = new HermesClient(hermesEndpoint);
155148

156-
let requiredPriceFeeds = new Set<`0x${string}`>();
149+
const hermesClient = new HermesClient(hermesEndpoint);
157150

151+
let requiredPriceFeeds = new Set<Address>();
158152
let pythUpdate: PythUpdate | undefined;
159153

160-
for (let i = 0; i < finalConfig.maxIter; i++) {
161-
let priceFeeds = new Set<`0x${string}`>();
154+
for (let i = 0; i < (config.maxIter ?? 5); i++) {
155+
const priceFeeds = await getPriceFeeds(
156+
client,
157+
pythContractAddress,
158+
call,
159+
config,
160+
pythUpdate,
161+
);
162162

163-
if (finalConfig.method === "debug_traceCall") {
164-
const bundledCall = pythUpdate
165-
? finalConfig.bundler(pythUpdate, call)
166-
: call;
167-
const traceResult = await traceActionsClient.debugTraceCall(bundledCall);
168-
priceFeeds = extractPythPriceFeedsFromDebugTraceCall(
169-
traceResult,
170-
pythContractAddress,
171-
);
163+
if (priceFeeds.isSubsetOf(requiredPriceFeeds)) {
164+
break;
172165
} else {
173-
const calls = pythUpdate ? [pythUpdate.call, call] : [call];
174-
const traceResult = await traceActionsClient.traceCallMany(calls);
175-
priceFeeds = extractPythPriceFeedsFromTraceCallMany(
176-
traceResult,
166+
requiredPriceFeeds = requiredPriceFeeds.union(priceFeeds);
167+
pythUpdate = await getPythUpdate(
168+
client,
169+
hermesClient,
170+
requiredPriceFeeds,
177171
pythContractAddress,
172+
call,
178173
);
179174
}
180-
181-
const oldSize = requiredPriceFeeds.size;
182-
requiredPriceFeeds = new Set([...requiredPriceFeeds, ...priceFeeds]);
183-
184-
if (oldSize === requiredPriceFeeds.size) {
185-
break;
186-
}
187-
188-
const hermesResponse = await hermesClient.getLatestPriceUpdates([
189-
...requiredPriceFeeds,
190-
]);
191-
const updateData = hermesResponse.binary.data.map(
192-
(data) => ("0x" + data) as `0x${string}`,
193-
);
194-
195-
const updateFee = await getUpdateFee(
196-
client,
197-
pythContractAddress,
198-
updateData,
199-
);
200-
201-
pythUpdate = {
202-
call: {
203-
to: pythContractAddress,
204-
data: encodeFunctionData({
205-
abi: IPythAbi,
206-
functionName: "updatePriceFeeds",
207-
args: [updateData],
208-
}),
209-
from: call.from,
210-
value: updateFee,
211-
},
212-
updateData,
213-
updateFee,
214-
};
215175
}
216176

217177
return pythUpdate;
218178
}
179+
180+
const getPythUpdate = async <
181+
transport extends Transport,
182+
chain extends Chain | undefined,
183+
>(
184+
client: PublicClient<transport, chain>,
185+
hermesClient: HermesClient,
186+
priceFeeds: Set<Address>,
187+
pythContractAddress: Address,
188+
call: CallRequest,
189+
) => {
190+
const hermesResponse = await hermesClient.getLatestPriceUpdates([
191+
...priceFeeds,
192+
]);
193+
const updateData = hermesResponse.binary.data.map<Hex>((data) => `0x${data}`);
194+
const updateFee = await getUpdateFee(client, pythContractAddress, updateData);
195+
return {
196+
call: {
197+
to: pythContractAddress,
198+
data: encodeFunctionData({
199+
abi: IPythAbi,
200+
functionName: "updatePriceFeeds",
201+
args: [updateData],
202+
}),
203+
from: call.from,
204+
value: updateFee,
205+
},
206+
updateData,
207+
updateFee,
208+
};
209+
};
210+
211+
/**
212+
* Get the price feeds from the trace of the given call.
213+
*/
214+
const getPriceFeeds = async <
215+
transport extends Transport,
216+
chain extends Chain | undefined,
217+
>(
218+
client: PublicClient<transport, chain>,
219+
pythContractAddress: Address,
220+
call: CallRequest,
221+
config: Config,
222+
pythUpdate: PythUpdate | undefined,
223+
) => {
224+
switch (config.method) {
225+
case "debug_traceCall": {
226+
return extractPythPriceFeedsFromDebugTraceCall(
227+
await client
228+
.extend(debugTraceCallAction)
229+
.debugTraceCall(pythUpdate ? config.bundler(pythUpdate, call) : call),
230+
pythContractAddress,
231+
);
232+
}
233+
case "trace_callMany": {
234+
return extractPythPriceFeedsFromTraceCallMany(
235+
await client
236+
.extend(traceCallManyAction)
237+
.traceCallMany(pythUpdate ? [pythUpdate.call, call] : [call]),
238+
pythContractAddress,
239+
);
240+
}
241+
}
242+
};

0 commit comments

Comments
 (0)