From c563309071d426ce5a641e33e5e6f74e5c62d3c3 Mon Sep 17 00:00:00 2001 From: jxom <7336481+jxom@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:20:24 +1000 Subject: [PATCH 1/2] fix(siwe): restore number chain IDs --- .changeset/tidy-dodos-count.md | 5 +++++ site/src/pages/guides/siwe.md | 6 +++--- src/core/Siwe.ts | 12 ++++++------ src/core/_test/Siwe.test.ts | 14 ++++++-------- src/index.ts | 4 ++-- 5 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 .changeset/tidy-dodos-count.md diff --git a/.changeset/tidy-dodos-count.md b/.changeset/tidy-dodos-count.md new file mode 100644 index 00000000..20384f32 --- /dev/null +++ b/.changeset/tidy-dodos-count.md @@ -0,0 +1,5 @@ +--- +'ox': patch +--- + +Restored number-valued SIWE chain IDs for message creation and parsing. diff --git a/site/src/pages/guides/siwe.md b/site/src/pages/guides/siwe.md index 16fb35c3..bae8af95 100644 --- a/site/src/pages/guides/siwe.md +++ b/site/src/pages/guides/siwe.md @@ -45,7 +45,7 @@ Before you can create a SIWE message, you need to source the following informati function onClick() { const data = { address: '', // e.g. Wagmi `useAccount()`/`getAccount()` - chainId: 1n, + chainId: 1, domain: window.location.host, nonce: '', // e.g. `await getNonceFromServer()` uri: window.location.origin, @@ -62,7 +62,7 @@ import { Siwe } from 'ox' function onClick() { const data = { address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - chainId: 1n, + chainId: 1, domain: 'example.com', nonce: '65ed4681d4efe0270b923ff5f4b097b1c95974dc33aeebecd5724c42fd86dfd25dc70b27ef836b2aa22e68f19ebcccc1', uri: 'https://example.com/path', @@ -94,7 +94,7 @@ import { Hex, Provider, Siwe } from 'ox' async function onClick() { const data = { address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - chainId: 1n, + chainId: 1, domain: 'example.com', nonce: '65ed4681d4efe0270b923ff5f4b097b1c95974dc33aeebecd5724c42fd86dfd25dc70b27ef836b2aa22e68f19ebcccc1', uri: 'https://example.com/path', diff --git a/src/core/Siwe.ts b/src/core/Siwe.ts index fb65416a..badb015d 100644 --- a/src/core/Siwe.ts +++ b/src/core/Siwe.ts @@ -32,7 +32,7 @@ export type Message = { /** * The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) Chain ID to which the session is bound, */ - chainId: bigint + chainId: number /** * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority that is requesting the signing. */ @@ -88,7 +88,7 @@ export type Message = { * * Siwe.createMessage({ * address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * chainId: 1n, + * chainId: 1, * domain: 'example.com', * nonce: 'foobarbaz', * uri: 'https://example.com/path', @@ -126,7 +126,7 @@ export function createMessage(value: Message): string { // Validate fields { // Required fields - if (typeof chainId !== 'bigint' || chainId < 0n) + if (!Number.isInteger(chainId) || chainId < 0) throw new InvalidMessageFieldError({ field: 'chainId', metaMessages: [ @@ -351,7 +351,7 @@ function splitUri(value: string) { * Issued At: 2023-02-01T00:00:00.000Z`) * // @log: { * // @log: address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * // @log: chainId: 1n, + * // @log: chainId: 1, * // @log: domain: 'example.com', * // @log: issuedAt: '2023-02-01T00:00:00.000Z', * // @log: nonce: 'foobarbaz', @@ -387,7 +387,7 @@ export function parseMessage(message: string): ExactPartial { return { ...prefix, ...suffix, - ...(chainId ? { chainId: BigInt(chainId) } : {}), + ...(chainId ? { chainId: Number(chainId) } : {}), ...(expirationTime ? { expirationTime: new Date(expirationTime) } : {}), ...(issuedAt ? { issuedAt: new Date(issuedAt) } : {}), ...(notBefore ? { notBefore: new Date(notBefore) } : {}), @@ -410,7 +410,7 @@ export function parseMessage(message: string): ExactPartial { * domain: 'example.com', * message: { * address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * chainId: 1n, + * chainId: 1, * domain: 'example.com', * nonce: 'foobarbaz', * uri: 'https://example.com/path', diff --git a/src/core/_test/Siwe.test.ts b/src/core/_test/Siwe.test.ts index 24ba6545..5dd310cb 100644 --- a/src/core/_test/Siwe.test.ts +++ b/src/core/_test/Siwe.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test, vi } from 'vp/test' describe('createMessage', () => { const message = { address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - chainId: 1n, + chainId: 1, domain: 'example.com', nonce: 'foobarbaz', uri: 'https://example.com/path', @@ -277,10 +277,8 @@ describe('createMessage', () => { }) test('behavior: invalid chainId', () => { - expect(() => - // @ts-expect-error chain IDs must be bigint. - Siwe.createMessage({ ...message, chainId: 1.1 }), - ).toThrowErrorMatchingInlineSnapshot(` + expect(() => Siwe.createMessage({ ...message, chainId: 1.1 })) + .toThrowErrorMatchingInlineSnapshot(` [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "chainId". - Chain ID must be a EIP-155 chain ID. @@ -478,7 +476,7 @@ Issued At: 2023-02-01T00:00:00.000Z` expect(parsed).toMatchInlineSnapshot(` { "address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", - "chainId": 1n, + "chainId": 1, "domain": "example.com", "issuedAt": 2023-02-01T00:00:00.000Z, "nonce": "foobarbaz", @@ -626,7 +624,7 @@ Request ID: 123e4567-e89b-12d3-a456-426614174000` const parsed = Siwe.parseMessage(message) expect(parsed).toMatchInlineSnapshot(` { - "chainId": 1n, + "chainId": 1, "issuedAt": 2023-02-01T00:00:00.000Z, "nonce": "foobarbaz", "requestId": "123e4567-e89b-12d3-a456-426614174000", @@ -646,7 +644,7 @@ Request ID: 123e4567-e89b-12d3-a456-426614174000` describe('validateMessage', () => { const message = { address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - chainId: 1n, + chainId: 1, domain: 'example.com', nonce: 'foobarbaz', uri: 'https://example.com/path', diff --git a/src/index.ts b/src/index.ts index bdc30201..1de3d9d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2472,7 +2472,7 @@ export * as Signature from './core/Signature.js' * * Siwe.createMessage({ * address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * chainId: 1n, + * chainId: 1, * domain: 'example.com', * nonce: 'foobarbaz', * uri: 'https://example.com/path', @@ -2544,7 +2544,7 @@ export * as Signature from './core/Signature.js' * domain: 'example.com', * message: { * address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * chainId: 1n, + * chainId: 1, * domain: 'example.com', * nonce: 'foobarbaz', * uri: 'https://example.com/path', From a1e506f077833217ce82ecc60a97bfa2dceb1529 Mon Sep 17 00:00:00 2001 From: jxom <7336481+jxom@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:20:40 +1000 Subject: [PATCH 2/2] docs: add v0 migration guide --- site/src/pages/_root.css | 5 + site/src/pages/migrating-from-v0.md | 184 ++++++++++++++++++++++++++++ site/vocs.config.ts | 6 + 3 files changed, 195 insertions(+) create mode 100644 site/src/pages/migrating-from-v0.md diff --git a/site/src/pages/_root.css b/site/src/pages/_root.css index 220893ec..6620ed21 100644 --- a/site/src/pages/_root.css +++ b/site/src/pages/_root.css @@ -163,6 +163,11 @@ body, --vocs-spacing-content: var(--max-w); } +/* Account for the fixed banner on the landing page. */ +[data-layout='blank']:has(.landing-pattern) > [data-v-main] { + padding-top: var(--vocs-spacing-banner); +} + /* Landing page: tighten top padding above the header */ [data-layout='blank']:has(.landing-pattern) [data-v-content] { padding-top: 2em; diff --git a/site/src/pages/migrating-from-v0.md b/site/src/pages/migrating-from-v0.md new file mode 100644 index 00000000..9f820c95 --- /dev/null +++ b/site/src/pages/migrating-from-v0.md @@ -0,0 +1,184 @@ +--- +description: "Migrate applications from Ox v0 to v1." +--- + +# Migrating from v0 + +Ox v1 includes breaking changes to decoded addresses, cryptographic values, blob handling, and Tempo types. This guide covers every breaking change and the migration required for each one. + +First, upgrade Ox to v1. + +:::code-group +```bash [pnpm] +pnpm add ox@^1 +``` + +```bash [npm] +npm install ox@^1 +``` + +```bash [yarn] +yarn add ox@^1 +``` + +```bash [bun] +bun add ox@^1 +``` +::: + +## ABI addresses are checksummed + +ABI decode functions now checksum decoded addresses by default. If your application compares decoded addresses as case-sensitive strings, normalize both sides or pass `checksumAddress: false` to preserve the v0 behavior. + +```ts +const values = AbiParameters.decode(parameters, data) // [!code --] +const values = AbiParameters.decode(parameters, data, { // [!code ++] + checksumAddress: false, // [!code ++] +}) // [!code ++] +``` + +The option is also available on the higher-level ABI decode functions that return addresses. + +## Cryptographic coordinates use padded hex + +ECDSA and BLS coordinate fields now use padded `Hex.Hex` strings instead of `bigint`. This includes `r`, `s`, `x`, `y`, and BLS `Fp`/`Fp2` values on `Signature`, `PublicKey`, `BlsPoint`, `Transaction`, `Authorization`, `TxEnvelope`, and related Tempo and ERC types. + +ECDSA coordinates are 32 bytes. BLS12-381 coordinates are 48 bytes. The `bigintType` generic has been removed. + +```ts +const signature = Signature.from({ // [!code --] + r: 0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bfn, // [!code --] + s: 0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8n, // [!code --] + yParity: 1, // [!code --] +}) // [!code --] +const signature = Signature.from({ // [!code ++] + r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf', // [!code ++] + s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8', // [!code ++] + yParity: 1, // [!code ++] +}) // [!code ++] +``` + +Use `Hex.fromNumber(value, { size: 32 })` for ECDSA values and `Hex.fromNumber(value, { size: 48 })` for BLS12-381 values when migrating stored bigints. + +## Noble and Scure dependencies use v2 + +Ox now uses v2 of `@noble/ciphers`, `@noble/curves`, `@noble/hashes`, `@scure/bip32`, and `@scure/bip39`. + +ECDSA signatures now default to `lowS: true` for both `Secp256k1` and `P256`. In v0, `P256` signatures could have high-S values. + +If you use the `noble` re-exports on `Secp256k1`, `P256`, `Ed25519`, `X25519`, or `Bls`, migrate to the noble v2 API. Notable renames include: + +| v0 | v1 | +| --- | --- | +| `ProjectivePoint` or `ExtendedPoint` | `Point` | +| `bls.sign` and `bls.verify` | `bls.longSignatures.sign` and `bls.longSignatures.verify` | + +Refer to the relevant noble and Scure v2 changelogs for the complete upstream API changes. + +## PeerDAS replaces blob sidecars + +The EIP-4844 blob-sidecar API has been removed in favor of PeerDAS (EIP-7594). + +- `Kzg.Kzg` no longer includes `computeBlobKzgProof`. +- `Blobs.toSidecars`, `Blobs.toProofs`, and `Blobs.sidecarsToVersionedHashes` have been removed. +- `Blobs.BlobSidecar` and `Blobs.BlobSidecars` have been removed. +- `TxEnvelopeEip4844.sidecars` and the legacy network-wrapper serialization have been removed. + +Use a PeerDAS-capable KZG backend. It must provide `blobToKzgCommitment`, `computeCells`, `computeCellsAndKzgProofs`, `recoverCellsAndKzgProofs`, and `verifyCellKzgProofBatch`. + +Use `BlobCells` to construct and verify cells and data columns. + +```ts +const sidecars = Blobs.toSidecars(blobs, { kzg }) // [!code --] +const versionedHashes = Blobs.sidecarsToVersionedHashes(sidecars) // [!code --] +const columns = BlobCells.toDataColumns(blobs, { kzg }) // [!code ++] +const versionedHashes = Blobs.toVersionedHashes(blobs, { kzg }) // [!code ++] +``` + +`Kzg.Kzg.blobToKzgCommitment` and `Blobs.toVersionedHashes` remain available for transaction versioned-hash derivation. + +## Tempo addresses use plain hex + +The `TempoAddress` module and its `tempox`-prefixed address format have been removed. Tempo modules now accept plain `Address.Address` hex values. + +Remove calls to `TempoAddress.format`, `TempoAddress.parse`, and `TempoAddress.resolve` at application boundaries. The `addressType` generic has also been removed from `Call`, `TxEnvelopeTempo`, `KeyAuthorization`, `AuthorizationTempo`, and `TransactionRequest`. + +```ts +import { TempoAddress } from 'ox/tempo' // [!code --] + +const formatted = TempoAddress.format(address) // [!code --] +const { address: resolved } = TempoAddress.parse(formatted) // [!code --] +const resolved = address // [!code ++] +``` + +## Tempo tokens use addresses + +The Tempo `TokenId` module has been removed. `feeToken`, `Channel.token`, and the token arguments passed to `PoolId.from` now accept `Address.Address` values instead of numeric token IDs. + +```ts +const envelope = TxEnvelopeTempo.from({ + // ... + feeToken: 1n, // [!code --] + feeToken: '0x20c0000000000000000000000000000000000001', // [!code ++] +}) +``` + +`Channel.Resolved` has also been removed. Use `Channel.Channel` with address-valued token fields. + +## Tempo multisig no longer uses `config_id` + +TIP-1061 multisig account addresses now derive directly from the initial configuration. `MultisigConfig.toId` and the `genesisConfigId` fields have been removed. + +```ts +const id = MultisigConfig.toId(genesisConfig) // [!code --] +const account = MultisigConfig.getAddress({ genesisConfigId: id }) // [!code --] +const account = MultisigConfig.getAddress(genesisConfig) // [!code ++] + +const payload = MultisigConfig.getSignPayload({ // [!code --] + payload: transactionPayload, // [!code --] + account, // [!code --] + genesisConfigId: id, // [!code --] +}) // [!code --] +const payload = MultisigConfig.getSignPayload({ // [!code ++] + payload: transactionPayload, // [!code ++] + account, // [!code ++] +}) // [!code ++] + +const envelope = SignatureEnvelope.from({ // [!code --] + account, // [!code --] + genesisConfigId: id, // [!code --] + signatures, // [!code --] +}) // [!code --] +const envelope = SignatureEnvelope.from({ account, signatures }) // [!code ++] +``` + +Owner approval digests now bind only the account. The signature wire format is `0x05 || rlp([account, signatures, init?])`, `MultisigConfig.maxOwners` is 255 with `u8` weights, and owner approvals may contain nested multisig signatures. + +## RPC schema codecs return native quantities + +The `ox/zod` RPC schema codecs now decode scalar quantity results to native values. Balance, gas, fee, and block-number quantities decode to `bigint`; chain IDs, transaction counts, and block transaction counts decode to `number`. + +Use `RpcSchema.FromZod` when you need the raw JSON-RPC wire types. Use the codec return types when consuming decoded values. + +## Transaction envelope type detection expects native types + +`TransactionEnvelope.getType` now rejects RPC-style type strings such as `'0x0'` through `'0x4'`. Convert RPC transaction requests before detecting their envelope type. + +```ts +const type = TransactionEnvelope.getType(rpcRequest) // [!code --] +const request = TransactionRequest.fromRpc(rpcRequest) // [!code ++] +const type = TransactionEnvelope.getType(request) // [!code ++] +``` + +## Stricter validation + +Ox v1 rejects malformed values that v0 sometimes accepted or normalized. Audit code that relies on permissive parsing, particularly for: + +- undersized ABI data, invalid packed-array lengths, and anonymous event topics; +- weak or invalid keystore KDF parameters; +- malformed public keys, blooms, typed data, COSE keys, values, RLP, and JSON-RPC responses; +- non-32-byte access-list storage keys; +- EIP-4844 envelopes without `blobVersionedHashes`; and +- invalid WebAuthn assertion flags or extension data. + +See the [v1.0.0 changelog](https://github.com/wevm/ox/blob/main/CHANGELOG.md#100) for the full list of corrected validation and serialization behavior. diff --git a/site/vocs.config.ts b/site/vocs.config.ts index d947d00f..a98ccbd8 100644 --- a/site/vocs.config.ts +++ b/site/vocs.config.ts @@ -34,6 +34,11 @@ export default defineConfig({ } : {}), accentColor: 'light-dark(#b8421d, #e85d35)', + banner: { + content: 'Looking for Ox v0? View the v0 documentation.', + dismissId: 'v0-docs', + href: 'https://v0.oxlib.sh', + }, baseUrl: pkg.version.includes('-beta.') ? 'https://v1.oxlib.sh' : process.env.VERCEL_ENV === 'production' @@ -62,6 +67,7 @@ export default defineConfig({ { text: 'Imports & Bundle Size', link: '/imports' }, { text: 'Error Handling', link: '/error-handling' }, { text: 'Platform Compatibility', link: '/platform-compatibility' }, + { text: 'Migrating from v0', link: '/migrating-from-v0' }, { text: 'Guides', items: [