block-at-timestamp
Advanced tools
Comparing version 1.0.3 to 2.0.0
@@ -1,2 +0,3 @@ | ||
import type { Block, Provider } from "@ethersproject/abstract-provider"; | ||
import { Provider } from "@ethersproject/abstract-provider"; | ||
import { Block } from "./provider.js"; | ||
/** | ||
@@ -28,4 +29,4 @@ * Set the verbose state of the blockAtTimestamp(...) function. If this | ||
*/ | ||
export declare function blockAtTimestamp(provider: Provider, timestamp: number, { targetRangeSeconds }: { | ||
export declare function blockAtTimestamp(provider: Provider | string, timestamp: bigint | number, { targetRangeSeconds }: { | ||
targetRangeSeconds?: number | undefined; | ||
}): Promise<Block>; |
@@ -1,10 +0,2 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import { batProvider } from "./provider.js"; | ||
// Verbose state: | ||
@@ -37,2 +29,5 @@ let _verbose = false; | ||
} | ||
function bigIntAbs(x) { | ||
return x < 0n ? x * -1n : x; | ||
} | ||
/** | ||
@@ -51,84 +46,86 @@ * Queries the provider for a block within the given time range of the | ||
*/ | ||
export function blockAtTimestamp(provider, timestamp, { targetRangeSeconds = 60 }) { | ||
var _a, _b; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const start = performance.now(); | ||
try { | ||
logVerbose(`Looking for block at [${timestamp}] sec, within range [${targetRangeSeconds}] sec...`); | ||
// Check if valid range was given: | ||
if (targetRangeSeconds < 1) | ||
throw new Error("targetRangeSeconds too small: must be at least 1 second"); | ||
// Get starting block range: | ||
let lb = yield provider.getBlock(0); | ||
let ub = yield provider.getBlock(yield provider.getBlockNumber()); | ||
let estBlock = ub; | ||
// Check if target timestamp is outside of range: | ||
if (timestamp <= lb.timestamp) { | ||
logVerbose(`Timestamp is less than earliest block. Returning block #${lb.number}.`); | ||
return lb; | ||
export async function blockAtTimestamp(provider, timestamp, { targetRangeSeconds = 60 }) { | ||
const batp = batProvider(provider); | ||
const timestampAsBigInt = typeof timestamp === "number" ? BigInt(timestamp) : timestamp; | ||
const start = performance.now(); | ||
try { | ||
logVerbose(`Looking for block at [${timestampAsBigInt}] sec, within range [${targetRangeSeconds}] sec...`); | ||
// Check if valid range was given: | ||
if (targetRangeSeconds < 1) | ||
throw new Error("targetRangeSeconds too small: must be at least 1 second"); | ||
// Get starting block range: | ||
let lb = await batp.getBlock(0n); | ||
let ub = await batp.getBlock(await batp.getCurrentBlockNumber() - 1n); // some RPCs have trouble fetching block info by the most recent number, so we lag behind 1 block | ||
let estBlock = ub; | ||
// Check if target timestamp is outside of range: | ||
if (timestampAsBigInt <= lb.timestamp) { | ||
logVerbose(`Timestamp is less than earliest block. Returning block #${lb.number}.`); | ||
return lb; | ||
} | ||
if (timestampAsBigInt >= ub.timestamp) { | ||
logVerbose(`Timestamp is greater than latest block. Returning block #${ub.number}.`); | ||
return ub; | ||
} | ||
// Define helpers for verbose progress logging: | ||
let checks = 0; | ||
const formatBlock = (block, name) => { | ||
return `[${name}: { block: ${block.number}, timestamp: ${block.timestamp}, diff: ${block.timestamp - timestampAsBigInt} }]`; | ||
}; | ||
const logCheck = (estimate) => { | ||
logVerbose(`(Check #${++checks}): \n\t${formatBlock(lb, "lowerBound")}\n\t${formatBlock(estimate, " checking")}\n\t${formatBlock(ub, "upperBound")}`); | ||
}; | ||
// Log first check: | ||
logCheck(estBlock); | ||
// Iterate, alternating between two methods, binary squeeze and block rate estimates: | ||
do { | ||
// Method 1: Binary squeeze: | ||
const mb = await batp.getBlock(lb.number + (ub.number - lb.number) / 2n); | ||
logCheck(mb); | ||
if (mb.timestamp > timestampAsBigInt) { | ||
ub = mb; | ||
} | ||
if (timestamp >= ub.timestamp) { | ||
logVerbose(`Timestamp is greater than latest block. Returning block #${ub.number}.`); | ||
return ub; | ||
else if (mb.timestamp < timestampAsBigInt) { | ||
lb = mb; | ||
} | ||
// Define helpers for verbose progress logging: | ||
let checks = 0; | ||
const formatBlock = (block, name) => { | ||
return `[${name}: { block: ${block.number}, timestamp: ${block.timestamp}, diff: ${block.timestamp - timestamp} }]`; | ||
}; | ||
const logCheck = (estimate) => { | ||
logVerbose(`(Check #${++checks}): \n\t${formatBlock(lb, "lowerBound")}\n\t${formatBlock(estimate, " checking")}\n\t${formatBlock(ub, "upperBound")}`); | ||
}; | ||
// Log first check: | ||
else { | ||
logVerbose(`Found valid block during binary squeeze. Returning block #${mb.number}.`); | ||
return mb; | ||
} | ||
// Check if we have any blocks left to query: | ||
const blockDiff = ub.number - lb.number; | ||
if (blockDiff <= 1) { | ||
let closest = ub; | ||
if (bigIntAbs(lb.timestamp - timestampAsBigInt) < bigIntAbs(ub.timestamp - timestampAsBigInt)) | ||
closest = lb; | ||
logVerbose(`No more blocks to check. Returning closest block: #${closest.number}.`); | ||
return closest; | ||
} | ||
// Method 2: Estimate from avg block rate in new range: | ||
const timeDiff = ub.timestamp - lb.timestamp; | ||
const precision = BigInt(1e6); | ||
const avgSecBlock = precision * timeDiff / blockDiff; | ||
let estBlockNumber = (precision * (timestampAsBigInt - lb.timestamp)) / avgSecBlock + lb.number; | ||
if (estBlockNumber > ub.number) | ||
estBlockNumber = ub.number; | ||
estBlock = await batp.getBlock(estBlockNumber); | ||
logCheck(estBlock); | ||
// Iterate, alternating between two methods, binary squeeze and block rate estimates: | ||
do { | ||
// Method 1: Binary squeeze: | ||
const mb = yield provider.getBlock(Math.floor(lb.number + (ub.number - lb.number) / 2)); | ||
logCheck(mb); | ||
if (mb.timestamp > timestamp) { | ||
ub = mb; | ||
} | ||
else if (mb.timestamp < timestamp) { | ||
lb = mb; | ||
} | ||
else { | ||
logVerbose(`Found valid block during binary squeeze. Returning block #${mb.number}.`); | ||
return mb; | ||
} | ||
// Check if we have any blocks left to query: | ||
const blockDiff = ub.number - lb.number; | ||
if (blockDiff <= 1) { | ||
let closest = ub; | ||
if (Math.abs(lb.timestamp - timestamp) < Math.abs(ub.timestamp - timestamp)) | ||
closest = lb; | ||
logVerbose(`No more blocks to check. Returning closest block: #${closest.number}.`); | ||
return closest; | ||
} | ||
// Method 2: Estimate from avg block rate in new range: | ||
const timeDiff = ub.timestamp - lb.timestamp; | ||
const avgSecBlock = timeDiff / blockDiff; | ||
const estBlockNumber = Math.floor((timestamp - lb.timestamp) / avgSecBlock) + lb.number; | ||
estBlock = yield provider.getBlock(estBlockNumber); | ||
logCheck(estBlock); | ||
if (estBlock.timestamp > timestamp) { | ||
ub = estBlock; | ||
} | ||
else { | ||
lb = estBlock; | ||
} | ||
} while (Math.abs(estBlock.timestamp - timestamp) > targetRangeSeconds); | ||
logVerbose(`Found valid block with block rate estimation. Returning block #${estBlock.number}.`); | ||
return estBlock; | ||
} | ||
catch (err) { | ||
if (_verbose) | ||
console.log(`Error: ${(_b = (_a = err) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : err}`); | ||
throw err; | ||
} | ||
finally { | ||
logVerbose(`Completed search in ${performance.now() - start} ms.`); | ||
} | ||
}); | ||
if (estBlock.timestamp > timestampAsBigInt) { | ||
ub = estBlock; | ||
} | ||
else { | ||
lb = estBlock; | ||
} | ||
} while (bigIntAbs(estBlock.timestamp - timestampAsBigInt) > targetRangeSeconds); | ||
logVerbose(`Found valid block with block rate estimation. Returning block #${estBlock.number}.`); | ||
return estBlock; | ||
} | ||
catch (err) { | ||
if (_verbose) | ||
console.log(`Error: ${err?.message ?? err}`); | ||
throw err; | ||
} | ||
finally { | ||
logVerbose(`Completed search in ${performance.now() - start} ms.`); | ||
} | ||
} | ||
//# sourceMappingURL=index.js.map |
@@ -1,15 +0,8 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import { blockAtTimestamp, setVerbose } from "./index.js"; | ||
import { ethers } from "ethers"; | ||
setVerbose(true); | ||
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); | ||
const main = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const main = async () => { | ||
if (!process.env.RPC_URL) | ||
throw new Error("Missing RPC_URL env var!"); | ||
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); | ||
const nowInSeconds = Math.floor(Date.now() / 1000); | ||
@@ -19,6 +12,10 @@ const secondsInAWeek = 60 * 60 * 24 * 7; | ||
const targetRangeSeconds = 60 * 60 * 24; | ||
const blockOneWeekAgo = yield blockAtTimestamp(provider, oneWeekAgo, { targetRangeSeconds }); | ||
console.log(blockOneWeekAgo); | ||
}); | ||
const blockOneWeekAgoRpc = await blockAtTimestamp(process.env.RPC_URL, oneWeekAgo, { targetRangeSeconds }); | ||
const blockOneWeekAgoProvider = await blockAtTimestamp(provider, oneWeekAgo, { targetRangeSeconds }); | ||
console.log({ | ||
rpcRes: blockOneWeekAgoRpc, | ||
providerRes: blockOneWeekAgoProvider | ||
}); | ||
}; | ||
main().catch(console.error); | ||
//# sourceMappingURL=test.js.map |
{ | ||
"name": "block-at-timestamp", | ||
"version": "1.0.3", | ||
"description": "Ethers Provider extension to quickly find a block close to a target timestamp.", | ||
"version": "2.0.0", | ||
"description": "Quickly find a block close to a target timestamp using an RPC URL or ethers Provider.", | ||
"module": "dist/index.js", | ||
@@ -34,5 +34,5 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@types/node": "^18.11.18", | ||
"@types/node": "^18.18.6", | ||
"ethers": "^5.7.2" | ||
} | ||
} |
@@ -7,3 +7,3 @@ # block-at-timestamp | ||
Ethers Provider extension to quickly find a block close to a target timestamp. | ||
Quickly find a block close to a target timestamp using an RPC URL or ethers Provider. | ||
@@ -20,8 +20,6 @@ ## Installation | ||
```js | ||
import { ethers } from "ethers"; | ||
import { blockAtTimestamp } from "block-at-timestamp"; | ||
let provider = new ethers.providers.JsonRpcProvider("https://rpc-provider.example/"); | ||
let timestamp = 1674420070; // unix timestamp in seconds | ||
let block = await blockAtTimestamp(provider, timestamp); | ||
let block = await blockAtTimestamp("https://rpc-provider.example/", timestamp); | ||
``` | ||
@@ -28,0 +26,0 @@ |
@@ -1,2 +0,3 @@ | ||
import type { Block, Provider } from "@ethersproject/abstract-provider"; | ||
import { Provider } from "@ethersproject/abstract-provider"; | ||
import { BatProvider, Block, RpcUrl, batProvider } from "./provider.js"; | ||
@@ -33,2 +34,6 @@ // Verbose state: | ||
function bigIntAbs(x: bigint) { | ||
return x < 0n ? x * -1n : x; | ||
} | ||
/** | ||
@@ -47,6 +52,8 @@ * Queries the provider for a block within the given time range of the | ||
*/ | ||
export async function blockAtTimestamp(provider: Provider, timestamp: number, { targetRangeSeconds = 60 }) { | ||
export async function blockAtTimestamp(provider: Provider | string, timestamp: bigint | number, { targetRangeSeconds = 60 }) { | ||
const batp: BatProvider = batProvider(provider); | ||
const timestampAsBigInt = typeof timestamp === "number" ? BigInt(timestamp) : timestamp; | ||
const start = performance.now(); | ||
try { | ||
logVerbose(`Looking for block at [${timestamp}] sec, within range [${targetRangeSeconds}] sec...`); | ||
logVerbose(`Looking for block at [${timestampAsBigInt}] sec, within range [${targetRangeSeconds}] sec...`); | ||
@@ -57,12 +64,12 @@ // Check if valid range was given: | ||
// Get starting block range: | ||
let lb = await provider.getBlock(0); | ||
let ub = await provider.getBlock(await provider.getBlockNumber()); | ||
let lb = await batp.getBlock(0n); | ||
let ub = await batp.getBlock(await batp.getCurrentBlockNumber() - 1n); // some RPCs have trouble fetching block info by the most recent number, so we lag behind 1 block | ||
let estBlock = ub; | ||
// Check if target timestamp is outside of range: | ||
if(timestamp <= lb.timestamp) { | ||
if(timestampAsBigInt <= lb.timestamp) { | ||
logVerbose(`Timestamp is less than earliest block. Returning block #${lb.number}.`); | ||
return lb; | ||
} | ||
if(timestamp >= ub.timestamp) { | ||
if(timestampAsBigInt >= ub.timestamp) { | ||
logVerbose(`Timestamp is greater than latest block. Returning block #${ub.number}.`); | ||
@@ -75,3 +82,3 @@ return ub; | ||
const formatBlock = (block: Block, name: string) => { | ||
return `[${name}: { block: ${block.number}, timestamp: ${block.timestamp}, diff: ${block.timestamp - timestamp} }]`; | ||
return `[${name}: { block: ${block.number}, timestamp: ${block.timestamp}, diff: ${block.timestamp - timestampAsBigInt} }]`; | ||
}; | ||
@@ -89,7 +96,7 @@ const logCheck = (estimate: Block) => { | ||
// Method 1: Binary squeeze: | ||
const mb = await provider.getBlock(Math.floor(lb.number + (ub.number - lb.number) / 2)); | ||
const mb = await batp.getBlock(lb.number + (ub.number - lb.number) / 2n); | ||
logCheck(mb); | ||
if(mb.timestamp > timestamp) { | ||
if(mb.timestamp > timestampAsBigInt) { | ||
ub = mb; | ||
} else if(mb.timestamp < timestamp) { | ||
} else if(mb.timestamp < timestampAsBigInt) { | ||
lb = mb; | ||
@@ -105,3 +112,3 @@ } else { | ||
let closest = ub; | ||
if(Math.abs(lb.timestamp - timestamp) < Math.abs(ub.timestamp - timestamp)) closest = lb; | ||
if(bigIntAbs(lb.timestamp - timestampAsBigInt) < bigIntAbs(ub.timestamp - timestampAsBigInt)) closest = lb; | ||
logVerbose(`No more blocks to check. Returning closest block: #${closest.number}.`); | ||
@@ -113,7 +120,9 @@ return closest; | ||
const timeDiff = ub.timestamp - lb.timestamp; | ||
const avgSecBlock = timeDiff / blockDiff; | ||
const estBlockNumber = Math.floor((timestamp - lb.timestamp) / avgSecBlock) + lb.number; | ||
estBlock = await provider.getBlock(estBlockNumber); | ||
const precision = BigInt(1e6); | ||
const avgSecBlock = precision * timeDiff / blockDiff; | ||
let estBlockNumber = (precision * (timestampAsBigInt - lb.timestamp)) / avgSecBlock + lb.number; | ||
if (estBlockNumber > ub.number) estBlockNumber = ub.number; | ||
estBlock = await batp.getBlock(estBlockNumber); | ||
logCheck(estBlock); | ||
if(estBlock.timestamp > timestamp) { | ||
if(estBlock.timestamp > timestampAsBigInt) { | ||
ub = estBlock; | ||
@@ -123,3 +132,3 @@ } else { | ||
} | ||
} while(Math.abs(estBlock.timestamp - timestamp) > targetRangeSeconds); | ||
} while(bigIntAbs(estBlock.timestamp - timestampAsBigInt) > targetRangeSeconds); | ||
logVerbose(`Found valid block with block rate estimation. Returning block #${estBlock.number}.`); | ||
@@ -126,0 +135,0 @@ return estBlock; |
@@ -6,4 +6,5 @@ import { blockAtTimestamp, setVerbose } from "./index.js"; | ||
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); | ||
const main = async () => { | ||
if (!process.env.RPC_URL) throw new Error("Missing RPC_URL env var!"); | ||
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); | ||
const nowInSeconds = Math.floor(Date.now() / 1000); | ||
@@ -13,5 +14,9 @@ const secondsInAWeek = 60 * 60 * 24 * 7; | ||
const targetRangeSeconds = 60 * 60 * 24; | ||
const blockOneWeekAgo = await blockAtTimestamp(provider, oneWeekAgo, { targetRangeSeconds }); | ||
console.log(blockOneWeekAgo); | ||
const blockOneWeekAgoRpc = await blockAtTimestamp(process.env.RPC_URL, oneWeekAgo, { targetRangeSeconds }); | ||
const blockOneWeekAgoProvider = await blockAtTimestamp(provider, oneWeekAgo, { targetRangeSeconds }); | ||
console.log({ | ||
rpcRes: blockOneWeekAgoRpc, | ||
providerRes: blockOneWeekAgoProvider | ||
}); | ||
}; | ||
main().catch(console.error); |
@@ -10,3 +10,3 @@ { | ||
"moduleResolution": "Node", | ||
"target": "ES6", | ||
"target": "ES2020", | ||
"sourceMap": true, | ||
@@ -13,0 +13,0 @@ "resolveJsonModule": true, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
32323
16
577
44
3
2