UNPKG

ethstorage-sdk-ts

Version:

eip-4844 blobs upload sdk from ethstorage-sdk

189 lines (161 loc) 4.7 kB
import { BytesLike, TransactionRequest, ethers } from "ethers"; import { loadKZG } from "kzg-wasm"; function sleep(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } function parseBigintValue(value: any) { if (typeof value == "bigint") { return "0x" + value.toString(16); } if (typeof value == "object") { const { _hex } = value; const c = BigInt(_hex); return "0x" + c.toString(16); } return value; } function computeVersionedHash( commitment: BytesLike, blobCommitmentVersion: number ) { const computedVersionedHash = new Uint8Array(32); computedVersionedHash.set([blobCommitmentVersion], 0); const hash = ethers.getBytes(ethers.sha256(commitment)); computedVersionedHash.set(hash.subarray(1), 1); return computedVersionedHash; } function commitmentsToVersionedHashes(commitment: BytesLike) { return computeVersionedHash(commitment, 0x01); } // blob gas price const MIN_BLOB_GASPRICE = 1n; const BLOB_GASPRICE_UPDATE_FRACTION = 3338477n; function fakeExponential( factor: bigint, numerator: bigint, denominator: bigint ) { let i = 1n; let output = 0n; let numerator_accum = factor * denominator; while (numerator_accum > 0) { output += BigInt(numerator_accum); numerator_accum = (numerator_accum * BigInt(numerator)) / (denominator * i); i++; } return output / denominator; } export class BlobUploader { #kzg: any; #jsonRpc; #provider; #wallet; constructor(rpc: string, pk: string | ethers.SigningKey) { this.#jsonRpc = rpc; this.#provider = new ethers.JsonRpcProvider(rpc); this.#wallet = new ethers.Wallet(pk, this.#provider); } async #getKzg() { if (!this.#kzg) { this.#kzg = await loadKZG(); } return this.#kzg; } async getNonce() { return await this.#wallet.getNonce(); } async getBlobGasPrice() { // get current block const block = (await this.#provider.getBlock("latest")) as any; const excessBlobGas = BigInt(block.excessBlobGas); return fakeExponential( MIN_BLOB_GASPRICE, excessBlobGas, BLOB_GASPRICE_UPDATE_FRACTION ); } async getGasPrice() { return await this.#provider.getFeeData(); } async estimateGas(params: any) { const limit = await this.#provider.send("eth_estimateGas", [params]); if (limit) { return (BigInt(limit) * 6n) / 5n; } return null; } async sendTx(tx: TransactionRequest, blobs: any[]) { if (!blobs) { return await this.#wallet.sendTransaction(tx); } // blobs const kzg = await this.#getKzg(); const ethersBlobs = []; const versionedHashes = []; for (let i = 0; i < blobs.length; i++) { const blob = blobs[i]; const commitment = kzg.blobToKzgCommitment(blob); const proof = kzg.computeBlobKzgProof(blob, commitment); ethersBlobs.push({ data: blob, proof: proof, commitment: commitment, }); const hash = commitmentsToVersionedHashes(commitment); versionedHashes.push(ethers.hexlify(hash)); } let { to, value, data, gasLimit, maxFeePerBlobGas } = tx; if (gasLimit == null) { const hexValue = parseBigintValue(value); gasLimit = await this.estimateGas({ from: this.#wallet.address, to, data, value: hexValue, blobVersionedHashes: versionedHashes, }); if (gasLimit == null) { throw Error("estimateGas: execution reverted"); } tx.gasLimit = gasLimit; } if (maxFeePerBlobGas == null) { maxFeePerBlobGas = await this.getBlobGasPrice(); maxFeePerBlobGas = (maxFeePerBlobGas * 6n) / 5n; tx.maxFeePerBlobGas = maxFeePerBlobGas; } // send tx.type = 3; tx.blobVersionedHashes = versionedHashes; tx.blobs = ethersBlobs; tx.kzg = kzg; return await this.#wallet.sendTransaction(tx); } async getBlobHash(blob: any) { const kzg = await this.#getKzg(); const commit = kzg.blobToKzgCommitment(blob); const localHash = commitmentsToVersionedHashes(commit); const hash = new Uint8Array(32); hash.set(localHash.subarray(0, 32 - 8)); return ethers.hexlify(hash); } async isTransactionMined(transactionHash: string) { const txReceipt = await this.#provider.getTransactionReceipt( transactionHash ); if (txReceipt && txReceipt.blockNumber) { return txReceipt; } } async getTxReceipt(transactionHash: string) { let txReceipt; while (!txReceipt) { txReceipt = await this.isTransactionMined(transactionHash); if (txReceipt) break; await sleep(5000); } return txReceipt; } }