From f4b0f7a3009faee9b116bd2d947322b85ecd81f3 Mon Sep 17 00:00:00 2001 From: Michael Otis <34221002+michaelotis@users.noreply.github.com> Date: Tue, 21 May 2024 16:33:02 -0500 Subject: [PATCH 01/16] feat: add L3X Protocol (#2281) * Add L3X Protocol * Add changeset * Remove contracts section --- .changeset/cold-candles-visit.md | 5 +++++ src/chains/definitions/l3x.ts | 21 +++++++++++++++++++++ src/chains/definitions/l3xTestnet.ts | 21 +++++++++++++++++++++ src/chains/index.ts | 2 ++ 4 files changed, 49 insertions(+) create mode 100644 .changeset/cold-candles-visit.md create mode 100644 src/chains/definitions/l3x.ts create mode 100644 src/chains/definitions/l3xTestnet.ts diff --git a/.changeset/cold-candles-visit.md b/.changeset/cold-candles-visit.md new file mode 100644 index 0000000000..dcb03a9e13 --- /dev/null +++ b/.changeset/cold-candles-visit.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Add L3X Protocol diff --git a/src/chains/definitions/l3x.ts b/src/chains/definitions/l3x.ts new file mode 100644 index 0000000000..02b3a219c6 --- /dev/null +++ b/src/chains/definitions/l3x.ts @@ -0,0 +1,21 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const l3x = /*#__PURE__*/ defineChain({ + id: 12324, + name: 'L3X Protocol', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://rpc-mainnet.l3x.com'], + webSocket: ['wss://rpc-mainnet.l3x.com'], + }, + }, + blockExplorers: { + default: { + name: 'L3X Mainnet Explorer', + url: 'https://explorer.l3x.com', + apiUrl: 'https://explorer.l3x.com/api/v2', + }, + }, + testnet: false, +}) diff --git a/src/chains/definitions/l3xTestnet.ts b/src/chains/definitions/l3xTestnet.ts new file mode 100644 index 0000000000..70a79b9506 --- /dev/null +++ b/src/chains/definitions/l3xTestnet.ts @@ -0,0 +1,21 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const l3xTestnet = /*#__PURE__*/ defineChain({ + id: 12325, + name: 'L3X Protocol Testnet', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://rpc-testnet.l3x.com'], + webSocket: ['wss://rpc-testnet.l3x.com'], + }, + }, + blockExplorers: { + default: { + name: 'L3X Testnet Explorer', + url: 'https://explorer-testnet.l3x.com', + apiUrl: 'https://explorer-testnet.l3x.com/api/v2', + }, + }, + testnet: true, +}) diff --git a/src/chains/index.ts b/src/chains/index.ts index 303a6c1716..f21ffe1845 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -124,6 +124,8 @@ export { klaytn } from './definitions/klaytn.js' export { klaytnBaobab } from './definitions/klaytnBaobab.js' export { kroma } from './definitions/kroma.js' export { kromaSepolia } from './definitions/kromaSepolia.js' +export { l3x } from './definitions/l3x.js' +export { l3xTestnet } from './definitions/l3xTestnet.js' export { lightlinkPegasus } from './definitions/lightlinkPegasus.js' export { lightlinkPhoenix } from './definitions/lightlinkPhoenix.js' export { linea } from './definitions/linea.js' From 80a83a211cad42f31f42348b22b7d830f5590b0e Mon Sep 17 00:00:00 2001 From: jxom Date: Wed, 22 May 2024 07:33:17 +1000 Subject: [PATCH 02/16] Update cold-candles-visit.md --- .changeset/cold-candles-visit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-candles-visit.md b/.changeset/cold-candles-visit.md index dcb03a9e13..61e0da8f1f 100644 --- a/.changeset/cold-candles-visit.md +++ b/.changeset/cold-candles-visit.md @@ -2,4 +2,4 @@ "viem": patch --- -Add L3X Protocol +Added L3X Protocol chain. From d7eee1da79648975df06e2f46a011b2d224ef78a Mon Sep 17 00:00:00 2001 From: Nidz The Fact <106298826+nidz-the-fact@users.noreply.github.com> Date: Wed, 22 May 2024 04:36:32 +0700 Subject: [PATCH 03/16] feat: add thaiChain chain. (#2283) * Create odd-sheep-doubt.md * Create jbcTestnet.ts * Update index.ts * Create thaiChain.ts * Update index.ts * Update odd-sheep-doubt.md * Update thaiChain.ts * Update jbcTestnet.ts --------- Co-authored-by: jxom --- .changeset/odd-sheep-doubt.md | 5 +++++ src/chains/definitions/jbcTestnet.ts | 4 ---- src/chains/definitions/thaiChain.ts | 26 ++++++++++++++++++++++++++ src/chains/index.ts | 1 + 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 .changeset/odd-sheep-doubt.md create mode 100644 src/chains/definitions/thaiChain.ts diff --git a/.changeset/odd-sheep-doubt.md b/.changeset/odd-sheep-doubt.md new file mode 100644 index 0000000000..71785497bc --- /dev/null +++ b/.changeset/odd-sheep-doubt.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added thaiChain chain. diff --git a/src/chains/definitions/jbcTestnet.ts b/src/chains/definitions/jbcTestnet.ts index ad7043b954..682e1777f7 100644 --- a/src/chains/definitions/jbcTestnet.ts +++ b/src/chains/definitions/jbcTestnet.ts @@ -3,15 +3,11 @@ import { defineChain } from '../../utils/chain/defineChain.js' export const jbcTestnet = /*#__PURE__*/ defineChain({ id: 88991, name: 'Jibchain Testnet', - network: 'jbcTestnet', nativeCurrency: { name: 'tJBC', symbol: 'tJBC', decimals: 18 }, rpcUrls: { default: { http: ['https://rpc.testnet.jibchain.net'], }, - public: { - http: ['https://rpc.testnet.jibchain.net'], - }, }, blockExplorers: { default: { diff --git a/src/chains/definitions/thaiChain.ts b/src/chains/definitions/thaiChain.ts new file mode 100644 index 0000000000..f6bdde4a9f --- /dev/null +++ b/src/chains/definitions/thaiChain.ts @@ -0,0 +1,26 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const thaiChain = /*#__PURE__*/ defineChain({ + id: 7, + name: 'ThaiChain', + nativeCurrency: { name: 'TCH', symbol: 'TCH', decimals: 18 }, + rpcUrls: { + default: { + http: ['hhttps://rpc.thaichain.org'], + }, + }, + blockExplorers: { + default: { + name: 'Blockscout', + url: 'https://exp.thaichain.org', + apiUrl: 'https://exp.thaichain.org/api', + }, + }, + contracts: { + multicall3: { + address: '0x0DaD6130e832c21719C5CE3bae93454E16A84826', + blockCreated: 4806386, + }, + }, + testnet: false, +}) diff --git a/src/chains/index.ts b/src/chains/index.ts index f21ffe1845..59f122e9b1 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -248,6 +248,7 @@ export { telcoinTestnet } from './definitions/telcoinTestnet.js' export { telos } from './definitions/telos.js' export { telosTestnet } from './definitions/telosTestnet.js' export { tenet } from './definitions/tenet.js' +export { thaiChain } from './definitions/thaiChain.js' export { thunderTestnet } from './definitions/thunderTestnet.js' export { vechain } from './definitions/vechain.js' export { wanchain } from './definitions/wanchain.js' From 86bd2a7dbfca7d1cd118a9feaac07a69a8da0d19 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 22 May 2024 00:37:52 +0300 Subject: [PATCH 04/16] feat: update Metis Explorer Definitions (#2284) * Update metis.ts * Create poor-berries-look.md --------- Co-authored-by: jxom --- .changeset/poor-berries-look.md | 5 +++++ src/chains/definitions/metis.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/poor-berries-look.md diff --git a/.changeset/poor-berries-look.md b/.changeset/poor-berries-look.md new file mode 100644 index 0000000000..2c561790df --- /dev/null +++ b/.changeset/poor-berries-look.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Updated Metis Explorer Definitions diff --git a/src/chains/definitions/metis.ts b/src/chains/definitions/metis.ts index ed4c89f820..cc55b99ec6 100644 --- a/src/chains/definitions/metis.ts +++ b/src/chains/definitions/metis.ts @@ -13,6 +13,11 @@ export const metis = /*#__PURE__*/ defineChain({ }, blockExplorers: { default: { + name: 'Metis Explorer', + url: 'https://explorer.metis.io', + apiUrl: 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', + }, + blockscout: { name: 'Andromeda Explorer', url: 'https://andromeda-explorer.metis.io', apiUrl: 'https://andromeda-explorer.metis.io/api', From 22e6a9ccee30ac05eda00bc5d065ffab159f8de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sim=C3=A3o?= Date: Tue, 21 May 2024 22:38:10 +0100 Subject: [PATCH 05/16] docs: update withdrawals.md (#2285) * Update withdrawals.md * Update withdrawals.md --- site/pages/op-stack/guides/withdrawals.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/pages/op-stack/guides/withdrawals.md b/site/pages/op-stack/guides/withdrawals.md index c52a7367bc..d63f223395 100644 --- a/site/pages/op-stack/guides/withdrawals.md +++ b/site/pages/op-stack/guides/withdrawals.md @@ -591,7 +591,7 @@ export const walletClientL2 = createWalletClient({ ::: :::tip -You can utilize the [`getTimeToFinalize`](/op-stack/actions/getTimeToFinalize) Action if you want to extract the estimated time left to prove the withdrawal from the `waitToFinalize` method and display it to the user or store in a database. +You can utilize the [`getTimeToFinalize`](/op-stack/actions/getTimeToFinalize) Action if you want to extract the estimated time left to finalize the withdrawal from the `waitToFinalize` method and display it to the user or store in a database. ```ts const { seconds, timestamp } = await publicClientL1.getTimeToFinalize({ @@ -602,7 +602,7 @@ const { seconds, timestamp } = await publicClientL1.getTimeToFinalize({ ::: :::warning -If you aren't using the `waitToFinalize` Action, it is highly recommended to check if the withdrawal is ready to be proved by using the [`getWithdrawalStatus`](/op-stack/actions/getWithdrawalStatus) Action. This will prevent you from proving a withdrawal that isn't ready yet. +If you aren't using the `waitToFinalize` Action, it is highly recommended to check if the withdrawal is ready to be finalized by using the [`getWithdrawalStatus`](/op-stack/actions/getWithdrawalStatus) Action. This will prevent you from finalizing a withdrawal that isn't ready yet. ```ts const status = await publicClientL1.getWithdrawalStatus({ @@ -614,4 +614,4 @@ if (status === 'ready-to-finalize') { // ... } ``` -::: \ No newline at end of file +::: From 02111808aa2fba3a0455f94c36cfefe5331e3927 Mon Sep 17 00:00:00 2001 From: Daniil Popenko Date: Wed, 22 May 2024 00:38:24 +0300 Subject: [PATCH 06/16] chore: README.md typo (#2286) fix: README.md typo --- src/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.md b/src/README.md index 24e54b358d..fa428b422c 100644 --- a/src/README.md +++ b/src/README.md @@ -60,7 +60,7 @@ - First-class support for [Anvil](https://book.getfoundry.sh/), [Hardhat](https://hardhat.org/) & [Ganache](https://trufflesuite.com/ganache/) - Test suite running against [forked](https://ethereum.org/en/glossary/#fork) Ethereum network -... and a lot lot more. +... and a lot more. ## Overview From 8525dfcd8f482d55925bebfe57043fb70db6687d Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 21 May 2024 17:39:06 -0400 Subject: [PATCH 07/16] feat: add multilcall3 contract (#2287) * add multilcall3 contract add multicall3 contract to Flow Previewnet * Create green-clocks-smash.md --------- Co-authored-by: jxom --- .changeset/green-clocks-smash.md | 5 +++++ src/chains/definitions/flowPreviewnet.ts | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/green-clocks-smash.md diff --git a/.changeset/green-clocks-smash.md b/.changeset/green-clocks-smash.md new file mode 100644 index 0000000000..f6c151d41b --- /dev/null +++ b/.changeset/green-clocks-smash.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added multilcall3 contract to Flow Previewnet. diff --git a/src/chains/definitions/flowPreviewnet.ts b/src/chains/definitions/flowPreviewnet.ts index 1f9bbe2d19..d7ac6fe30c 100644 --- a/src/chains/definitions/flowPreviewnet.ts +++ b/src/chains/definitions/flowPreviewnet.ts @@ -19,4 +19,10 @@ export const flowPreviewnet = /*#__PURE__*/ defineChain({ url: 'https://previewnet.flowdiver.io', }, }, + contracts: { + multicall3: { + address: "0xca11bde05977b3631167028862be2a173976ca11", + blockCreated: 6205, + }, + }, }) From 846bf3d4ab87493cdf1a7320a95e2eb2018df0aa Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 21 May 2024 21:42:17 +0000 Subject: [PATCH 08/16] chore: format --- src/chains/definitions/flowPreviewnet.ts | 2 +- src/chains/definitions/metis.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/chains/definitions/flowPreviewnet.ts b/src/chains/definitions/flowPreviewnet.ts index d7ac6fe30c..85977f6f32 100644 --- a/src/chains/definitions/flowPreviewnet.ts +++ b/src/chains/definitions/flowPreviewnet.ts @@ -21,7 +21,7 @@ export const flowPreviewnet = /*#__PURE__*/ defineChain({ }, contracts: { multicall3: { - address: "0xca11bde05977b3631167028862be2a173976ca11", + address: '0xca11bde05977b3631167028862be2a173976ca11', blockCreated: 6205, }, }, diff --git a/src/chains/definitions/metis.ts b/src/chains/definitions/metis.ts index cc55b99ec6..3525dcb08c 100644 --- a/src/chains/definitions/metis.ts +++ b/src/chains/definitions/metis.ts @@ -15,7 +15,8 @@ export const metis = /*#__PURE__*/ defineChain({ default: { name: 'Metis Explorer', url: 'https://explorer.metis.io', - apiUrl: 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', + apiUrl: + 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', }, blockscout: { name: 'Andromeda Explorer', From 4dc6d324e505667a4820d1069ece35cf5488d1ac Mon Sep 17 00:00:00 2001 From: awkweb Date: Tue, 21 May 2024 17:54:38 -0400 Subject: [PATCH 09/16] feat: siwe (#2280) * feat: siwe * wip: check * wip: parse * refactor: comments and errors * chore: format * chore: add exports * test: utils * chore: format * refactor: time-related data types * refactor: rename utils * feat(siwe): action * chore: format * docs: siwe * chore: format * docs: up * chore: snaps * test: boost coverage --------- Co-authored-by: tmm --- .../docs/actions/public/verifyMessage.md | 4 +- .../docs/actions/public/verifyTypedData.md | 4 +- site/pages/docs/ens/utilities/labelhash.md | 4 +- site/pages/docs/glossary/errors.md | 6 + .../docs/siwe/actions/verifySiweMessage.md | 245 +++++++++++++ .../docs/siwe/utilities/createSiweMessage.md | 295 ++++++++++++++++ .../docs/siwe/utilities/generateSiweNonce.md | 27 ++ .../docs/siwe/utilities/parseSiweMessage.md | 52 +++ .../siwe/utilities/validateSiweMessage.md | 76 ++++ site/sidebar.ts | 36 ++ src/actions/public/verifyMessage.ts | 19 +- src/actions/siwe/verifySiweMessage.test.ts | 69 ++++ src/actions/siwe/verifySiweMessage.ts | 90 +++++ src/clients/createClient.test.ts | 1 + src/clients/createPublicClient.test.ts | 5 + src/clients/createTestClient.test.ts | 1 + src/clients/createWalletClient.test.ts | 1 + src/clients/decorators/public.test.ts | 26 ++ src/clients/decorators/public.ts | 37 ++ src/errors/siwe.test.ts | 26 ++ src/errors/siwe.ts | 20 ++ src/jsr.json | 2 +- src/package.json | 6 + src/siwe/index.ts | 29 ++ src/siwe/package.json | 6 + src/utils/siwe/createSiweMessage.test.ts | 334 ++++++++++++++++++ src/utils/siwe/createSiweMessage.ts | 168 +++++++++ src/utils/siwe/generateSiweNonce.test.ts | 8 + src/utils/siwe/generateSiweNonce.ts | 15 + src/utils/siwe/parseSiweMessage.test.ts | 169 +++++++++ src/utils/siwe/parseSiweMessage.ts | 55 +++ src/utils/siwe/types.ts | 61 ++++ src/utils/siwe/utils.test.ts | 42 +++ src/utils/siwe/utils.ts | 51 +++ src/utils/siwe/validateSiweMessage.test.ts | 109 ++++++ src/utils/siwe/validateSiweMessage.ts | 70 ++++ 36 files changed, 2153 insertions(+), 16 deletions(-) create mode 100644 site/pages/docs/siwe/actions/verifySiweMessage.md create mode 100644 site/pages/docs/siwe/utilities/createSiweMessage.md create mode 100644 site/pages/docs/siwe/utilities/generateSiweNonce.md create mode 100644 site/pages/docs/siwe/utilities/parseSiweMessage.md create mode 100644 site/pages/docs/siwe/utilities/validateSiweMessage.md create mode 100644 src/actions/siwe/verifySiweMessage.test.ts create mode 100644 src/actions/siwe/verifySiweMessage.ts create mode 100644 src/errors/siwe.test.ts create mode 100644 src/errors/siwe.ts create mode 100644 src/siwe/index.ts create mode 100644 src/siwe/package.json create mode 100644 src/utils/siwe/createSiweMessage.test.ts create mode 100644 src/utils/siwe/createSiweMessage.ts create mode 100644 src/utils/siwe/generateSiweNonce.test.ts create mode 100644 src/utils/siwe/generateSiweNonce.ts create mode 100644 src/utils/siwe/parseSiweMessage.test.ts create mode 100644 src/utils/siwe/parseSiweMessage.ts create mode 100644 src/utils/siwe/types.ts create mode 100644 src/utils/siwe/utils.test.ts create mode 100644 src/utils/siwe/utils.ts create mode 100644 src/utils/siwe/validateSiweMessage.test.ts create mode 100644 src/utils/siwe/validateSiweMessage.ts diff --git a/site/pages/docs/actions/public/verifyMessage.md b/site/pages/docs/actions/public/verifyMessage.md index e75b1316c8..4c32ccff3c 100644 --- a/site/pages/docs/actions/public/verifyMessage.md +++ b/site/pages/docs/actions/public/verifyMessage.md @@ -60,7 +60,7 @@ export const [account] = await walletClient.getAddresses() `boolean` -Wheather the signed message is valid for the given address. +Whether the signed message is valid for the given address. ## Parameters @@ -169,4 +169,4 @@ const valid = await publicClient.verifyMessage({ ## JSON-RPC Method -[`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call) to a deployless [universal signature validator contract](https://eips.ethereum.org/EIPS/eip-6492). \ No newline at end of file +[`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call) to a deployless [universal signature validator contract](https://eips.ethereum.org/EIPS/eip-6492). diff --git a/site/pages/docs/actions/public/verifyTypedData.md b/site/pages/docs/actions/public/verifyTypedData.md index 2fbf6034e6..0d170a7ddc 100644 --- a/site/pages/docs/actions/public/verifyTypedData.md +++ b/site/pages/docs/actions/public/verifyTypedData.md @@ -102,7 +102,7 @@ export const [account] = await walletClient.getAddresses() `boolean` -Wheather the signed message is valid for the given address. +Whether the signed message is valid for the given address. ## Parameters @@ -459,4 +459,4 @@ const valid = await publicClient.verifyTypedData({ ## JSON-RPC Method -[`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call) to a deployless [universal signature validator contract](https://eips.ethereum.org/EIPS/eip-6492). \ No newline at end of file +[`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call) to a deployless [universal signature validator contract](https://eips.ethereum.org/EIPS/eip-6492). diff --git a/site/pages/docs/ens/utilities/labelhash.md b/site/pages/docs/ens/utilities/labelhash.md index 4fe38ec8a3..b484e51845 100644 --- a/site/pages/docs/ens/utilities/labelhash.md +++ b/site/pages/docs/ens/utilities/labelhash.md @@ -25,8 +25,6 @@ labelhash(normalize('awkweb')) // [!code focus:2] Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS labels](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `labelhash`. You can use the built-in [`normalize`](/docs/ens/utilities/normalize) function for this. ::: -### - ## Returns `string` @@ -39,4 +37,4 @@ The hashed ENS label. - **Type:** `string` -A ENS label. \ No newline at end of file +A ENS label. diff --git a/site/pages/docs/glossary/errors.md b/site/pages/docs/glossary/errors.md index af52e31e4c..2f64303a99 100644 --- a/site/pages/docs/glossary/errors.md +++ b/site/pages/docs/glossary/errors.md @@ -137,6 +137,12 @@ When address is invalid. ### `UnsupportedProviderMethodError` ### `UserRejectedRequestError` +## SIWE + +### CreateSiweMessageErrorType +### SiweInvalidMessageFieldErrorType +### VerifySiweMessageErrorType + ## Transaction ### `FeeConflictError` diff --git a/site/pages/docs/siwe/actions/verifySiweMessage.md b/site/pages/docs/siwe/actions/verifySiweMessage.md new file mode 100644 index 0000000000..2bbdb5ce58 --- /dev/null +++ b/site/pages/docs/siwe/actions/verifySiweMessage.md @@ -0,0 +1,245 @@ +--- +description: Verifies EIP-4361 formatted message was signed. +--- + +# verifySiweMessage + +Verifies [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message was signed. + +See [`createSiweMessage`](/docs/siwe/utilities/createSiweMessage) for info on how to create a EIP-4361 formatted message. + +## Usage + +:::code-group + +```ts twoslash [example.ts] +import { account, walletClient, publicClient } from './client' +import { message } from './message' + +const signature = await walletClient.signMessage({ account, message }) +// [!code focus:99] +const valid = await publicClient.verifySiweMessage({ + message, + signature, +}) +// @log: true +``` + +```ts twoslash [client.ts] filename="client.ts" +import 'viem/window' +// ---cut--- +import { createPublicClient, createWalletClient, custom, http } from 'viem' +import { mainnet } from 'viem/chains' + +export const publicClient = createPublicClient({ + chain: mainnet, + transport: http() +}) + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum!) +}) + +// @log: ↓ JSON-RPC Account +export const [account] = await walletClient.getAddresses() + +// @log: ↓ Local Account +// export const account = privateKeyToAccount(...) +``` + +```ts twoslash [message.ts] filename="message.ts" +// ---cut--- +import { createSiweMessage, generateSiweNonce } from 'viem/siwe' +import { mainnet } from 'viem/chains' +import { account } from './client' + +export const message = createSiweMessage({ + address: account.address, + chainId: mainnet.id, + domain: 'example.com', + nonce: generateSiweNonce(), + uri: 'https://example.com/path', + version: '1', +}) +``` + +::: + +## Returns + +`boolean` + +Whether the signed message is valid for the given address. + +## Parameters + +### message + +- **Type:** `string` + +[EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message to be verified. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +import { createSiweMessage, generateSiweNonce } from 'viem/siwe' +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + message: createSiweMessage({ // [!code focus:1] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus:1] + chainId: 1, // [!code focus:1] + domain: 'example.com', // [!code focus:1] + nonce: generateSiweNonce(), // [!code focus:1] + uri: 'https://example.com/path', // [!code focus:1] + version: '1', // [!code focus:1] + }), // [!code focus:1] + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### signature + +- **Type:** `Hex` + +The signature that was generated by signing the message with the address's signer. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', // [!code focus:1] +}) +``` + +### address (optional) + +- **Type:** `Address` + +Ethereum address to check against. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### blockNumber (optional) + +- **Type:** `number` + +Only used when verifying a message that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + blockNumber: 42069n, // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### blockTag (optional) + +- **Type:** `'latest' | 'earliest' | 'pending' | 'safe' | 'finalized'` +- **Default:** `'latest'` + +Only used when verifying a message that was signed by a Smart Contract Account. The block tag to check if the contract was already deployed. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + blockTag: 'safe', // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### domain (optional) + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority to check against. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + domain: 'viem.sh', // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### nonce (optional) + +- **Type:** `string` + +Random string to check against. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +import { generateSiweNonce } from 'viem/siwe' +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + nonce: generateSiweNonce(), // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### scheme (optional) + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme to check against. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + scheme: 'https', // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + +### time (optional) + +- **Type:** `Date` +- **Default:** `new Date()` + +Current time to check optional [`expirationTime`](http://localhost:5173/docs/siwe/utilities/createSiweMessage#expirationtime-optional) and [`notBefore`](/docs/siwe/utilities/createSiweMessage#notbefore-optional) message fields. + +```ts twoslash +// [!include ~/snippets/publicClient.ts] +declare const message: string +// ---cut--- +const valid = await publicClient.verifySiweMessage({ + time: new Date(), // [!code focus:1] + message, + signature: + '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` + diff --git a/site/pages/docs/siwe/utilities/createSiweMessage.md b/site/pages/docs/siwe/utilities/createSiweMessage.md new file mode 100644 index 0000000000..2f3ee31a76 --- /dev/null +++ b/site/pages/docs/siwe/utilities/createSiweMessage.md @@ -0,0 +1,295 @@ +--- +description: Creates EIP-4361 formatted message. +--- + +# createSiweMessage + +Creates [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message. + +## Import + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' +``` + +## Usage + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +}) +``` + +## Returns + +`string` + +EIP-4361 formatted message. + +## Parameters + +### address + +- **Type:** `Address` + +The Ethereum address performing the signing. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +}) +``` + +### chainId + +- **Type:** `number` + +The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) Chain ID to which the session is bound. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + chainId: 1, // [!code focus] + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +}) +``` + +### domain + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority that is requesting the signing. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', // [!code focus] + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +}) +``` + +### nonce + +- **Type:** `string` + +A random string typically chosen by the relying party and used to prevent replay attacks. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', // [!code focus] + uri: 'https://example.com/path', + version: '1', +}) +``` + +### uri + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) URI referring to the resource that is the subject of the signing (as in the subject of a claim). + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', // [!code focus] + version: '1', +}) +``` + +### version + +- **Type:** `'1'` + +The current version of the SIWE Message. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', // [!code focus] +}) +``` + +### expirationTime (optional) + +- **Type:** `Date` + +Time when the signed authentication message is no longer valid. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + expirationTime: new Date(), // [!code focus] +}) +``` + +### issuedAt (optional) + +- **Type:** `Date` + +Time when the message was generated, typically the current time. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + issuedAt: new Date(), // [!code focus] +}) +``` + +### notBefore (optional) + +- **Type:** `Date` + +Time when the signed authentication message will become valid. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + notBefore: new Date(), // [!code focus] +}) +``` + +### requestId (optional) + +- **Type:** `string` + +A system-specific identifier that may be used to uniquely refer to the sign-in request. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + requestId: '123e4567-e89b-12d3-a456-426614174000', // [!code focus] +}) +``` + +### resources (optional) + +- **Type:** `string[]` + +A list of information or references to information the user wishes to have resolved as part of authentication by the relying party. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + resources: [ // [!code focus] + 'https://example.com/foo', // [!code focus] + 'https://example.com/bar', // [!code focus] + 'https://example.com/baz', // [!code focus] + ], // [!code focus] +}) +``` + +### scheme (optional) + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme of the origin of the request. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + scheme: 'https', // [!code focus] +}) +``` + +### statement (optional) + +- **Type:** `string` + +A human-readable ASCII assertion that the user will sign. + +```ts twoslash +import { createSiweMessage } from 'viem/siwe' + +const message = createSiweMessage({ + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + statement: 'I accept the ExampleOrg Terms of Service: https://example.com/tos', // [!code focus] +}) +``` + diff --git a/site/pages/docs/siwe/utilities/generateSiweNonce.md b/site/pages/docs/siwe/utilities/generateSiweNonce.md new file mode 100644 index 0000000000..9fcbf575a6 --- /dev/null +++ b/site/pages/docs/siwe/utilities/generateSiweNonce.md @@ -0,0 +1,27 @@ +--- +description: Generates random EIP-4361 nonce. +--- + +# generateSiweNonce + +Generates random [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) nonce. + +## Import + +```ts twoslash +import { generateSiweNonce } from 'viem/siwe' +``` + +## Usage + +```ts twoslash +import { generateSiweNonce } from 'viem/siwe' + +const nonce = generateSiweNonce() +``` + +## Returns + +`string` + +A randomly generated EIP-4361 nonce. diff --git a/site/pages/docs/siwe/utilities/parseSiweMessage.md b/site/pages/docs/siwe/utilities/parseSiweMessage.md new file mode 100644 index 0000000000..7410712743 --- /dev/null +++ b/site/pages/docs/siwe/utilities/parseSiweMessage.md @@ -0,0 +1,52 @@ +--- +description: Parses EIP-4361 formatted message into message fields object. +--- + +# parseSiweMessage + +Parses [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message into message fields object. + +## Import + +```ts twoslash +import { parseSiweMessage } from 'viem/siwe' +``` + +## Usage + +```ts twoslash +import { parseSiweMessage } from 'viem/siwe' + +const message = `example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z` +const fields = parseSiweMessage(message) +fields.address +// ^? + + + +``` + +## Returns + +`SiweMessage` + +EIP-4361 fields object + +## Parameters + +### message + +- **Type:** `string` + +EIP-4361 formatted message + + diff --git a/site/pages/docs/siwe/utilities/validateSiweMessage.md b/site/pages/docs/siwe/utilities/validateSiweMessage.md new file mode 100644 index 0000000000..6bfc4b30b0 --- /dev/null +++ b/site/pages/docs/siwe/utilities/validateSiweMessage.md @@ -0,0 +1,76 @@ +--- +description: Validates EIP-4361 message. +--- + +# validateSiweMessage + +Validates [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) message. + +## Import + +```ts twoslash +import { validateSiweMessage } from 'viem/siwe' +``` + +## Usage + +```ts twoslash +import { validateSiweMessage } from 'viem/siwe' + +const valid = validateSiweMessage({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + message: { + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + }, +}) +``` + +## Returns + +`boolean` + +Whether the message fields are valid. + +## Parameters + +### message + +- **Type:** `Partial` + +EIP-4361 message fields. + +### address (optional) + +- **Type:** `string` + +Ethereum address to check against. + +### domain (optional) + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority to check against. + +### nonce (optional) + +- **Type:** `string` + +Random string to check against. + +### scheme (optional) + +- **Type:** `string` + +[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme to check against. + +### time (optional) + +- **Type:** `Date` +- **Default:** `new Date()` + +Current time to check optional [`expirationTime`](http://localhost:5173/docs/siwe/utilities/createSiweMessage#expirationtime-optional) and [`notBefore`](/docs/siwe/utilities/createSiweMessage#notbefore-optional) message fields. diff --git a/site/sidebar.ts b/site/sidebar.ts index 0aed8371b2..5e32d88d1f 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -642,6 +642,42 @@ export const sidebar = { }, ], }, + { + text: 'SIWE', + collapsed: true, + items: [ + { + text: 'Actions', + items: [ + { + text: 'verifySiweMessage', + link: '/docs/siwe/actions/verifySiweMessage', + }, + ], + }, + { + text: 'Utilities', + items: [ + { + text: 'createSiweMessage', + link: '/docs/siwe/utilities/createSiweMessage', + }, + { + text: 'generateSiweNonce', + link: '/docs/siwe/utilities/generateSiweNonce', + }, + { + text: 'parseSiweMessage', + link: '/docs/siwe/utilities/parseSiweMessage', + }, + { + text: 'validateSiweMessage', + link: '/docs/siwe/utilities/validateSiweMessage', + }, + ], + }, + ], + }, { text: 'ABI', collapsed: true, diff --git a/src/actions/public/verifyMessage.ts b/src/actions/public/verifyMessage.ts index fdc9ab6edd..ed2c4e1ebc 100644 --- a/src/actions/public/verifyMessage.ts +++ b/src/actions/public/verifyMessage.ts @@ -10,6 +10,7 @@ import type { SignableMessage, Signature, } from '../../types/misc.js' +import type { Prettify } from '../../types/utils.js' import { hashMessage } from '../../utils/signature/hashMessage.js' import type { HashMessageErrorType } from '../../utils/signature/hashMessage.js' import { @@ -18,14 +19,16 @@ import { verifyHash, } from './verifyHash.js' -export type VerifyMessageParameters = Omit & { - /** The address that signed the original message. */ - address: Address - /** The message to be verified. */ - message: SignableMessage - /** The signature that was generated by signing the message with the address's private key. */ - signature: Hex | ByteArray | Signature -} +export type VerifyMessageParameters = Prettify< + Omit & { + /** The address that signed the original message. */ + address: Address + /** The message to be verified. */ + message: SignableMessage + /** The signature that was generated by signing the message with the address's private key. */ + signature: Hex | ByteArray | Signature + } +> export type VerifyMessageReturnType = boolean diff --git a/src/actions/siwe/verifySiweMessage.test.ts b/src/actions/siwe/verifySiweMessage.test.ts new file mode 100644 index 0000000000..b1c0877187 --- /dev/null +++ b/src/actions/siwe/verifySiweMessage.test.ts @@ -0,0 +1,69 @@ +import { expect, test } from 'vitest' + +import { accounts } from '~test/src/constants.js' +import { mainnetClient } from '~test/src/utils.js' + +import { signMessage } from '../../accounts/utils/signMessage.js' +import { createSiweMessage } from '../../utils/siwe/createSiweMessage.js' +import { verifySiweMessage } from './verifySiweMessage.js' + +const account = accounts[0] + +test('default', async () => { + const message = createSiweMessage({ + address: account.address, + chainId: mainnetClient.chain.id, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + }) + + const signature = await signMessage({ + message, + privateKey: account.privateKey, + }) + + expect( + await verifySiweMessage(mainnetClient, { + message, + signature, + }), + ).toBeTruthy() +}) + +test('behavior: invalid message fields', async () => { + const message = createSiweMessage({ + address: account.address, + chainId: mainnetClient.chain.id, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + }) + const signature = await signMessage({ + message, + privateKey: account.privateKey, + }) + expect( + await verifySiweMessage(mainnetClient, { + domain: 'viem.sh', + message, + signature, + }), + ).toBeFalsy() +}) + +test('behavior: invalid message', async () => { + const message = 'foobarbaz' + const signature = await signMessage({ + message, + privateKey: account.privateKey, + }) + expect( + await verifySiweMessage(mainnetClient, { + message, + signature, + }), + ).toBeFalsy() +}) diff --git a/src/actions/siwe/verifySiweMessage.ts b/src/actions/siwe/verifySiweMessage.ts new file mode 100644 index 0000000000..6156a7a1a2 --- /dev/null +++ b/src/actions/siwe/verifySiweMessage.ts @@ -0,0 +1,90 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { ErrorType } from '../../errors/utils.js' +import type { Chain } from '../../types/chain.js' +import type { Hex } from '../../types/misc.js' +import type { Prettify } from '../../types/utils.js' +import { hashMessage } from '../../utils/signature/hashMessage.js' +import type { HashMessageErrorType } from '../../utils/signature/hashMessage.js' +import { parseSiweMessage } from '../../utils/siwe/parseSiweMessage.js' +import { + type ValidateSiweMessageParameters, + validateSiweMessage, +} from '../../utils/siwe/validateSiweMessage.js' +import { + type VerifyHashErrorType, + type VerifyHashParameters, + verifyHash, +} from '../public/verifyHash.js' + +export type VerifySiweMessageParameters = Prettify< + Pick & + Pick< + ValidateSiweMessageParameters, + 'address' | 'domain' | 'nonce' | 'scheme' | 'time' + > & { + /** + * EIP-4361 formatted message. + */ + message: string + /** + * Signature to check against. + */ + signature: Hex + } +> + +export type VerifySiweMessageReturnType = boolean + +export type VerifySiweMessageErrorType = + | HashMessageErrorType + | VerifyHashErrorType + | ErrorType + +/** + * Verifies [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message was signed. + * + * Compatible with Smart Contract Accounts & Externally Owned Accounts via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + * + * - Docs {@link https://viem.sh/docs/siwe/actions/verifySiweMessage} + * + * @param client - Client to use. + * @param parameters - {@link VerifySiweMessageParameters} + * @returns Whether or not the signature is valid. {@link VerifySiweMessageReturnType} + */ +export async function verifySiweMessage( + client: Client, + parameters: VerifySiweMessageParameters, +): Promise { + const { + address, + domain, + message, + nonce, + scheme, + signature, + time = new Date(), + ...callRequest + } = parameters + + const parsed = parseSiweMessage(message) + if (!parsed.address) return false + + const isValid = validateSiweMessage({ + address, + domain, + message: parsed, + nonce, + scheme, + time, + }) + if (!isValid) return false + + const hash = hashMessage(message) + return verifyHash(client, { + address: parsed.address, + hash, + signature, + ...callRequest, + }) +} diff --git a/src/clients/createClient.test.ts b/src/clients/createClient.test.ts index aff6420128..ba1a2e4b95 100644 --- a/src/clients/createClient.test.ts +++ b/src/clients/createClient.test.ts @@ -544,6 +544,7 @@ describe('extends', () => { "type": "base", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], diff --git a/src/clients/createPublicClient.test.ts b/src/clients/createPublicClient.test.ts index 865e0ce41c..d5fb8fb454 100644 --- a/src/clients/createPublicClient.test.ts +++ b/src/clients/createPublicClient.test.ts @@ -89,6 +89,7 @@ test('creates', () => { "type": "publicClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], @@ -226,6 +227,7 @@ describe('transports', () => { "type": "publicClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], @@ -327,6 +329,7 @@ describe('transports', () => { "type": "publicClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], @@ -406,6 +409,7 @@ describe('transports', () => { "type": "publicClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], @@ -549,6 +553,7 @@ test('extend', () => { "type": "publicClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchAsset": [Function], diff --git a/src/clients/createTestClient.test.ts b/src/clients/createTestClient.test.ts index ac07fa576a..e9f7829cf3 100644 --- a/src/clients/createTestClient.test.ts +++ b/src/clients/createTestClient.test.ts @@ -413,6 +413,7 @@ test('extend', () => { "type": "testClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchAsset": [Function], diff --git a/src/clients/createWalletClient.test.ts b/src/clients/createWalletClient.test.ts index 0925fa145e..8ded4060fb 100644 --- a/src/clients/createWalletClient.test.ts +++ b/src/clients/createWalletClient.test.ts @@ -495,6 +495,7 @@ test('extend', () => { "type": "walletClient", "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchAsset": [Function], diff --git a/src/clients/decorators/public.test.ts b/src/clients/decorators/public.test.ts index 075408ad9c..a8026f1e6f 100644 --- a/src/clients/decorators/public.test.ts +++ b/src/clients/decorators/public.test.ts @@ -11,6 +11,7 @@ import { parseEther } from '../../utils/unit/parseEther.js' import { anvilMainnet } from '../../../test/src/anvil.js' import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js' +import { signMessage } from '../../accounts/utils/signMessage.js' import { mine, reset, @@ -18,6 +19,7 @@ import { signTransaction, } from '../../actions/index.js' import { base } from '../../chains/index.js' +import { createSiweMessage } from '../../utils/siwe/createSiweMessage.js' import { wait } from '../../utils/wait.js' import { createPublicClient } from '../createPublicClient.js' import { http } from '../transports/http.js' @@ -68,6 +70,7 @@ test('default', async () => { "simulateContract": [Function], "uninstallFilter": [Function], "verifyMessage": [Function], + "verifySiweMessage": [Function], "verifyTypedData": [Function], "waitForTransactionReceipt": [Function], "watchBlockNumber": [Function], @@ -450,6 +453,29 @@ describe('smoke test', () => { ).toBe(true) }) + test('verifySiweMessage', async () => { + const account = accounts[0] + const message = createSiweMessage({ + address: account.address, + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', + }) + const signature = await signMessage({ + message, + privateKey: account.privateKey, + }) + + expect( + await client.verifySiweMessage({ + message, + signature, + }), + ).toBe(true) + }) + test('verifyTypedData', async () => { expect( await client.verifyTypedData({ diff --git a/src/clients/decorators/public.ts b/src/clients/decorators/public.ts index c72f5f4f2e..f48ec22dc4 100644 --- a/src/clients/decorators/public.ts +++ b/src/clients/decorators/public.ts @@ -220,6 +220,11 @@ import { type WatchPendingTransactionsReturnType, watchPendingTransactions, } from '../../actions/public/watchPendingTransactions.js' +import { + type VerifySiweMessageParameters, + type VerifySiweMessageReturnType, + verifySiweMessage, +} from '../../actions/siwe/verifySiweMessage.js' import { type PrepareTransactionRequestParameters, type PrepareTransactionRequestRequest, @@ -1511,9 +1516,40 @@ export type PublicActions< accountOverride > > + /** + * Verify that a message was signed by the provided address. + * + * Compatible with Smart Contract Accounts & Externally Owned Accounts via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + * + * - Docs {@link https://viem.sh/docs/actions/public/verifyMessage} + * + * @param parameters - {@link VerifyMessageParameters} + * @returns Whether or not the signature is valid. {@link VerifyMessageReturnType} + */ verifyMessage: ( args: VerifyMessageParameters, ) => Promise + /** + * Verifies [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) formatted message was signed. + * + * Compatible with Smart Contract Accounts & Externally Owned Accounts via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + * + * - Docs {@link https://viem.sh/docs/siwe/actions/verifySiweMessage} + * + * @param parameters - {@link VerifySiweMessageParameters} + * @returns Whether or not the signature is valid. {@link VerifySiweMessageReturnType} + */ + verifySiweMessage: ( + args: VerifySiweMessageParameters, + ) => Promise + /** + * Verify that typed data was signed by the provided address. + * + * - Docs {@link https://viem.sh/docs/actions/public/verifyTypedData} + * + * @param parameters - {@link VerifyTypedDataParameters} + * @returns Whether or not the signature is valid. {@link VerifyTypedDataReturnType} + */ verifyTypedData: ( args: VerifyTypedDataParameters, ) => Promise @@ -1807,6 +1843,7 @@ export function publicActions< sendRawTransaction: (args) => sendRawTransaction(client, args), simulateContract: (args) => simulateContract(client, args), verifyMessage: (args) => verifyMessage(client, args), + verifySiweMessage: (args) => verifySiweMessage(client, args), verifyTypedData: (args) => verifyTypedData(client, args), uninstallFilter: (args) => uninstallFilter(client, args), waitForTransactionReceipt: (args) => diff --git a/src/errors/siwe.test.ts b/src/errors/siwe.test.ts new file mode 100644 index 0000000000..147278629e --- /dev/null +++ b/src/errors/siwe.test.ts @@ -0,0 +1,26 @@ +import { expect, test } from 'vitest' + +import { SiweInvalidMessageFieldError } from './siwe.js' + +test('SiweInvalidMessageFieldError', () => { + expect( + new SiweInvalidMessageFieldError({ + field: 'nonce', + metaMessages: [ + '- Nonce must be at least 8 characters.', + '- Nonce must be alphanumeric.', + '', + 'Provided value: foobarbaz$', + ], + }), + ).toMatchInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "nonce". + + - Nonce must be at least 8 characters. + - Nonce must be alphanumeric. + + Provided value: foobarbaz$ + + Version: viem@1.0.2] + `) +}) diff --git a/src/errors/siwe.ts b/src/errors/siwe.ts new file mode 100644 index 0000000000..c274b25f4d --- /dev/null +++ b/src/errors/siwe.ts @@ -0,0 +1,20 @@ +import { BaseError } from './base.js' + +export type SiweInvalidMessageFieldErrorType = SiweInvalidMessageFieldError & { + name: 'SiweInvalidMessageFieldError' +} +export class SiweInvalidMessageFieldError extends BaseError { + override name = 'SiweInvalidMessageFieldError' + constructor(parameters: { + docsPath?: string | undefined + field: string + metaMessages?: string[] | undefined + }) { + const { docsPath, field, metaMessages } = parameters + super(`Invalid Sign-In with Ethereum message field "${field}".`, { + docsPath, + docsSlug: 'TODO', + metaMessages, + }) + } +} diff --git a/src/jsr.json b/src/jsr.json index 0a7c04c8e3..1d5029956e 100644 --- a/src/jsr.json +++ b/src/jsr.json @@ -16,7 +16,7 @@ "./zksync": "./zksync/index.ts" }, "publish": { - "include": ["LICENSE", "README.md", "**/*.ts"], + "include": ["LICENSE", "README.md", "CHANGELOG.md", "**/*.ts"], "exclude": ["**/*.bench.ts", "**/*.test.ts", "**/*.test-d.ts"] } } diff --git a/src/package.json b/src/package.json index 1ee6696c47..ec6ad22f80 100644 --- a/src/package.json +++ b/src/package.json @@ -69,6 +69,11 @@ "import": "./_esm/op-stack/index.js", "default": "./_cjs/op-stack/index.js" }, + "./siwe": { + "types": "./_types/siwe/index.d.ts", + "import": "./_esm/siwe/index.js", + "default": "./_cjs/siwe/index.js" + }, "./utils": { "types": "./_types/utils/index.d.ts", "import": "./_esm/utils/index.js", @@ -97,6 +102,7 @@ "experimental": ["./_types/experimental/index.d.ts"], "node": ["./_types/node/index.d.ts"], "op-stack": ["./_types/op-stack/index.d.ts"], + "siwe": ["./_types/siwe/index.d.ts"], "utils": ["./_types/utils/index.d.ts"], "window": ["./_types/window/index.d.ts"], "zksync": ["./_types/zksync/index.d.ts"] diff --git a/src/siwe/index.ts b/src/siwe/index.ts new file mode 100644 index 0000000000..cd9ac6bbfe --- /dev/null +++ b/src/siwe/index.ts @@ -0,0 +1,29 @@ +export { + verifySiweMessage, + type VerifySiweMessageParameters, + type VerifySiweMessageReturnType, + type VerifySiweMessageErrorType, +} from '../actions/siwe/verifySiweMessage.js' + +export { + createSiweMessage, + type CreateSiweMessageParameters, + type CreateSiweMessageReturnType, + type CreateSiweMessageErrorType, +} from '../utils/siwe/createSiweMessage.js' + +export { generateSiweNonce } from '../utils/siwe/generateSiweNonce.js' +export { parseSiweMessage } from '../utils/siwe/parseSiweMessage.js' + +export { + validateSiweMessage, + type ValidateSiweMessageParameters, + type ValidateSiweMessageReturnType, +} from '../utils/siwe/validateSiweMessage.js' + +export type { SiweMessage } from '../utils/siwe/types.js' + +export { + type SiweInvalidMessageFieldErrorType, + SiweInvalidMessageFieldError, +} from '../errors/siwe.js' diff --git a/src/siwe/package.json b/src/siwe/package.json new file mode 100644 index 0000000000..c5a1c05489 --- /dev/null +++ b/src/siwe/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "types": "../_types/siwe/index.d.ts", + "module": "../_esm/siwe/index.js", + "main": "../_cjs/siwe/index.js" +} diff --git a/src/utils/siwe/createSiweMessage.test.ts b/src/utils/siwe/createSiweMessage.test.ts new file mode 100644 index 0000000000..9012a4be95 --- /dev/null +++ b/src/utils/siwe/createSiweMessage.test.ts @@ -0,0 +1,334 @@ +import { expect, test, vi } from 'vitest' + +import { mainnet } from '../../chains/definitions/mainnet.js' +import { createSiweMessage } from './createSiweMessage.js' +import type { SiweMessage } from './types.js' + +const message = { + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + chainId: mainnet.id, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +} satisfies SiweMessage + +test('default', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect(createSiweMessage(message)).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z" + `) + + vi.useRealTimers() +}) + +test('parameters: scheme', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + scheme: 'https', + }), + ).toMatchInlineSnapshot(` + "https://example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z" + `) + + vi.useRealTimers() +}) + +test('parameters: statement', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + statement: + 'I accept the ExampleOrg Terms of Service: https://example.com/tos', + }), + ).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + I accept the ExampleOrg Terms of Service: https://example.com/tos + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z" + `) + + vi.useRealTimers() +}) + +test('parameters: issuedAt', () => { + const issuedAt = new Date(Date.UTC(2022, 1, 4)) + expect(createSiweMessage({ ...message, issuedAt })).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2022-02-04T00:00:00.000Z" + `) +}) + +test('parameters: expirationTime', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + expirationTime: new Date(Date.UTC(2022, 1, 4)), + }), + ).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z + Expiration Time: 2022-02-04T00:00:00.000Z" + `) + + vi.useRealTimers() +}) + +test('parameters: notBefore', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + notBefore: new Date(Date.UTC(2022, 1, 4)), + }), + ).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z + Not Before: 2022-02-04T00:00:00.000Z" + `) + + vi.useRealTimers() +}) + +test('parameters: requestId', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + requestId: '123e4567-e89b-12d3-a456-426614174000', + }), + ).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z + Request ID: 123e4567-e89b-12d3-a456-426614174000" + `) + + vi.useRealTimers() +}) + +test('parameters: resources', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + createSiweMessage({ + ...message, + resources: [ + 'https://example.com/foo', + 'https://example.com/bar', + 'https://example.com/baz', + ], + }), + ).toMatchInlineSnapshot(` + "example.com wants you to sign in with your Ethereum account: + 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + + URI: https://example.com/path + Version: 1 + Chain ID: 1 + Nonce: foobarbaz + Issued At: 2023-02-01T00:00:00.000Z + Resources: + - https://example.com/foo + - https://example.com/bar + - https://example.com/baz" + `) + + vi.useRealTimers() +}) + +test('behavior: invalid address', () => { + expect(() => + createSiweMessage({ ...message, address: '0xfoobarbaz' }), + ).toThrowErrorMatchingInlineSnapshot(` + [InvalidAddressError: Address "0xfoobarbaz" is invalid. + + - Address must be a hex value of 20 bytes (40 hex characters). + - Address must match its checksum counterpart. + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid chainId', () => { + expect(() => + createSiweMessage({ ...message, chainId: 1.1 }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "chainId". + + - Chain ID must be a EIP-155 chain ID. + - See https://eips.ethereum.org/EIPS/eip-155 + + Provided value: 1.1 + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid domain', () => { + expect(() => + createSiweMessage({ ...message, domain: '#foo' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "domain". + + - Domain must be an RFC 3986 authority. + - See https://www.rfc-editor.org/rfc/rfc3986 + + Provided value: #foo + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid nonce', () => { + expect(() => + createSiweMessage({ ...message, nonce: '#foo' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "nonce". + + - Nonce must be at least 8 characters. + - Nonce must be alphanumeric. + + Provided value: #foo + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid uri', () => { + expect(() => + createSiweMessage({ ...message, uri: '#foo' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "uri". + + - URI must be a RFC 3986 URI referring to the resource that is the subject of the signing. + - See https://www.rfc-editor.org/rfc/rfc3986 + + Provided value: #foo + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid version', () => { + expect(() => + // @ts-expect-error + createSiweMessage({ ...message, version: '2' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "version". + + - Version must be '1'. + + Provided value: 2 + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid scheme', () => { + expect(() => + createSiweMessage({ ...message, scheme: 'foo_bar' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "scheme". + + - Scheme must be an RFC 3986 URI scheme. + - See https://www.rfc-editor.org/rfc/rfc3986#section-3.1 + + Provided value: foo_bar + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid statement', () => { + expect(() => + createSiweMessage({ ...message, statement: 'foo\nbar' }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "statement". + + - Statement must not include '\\n'. + + Provided value: foo + bar + + Version: viem@1.0.2] + `) +}) + +test('behavior: invalid resources', () => { + expect(() => + createSiweMessage({ + ...message, + resources: ['https://example.com', 'foo'], + }), + ).toThrowErrorMatchingInlineSnapshot(` + [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "resources". + + - Every resource must be a RFC 3986 URI. + - See https://www.rfc-editor.org/rfc/rfc3986 + + Provided value: foo + + Version: viem@1.0.2] + `) +}) diff --git a/src/utils/siwe/createSiweMessage.ts b/src/utils/siwe/createSiweMessage.ts new file mode 100644 index 0000000000..1ebb729138 --- /dev/null +++ b/src/utils/siwe/createSiweMessage.ts @@ -0,0 +1,168 @@ +import { + SiweInvalidMessageFieldError, + type SiweInvalidMessageFieldErrorType, +} from '../../errors/siwe.js' +import type { ErrorType } from '../../errors/utils.js' +import { type GetAddressErrorType, getAddress } from '../address/getAddress.js' +import type { SiweMessage } from './types.js' +import { isUri } from './utils.js' + +export type CreateSiweMessageParameters = SiweMessage + +export type CreateSiweMessageReturnType = string + +export type CreateSiweMessageErrorType = + | GetAddressErrorType + | SiweInvalidMessageFieldErrorType + | ErrorType + +/** + * @description Creates EIP-4361 formatted message. + * + * @example + * const message = createMessage({ + * address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * chainId: 1, + * domain: 'example.com', + * nonce: 'foobarbaz', + * uri: 'https://example.com/path', + * version: '1', + * }) + * + * @see https://eips.ethereum.org/EIPS/eip-4361 + */ +export function createSiweMessage( + parameters: CreateSiweMessageParameters, +): CreateSiweMessageReturnType { + const { + chainId, + domain, + expirationTime, + issuedAt = new Date(), + nonce, + notBefore, + requestId, + resources, + scheme, + uri, + version, + } = parameters + + // Validate fields + { + // Required fields + if (chainId !== Math.floor(chainId)) + throw new SiweInvalidMessageFieldError({ + field: 'chainId', + metaMessages: [ + '- Chain ID must be a EIP-155 chain ID.', + '- See https://eips.ethereum.org/EIPS/eip-155', + '', + `Provided value: ${chainId}`, + ], + }) + if (!domainRegex.test(domain)) + throw new SiweInvalidMessageFieldError({ + field: 'domain', + metaMessages: [ + '- Domain must be an RFC 3986 authority.', + '- See https://www.rfc-editor.org/rfc/rfc3986', + '', + `Provided value: ${domain}`, + ], + }) + if (!nonceRegex.test(nonce)) + throw new SiweInvalidMessageFieldError({ + field: 'nonce', + metaMessages: [ + '- Nonce must be at least 8 characters.', + '- Nonce must be alphanumeric.', + '', + `Provided value: ${nonce}`, + ], + }) + if (!isUri(uri)) + throw new SiweInvalidMessageFieldError({ + field: 'uri', + metaMessages: [ + '- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.', + '- See https://www.rfc-editor.org/rfc/rfc3986', + '', + `Provided value: ${uri}`, + ], + }) + if (version !== '1') + throw new SiweInvalidMessageFieldError({ + field: 'version', + metaMessages: [ + "- Version must be '1'.", + '', + `Provided value: ${version}`, + ], + }) + + // Optional fields + if (scheme && !schemeRegex.test(scheme)) + throw new SiweInvalidMessageFieldError({ + field: 'scheme', + metaMessages: [ + '- Scheme must be an RFC 3986 URI scheme.', + '- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1', + '', + `Provided value: ${scheme}`, + ], + }) + const statement = parameters.statement + if (statement?.includes('\n')) + throw new SiweInvalidMessageFieldError({ + field: 'statement', + metaMessages: [ + "- Statement must not include '\\n'.", + '', + `Provided value: ${statement}`, + ], + }) + } + + // Construct message + const address = getAddress(parameters.address) + const origin = (() => { + if (scheme) return `${scheme}://${domain}` + return domain + })() + const statement = (() => { + if (!parameters.statement) return '' + return `\n${parameters.statement}\n` + })() + const prefix = `${origin} wants you to sign in with your Ethereum account:\n${address}\n${statement}` + + let suffix = `URI: ${uri}\nVersion: ${version}\nChain ID: ${chainId}\nNonce: ${nonce}\nIssued At: ${issuedAt.toISOString()}` + + if (expirationTime) + suffix += `\nExpiration Time: ${expirationTime.toISOString()}` + if (notBefore) suffix += `\nNot Before: ${notBefore.toISOString()}` + if (requestId) suffix += `\nRequest ID: ${requestId}` + if (resources) { + let content = '\nResources:' + for (const resource of resources) { + if (!isUri(resource)) + throw new SiweInvalidMessageFieldError({ + field: 'resources', + metaMessages: [ + '- Every resource must be a RFC 3986 URI.', + '- See https://www.rfc-editor.org/rfc/rfc3986', + '', + `Provided value: ${resource}`, + ], + }) + content += `\n- ${resource}` + } + suffix += content + } + + return `${prefix}\n${suffix}` +} + +const domainRegex = /^(?:(?:(?!-)[a-zA-Z0-9-]{1,63}(? { + const nonce = generateSiweNonce() + expect(nonce.length).toMatchInlineSnapshot('96') +}) diff --git a/src/utils/siwe/generateSiweNonce.ts b/src/utils/siwe/generateSiweNonce.ts new file mode 100644 index 0000000000..967c86eb3b --- /dev/null +++ b/src/utils/siwe/generateSiweNonce.ts @@ -0,0 +1,15 @@ +import { uid } from '../../utils/uid.js' + +/** + * @description Generates random EIP-4361 nonce. + * + * @example + * const nonce = generateNonce() + * + * @see https://eips.ethereum.org/EIPS/eip-4361 + * + * @returns A randomly generated EIP-4361 nonce. + */ +export function generateSiweNonce(): string { + return uid(96) +} diff --git a/src/utils/siwe/parseSiweMessage.test.ts b/src/utils/siwe/parseSiweMessage.test.ts new file mode 100644 index 0000000000..e3a3a8cb7a --- /dev/null +++ b/src/utils/siwe/parseSiweMessage.test.ts @@ -0,0 +1,169 @@ +import { expect, test } from 'vitest' + +import { parseSiweMessage } from './parseSiweMessage.js' + +test('default', () => { + const message = `example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z` + const parsed = parseSiweMessage(message) + expect(parsed).toMatchInlineSnapshot(` + { + "address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + "chainId": 1, + "domain": "example.com", + "issuedAt": 2023-02-01T00:00:00.000Z, + "nonce": "foobarbaz", + "statement": "I accept the ExampleOrg Terms of Service: https://example.com/tos", + "uri": "https://example.com/path", + "version": "1", + } + `) +}) + +test('behavior: with scheme', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z` + const parsed = parseSiweMessage(message) + expect(parsed.scheme).toMatchInlineSnapshot(`"https"`) +}) + +test('behavior: with statement', () => { + const message = `example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z` + const parsed = parseSiweMessage(message) + expect(parsed.statement).toMatchInlineSnapshot( + `"I accept the ExampleOrg Terms of Service: https://example.com/tos"`, + ) +}) + +test('behavior: with expirationTime', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z +Expiration Time: 2022-02-04T00:00:00.000Z` + const parsed = parseSiweMessage(message) + expect(parsed.expirationTime).toMatchInlineSnapshot( + '2022-02-04T00:00:00.000Z', + ) +}) + +test('behavior: with notBefore', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z +Not Before: 2022-02-04T00:00:00.000Z` + const parsed = parseSiweMessage(message) + expect(parsed.notBefore).toMatchInlineSnapshot('2022-02-04T00:00:00.000Z') +}) + +test('behavior: with requestId', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z +Request ID: 123e4567-e89b-12d3-a456-426614174000` + const parsed = parseSiweMessage(message) + expect(parsed.requestId).toMatchInlineSnapshot( + `"123e4567-e89b-12d3-a456-426614174000"`, + ) +}) + +test('behavior: with resources', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z +Resources: +- https://example.com/foo +- https://example.com/bar +- https://example.com/baz` + const parsed = parseSiweMessage(message) + expect(parsed.resources).toMatchInlineSnapshot(` + [ + "https://example.com/foo", + "https://example.com/bar", + "https://example.com/baz", + ] + `) +}) + +test('behavior: no suffix', () => { + const message = `https://example.com wants you to sign in with your Ethereum account: +0xA0Cf798816D4b9b9866b5330EEa46a18382f251e + +` + const parsed = parseSiweMessage(message) + expect(parsed).toMatchInlineSnapshot(` + { + "address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + "domain": "example.com", + "scheme": "https", + } + `) +}) + +test('behavior: no prefix', () => { + const message = `URI: https://example.com/path +Version: 1 +Chain ID: 1 +Nonce: foobarbaz +Issued At: 2023-02-01T00:00:00.000Z +Request ID: 123e4567-e89b-12d3-a456-426614174000` + const parsed = parseSiweMessage(message) + expect(parsed).toMatchInlineSnapshot(` + { + "chainId": 1, + "issuedAt": 2023-02-01T00:00:00.000Z, + "nonce": "foobarbaz", + "requestId": "123e4567-e89b-12d3-a456-426614174000", + "uri": "https://example.com/path", + "version": "1", + } + `) +}) + +test('behavior: bogus message', () => { + const message = 'foobarbaz' + const parsed = parseSiweMessage(message) + expect(parsed).toMatchInlineSnapshot('{}') +}) diff --git a/src/utils/siwe/parseSiweMessage.ts b/src/utils/siwe/parseSiweMessage.ts new file mode 100644 index 0000000000..08504219e0 --- /dev/null +++ b/src/utils/siwe/parseSiweMessage.ts @@ -0,0 +1,55 @@ +import type { Address } from 'abitype' + +import type { ExactPartial, Prettify } from '../../types/utils.js' +import type { SiweMessage } from './types.js' + +/** + * @description Parses EIP-4361 formatted message into message fields object. + * + * @see https://eips.ethereum.org/EIPS/eip-4361 + * + * @returns EIP-4361 fields object + */ +export function parseSiweMessage( + message: string, +): Prettify> { + const { scheme, statement, ...prefix } = (message.match(prefixRegex) + ?.groups ?? {}) as { + address: Address + domain: string + scheme?: string + statement?: string + } + const { chainId, expirationTime, issuedAt, notBefore, requestId, ...suffix } = + (message.match(suffixRegex)?.groups ?? {}) as { + chainId: string + expirationTime?: string + issuedAt?: string + nonce: string + notBefore?: string + requestId?: string + uri: string + version: '1' + } + const resources = message.split('Resources:')[1]?.split('\n- ').slice(1) + return { + ...prefix, + ...suffix, + ...(chainId ? { chainId: Number(chainId) } : {}), + ...(expirationTime ? { expirationTime: new Date(expirationTime) } : {}), + ...(issuedAt ? { issuedAt: new Date(issuedAt) } : {}), + ...(notBefore ? { notBefore: new Date(notBefore) } : {}), + ...(requestId ? { requestId } : {}), + ...(resources ? { resources } : {}), + ...(scheme ? { scheme } : {}), + ...(statement ? { statement } : {}), + } +} + +// https://regexr.com/80gdj +const prefixRegex = + /^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/ + +// https://regexr.com/80gf9 +const suffixRegex = + /(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/ diff --git a/src/utils/siwe/types.ts b/src/utils/siwe/types.ts new file mode 100644 index 0000000000..ead665e157 --- /dev/null +++ b/src/utils/siwe/types.ts @@ -0,0 +1,61 @@ +import type { Address } from 'abitype' + +/** + * @description EIP-4361 message fields + * + * @see https://eips.ethereum.org/EIPS/eip-4361 + */ +export type SiweMessage = { + /** + * The Ethereum address performing the signing. + */ + address: Address + /** + * The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) Chain ID to which the session is bound, + */ + chainId: number + /** + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority that is requesting the signing. + */ + domain: string + /** + * Time when the signed authentication message is no longer valid. + */ + expirationTime?: Date | undefined + /** + * Time when the message was generated, typically the current time. + */ + issuedAt?: Date | undefined + /** + * A random string typically chosen by the relying party and used to prevent replay attacks. + */ + nonce: string + /** + * Time when the signed authentication message will become valid. + */ + notBefore?: Date | undefined + /** + * A system-specific identifier that may be used to uniquely refer to the sign-in request. + */ + requestId?: string | undefined + /** + * A list of information or references to information the user wishes to have resolved as part of authentication by the relying party. + */ + resources?: string[] | undefined + /** + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme of the origin of the request. + */ + scheme?: string | undefined + /** + * A human-readable ASCII assertion that the user will sign. + */ + statement?: string | undefined + /** + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) URI referring to the resource that is the subject of the signing (as in the subject of a claim). + */ + uri: string + /** + * The current version of the SIWE Message. + */ + version: '1' +} diff --git a/src/utils/siwe/utils.test.ts b/src/utils/siwe/utils.test.ts new file mode 100644 index 0000000000..5179dea79c --- /dev/null +++ b/src/utils/siwe/utils.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from 'vitest' + +import { isUri } from './utils.js' + +test('isUri - default', () => { + expect(isUri('https://example.com/foo')).toMatchInlineSnapshot( + `"https://example.com/foo"`, + ) +}) + +test('isUri - behavior: check for illegal characters', () => { + expect(isUri('^')).toBeFalsy() +}) + +test('isUri - incomplete hex escapes', () => { + expect(isUri('%$#')).toBeFalsy() + expect(isUri('%0:#')).toBeFalsy() +}) + +test('isUri - missing scheme', () => { + expect(isUri('example.com/foo')).toBeFalsy() +}) + +test('isUri - authority with missing path', () => { + expect(isUri('1http:////foo.html')).toBeFalsy() +}) + +test('isUri - scheme begins with letter', () => { + expect(isUri('$https://example.com/foo')).toBeFalsy() +}) + +test('isUri - query', () => { + expect(isUri('https://example.com/foo?bar')).toMatchInlineSnapshot( + `"https://example.com/foo?bar"`, + ) +}) + +test('isUri - fragment', () => { + expect(isUri('https://example.com/foo#bar')).toMatchInlineSnapshot( + `"https://example.com/foo#bar"`, + ) +}) diff --git a/src/utils/siwe/utils.ts b/src/utils/siwe/utils.ts new file mode 100644 index 0000000000..240d980031 --- /dev/null +++ b/src/utils/siwe/utils.ts @@ -0,0 +1,51 @@ +export function isUri(value: string) { + // based on https://github.com/ogt/valid-url + + // check for illegal characters + if (/[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i.test(value)) + return false + + // check for hex escapes that aren't complete + if (/%[^0-9a-f]/i.test(value)) return false + if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value)) return false + + // from RFC 3986 + const splitted = splitUri(value) + const scheme = splitted[1] + const authority = splitted[2] + const path = splitted[3] + const query = splitted[4] + const fragment = splitted[5] + + // scheme and path are required, though the path can be empty + if (!(scheme?.length && path.length >= 0)) return false + + // if authority is present, the path must be empty or begin with a / + if (authority?.length) { + if (!(path.length === 0 || /^\//.test(path))) return false + } else { + // if authority is not present, the path must not start with // + if (/^\/\//.test(path)) return false + } + + // scheme must begin with a letter, then consist of letters, digits, +, ., or - + if (!/^[a-z][a-z0-9\+\-\.]*$/.test(scheme.toLowerCase())) return false + + let out = '' + // re-assemble the URL per section 5.3 in RFC 3986 + out += `${scheme}:` + if (authority?.length) out += `//${authority}` + + out += path + + if (query?.length) out += `?${query}` + if (fragment?.length) out += `#${fragment}` + + return out +} + +function splitUri(value: string) { + return value.match( + /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + )! +} diff --git a/src/utils/siwe/validateSiweMessage.test.ts b/src/utils/siwe/validateSiweMessage.test.ts new file mode 100644 index 0000000000..e49683747d --- /dev/null +++ b/src/utils/siwe/validateSiweMessage.test.ts @@ -0,0 +1,109 @@ +import { expect, test, vi } from 'vitest' + +import type { SiweMessage } from './types.js' +import { validateSiweMessage } from './validateSiweMessage.js' + +const message = { + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + chainId: 1, + domain: 'example.com', + nonce: 'foobarbaz', + uri: 'https://example.com/path', + version: '1', +} satisfies SiweMessage + +test('default', () => { + expect( + validateSiweMessage({ + message, + }), + ).toBeTruthy() +}) + +test('behavior: invalid address', () => { + expect( + validateSiweMessage({ + message: { + ...message, + address: undefined, + }, + }), + ).toBeFalsy() +}) + +test('behavior: address mismatch', () => { + expect( + validateSiweMessage({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + message, + }), + ).toBeFalsy() +}) + +test('behavior: invalid address', () => { + expect( + validateSiweMessage({ + address: '0xfoobarbaz', + message, + }), + ).toBeFalsy() +}) + +test('behavior: domain mismatch', () => { + expect( + validateSiweMessage({ + domain: 'viem.sh', + message, + }), + ).toBeFalsy() +}) + +test('behavior: nonce mismatch', () => { + expect( + validateSiweMessage({ + nonce: 'f0obarbaz', + message, + }), + ).toBeFalsy() +}) + +test('behavior: scheme mismatch', () => { + expect( + validateSiweMessage({ + scheme: 'http', + message: { + ...message, + scheme: 'https', + }, + }), + ).toBeFalsy() +}) + +test('behavior: time is after expirationTime', () => { + expect( + validateSiweMessage({ + message: { + ...message, + expirationTime: new Date(Date.UTC(2024, 1, 1)), + }, + time: new Date(Date.UTC(2025, 1, 1)), + }), + ).toBeFalsy() +}) + +test('behavior: time is before notBefore', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + + expect( + validateSiweMessage({ + message: { + ...message, + notBefore: new Date(Date.UTC(2024, 1, 1)), + }, + time: new Date(Date.UTC(2023, 1, 1)), + }), + ).toBeFalsy() + + vi.useRealTimers() +}) diff --git a/src/utils/siwe/validateSiweMessage.ts b/src/utils/siwe/validateSiweMessage.ts new file mode 100644 index 0000000000..2147a2f145 --- /dev/null +++ b/src/utils/siwe/validateSiweMessage.ts @@ -0,0 +1,70 @@ +import type { Address } from 'abitype' + +import type { ExactPartial } from '../../types/utils.js' +import { isAddressEqual } from '../address/isAddressEqual.js' +import type { SiweMessage } from './types.js' + +export type ValidateSiweMessageParameters = { + /** + * Ethereum address to check against. + */ + address?: Address | undefined + /** + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority to check against. + */ + domain?: string | undefined + /** + * EIP-4361 message fields. + */ + message: ExactPartial + /** + * Random string to check against. + */ + nonce?: string | undefined + /** + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme to check against. + */ + scheme?: string | undefined + /** + * Current time to check optional `expirationTime` and `notBefore` fields. + * + * @default new Date() + */ + time?: Date | undefined +} + +export type ValidateSiweMessageReturnType = boolean + +/** + * @description Validates EIP-4361 message. + * + * @see https://eips.ethereum.org/EIPS/eip-4361 + */ +export function validateSiweMessage( + parameters: ValidateSiweMessageParameters, +): ValidateSiweMessageReturnType { + const { + address, + domain, + message, + nonce, + scheme, + time = new Date(), + } = parameters + + if (domain && message.domain !== domain) return false + if (nonce && message.nonce !== nonce) return false + if (scheme && message.scheme !== scheme) return false + + if (message.expirationTime && time >= message.expirationTime) return false + if (message.notBefore && time < message.notBefore) return false + + try { + if (!message.address) return false + if (address && !isAddressEqual(message.address, address)) return false + } catch { + return false + } + + return true +} From 09aabb112a8de23198a8c3d587f6242d8bfc7b14 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 18:44:52 -0400 Subject: [PATCH 10/16] chore: version package (#2288) Version Packages Co-authored-by: github-actions[bot] --- .changeset/cold-candles-visit.md | 5 --- .changeset/green-clocks-smash.md | 5 --- .changeset/odd-sheep-doubt.md | 5 --- .changeset/poor-berries-look.md | 5 --- src/CHANGELOG.md | 12 ++++++ src/package.json | 67 ++++++++++++++++++++++++-------- 6 files changed, 63 insertions(+), 36 deletions(-) delete mode 100644 .changeset/cold-candles-visit.md delete mode 100644 .changeset/green-clocks-smash.md delete mode 100644 .changeset/odd-sheep-doubt.md delete mode 100644 .changeset/poor-berries-look.md diff --git a/.changeset/cold-candles-visit.md b/.changeset/cold-candles-visit.md deleted file mode 100644 index 61e0da8f1f..0000000000 --- a/.changeset/cold-candles-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added L3X Protocol chain. diff --git a/.changeset/green-clocks-smash.md b/.changeset/green-clocks-smash.md deleted file mode 100644 index f6c151d41b..0000000000 --- a/.changeset/green-clocks-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added multilcall3 contract to Flow Previewnet. diff --git a/.changeset/odd-sheep-doubt.md b/.changeset/odd-sheep-doubt.md deleted file mode 100644 index 71785497bc..0000000000 --- a/.changeset/odd-sheep-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added thaiChain chain. diff --git a/.changeset/poor-berries-look.md b/.changeset/poor-berries-look.md deleted file mode 100644 index 2c561790df..0000000000 --- a/.changeset/poor-berries-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Updated Metis Explorer Definitions diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index e8a9152330..1c98b75580 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,5 +1,17 @@ # viem +## 2.11.2 + +### Patch Changes + +- [#2281](https://github.com/wevm/viem/pull/2281) [`f4b0f7a3009faee9b116bd2d947322b85ecd81f3`](https://github.com/wevm/viem/commit/f4b0f7a3009faee9b116bd2d947322b85ecd81f3) Thanks [@michaelotis](https://github.com/michaelotis)! - Added L3X Protocol chain. + +- [#2287](https://github.com/wevm/viem/pull/2287) [`8525dfcd8f482d55925bebfe57043fb70db6687d`](https://github.com/wevm/viem/commit/8525dfcd8f482d55925bebfe57043fb70db6687d) Thanks [@nialexsan](https://github.com/nialexsan)! - Added multilcall3 contract to Flow Previewnet. + +- [#2283](https://github.com/wevm/viem/pull/2283) [`d7eee1da79648975df06e2f46a011b2d224ef78a`](https://github.com/wevm/viem/commit/d7eee1da79648975df06e2f46a011b2d224ef78a) Thanks [@nidz-the-fact](https://github.com/nidz-the-fact)! - Added thaiChain chain. + +- [#2284](https://github.com/wevm/viem/pull/2284) [`86bd2a7dbfca7d1cd118a9feaac07a69a8da0d19`](https://github.com/wevm/viem/commit/86bd2a7dbfca7d1cd118a9feaac07a69a8da0d19) Thanks [@ezynda3](https://github.com/ezynda3)! - Updated Metis Explorer Definitions + ## 2.11.1 ### Patch Changes diff --git a/src/package.json b/src/package.json index ec6ad22f80..c1a37b2577 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "viem", "description": "TypeScript Interface for Ethereum", - "version": "2.11.1", + "version": "2.11.2", "type": "module", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -93,19 +93,45 @@ }, "typesVersions": { "*": { - "accounts": ["./_types/accounts/index.d.ts"], - "actions": ["./_types/actions/index.d.ts"], - "celo": ["./_types/celo/index.d.ts"], - "chains": ["./_types/chains/index.d.ts"], - "chains/utils": ["./_types/chains/utils.d.ts"], - "ens": ["./_types/ens/index.d.ts"], - "experimental": ["./_types/experimental/index.d.ts"], - "node": ["./_types/node/index.d.ts"], - "op-stack": ["./_types/op-stack/index.d.ts"], - "siwe": ["./_types/siwe/index.d.ts"], - "utils": ["./_types/utils/index.d.ts"], - "window": ["./_types/window/index.d.ts"], - "zksync": ["./_types/zksync/index.d.ts"] + "accounts": [ + "./_types/accounts/index.d.ts" + ], + "actions": [ + "./_types/actions/index.d.ts" + ], + "celo": [ + "./_types/celo/index.d.ts" + ], + "chains": [ + "./_types/chains/index.d.ts" + ], + "chains/utils": [ + "./_types/chains/utils.d.ts" + ], + "ens": [ + "./_types/ens/index.d.ts" + ], + "experimental": [ + "./_types/experimental/index.d.ts" + ], + "node": [ + "./_types/node/index.d.ts" + ], + "op-stack": [ + "./_types/op-stack/index.d.ts" + ], + "siwe": [ + "./_types/siwe/index.d.ts" + ], + "utils": [ + "./_types/utils/index.d.ts" + ], + "window": [ + "./_types/window/index.d.ts" + ], + "zksync": [ + "./_types/zksync/index.d.ts" + ] } }, "peerDependencies": { @@ -129,12 +155,21 @@ "license": "MIT", "homepage": "https://viem.sh", "repository": "wevm/viem", - "authors": ["awkweb.eth", "jxom.eth"], + "authors": [ + "awkweb.eth", + "jxom.eth" + ], "funding": [ { "type": "github", "url": "https://github.com/sponsors/wevm" } ], - "keywords": ["eth", "ethereum", "dapps", "wallet", "web3"] + "keywords": [ + "eth", + "ethereum", + "dapps", + "wallet", + "web3" + ] } From d555a32db30129dfdface6a755262faf17463a58 Mon Sep 17 00:00:00 2001 From: tmm Date: Tue, 21 May 2024 22:46:00 +0000 Subject: [PATCH 11/16] chore: format --- src/package.json | 65 +++++++++++------------------------------------- 1 file changed, 15 insertions(+), 50 deletions(-) diff --git a/src/package.json b/src/package.json index c1a37b2577..497ae6a572 100644 --- a/src/package.json +++ b/src/package.json @@ -93,45 +93,19 @@ }, "typesVersions": { "*": { - "accounts": [ - "./_types/accounts/index.d.ts" - ], - "actions": [ - "./_types/actions/index.d.ts" - ], - "celo": [ - "./_types/celo/index.d.ts" - ], - "chains": [ - "./_types/chains/index.d.ts" - ], - "chains/utils": [ - "./_types/chains/utils.d.ts" - ], - "ens": [ - "./_types/ens/index.d.ts" - ], - "experimental": [ - "./_types/experimental/index.d.ts" - ], - "node": [ - "./_types/node/index.d.ts" - ], - "op-stack": [ - "./_types/op-stack/index.d.ts" - ], - "siwe": [ - "./_types/siwe/index.d.ts" - ], - "utils": [ - "./_types/utils/index.d.ts" - ], - "window": [ - "./_types/window/index.d.ts" - ], - "zksync": [ - "./_types/zksync/index.d.ts" - ] + "accounts": ["./_types/accounts/index.d.ts"], + "actions": ["./_types/actions/index.d.ts"], + "celo": ["./_types/celo/index.d.ts"], + "chains": ["./_types/chains/index.d.ts"], + "chains/utils": ["./_types/chains/utils.d.ts"], + "ens": ["./_types/ens/index.d.ts"], + "experimental": ["./_types/experimental/index.d.ts"], + "node": ["./_types/node/index.d.ts"], + "op-stack": ["./_types/op-stack/index.d.ts"], + "siwe": ["./_types/siwe/index.d.ts"], + "utils": ["./_types/utils/index.d.ts"], + "window": ["./_types/window/index.d.ts"], + "zksync": ["./_types/zksync/index.d.ts"] } }, "peerDependencies": { @@ -155,21 +129,12 @@ "license": "MIT", "homepage": "https://viem.sh", "repository": "wevm/viem", - "authors": [ - "awkweb.eth", - "jxom.eth" - ], + "authors": ["awkweb.eth", "jxom.eth"], "funding": [ { "type": "github", "url": "https://github.com/sponsors/wevm" } ], - "keywords": [ - "eth", - "ethereum", - "dapps", - "wallet", - "web3" - ] + "keywords": ["eth", "ethereum", "dapps", "wallet", "web3"] } From 507533bbab2dc38a94de19e89950780fb9033fde Mon Sep 17 00:00:00 2001 From: awkweb Date: Tue, 21 May 2024 18:46:13 -0400 Subject: [PATCH 12/16] Revert "chore: version package" (#2290) Revert "chore: version package (#2288)" This reverts commit 09aabb112a8de23198a8c3d587f6242d8bfc7b14. --- .changeset/cold-candles-visit.md | 5 +++++ .changeset/green-clocks-smash.md | 5 +++++ .changeset/odd-sheep-doubt.md | 5 +++++ .changeset/poor-berries-look.md | 5 +++++ src/CHANGELOG.md | 12 ------------ src/package.json | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 .changeset/cold-candles-visit.md create mode 100644 .changeset/green-clocks-smash.md create mode 100644 .changeset/odd-sheep-doubt.md create mode 100644 .changeset/poor-berries-look.md diff --git a/.changeset/cold-candles-visit.md b/.changeset/cold-candles-visit.md new file mode 100644 index 0000000000..61e0da8f1f --- /dev/null +++ b/.changeset/cold-candles-visit.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added L3X Protocol chain. diff --git a/.changeset/green-clocks-smash.md b/.changeset/green-clocks-smash.md new file mode 100644 index 0000000000..f6c151d41b --- /dev/null +++ b/.changeset/green-clocks-smash.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added multilcall3 contract to Flow Previewnet. diff --git a/.changeset/odd-sheep-doubt.md b/.changeset/odd-sheep-doubt.md new file mode 100644 index 0000000000..71785497bc --- /dev/null +++ b/.changeset/odd-sheep-doubt.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added thaiChain chain. diff --git a/.changeset/poor-berries-look.md b/.changeset/poor-berries-look.md new file mode 100644 index 0000000000..2c561790df --- /dev/null +++ b/.changeset/poor-berries-look.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Updated Metis Explorer Definitions diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 1c98b75580..e8a9152330 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,17 +1,5 @@ # viem -## 2.11.2 - -### Patch Changes - -- [#2281](https://github.com/wevm/viem/pull/2281) [`f4b0f7a3009faee9b116bd2d947322b85ecd81f3`](https://github.com/wevm/viem/commit/f4b0f7a3009faee9b116bd2d947322b85ecd81f3) Thanks [@michaelotis](https://github.com/michaelotis)! - Added L3X Protocol chain. - -- [#2287](https://github.com/wevm/viem/pull/2287) [`8525dfcd8f482d55925bebfe57043fb70db6687d`](https://github.com/wevm/viem/commit/8525dfcd8f482d55925bebfe57043fb70db6687d) Thanks [@nialexsan](https://github.com/nialexsan)! - Added multilcall3 contract to Flow Previewnet. - -- [#2283](https://github.com/wevm/viem/pull/2283) [`d7eee1da79648975df06e2f46a011b2d224ef78a`](https://github.com/wevm/viem/commit/d7eee1da79648975df06e2f46a011b2d224ef78a) Thanks [@nidz-the-fact](https://github.com/nidz-the-fact)! - Added thaiChain chain. - -- [#2284](https://github.com/wevm/viem/pull/2284) [`86bd2a7dbfca7d1cd118a9feaac07a69a8da0d19`](https://github.com/wevm/viem/commit/86bd2a7dbfca7d1cd118a9feaac07a69a8da0d19) Thanks [@ezynda3](https://github.com/ezynda3)! - Updated Metis Explorer Definitions - ## 2.11.1 ### Patch Changes diff --git a/src/package.json b/src/package.json index 497ae6a572..ec6ad22f80 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "viem", "description": "TypeScript Interface for Ethereum", - "version": "2.11.2", + "version": "2.11.1", "type": "module", "main": "./_cjs/index.js", "module": "./_esm/index.js", From b06c56fd6ace6f6cadbeac6da359d650ff037cc1 Mon Sep 17 00:00:00 2001 From: Tom Meagher Date: Tue, 21 May 2024 18:47:20 -0400 Subject: [PATCH 13/16] chore: changeset --- .changeset/loud-cycles-worry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-cycles-worry.md diff --git a/.changeset/loud-cycles-worry.md b/.changeset/loud-cycles-worry.md new file mode 100644 index 0000000000..cc7d633452 --- /dev/null +++ b/.changeset/loud-cycles-worry.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added Sign-In with Ethereum support. From fd3d8afa093a8fd96bded6029cb634b4dbdb31e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 18:49:28 -0400 Subject: [PATCH 14/16] chore: version package (#2291) Co-authored-by: github-actions[bot] --- .changeset/cold-candles-visit.md | 5 --- .changeset/green-clocks-smash.md | 5 --- .changeset/loud-cycles-worry.md | 5 --- .changeset/odd-sheep-doubt.md | 5 --- .changeset/poor-berries-look.md | 5 --- src/CHANGELOG.md | 16 ++++++++ src/errors/version.ts | 2 +- src/jsr.json | 17 ++++++-- src/package.json | 67 ++++++++++++++++++++++++-------- 9 files changed, 81 insertions(+), 46 deletions(-) delete mode 100644 .changeset/cold-candles-visit.md delete mode 100644 .changeset/green-clocks-smash.md delete mode 100644 .changeset/loud-cycles-worry.md delete mode 100644 .changeset/odd-sheep-doubt.md delete mode 100644 .changeset/poor-berries-look.md diff --git a/.changeset/cold-candles-visit.md b/.changeset/cold-candles-visit.md deleted file mode 100644 index 61e0da8f1f..0000000000 --- a/.changeset/cold-candles-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added L3X Protocol chain. diff --git a/.changeset/green-clocks-smash.md b/.changeset/green-clocks-smash.md deleted file mode 100644 index f6c151d41b..0000000000 --- a/.changeset/green-clocks-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added multilcall3 contract to Flow Previewnet. diff --git a/.changeset/loud-cycles-worry.md b/.changeset/loud-cycles-worry.md deleted file mode 100644 index cc7d633452..0000000000 --- a/.changeset/loud-cycles-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": minor ---- - -Added Sign-In with Ethereum support. diff --git a/.changeset/odd-sheep-doubt.md b/.changeset/odd-sheep-doubt.md deleted file mode 100644 index 71785497bc..0000000000 --- a/.changeset/odd-sheep-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Added thaiChain chain. diff --git a/.changeset/poor-berries-look.md b/.changeset/poor-berries-look.md deleted file mode 100644 index 2c561790df..0000000000 --- a/.changeset/poor-berries-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Updated Metis Explorer Definitions diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index e8a9152330..d178a70976 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,5 +1,21 @@ # viem +## 2.12.0 + +### Minor Changes + +- [`b06c56fd6ace6f6cadbeac6da359d650ff037cc1`](https://github.com/wevm/viem/commit/b06c56fd6ace6f6cadbeac6da359d650ff037cc1) Thanks [@tmm](https://github.com/tmm)! - Added Sign-In with Ethereum support. + +### Patch Changes + +- [#2290](https://github.com/wevm/viem/pull/2290) [`507533bbab2dc38a94de19e89950780fb9033fde`](https://github.com/wevm/viem/commit/507533bbab2dc38a94de19e89950780fb9033fde) Thanks [@tmm](https://github.com/tmm)! - Added L3X Protocol chain. + +- [#2290](https://github.com/wevm/viem/pull/2290) [`507533bbab2dc38a94de19e89950780fb9033fde`](https://github.com/wevm/viem/commit/507533bbab2dc38a94de19e89950780fb9033fde) Thanks [@tmm](https://github.com/tmm)! - Added multilcall3 contract to Flow Previewnet. + +- [#2290](https://github.com/wevm/viem/pull/2290) [`507533bbab2dc38a94de19e89950780fb9033fde`](https://github.com/wevm/viem/commit/507533bbab2dc38a94de19e89950780fb9033fde) Thanks [@tmm](https://github.com/tmm)! - Added thaiChain chain. + +- [#2290](https://github.com/wevm/viem/pull/2290) [`507533bbab2dc38a94de19e89950780fb9033fde`](https://github.com/wevm/viem/commit/507533bbab2dc38a94de19e89950780fb9033fde) Thanks [@tmm](https://github.com/tmm)! - Updated Metis Explorer Definitions + ## 2.11.1 ### Patch Changes diff --git a/src/errors/version.ts b/src/errors/version.ts index 1458c59d8c..475bf798db 100644 --- a/src/errors/version.ts +++ b/src/errors/version.ts @@ -1 +1 @@ -export const version = '2.11.0' +export const version = '2.12.0' diff --git a/src/jsr.json b/src/jsr.json index 1d5029956e..0dab532a6e 100644 --- a/src/jsr.json +++ b/src/jsr.json @@ -1,6 +1,6 @@ { "name": "@wevm/viem", - "version": "2.11.0", + "version": "2.12.0", "exports": { ".": "./index.ts", "./accounts": "./accounts/index.ts", @@ -16,7 +16,16 @@ "./zksync": "./zksync/index.ts" }, "publish": { - "include": ["LICENSE", "README.md", "CHANGELOG.md", "**/*.ts"], - "exclude": ["**/*.bench.ts", "**/*.test.ts", "**/*.test-d.ts"] + "include": [ + "LICENSE", + "README.md", + "CHANGELOG.md", + "**/*.ts" + ], + "exclude": [ + "**/*.bench.ts", + "**/*.test.ts", + "**/*.test-d.ts" + ] } -} +} \ No newline at end of file diff --git a/src/package.json b/src/package.json index ec6ad22f80..5ca8284570 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "viem", "description": "TypeScript Interface for Ethereum", - "version": "2.11.1", + "version": "2.12.0", "type": "module", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -93,19 +93,45 @@ }, "typesVersions": { "*": { - "accounts": ["./_types/accounts/index.d.ts"], - "actions": ["./_types/actions/index.d.ts"], - "celo": ["./_types/celo/index.d.ts"], - "chains": ["./_types/chains/index.d.ts"], - "chains/utils": ["./_types/chains/utils.d.ts"], - "ens": ["./_types/ens/index.d.ts"], - "experimental": ["./_types/experimental/index.d.ts"], - "node": ["./_types/node/index.d.ts"], - "op-stack": ["./_types/op-stack/index.d.ts"], - "siwe": ["./_types/siwe/index.d.ts"], - "utils": ["./_types/utils/index.d.ts"], - "window": ["./_types/window/index.d.ts"], - "zksync": ["./_types/zksync/index.d.ts"] + "accounts": [ + "./_types/accounts/index.d.ts" + ], + "actions": [ + "./_types/actions/index.d.ts" + ], + "celo": [ + "./_types/celo/index.d.ts" + ], + "chains": [ + "./_types/chains/index.d.ts" + ], + "chains/utils": [ + "./_types/chains/utils.d.ts" + ], + "ens": [ + "./_types/ens/index.d.ts" + ], + "experimental": [ + "./_types/experimental/index.d.ts" + ], + "node": [ + "./_types/node/index.d.ts" + ], + "op-stack": [ + "./_types/op-stack/index.d.ts" + ], + "siwe": [ + "./_types/siwe/index.d.ts" + ], + "utils": [ + "./_types/utils/index.d.ts" + ], + "window": [ + "./_types/window/index.d.ts" + ], + "zksync": [ + "./_types/zksync/index.d.ts" + ] } }, "peerDependencies": { @@ -129,12 +155,21 @@ "license": "MIT", "homepage": "https://viem.sh", "repository": "wevm/viem", - "authors": ["awkweb.eth", "jxom.eth"], + "authors": [ + "awkweb.eth", + "jxom.eth" + ], "funding": [ { "type": "github", "url": "https://github.com/sponsors/wevm" } ], - "keywords": ["eth", "ethereum", "dapps", "wallet", "web3"] + "keywords": [ + "eth", + "ethereum", + "dapps", + "wallet", + "web3" + ] } From 329e8f54043d42ed065dfe22de8a8888b706dd5d Mon Sep 17 00:00:00 2001 From: tmm Date: Tue, 21 May 2024 22:51:02 +0000 Subject: [PATCH 15/16] chore: format --- src/jsr.json | 15 +++-------- src/package.json | 65 +++++++++++------------------------------------- 2 files changed, 18 insertions(+), 62 deletions(-) diff --git a/src/jsr.json b/src/jsr.json index 0dab532a6e..6670cf90bf 100644 --- a/src/jsr.json +++ b/src/jsr.json @@ -16,16 +16,7 @@ "./zksync": "./zksync/index.ts" }, "publish": { - "include": [ - "LICENSE", - "README.md", - "CHANGELOG.md", - "**/*.ts" - ], - "exclude": [ - "**/*.bench.ts", - "**/*.test.ts", - "**/*.test-d.ts" - ] + "include": ["LICENSE", "README.md", "CHANGELOG.md", "**/*.ts"], + "exclude": ["**/*.bench.ts", "**/*.test.ts", "**/*.test-d.ts"] } -} \ No newline at end of file +} diff --git a/src/package.json b/src/package.json index 5ca8284570..52859cd829 100644 --- a/src/package.json +++ b/src/package.json @@ -93,45 +93,19 @@ }, "typesVersions": { "*": { - "accounts": [ - "./_types/accounts/index.d.ts" - ], - "actions": [ - "./_types/actions/index.d.ts" - ], - "celo": [ - "./_types/celo/index.d.ts" - ], - "chains": [ - "./_types/chains/index.d.ts" - ], - "chains/utils": [ - "./_types/chains/utils.d.ts" - ], - "ens": [ - "./_types/ens/index.d.ts" - ], - "experimental": [ - "./_types/experimental/index.d.ts" - ], - "node": [ - "./_types/node/index.d.ts" - ], - "op-stack": [ - "./_types/op-stack/index.d.ts" - ], - "siwe": [ - "./_types/siwe/index.d.ts" - ], - "utils": [ - "./_types/utils/index.d.ts" - ], - "window": [ - "./_types/window/index.d.ts" - ], - "zksync": [ - "./_types/zksync/index.d.ts" - ] + "accounts": ["./_types/accounts/index.d.ts"], + "actions": ["./_types/actions/index.d.ts"], + "celo": ["./_types/celo/index.d.ts"], + "chains": ["./_types/chains/index.d.ts"], + "chains/utils": ["./_types/chains/utils.d.ts"], + "ens": ["./_types/ens/index.d.ts"], + "experimental": ["./_types/experimental/index.d.ts"], + "node": ["./_types/node/index.d.ts"], + "op-stack": ["./_types/op-stack/index.d.ts"], + "siwe": ["./_types/siwe/index.d.ts"], + "utils": ["./_types/utils/index.d.ts"], + "window": ["./_types/window/index.d.ts"], + "zksync": ["./_types/zksync/index.d.ts"] } }, "peerDependencies": { @@ -155,21 +129,12 @@ "license": "MIT", "homepage": "https://viem.sh", "repository": "wevm/viem", - "authors": [ - "awkweb.eth", - "jxom.eth" - ], + "authors": ["awkweb.eth", "jxom.eth"], "funding": [ { "type": "github", "url": "https://github.com/sponsors/wevm" } ], - "keywords": [ - "eth", - "ethereum", - "dapps", - "wallet", - "web3" - ] + "keywords": ["eth", "ethereum", "dapps", "wallet", "web3"] } From 4070e059dc42a64b0dc5a464451b1f0d5e392074 Mon Sep 17 00:00:00 2001 From: Yannick <97727388+Mill1995@users.noreply.github.com> Date: Wed, 22 May 2024 15:09:15 +0200 Subject: [PATCH 16/16] Fixed typo inside migration-guide.mdx (#2293) --- site/pages/docs/migration-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/docs/migration-guide.mdx b/site/pages/docs/migration-guide.mdx index 70ce270c5c..bee7bb2a56 100644 --- a/site/pages/docs/migration-guide.mdx +++ b/site/pages/docs/migration-guide.mdx @@ -368,7 +368,7 @@ const hash = await walletClient.sendTransaction({ ### `recoverAddress`, `recoverMessageAddress`, `verifyMessage` are now async -he following functions are now `async` functions instead of synchronous functions: +The following functions are now `async` functions instead of synchronous functions: - `recoverAddress` - `recoverMessageAddress`