- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
944 lines
27 KiB
TypeScript
944 lines
27 KiB
TypeScript
import * as Bytes from './Bytes.js'
|
||
import * as Errors from './Errors.js'
|
||
import * as Hash from './Hash.js'
|
||
import * as Hex from './Hex.js'
|
||
import * as Kzg from './Kzg.js'
|
||
import * as Cursor from './internal/cursor.js'
|
||
import type { Compute, Mutable, OneOf, UnionCompute } from './internal/types.js'
|
||
|
||
/** Blob limit per transaction. */
|
||
const blobsPerTransaction = 6
|
||
|
||
/** The number of bytes in a BLS scalar field element. */
|
||
export const bytesPerFieldElement = 32
|
||
|
||
/** The number of field elements in a blob. */
|
||
export const fieldElementsPerBlob = 4096
|
||
|
||
/** The number of bytes in a blob. */
|
||
export const bytesPerBlob = bytesPerFieldElement * fieldElementsPerBlob
|
||
|
||
/** Blob bytes limit per transaction. */
|
||
export const maxBytesPerTransaction =
|
||
bytesPerBlob * blobsPerTransaction -
|
||
// terminator byte (0x80).
|
||
1 -
|
||
// zero byte (0x00) appended to each field element.
|
||
1 * fieldElementsPerBlob * blobsPerTransaction
|
||
|
||
/** Root type for a Blob. */
|
||
export type Blob<type extends Hex.Hex | Bytes.Bytes = Hex.Hex | Bytes.Bytes> =
|
||
type
|
||
|
||
/** A list of {@link ox#Blobs.Blob}. */
|
||
export type Blobs<type extends Hex.Hex | Bytes.Bytes = Hex.Hex | Bytes.Bytes> =
|
||
readonly Blob<type>[]
|
||
|
||
/** Type for a Blob Sidecar that contains a blob, as well as its KZG commitment and proof. */
|
||
export type BlobSidecar<
|
||
type extends Hex.Hex | Bytes.Bytes = Hex.Hex | Bytes.Bytes,
|
||
> = Compute<{
|
||
/** The blob associated with the transaction. */
|
||
blob: type
|
||
/** The KZG commitment corresponding to this blob. */
|
||
commitment: type
|
||
/** The KZG proof corresponding to this blob and commitment. */
|
||
proof: type
|
||
}>
|
||
|
||
/**
|
||
* Transform a list of Commitments to Blob Versioned Hashes.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHashes = Blobs.commitmentsToVersionedHashes(commitments) // [!code focus]
|
||
* // @log: ['0x...', '0x...']
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type for the Versioned Hashes with the `as` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHashes = Blobs.commitmentsToVersionedHashes(commitments, {
|
||
* as: 'Bytes', // [!code focus]
|
||
* })
|
||
* // @log: [Uint8Array [ ... ], Uint8Array [ ... ]]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Versioning Hashes
|
||
*
|
||
* It is possible to configure the version for the Versioned Hashes with the `version` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHashes = Blobs.commitmentsToVersionedHashes(commitments, {
|
||
* version: 2, // [!code focus]
|
||
* })
|
||
* ```
|
||
*
|
||
* @param commitments - A list of commitments.
|
||
* @param options - Options.
|
||
* @returns A list of Blob Versioned Hashes.
|
||
*/
|
||
export function commitmentsToVersionedHashes<
|
||
const commitments extends readonly Bytes.Bytes[] | readonly Hex.Hex[],
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (commitments extends readonly Hex.Hex[] ? 'Hex' : never)
|
||
| (commitments extends readonly Bytes.Bytes[] ? 'Bytes' : never),
|
||
>(
|
||
commitments: commitments | readonly Bytes.Bytes[] | readonly Hex.Hex[],
|
||
options: commitmentsToVersionedHashes.Options<as> = {},
|
||
): commitmentsToVersionedHashes.ReturnType<as> {
|
||
const { version } = options
|
||
|
||
const as =
|
||
options.as ?? (typeof commitments[0] === 'string' ? 'Hex' : 'Bytes')
|
||
|
||
const hashes: Uint8Array[] | Hex.Hex[] = []
|
||
for (const commitment of commitments) {
|
||
hashes.push(
|
||
commitmentToVersionedHash(commitment, {
|
||
as,
|
||
version,
|
||
}) as never,
|
||
)
|
||
}
|
||
return hashes as never
|
||
}
|
||
|
||
export declare namespace commitmentsToVersionedHashes {
|
||
type Options<as extends 'Hex' | 'Bytes' | undefined = undefined> = {
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
/** Version to tag onto the hashes. */
|
||
version?: number | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
|
||
type ErrorType = Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transform a Commitment to its Blob Versioned Hash.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const [commitment] = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHash = Blobs.commitmentToVersionedHash(commitment) // [!code focus]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type for the Versioned Hash with the `as` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const [commitment] = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHashes = Blobs.commitmentToVersionedHash(commitment, {
|
||
* as: 'Bytes', // [!code focus]
|
||
* })
|
||
* // @log: [Uint8Array [ ... ], Uint8Array [ ... ]]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Versioning Hashes
|
||
*
|
||
* It is possible to configure the version for the Versioned Hash with the `version` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const [commitment] = Blobs.toCommitments(blobs, { kzg })
|
||
* const versionedHashes = Blobs.commitmentToVersionedHash(commitment, {
|
||
* version: 2, // [!code focus]
|
||
* })
|
||
* ```
|
||
*
|
||
* @param commitment - The commitment.
|
||
* @param options - Options.
|
||
* @returns The Blob Versioned Hash.
|
||
*/
|
||
export function commitmentToVersionedHash<
|
||
const commitment extends Hex.Hex | Bytes.Bytes,
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (commitment extends Hex.Hex ? 'Hex' : never)
|
||
| (commitment extends Bytes.Bytes ? 'Bytes' : never),
|
||
>(
|
||
commitment: commitment | Hex.Hex | Bytes.Bytes,
|
||
options: commitmentToVersionedHash.Options<as> = {},
|
||
): commitmentToVersionedHash.ReturnType<as> {
|
||
const { version = 1 } = options
|
||
const as = options.as ?? (typeof commitment === 'string' ? 'Hex' : 'Bytes')
|
||
|
||
const versionedHash = Hash.sha256(commitment, { as: 'Bytes' })
|
||
versionedHash.set([version], 0)
|
||
return (
|
||
as === 'Bytes' ? versionedHash : Hex.fromBytes(versionedHash)
|
||
) as commitmentToVersionedHash.ReturnType<as>
|
||
}
|
||
|
||
export declare namespace commitmentToVersionedHash {
|
||
type Options<as extends 'Hex' | 'Bytes' | undefined = undefined> = {
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
/** Version to tag onto the hash. */
|
||
version?: number | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
||
| (as extends 'Bytes' ? Bytes.Bytes : never)
|
||
| (as extends 'Hex' ? Hex.Hex : never)
|
||
|
||
type ErrorType = Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms arbitrary data to {@link ox#Blobs.Blobs}.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* import { Blobs } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Creating Blobs from a String
|
||
*
|
||
* An example of creating Blobs from a string using {@link ox#Hex.(from:function)}:
|
||
*
|
||
* ```ts twoslash
|
||
* import { Blobs, Hex } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from(Hex.fromString('Hello world!'))
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type for the Blobs with the `as` option.
|
||
*
|
||
* ```ts twoslash
|
||
* import { Blobs } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef', { as: 'Bytes' })
|
||
* // ^?
|
||
*
|
||
*
|
||
* ```
|
||
*
|
||
* @param data - The data to convert to {@link ox#Blobs.Blobs}.
|
||
* @param options - Options.
|
||
* @returns The {@link ox#Blobs.Blobs}.
|
||
*/
|
||
export function from<
|
||
const data extends Hex.Hex | Bytes.Bytes,
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (data extends Hex.Hex ? 'Hex' : never)
|
||
| (data extends Bytes.Bytes ? 'Bytes' : never),
|
||
>(
|
||
data: data | Hex.Hex | Bytes.Bytes,
|
||
options: from.Options<as> = {},
|
||
): from.ReturnType<as> {
|
||
const as = options.as ?? (typeof data === 'string' ? 'Hex' : 'Bytes')
|
||
const data_ = (
|
||
typeof data === 'string' ? Bytes.fromHex(data) : data
|
||
) as Bytes.Bytes
|
||
|
||
const size_ = Bytes.size(data_)
|
||
if (!size_) throw new EmptyBlobError()
|
||
if (size_ > maxBytesPerTransaction)
|
||
throw new BlobSizeTooLargeError({
|
||
maxSize: maxBytesPerTransaction,
|
||
size: size_,
|
||
})
|
||
|
||
const blobs = []
|
||
|
||
let active = true
|
||
let position = 0
|
||
while (active) {
|
||
const blob = Cursor.create(new Uint8Array(bytesPerBlob))
|
||
|
||
let size = 0
|
||
while (size < fieldElementsPerBlob) {
|
||
const bytes = data_.slice(position, position + (bytesPerFieldElement - 1))
|
||
|
||
// Push a zero byte so the field element doesn't overflow the BLS modulus.
|
||
blob.pushByte(0x00)
|
||
|
||
// Push the current segment of data bytes.
|
||
blob.pushBytes(bytes)
|
||
|
||
// If we detect that the current segment of data bytes is less than 31 bytes,
|
||
// we can stop processing and push a terminator byte to indicate the end of the blob.
|
||
if (bytes.length < 31) {
|
||
blob.pushByte(0x80)
|
||
active = false
|
||
break
|
||
}
|
||
|
||
size++
|
||
position += 31
|
||
}
|
||
|
||
blobs.push(blob)
|
||
}
|
||
|
||
return (
|
||
as === 'Bytes'
|
||
? blobs.map((x) => x.bytes)
|
||
: blobs.map((x) => Hex.fromBytes(x.bytes))
|
||
) as never
|
||
}
|
||
|
||
export declare namespace from {
|
||
type Options<as extends 'Hex' | 'Bytes' | undefined = undefined> = {
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
|
||
type ErrorType =
|
||
| BlobSizeTooLargeError
|
||
| EmptyBlobError
|
||
| Bytes.fromHex.ErrorType
|
||
| Hex.fromBytes.ErrorType
|
||
| Cursor.create.ErrorType
|
||
| Bytes.size.ErrorType
|
||
| Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms a list of {@link ox#Blobs.BlobSidecars} to their Blob Versioned Hashes.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const sidecars = Blobs.toSidecars(blobs, { kzg })
|
||
* const versionedHashes = Blobs.sidecarsToVersionedHashes(sidecars) // [!code focus]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type for the Versioned Hashes with the `as` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const sidecars = Blobs.toSidecars(blobs, { kzg })
|
||
* const versionedHashes = Blobs.sidecarsToVersionedHashes(sidecars, {
|
||
* as: 'Bytes', // [!code focus]
|
||
* })
|
||
* // @log: [Uint8Array [ ... ], Uint8Array [ ... ]]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Versioning Hashes
|
||
*
|
||
* It is possible to configure the version for the Versioned Hashes with the `version` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const sidecars = Blobs.toSidecars(blobs, { kzg })
|
||
* const versionedHashes = Blobs.sidecarsToVersionedHashes(sidecars, {
|
||
* version: 2, // [!code focus]
|
||
* })
|
||
* ```
|
||
*
|
||
* @param sidecars - The {@link ox#Blobs.BlobSidecars} to transform to Blob Versioned Hashes.
|
||
* @param options - Options.
|
||
* @returns The versioned hashes.
|
||
*/
|
||
export function sidecarsToVersionedHashes<
|
||
const sidecars extends BlobSidecars,
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (sidecars extends BlobSidecars<Hex.Hex> ? 'Hex' : never)
|
||
| (sidecars extends BlobSidecars<Bytes.Bytes> ? 'Bytes' : never),
|
||
>(
|
||
sidecars: sidecars | BlobSidecars,
|
||
options: sidecarsToVersionedHashes.Options<as> = {},
|
||
): sidecarsToVersionedHashes.ReturnType<as> {
|
||
const { version } = options
|
||
|
||
const as =
|
||
options.as ?? (typeof sidecars[0]!.blob === 'string' ? 'Hex' : 'Bytes')
|
||
|
||
const hashes: Uint8Array[] | Hex.Hex[] = []
|
||
for (const { commitment } of sidecars) {
|
||
hashes.push(
|
||
commitmentToVersionedHash(commitment, {
|
||
as,
|
||
version,
|
||
}) as any,
|
||
)
|
||
}
|
||
return hashes as any
|
||
}
|
||
|
||
export declare namespace sidecarsToVersionedHashes {
|
||
type Options<as extends 'Hex' | 'Bytes' | undefined = undefined> = {
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
/** Version to tag onto the hashes. */
|
||
version?: number | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
|
||
type ErrorType = commitmentToVersionedHash.ErrorType | Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* import { Blobs, Hex } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const data = Blobs.to(blobs) // [!code focus]
|
||
* // @log: '0xdeadbeef'
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type with second argument.
|
||
*
|
||
* ```ts twoslash
|
||
* import { Blobs } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const data = Blobs.to(blobs, 'Bytes')
|
||
* // @log: Uint8Array [ 13, 174, 190, 239 ]
|
||
* ```
|
||
*
|
||
* @param blobs - The {@link ox#Blobs.Blobs} to transform.
|
||
* @param to - The type to transform to.
|
||
* @returns The originating data.
|
||
*/
|
||
export function to<
|
||
const blobs extends Blobs<Hex.Hex> | Blobs<Bytes.Bytes>,
|
||
to extends 'Hex' | 'Bytes' =
|
||
| (blobs extends Blobs<Hex.Hex> ? 'Hex' : never)
|
||
| (blobs extends Blobs<Bytes.Bytes> ? 'Bytes' : never),
|
||
>(
|
||
blobs: blobs | Blobs<Hex.Hex> | Blobs<Bytes.Bytes>,
|
||
to?: to | 'Hex' | 'Bytes' | undefined,
|
||
): to.ReturnType<to> {
|
||
const to_ = to ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes')
|
||
const blobs_ = (
|
||
typeof blobs[0] === 'string'
|
||
? blobs.map((x) => Bytes.fromHex(x as Hex.Hex))
|
||
: blobs
|
||
) as Bytes.Bytes[]
|
||
|
||
const length = blobs_.reduce((length, blob) => length + blob.length, 0)
|
||
const data = Cursor.create(new Uint8Array(length))
|
||
let active = true
|
||
|
||
for (const blob of blobs_) {
|
||
const cursor = Cursor.create(blob)
|
||
while (active && cursor.position < blob.length) {
|
||
// First byte will be a zero 0x00 byte – we can skip.
|
||
cursor.incrementPosition(1)
|
||
|
||
let consume = 31
|
||
if (blob.length - cursor.position < 31)
|
||
consume = blob.length - cursor.position
|
||
|
||
for (const _ in Array.from({ length: consume })) {
|
||
const byte = cursor.readByte()
|
||
const isTerminator =
|
||
byte === 0x80 && !cursor.inspectBytes(cursor.remaining).includes(0x80)
|
||
if (isTerminator) {
|
||
active = false
|
||
break
|
||
}
|
||
data.pushByte(byte)
|
||
}
|
||
}
|
||
}
|
||
|
||
const trimmedData = data.bytes.slice(0, data.position)
|
||
return (to_ === 'Hex' ? Hex.fromBytes(trimmedData) : trimmedData) as never
|
||
}
|
||
|
||
export declare namespace to {
|
||
type ReturnType<to extends 'Hex' | 'Bytes' = 'Hex'> =
|
||
| (to extends 'Bytes' ? Bytes.Bytes : never)
|
||
| (to extends 'Hex' ? Hex.Hex : never)
|
||
|
||
type ErrorType =
|
||
| Hex.fromBytes.ErrorType
|
||
| Bytes.fromHex.ErrorType
|
||
| Cursor.create.ErrorType
|
||
| Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* import { Blobs, Hex } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const data = Blobs.toHex(blobs) // [!code focus]
|
||
* // @log: '0xdeadbeef'
|
||
* ```
|
||
*/
|
||
export function toHex(
|
||
blobs: Blobs<Hex.Hex> | Blobs<Bytes.Bytes>,
|
||
): toHex.ReturnType {
|
||
return to(blobs, 'Hex')
|
||
}
|
||
|
||
export declare namespace toHex {
|
||
type ReturnType = to.ReturnType<'Hex'>
|
||
type ErrorType = to.ErrorType | Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* import { Blobs, Hex } from 'ox'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const data = Blobs.toBytes(blobs) // [!code focus]
|
||
* // @log: Uint8Array [ 13, 174, 190, 239 ]
|
||
* ```
|
||
*/
|
||
export function toBytes(
|
||
blobs: Blobs<Hex.Hex> | Blobs<Bytes.Bytes>,
|
||
): toBytes.ReturnType {
|
||
return to(blobs, 'Bytes')
|
||
}
|
||
|
||
/**
|
||
* Compute commitments from a list of {@link ox#Blobs.Blobs}.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg }) // [!code focus]
|
||
* ```
|
||
*
|
||
* @example
|
||
* ### Configuring Return Type
|
||
*
|
||
* It is possible to configure the return type with the `as` option.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, {
|
||
* as: 'Bytes', // [!code focus]
|
||
* kzg,
|
||
* })
|
||
* // @log: [Uint8Array [ ... ], Uint8Array [ ... ]]
|
||
* ```
|
||
*
|
||
* @param blobs - The {@link ox#Blobs.Blobs} to transform to commitments.
|
||
* @param options - Options.
|
||
* @returns The commitments.
|
||
*/
|
||
export function toCommitments<
|
||
const blobs extends Blobs<Bytes.Bytes> | Blobs<Hex.Hex>,
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (blobs extends Blobs<Hex.Hex> ? 'Hex' : never)
|
||
| (blobs extends Blobs<Bytes.Bytes> ? 'Bytes' : never),
|
||
>(
|
||
blobs: blobs | Blobs<Bytes.Bytes> | Blobs<Hex.Hex>,
|
||
options: toCommitments.Options<as>,
|
||
): toCommitments.ReturnType<as> {
|
||
const { kzg } = options
|
||
|
||
const as = options.as ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes')
|
||
const blobs_ = (
|
||
typeof blobs[0] === 'string'
|
||
? blobs.map((x) => Bytes.fromHex(x as any))
|
||
: blobs
|
||
) as Bytes.Bytes[]
|
||
|
||
const commitments: Bytes.Bytes[] = []
|
||
for (const blob of blobs_)
|
||
commitments.push(Uint8Array.from(kzg.blobToKzgCommitment(blob)))
|
||
|
||
return (
|
||
as === 'Bytes' ? commitments : commitments.map((x) => Hex.fromBytes(x))
|
||
) as never
|
||
}
|
||
|
||
export declare namespace toCommitments {
|
||
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
||
/** KZG implementation. */
|
||
kzg: Pick<Kzg.Kzg, 'blobToKzgCommitment'>
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex'> = Compute<
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
>
|
||
|
||
type ErrorType =
|
||
| Bytes.fromHex.ErrorType
|
||
| Hex.fromBytes.ErrorType
|
||
| Errors.GlobalErrorType
|
||
}
|
||
|
||
export declare namespace toBytes {
|
||
type ReturnType = to.ReturnType<'Bytes'>
|
||
type ErrorType = to.ErrorType | Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Compute the proofs for a list of {@link ox#Blobs.Blobs} and their commitments.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'viem'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg })
|
||
* const proofs = Blobs.toProofs(blobs, { commitments, kzg }) // [!code focus]
|
||
* ```
|
||
*
|
||
* @param blobs - The {@link ox#Blobs.Blobs} to compute proofs for.
|
||
* @param options - Options.
|
||
* @returns The Blob proofs.
|
||
*/
|
||
export function toProofs<
|
||
const blobs extends readonly Bytes.Bytes[] | readonly Hex.Hex[],
|
||
const commitments extends readonly Bytes.Bytes[] | readonly Hex.Hex[],
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (blobs extends readonly Hex.Hex[] ? 'Hex' : never)
|
||
| (blobs extends readonly Bytes.Bytes[] ? 'Bytes' : never),
|
||
>(
|
||
blobs: blobs | Blobs<Bytes.Bytes> | Blobs<Hex.Hex>,
|
||
options: toProofs.Options<blobs, commitments, as>,
|
||
): toProofs.ReturnType<as> {
|
||
const { kzg } = options
|
||
|
||
const as = options.as ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes')
|
||
|
||
const blobs_ = (
|
||
typeof blobs[0] === 'string'
|
||
? blobs.map((x) => Bytes.fromHex(x as any))
|
||
: blobs
|
||
) as Bytes.Bytes[]
|
||
const commitments = (
|
||
typeof options.commitments[0] === 'string'
|
||
? options.commitments.map((x) => Bytes.fromHex(x as any))
|
||
: options.commitments
|
||
) as Bytes.Bytes[]
|
||
|
||
const proofs: Bytes.Bytes[] = []
|
||
for (let i = 0; i < blobs_.length; i++) {
|
||
const blob = blobs_[i]!
|
||
const commitment = commitments[i]!
|
||
proofs.push(Uint8Array.from(kzg.computeBlobKzgProof(blob, commitment)))
|
||
}
|
||
|
||
return (
|
||
as === 'Bytes' ? proofs : proofs.map((x) => Hex.fromBytes(x))
|
||
) as never
|
||
}
|
||
|
||
export declare namespace toProofs {
|
||
type Options<
|
||
blobs extends Blobs<Bytes.Bytes> | Blobs<Hex.Hex> =
|
||
| Blobs<Bytes.Bytes>
|
||
| Blobs<Hex.Hex>,
|
||
commitments extends readonly Bytes.Bytes[] | readonly Hex.Hex[] =
|
||
| readonly Bytes.Bytes[]
|
||
| readonly Hex.Hex[],
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (blobs extends Blobs<Hex.Hex> ? 'Hex' : never)
|
||
| (blobs extends Blobs<Bytes.Bytes> ? 'Bytes' : never),
|
||
> = {
|
||
/** Commitments for the blobs. */
|
||
commitments: (commitments | readonly Bytes.Bytes[] | readonly Hex.Hex[]) &
|
||
(commitments extends blobs
|
||
? {}
|
||
: `commitments must be the same type as blobs`)
|
||
/** KZG implementation. */
|
||
kzg: Pick<Kzg.Kzg, 'computeBlobKzgProof'>
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
|
||
type ErrorType =
|
||
| Hex.fromBytes.ErrorType
|
||
| Bytes.fromHex.ErrorType
|
||
| Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Transforms {@link ox#Blobs.Blobs} into a {@link ox#Blobs.BlobSidecars} array.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const sidecars = Blobs.toSidecars(blobs, { kzg }) // [!code focus]
|
||
* ```
|
||
*
|
||
* @example
|
||
* You can also provide your own commitments and proofs if you do not want `toSidecars`
|
||
* to compute them.
|
||
*
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const commitments = Blobs.toCommitments(blobs, { kzg })
|
||
* const proofs = Blobs.toProofs(blobs, { commitments, kzg })
|
||
*
|
||
* const sidecars = Blobs.toSidecars(blobs, { commitments, kzg, proofs }) // [!code focus]
|
||
* ```
|
||
*
|
||
* @param blobs - The {@link ox#Blobs.Blobs} to transform into {@link ox#Blobs.BlobSidecars}.
|
||
* @param options - Options.
|
||
* @returns The {@link ox#Blobs.BlobSidecars}.
|
||
*/
|
||
export function toSidecars<
|
||
const blobs extends Blobs<Hex.Hex> | Blobs<Bytes.Bytes>,
|
||
>(
|
||
blobs: blobs,
|
||
options: toSidecars.Options<blobs>,
|
||
): toSidecars.ReturnType<blobs> {
|
||
const { kzg } = options
|
||
|
||
const commitments = options.commitments ?? toCommitments(blobs, { kzg: kzg! })
|
||
const proofs =
|
||
options.proofs ??
|
||
toProofs(blobs, { commitments: commitments as any, kzg: kzg! })
|
||
|
||
const sidecars: Mutable<BlobSidecars> = []
|
||
for (let i = 0; i < blobs.length; i++)
|
||
sidecars.push({
|
||
blob: blobs[i]!,
|
||
commitment: commitments[i]!,
|
||
proof: proofs[i]!,
|
||
})
|
||
|
||
return sidecars as never
|
||
}
|
||
|
||
export declare namespace toSidecars {
|
||
type Options<
|
||
blobs extends Blobs<Hex.Hex> | Blobs<Bytes.Bytes> =
|
||
| Blobs<Hex.Hex>
|
||
| Blobs<Bytes.Bytes>,
|
||
> = {
|
||
kzg?: Kzg.Kzg | undefined
|
||
} & OneOf<
|
||
| {}
|
||
| {
|
||
/** Commitment for each blob. */
|
||
commitments: blobs | readonly Hex.Hex[] | readonly Bytes.Bytes[]
|
||
/** Proof for each blob. */
|
||
proofs: blobs | readonly Hex.Hex[] | readonly Bytes.Bytes[]
|
||
}
|
||
>
|
||
|
||
type ReturnType<blobs extends Blobs<Hex.Hex> | Blobs<Bytes.Bytes>> =
|
||
UnionCompute<
|
||
| (blobs extends Blobs<Hex.Hex> ? BlobSidecars<Hex.Hex> : never)
|
||
| (blobs extends Blobs<Bytes.Bytes> ? BlobSidecars<Bytes.Bytes> : never)
|
||
>
|
||
|
||
type ErrorType = Errors.GlobalErrorType
|
||
}
|
||
|
||
/**
|
||
* Compute Blob Versioned Hashes from a list of {@link ox#Blobs.Blobs}.
|
||
*
|
||
* @example
|
||
* ```ts twoslash
|
||
* // @noErrors
|
||
* import { Blobs } from 'ox'
|
||
* import { kzg } from './kzg'
|
||
*
|
||
* const blobs = Blobs.from('0xdeadbeef')
|
||
* const versionedHashes = Blobs.toVersionedHashes(blobs, { kzg }) // [!code focus]
|
||
* ```
|
||
*
|
||
* @param blobs - The {@link ox#Blobs.Blobs} to transform into Blob Versioned Hashes.
|
||
* @param options - Options.
|
||
* @returns The Blob Versioned Hashes.
|
||
*/
|
||
export function toVersionedHashes<
|
||
const blobs extends Blobs<Bytes.Bytes> | Blobs<Hex.Hex>,
|
||
as extends 'Hex' | 'Bytes' =
|
||
| (blobs extends Blobs<Hex.Hex> ? 'Hex' : never)
|
||
| (blobs extends Blobs<Bytes.Bytes> ? 'Bytes' : never),
|
||
>(
|
||
blobs: blobs | Blobs<Bytes.Bytes> | Blobs<Hex.Hex>,
|
||
options: toVersionedHashes.Options<as>,
|
||
): toVersionedHashes.ReturnType<as> {
|
||
const commitments = toCommitments(blobs, options)
|
||
return commitmentsToVersionedHashes(commitments, options)
|
||
}
|
||
|
||
export declare namespace toVersionedHashes {
|
||
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
||
/** KZG implementation. */
|
||
kzg: Pick<Kzg.Kzg, 'blobToKzgCommitment'>
|
||
/** Return type. */
|
||
as?: as | 'Hex' | 'Bytes' | undefined
|
||
}
|
||
|
||
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex'> = Compute<
|
||
| (as extends 'Bytes' ? readonly Bytes.Bytes[] : never)
|
||
| (as extends 'Hex' ? readonly Hex.Hex[] : never)
|
||
>
|
||
|
||
type ErrorType =
|
||
| toCommitments.ErrorType
|
||
| commitmentsToVersionedHashes.ErrorType
|
||
| Errors.GlobalErrorType
|
||
}
|
||
|
||
/** A list of {@link ox#Blobs.BlobSidecar}. */
|
||
export type BlobSidecars<
|
||
type extends Hex.Hex | Bytes.Bytes = Hex.Hex | Bytes.Bytes,
|
||
> = readonly Compute<BlobSidecar<type>>[]
|
||
|
||
/** Thrown when the blob size is too large. */
|
||
export class BlobSizeTooLargeError extends Errors.BaseError {
|
||
override readonly name = 'Blobs.BlobSizeTooLargeError'
|
||
constructor({ maxSize, size }: { maxSize: number; size: number }) {
|
||
super('Blob size is too large.', {
|
||
metaMessages: [`Max: ${maxSize} bytes`, `Given: ${size} bytes`],
|
||
})
|
||
}
|
||
}
|
||
|
||
/** Thrown when the blob is empty. */
|
||
export class EmptyBlobError extends Errors.BaseError {
|
||
override readonly name = 'Blobs.EmptyBlobError'
|
||
constructor() {
|
||
super('Blob data must not be empty.')
|
||
}
|
||
}
|
||
|
||
/** Thrown when the blob versioned hashes are empty. */
|
||
export class EmptyBlobVersionedHashesError extends Errors.BaseError {
|
||
override readonly name = 'Blobs.EmptyBlobVersionedHashesError'
|
||
constructor() {
|
||
super('Blob versioned hashes must not be empty.')
|
||
}
|
||
}
|
||
|
||
/** Thrown when the blob versioned hash size is invalid. */
|
||
export class InvalidVersionedHashSizeError extends Errors.BaseError {
|
||
override readonly name = 'Blobs.InvalidVersionedHashSizeError'
|
||
constructor({
|
||
hash,
|
||
size,
|
||
}: {
|
||
hash: Hex.Hex
|
||
size: number
|
||
}) {
|
||
super(`Versioned hash "${hash}" size is invalid.`, {
|
||
metaMessages: ['Expected: 32', `Received: ${size}`],
|
||
})
|
||
}
|
||
}
|
||
|
||
/** Thrown when the blob versioned hash version is invalid. */
|
||
export class InvalidVersionedHashVersionError extends Errors.BaseError {
|
||
override readonly name = 'Blobs.InvalidVersionedHashVersionError'
|
||
constructor({
|
||
hash,
|
||
version,
|
||
}: {
|
||
hash: Hex.Hex
|
||
version: number
|
||
}) {
|
||
super(`Versioned hash "${hash}" version is invalid.`, {
|
||
metaMessages: [
|
||
`Expected: ${Kzg.versionedHashVersion}`,
|
||
`Received: ${version}`,
|
||
],
|
||
})
|
||
}
|
||
}
|