diff --git a/.changeset/all-rings-bow.md b/.changeset/all-rings-bow.md deleted file mode 100644 index 402bc8aee6..0000000000 --- a/.changeset/all-rings-bow.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -ethauth support diff --git a/.changeset/brave-papayas-join.md b/.changeset/brave-papayas-join.md deleted file mode 100644 index bd37b6ba89..0000000000 --- a/.changeset/brave-papayas-join.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -New chains, minor fixes diff --git a/.changeset/bright-pots-hope.md b/.changeset/bright-pots-hope.md deleted file mode 100644 index 6bd9a887ca..0000000000 --- a/.changeset/bright-pots-hope.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Beta release with dapp connector fixes diff --git a/.changeset/chilly-hands-carry.md b/.changeset/chilly-hands-carry.md deleted file mode 100644 index 82086927ce..0000000000 --- a/.changeset/chilly-hands-carry.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -New chains, relayer rpc fix diff --git a/.changeset/crisp-zoos-retire.md b/.changeset/crisp-zoos-retire.md deleted file mode 100644 index 84fd2a25c6..0000000000 --- a/.changeset/crisp-zoos-retire.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -dapp-client updates diff --git a/.changeset/cyan-radios-relax.md b/.changeset/cyan-radios-relax.md deleted file mode 100644 index ec408d6cd9..0000000000 --- a/.changeset/cyan-radios-relax.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Fix signer 404 error, minor fixes diff --git a/.changeset/four-beds-stare.md b/.changeset/four-beds-stare.md deleted file mode 100644 index 05d79c07d3..0000000000 --- a/.changeset/four-beds-stare.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -multicall3 optimization diff --git a/.changeset/free-tips-switch.md b/.changeset/free-tips-switch.md deleted file mode 100644 index 0921b6d355..0000000000 --- a/.changeset/free-tips-switch.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -3.0.0 beta diff --git a/.changeset/goofy-laws-serve.md b/.changeset/goofy-laws-serve.md deleted file mode 100644 index 690a5f76b4..0000000000 --- a/.changeset/goofy-laws-serve.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch -'@repo/eslint-config': patch -'@repo/typescript-config': patch -'@repo/ui': patch ---- - -Beta release for v3 diff --git a/.changeset/modern-views-write.md b/.changeset/modern-views-write.md deleted file mode 100644 index a70d6c1548..0000000000 --- a/.changeset/modern-views-write.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Final RC before 3.0.0 diff --git a/.changeset/new-turkeys-double.md b/.changeset/new-turkeys-double.md deleted file mode 100644 index 72f26beb9c..0000000000 --- a/.changeset/new-turkeys-double.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Apple auth fixes diff --git a/.changeset/nice-tips-slide.md b/.changeset/nice-tips-slide.md deleted file mode 100644 index 1595123e78..0000000000 --- a/.changeset/nice-tips-slide.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Apple auth fix diff --git a/.changeset/nine-areas-fall.md b/.changeset/nine-areas-fall.md deleted file mode 100644 index 8495b8a5c4..0000000000 --- a/.changeset/nine-areas-fall.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Userdata service updates diff --git a/.changeset/open-toes-marry.md b/.changeset/open-toes-marry.md deleted file mode 100644 index ec5bf32174..0000000000 --- a/.changeset/open-toes-marry.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch -'@repo/eslint-config': patch -'@repo/typescript-config': patch -'@repo/ui': patch ---- - -3.0.0-beta.3 with fixes diff --git a/.changeset/plain-feet-stare.md b/.changeset/plain-feet-stare.md deleted file mode 100644 index c99c82026b..0000000000 --- a/.changeset/plain-feet-stare.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -3.0.0-beta.2 with identity instrument updates diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index e6c3e91ee0..0000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "mode": "exit", - "tag": "beta", - "initialVersions": { - "docs": "0.1.0", - "web": "0.1.0", - "@0xsequence/api": "3.0.0-beta.18", - "@0xsequence/builder": "3.0.0-beta.18", - "@0xsequence/guard": "3.0.0-beta.18", - "@0xsequence/identity-instrument": "3.0.0-beta.18", - "@0xsequence/indexer": "3.0.0-beta.18", - "@0xsequence/marketplace": "3.0.0-beta.18", - "@0xsequence/metadata": "3.0.0-beta.18", - "@0xsequence/relayer": "3.0.0-beta.18", - "@0xsequence/userdata": "3.0.0-beta.18", - "@0xsequence/abi": "3.0.0-beta.18", - "@0xsequence/wallet-core": "3.0.0-beta.18", - "@0xsequence/dapp-client": "3.0.0-beta.18", - "@0xsequence/wallet-primitives": "3.0.0-beta.18", - "@0xsequence/wallet-wdk": "3.0.0-beta.18", - "@repo/eslint-config": "0.0.1-beta.1", - "@repo/typescript-config": "0.0.1-beta.1", - "@repo/ui": "0.0.1-beta.1" - }, - "changesets": [ - "all-rings-bow", - "brave-papayas-join", - "bright-pots-hope", - "chilly-hands-carry", - "crisp-zoos-retire", - "cyan-radios-relax", - "four-beds-stare", - "free-tips-switch", - "goofy-laws-serve", - "modern-views-write", - "new-turkeys-double", - "nice-tips-slide", - "nine-areas-fall", - "open-toes-marry", - "plain-feet-stare", - "smart-cups-exist", - "tiny-files-chew", - "wild-feet-carry", - "wise-heads-buy" - ] -} diff --git a/.changeset/smart-cups-exist.md b/.changeset/smart-cups-exist.md deleted file mode 100644 index 556adcbbd7..0000000000 --- a/.changeset/smart-cups-exist.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -Relayer fee options fix diff --git a/.changeset/tiny-files-chew.md b/.changeset/tiny-files-chew.md deleted file mode 100644 index 672546fd05..0000000000 --- a/.changeset/tiny-files-chew.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/userdata': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -dapp client updates for EOA login diff --git a/.changeset/wild-feet-carry.md b/.changeset/wild-feet-carry.md deleted file mode 100644 index 962942831c..0000000000 --- a/.changeset/wild-feet-carry.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -3.0.0-beta.1 diff --git a/.changeset/wise-heads-buy.md b/.changeset/wise-heads-buy.md deleted file mode 100644 index 1c35a4d355..0000000000 --- a/.changeset/wise-heads-buy.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@0xsequence/api': patch -'@0xsequence/builder': patch -'@0xsequence/guard': patch -'@0xsequence/identity-instrument': patch -'@0xsequence/indexer': patch -'@0xsequence/marketplace': patch -'@0xsequence/metadata': patch -'@0xsequence/relayer': patch -'@0xsequence/abi': patch -'@0xsequence/wallet-core': patch -'@0xsequence/dapp-client': patch -'@0xsequence/wallet-primitives': patch -'@0xsequence/wallet-wdk': patch ---- - -RC5 upgrade diff --git a/README.md b/README.md index 4c663ccf86..ae41ffdbd5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Sequence v3 core libraries and [wallet-contracts-v3](https://github.com/0xsequen - Run tests: `pnpm test` - > **Note:** Tests require [anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) and [forge](https://github.com/foundry-rs/foundry) to be installed. You can run a local anvil instance using `pnpm run test:anvil`. + > **Note:** Tests require [anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil) and [forge](https://github.com/foundry-rs/foundry) to be installed. You can run a local anvil instance using `pnpm run test:anvil`. - Linting and formatting is enforced via git hooks diff --git a/extras/docs/package.json b/extras/docs/package.json index 9c51ce6c11..ccf8cc5bbf 100644 --- a/extras/docs/package.json +++ b/extras/docs/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@repo/ui": "workspace:^", - "next": "^15.5.10", + "next": "^15.5.14", "react": "^19.2.3", "react-dom": "^19.2.3" }, diff --git a/extras/web/package.json b/extras/web/package.json index e7fd226c08..b8fdea4997 100644 --- a/extras/web/package.json +++ b/extras/web/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@repo/ui": "workspace:^", - "next": "^15.5.10", + "next": "^15.5.14", "react": "^19.2.3", "react-dom": "^19.2.3" }, diff --git a/packages/services/api/CHANGELOG.md b/packages/services/api/CHANGELOG.md index 761d84cf9c..de944ea36e 100644 --- a/packages/services/api/CHANGELOG.md +++ b/packages/services/api/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/api +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/api/package.json b/packages/services/api/package.json index 698af864c1..8d2f87a228 100644 --- a/packages/services/api/package.json +++ b/packages/services/api/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/api", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "api sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/api", "author": "Sequence Platforms ULC", diff --git a/packages/services/builder/CHANGELOG.md b/packages/services/builder/CHANGELOG.md index ee715b2368..e458893450 100644 --- a/packages/services/builder/CHANGELOG.md +++ b/packages/services/builder/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/builder +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/builder/package.json b/packages/services/builder/package.json index 07eca60235..1897f0ab87 100644 --- a/packages/services/builder/package.json +++ b/packages/services/builder/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/builder", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "builder sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/builder", "author": "Sequence Platforms ULC", diff --git a/packages/services/guard/CHANGELOG.md b/packages/services/guard/CHANGELOG.md index 931bccffe1..a705f48ba3 100644 --- a/packages/services/guard/CHANGELOG.md +++ b/packages/services/guard/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/guard +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/guard/package.json b/packages/services/guard/package.json index 5a9cf373df..fc489cd03c 100644 --- a/packages/services/guard/package.json +++ b/packages/services/guard/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/guard", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "guard sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/guard", "author": "Sequence Platforms ULC", diff --git a/packages/services/guard/src/sequence.ts b/packages/services/guard/src/sequence.ts index 6c17b75520..b2acc812af 100644 --- a/packages/services/guard/src/sequence.ts +++ b/packages/services/guard/src/sequence.ts @@ -50,7 +50,7 @@ export class Guard implements Types.Guard { throw new Types.AuthRequiredError('PIN') } console.error(error) - throw new Error('Error signing with guard') + throw new Error('Error signing with guard', { cause: error }) } } } diff --git a/packages/services/guard/test/sequence.test.ts b/packages/services/guard/test/sequence.test.ts index 45612d609f..ebc9a47b76 100644 --- a/packages/services/guard/test/sequence.test.ts +++ b/packages/services/guard/test/sequence.test.ts @@ -116,6 +116,21 @@ describe('Sequence', () => { ).rejects.toThrow('Error signing with guard') }) + it('Should preserve the original guard failure as cause', async () => { + mockFetch.mockRejectedValueOnce(new Error('Network error')) + + try { + await guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage) + throw new Error('Expected signPayload to throw') + } catch (error) { + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toBe('Error signing with guard') + expect((error as Error & { cause?: unknown }).cause).toBeInstanceOf(Error) + expect((error as Error & { cause?: Error }).cause?.name).toBe('WebrpcRequestFailed') + expect((error as Error & { cause?: Error }).cause?.message).toBe('request failed') + } + }) + it('Should include proper headers and signer address in request', async () => { const mockGuardAddress = '0x9876543210987654321098765432109876543210' as Address.Address const customGuard = new Guard('https://guard.sequence.app', mockGuardAddress, fetch) diff --git a/packages/services/identity-instrument/CHANGELOG.md b/packages/services/identity-instrument/CHANGELOG.md index 4f193f9d27..ff6cebf477 100644 --- a/packages/services/identity-instrument/CHANGELOG.md +++ b/packages/services/identity-instrument/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/identity-instrument +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/identity-instrument/package.json b/packages/services/identity-instrument/package.json index b695e47107..bb61d8e20c 100644 --- a/packages/services/identity-instrument/package.json +++ b/packages/services/identity-instrument/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/identity-instrument", - "version": "3.0.0-beta.19", + "version": "3.0.5", "license": "Apache-2.0", "type": "module", "publishConfig": { diff --git a/packages/services/indexer/CHANGELOG.md b/packages/services/indexer/CHANGELOG.md index 1091b17775..fb104e35fb 100644 --- a/packages/services/indexer/CHANGELOG.md +++ b/packages/services/indexer/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/indexer +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/indexer/package.json b/packages/services/indexer/package.json index 03dc328cd3..7ad0de895c 100644 --- a/packages/services/indexer/package.json +++ b/packages/services/indexer/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/indexer", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "indexer sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/indexer", "author": "Sequence Platforms ULC", diff --git a/packages/services/marketplace/CHANGELOG.md b/packages/services/marketplace/CHANGELOG.md index 0af122d3e3..5a97b94e85 100644 --- a/packages/services/marketplace/CHANGELOG.md +++ b/packages/services/marketplace/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/marketplace +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/marketplace/package.json b/packages/services/marketplace/package.json index cced49f58c..865e0e5b44 100644 --- a/packages/services/marketplace/package.json +++ b/packages/services/marketplace/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/marketplace", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "marketplace sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/marketplace", "author": "Sequence Platforms ULC", diff --git a/packages/services/metadata/CHANGELOG.md b/packages/services/metadata/CHANGELOG.md index efff94d5ba..3362b424e0 100644 --- a/packages/services/metadata/CHANGELOG.md +++ b/packages/services/metadata/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/metadata +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/metadata/package.json b/packages/services/metadata/package.json index 52583541af..acb19b042c 100644 --- a/packages/services/metadata/package.json +++ b/packages/services/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/metadata", - "version": "3.0.0-beta.19", + "version": "3.0.5", "publishConfig": { "access": "public" }, diff --git a/packages/services/relayer/CHANGELOG.md b/packages/services/relayer/CHANGELOG.md index 8f4fbbf981..abefb99b79 100644 --- a/packages/services/relayer/CHANGELOG.md +++ b/packages/services/relayer/CHANGELOG.md @@ -1,5 +1,91 @@ # @0xsequence/relayer +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/wallet-primitives@3.0.0 + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/relayer/package.json b/packages/services/relayer/package.json index 079fe9979c..0c1422d970 100644 --- a/packages/services/relayer/package.json +++ b/packages/services/relayer/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/relayer", - "version": "3.0.0-beta.19", + "version": "3.0.5", "type": "module", "publishConfig": { "access": "public" diff --git a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts index ca5dbe9c82..8a9ff4a7b4 100644 --- a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts +++ b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ -// sequence-relayer v0.4.1 7f8a4b83b00e0b6849c76c2ff0e23931e26b3d9f +// sequence-relayer v0.4.1 0a2503bc893179ba968b0015d7580aabf6a88dd4 // -- -// Code generated by Webrpc-gen@v0.30.2 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.32.2 with typescript generator. DO NOT EDIT. // // webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts -compat @@ -12,7 +12,7 @@ export const WebrpcVersion = 'v1' export const WebrpcSchemaVersion = 'v0.4.1' // Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '7f8a4b83b00e0b6849c76c2ff0e23931e26b3d9f' +export const WebrpcSchemaHash = '0a2503bc893179ba968b0015d7580aabf6a88dd4' // // Client interface @@ -72,6 +72,23 @@ export interface RelayerClient { feeOptions(req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise + /** + * Bridge gas endpoints for S2S calls + * Used for bridge fees (e.g., LayerZero messaging fees) that require msg.value to be fronted at runtime. + * bridgeGas will be included in fee calculation so the relayer gets reimbursed. + */ + sendMetaTxnWithBridgeGas( + req: SendMetaTxnWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + feeOptionsWithBridgeGas( + req: FeeOptionsWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + /** * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date */ @@ -81,6 +98,17 @@ export interface RelayerClient { signal?: AbortSignal, ): Promise + /** + * + * Sender administration + * + */ + startSender(req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise + + stopSender(req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise + + repairSender(req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise + getMetaTransactions( req: GetMetaTransactionsArgs, headers?: object, @@ -93,21 +121,6 @@ export interface RelayerClient { signal?: AbortSignal, ): Promise - /** - * Sent transactions from an account. If filter is omitted then it will return all transactions. - */ - sentTransactions(req: SentTransactionsArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * Pending transactions waiting to be mined for an account. This endpoint is just a sugar of `SentTransactions` - * with the filter set to pending: true. - */ - pendingTransactions( - req: PendingTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** * Legacy Gas Tank */ @@ -145,7 +158,7 @@ export interface RelayerClient { ): Promise /** - * Gas Sponsorship + * Project-Level Gas Sponsorship */ listGasSponsors(req: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal): Promise @@ -157,6 +170,39 @@ export interface RelayerClient { removeGasSponsor(req: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise + /** + * Ecosystem-level Gas Sponsorship + */ + listEcosystemGasSponsors( + req: ListEcosystemGasSponsorsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + getEcosystemGasSponsor( + req: GetEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + addEcosystemGasSponsor( + req: AddEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + updateEcosystemGasSponsor( + req: UpdateEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + removeEcosystemGasSponsor( + req: RemoveEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + /** * Gas Sponsor Lookup */ @@ -186,6 +232,12 @@ export interface RelayerClient { // Schema types // +export enum RepairOperation { + SKIP = 'SKIP', + REQUEUE = 'REQUEUE', + DROP = 'DROP', +} + export enum ETHTxnStatus { UNKNOWN = 'UNKNOWN', DROPPED = 'DROPPED', @@ -195,6 +247,7 @@ export enum ETHTxnStatus { PARTIALLY_FAILED = 'PARTIALLY_FAILED', FAILED = 'FAILED', PENDING_PRECONDITION = 'PENDING_PRECONDITION', + MINED = 'MINED', } export enum TransferType { @@ -221,7 +274,7 @@ export enum FeeTokenType { ERC1155_TOKEN = 'ERC1155_TOKEN', } -export enum SortOrder { +export enum Order { DESC = 'DESC', ASC = 'ASC', } @@ -250,7 +303,31 @@ export interface SenderStatus { index: number address: string etherBalance: number + enabled: boolean active: boolean + nonce?: NonceStatus + current?: CurrentStatus +} + +export interface NonceStatus { + chain: number + mempool: number +} + +export interface CurrentStatus { + transaction: string + first: TransactionStatus + latest?: TransactionStatus +} + +export interface TransactionStatus { + transaction: string + gas: number + gasPrice: string + priorityFee: string + time: string + age: string + error?: string } export interface RuntimeChecks {} @@ -287,6 +364,7 @@ export interface GasSponsor { id: number gasTankId: number projectId: number + ecosystemId: number chainId: number address: string name: string @@ -329,6 +407,7 @@ export interface MetaTxnLog { minedAt: string target: string input: string + bridgeGas?: string txnArgs: { [key: string]: any } txnReceipt?: { [key: string]: any } walletAddress: string @@ -402,11 +481,6 @@ export interface TxnLogTransfer { amounts: Array } -export interface SentTransactionsFilter { - pending?: boolean - failed?: boolean -} - export interface SimulateResult { executed: boolean succeeded: boolean @@ -443,19 +517,16 @@ export interface FeeToken { } export interface Page { - pageSize?: number - page?: number - more?: boolean - totalRecords?: number - column?: string - before?: any - after?: any - sort?: Array + pageSize: number + page: number + more: boolean + column: string + sort: Array } -export interface SortBy { +export interface Sort { column: string - order: SortOrder + order: Order } export interface PingArgs {} @@ -566,6 +637,33 @@ export interface FeeOptionsReturn { quote?: string } +export interface SendMetaTxnWithBridgeGasArgs { + call: MetaTxn + quote?: string + projectID?: number + bridgeGas: string + preconditions?: Array +} + +export interface SendMetaTxnWithBridgeGasReturn { + status: boolean + txnHash: string +} + +export interface FeeOptionsWithBridgeGasArgs { + wallet: string + to: string + data: string + simulate?: boolean + bridgeGas: string +} + +export interface FeeOptionsWithBridgeGasReturn { + options: Array + sponsored: boolean + quote?: string +} + export interface GetMetaTxnNetworkFeeOptionsArgs { walletConfig: any payload: string @@ -575,6 +673,26 @@ export interface GetMetaTxnNetworkFeeOptionsReturn { options: Array } +export interface StartSenderArgs { + sender: number +} + +export interface StartSenderReturn {} + +export interface StopSenderArgs { + sender: number +} + +export interface StopSenderReturn {} + +export interface RepairSenderArgs { + sender: number + nonce: number + operation: RepairOperation +} + +export interface RepairSenderReturn {} + export interface GetMetaTransactionsArgs { projectId: number page?: Page @@ -595,25 +713,6 @@ export interface GetTransactionCostReturn { cost: number } -export interface SentTransactionsArgs { - filter?: SentTransactionsFilter - page?: Page -} - -export interface SentTransactionsReturn { - page: Page - transactions: Array -} - -export interface PendingTransactionsArgs { - page?: Page -} - -export interface PendingTransactionsReturn { - page: Page - transactions: Array -} - export interface GetGasTankArgs { id: number } @@ -735,6 +834,58 @@ export interface RemoveGasSponsorReturn { status: boolean } +export interface ListEcosystemGasSponsorsArgs { + ecosystemId: number + page?: Page +} + +export interface ListEcosystemGasSponsorsReturn { + page: Page + gasSponsors: Array +} + +export interface GetEcosystemGasSponsorArgs { + ecosystemId: number + id: number +} + +export interface GetEcosystemGasSponsorReturn { + gasSponsor: GasSponsor +} + +export interface AddEcosystemGasSponsorArgs { + ecosystemId: number + address: string + name?: string + active?: boolean +} + +export interface AddEcosystemGasSponsorReturn { + status: boolean + gasSponsor: GasSponsor +} + +export interface UpdateEcosystemGasSponsorArgs { + ecosystemId: number + id: number + name?: string + active?: boolean +} + +export interface UpdateEcosystemGasSponsorReturn { + status: boolean + gasSponsor: GasSponsor +} + +export interface RemoveEcosystemGasSponsorArgs { + ecosystemId: number + id: number +} + +export interface RemoveEcosystemGasSponsorReturn { + status: boolean +} + export interface AddressGasSponsorsArgs { address: string page?: Page @@ -795,12 +946,16 @@ export class Relayer implements RelayerClient { updateMetaTxnGasLimits: (req: UpdateMetaTxnGasLimitsArgs) => ['Relayer', 'updateMetaTxnGasLimits', req] as const, feeTokens: () => ['Relayer', 'feeTokens'] as const, feeOptions: (req: FeeOptionsArgs) => ['Relayer', 'feeOptions', req] as const, + sendMetaTxnWithBridgeGas: (req: SendMetaTxnWithBridgeGasArgs) => + ['Relayer', 'sendMetaTxnWithBridgeGas', req] as const, + feeOptionsWithBridgeGas: (req: FeeOptionsWithBridgeGasArgs) => ['Relayer', 'feeOptionsWithBridgeGas', req] as const, getMetaTxnNetworkFeeOptions: (req: GetMetaTxnNetworkFeeOptionsArgs) => ['Relayer', 'getMetaTxnNetworkFeeOptions', req] as const, + startSender: (req: StartSenderArgs) => ['Relayer', 'startSender', req] as const, + stopSender: (req: StopSenderArgs) => ['Relayer', 'stopSender', req] as const, + repairSender: (req: RepairSenderArgs) => ['Relayer', 'repairSender', req] as const, getMetaTransactions: (req: GetMetaTransactionsArgs) => ['Relayer', 'getMetaTransactions', req] as const, getTransactionCost: (req: GetTransactionCostArgs) => ['Relayer', 'getTransactionCost', req] as const, - sentTransactions: (req: SentTransactionsArgs) => ['Relayer', 'sentTransactions', req] as const, - pendingTransactions: (req: PendingTransactionsArgs) => ['Relayer', 'pendingTransactions', req] as const, getGasTank: (req: GetGasTankArgs) => ['Relayer', 'getGasTank', req] as const, addGasTank: (req: AddGasTankArgs) => ['Relayer', 'addGasTank', req] as const, updateGasTank: (req: UpdateGasTankArgs) => ['Relayer', 'updateGasTank', req] as const, @@ -816,6 +971,14 @@ export class Relayer implements RelayerClient { addGasSponsor: (req: AddGasSponsorArgs) => ['Relayer', 'addGasSponsor', req] as const, updateGasSponsor: (req: UpdateGasSponsorArgs) => ['Relayer', 'updateGasSponsor', req] as const, removeGasSponsor: (req: RemoveGasSponsorArgs) => ['Relayer', 'removeGasSponsor', req] as const, + listEcosystemGasSponsors: (req: ListEcosystemGasSponsorsArgs) => + ['Relayer', 'listEcosystemGasSponsors', req] as const, + getEcosystemGasSponsor: (req: GetEcosystemGasSponsorArgs) => ['Relayer', 'getEcosystemGasSponsor', req] as const, + addEcosystemGasSponsor: (req: AddEcosystemGasSponsorArgs) => ['Relayer', 'addEcosystemGasSponsor', req] as const, + updateEcosystemGasSponsor: (req: UpdateEcosystemGasSponsorArgs) => + ['Relayer', 'updateEcosystemGasSponsor', req] as const, + removeEcosystemGasSponsor: (req: RemoveEcosystemGasSponsorArgs) => + ['Relayer', 'removeEcosystemGasSponsor', req] as const, addressGasSponsors: (req: AddressGasSponsorsArgs) => ['Relayer', 'addressGasSponsors', req] as const, getProjectBalance: (req: GetProjectBalanceArgs) => ['Relayer', 'getProjectBalance', req] as const, adjustProjectBalance: (req: AdjustProjectBalanceArgs) => ['Relayer', 'adjustProjectBalance', req] as const, @@ -897,10 +1060,7 @@ export class Relayer implements RelayerClient { } sendMetaTxn = (req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('SendMetaTxn'), - createHttpRequest(JsonEncode(req, 'SendMetaTxnArgs'), headers, signal), - ).then( + return this.fetch(this.url('SendMetaTxn'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'SendMetaTxnReturn') @@ -919,10 +1079,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('GetMetaTxnNonce'), - createHttpRequest(JsonEncode(req, 'GetMetaTxnNonceArgs'), headers, signal), - ).then( + return this.fetch(this.url('GetMetaTxnNonce'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'GetMetaTxnNonceReturn') @@ -941,10 +1098,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('GetMetaTxnReceipt'), - createHttpRequest(JsonEncode(req, 'GetMetaTxnReceiptArgs'), headers, signal), - ).then( + return this.fetch(this.url('GetMetaTxnReceipt'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'GetMetaTxnReceiptReturn') @@ -959,7 +1113,7 @@ export class Relayer implements RelayerClient { } simulate = (req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Simulate'), createHttpRequest(JsonEncode(req, 'SimulateArgs'), headers, signal)).then( + return this.fetch(this.url('Simulate'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'SimulateReturn') @@ -974,10 +1128,7 @@ export class Relayer implements RelayerClient { } simulateV3 = (req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('SimulateV3'), - createHttpRequest(JsonEncode(req, 'SimulateV3Args'), headers, signal), - ).then( + return this.fetch(this.url('SimulateV3'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'SimulateV3Return') @@ -996,10 +1147,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('UpdateMetaTxnGasLimits'), - createHttpRequest(JsonEncode(req, 'UpdateMetaTxnGasLimitsArgs'), headers, signal), - ).then( + return this.fetch(this.url('UpdateMetaTxnGasLimits'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'UpdateMetaTxnGasLimitsReturn') @@ -1029,10 +1177,7 @@ export class Relayer implements RelayerClient { } feeOptions = (req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('FeeOptions'), - createHttpRequest(JsonEncode(req, 'FeeOptionsArgs'), headers, signal), - ).then( + return this.fetch(this.url('FeeOptions'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'FeeOptionsReturn') @@ -1046,6 +1191,44 @@ export class Relayer implements RelayerClient { ) } + sendMetaTxnWithBridgeGas = ( + req: SendMetaTxnWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('SendMetaTxnWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'SendMetaTxnWithBridgeGasReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + feeOptionsWithBridgeGas = ( + req: FeeOptionsWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('FeeOptionsWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'FeeOptionsWithBridgeGasReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + getMetaTxnNetworkFeeOptions = ( req: GetMetaTxnNetworkFeeOptionsArgs, headers?: object, @@ -1053,7 +1236,7 @@ export class Relayer implements RelayerClient { ): Promise => { return this.fetch( this.url('GetMetaTxnNetworkFeeOptions'), - createHttpRequest(JsonEncode(req, 'GetMetaTxnNetworkFeeOptionsArgs'), headers, signal), + createHttpRequest(JsonEncode(req), headers, signal), ).then( (res) => { return buildResponse(res).then((_data) => { @@ -1068,18 +1251,11 @@ export class Relayer implements RelayerClient { ) } - getMetaTransactions = ( - req: GetMetaTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetMetaTransactions'), - createHttpRequest(JsonEncode(req, 'GetMetaTransactionsArgs'), headers, signal), - ).then( + startSender = (req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('StartSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMetaTransactionsReturn') + return JsonDecode(_data, 'StartSenderReturn') }) }, (error) => { @@ -1090,18 +1266,26 @@ export class Relayer implements RelayerClient { ) } - getTransactionCost = ( - req: GetTransactionCostArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTransactionCost'), - createHttpRequest(JsonEncode(req, 'GetTransactionCostArgs'), headers, signal), - ).then( + stopSender = (req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('StopSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTransactionCostReturn') + return JsonDecode(_data, 'StopSenderReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + repairSender = (req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('RepairSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'RepairSenderReturn') }) }, (error) => { @@ -1112,18 +1296,15 @@ export class Relayer implements RelayerClient { ) } - sentTransactions = ( - req: SentTransactionsArgs, + getMetaTransactions = ( + req: GetMetaTransactionsArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SentTransactions'), - createHttpRequest(JsonEncode(req, 'SentTransactionsArgs'), headers, signal), - ).then( + ): Promise => { + return this.fetch(this.url('GetMetaTransactions'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SentTransactionsReturn') + return JsonDecode(_data, 'GetMetaTransactionsReturn') }) }, (error) => { @@ -1134,18 +1315,15 @@ export class Relayer implements RelayerClient { ) } - pendingTransactions = ( - req: PendingTransactionsArgs, + getTransactionCost = ( + req: GetTransactionCostArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PendingTransactions'), - createHttpRequest(JsonEncode(req, 'PendingTransactionsArgs'), headers, signal), - ).then( + ): Promise => { + return this.fetch(this.url('GetTransactionCost'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PendingTransactionsReturn') + return JsonDecode(_data, 'GetTransactionCostReturn') }) }, (error) => { @@ -1157,10 +1335,7 @@ export class Relayer implements RelayerClient { } getGasTank = (req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetGasTank'), - createHttpRequest(JsonEncode(req, 'GetGasTankArgs'), headers, signal), - ).then( + return this.fetch(this.url('GetGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'GetGasTankReturn') @@ -1175,10 +1350,7 @@ export class Relayer implements RelayerClient { } addGasTank = (req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('AddGasTank'), - createHttpRequest(JsonEncode(req, 'AddGasTankArgs'), headers, signal), - ).then( + return this.fetch(this.url('AddGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'AddGasTankReturn') @@ -1193,10 +1365,7 @@ export class Relayer implements RelayerClient { } updateGasTank = (req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('UpdateGasTank'), - createHttpRequest(JsonEncode(req, 'UpdateGasTankArgs'), headers, signal), - ).then( + return this.fetch(this.url('UpdateGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'UpdateGasTankReturn') @@ -1217,7 +1386,7 @@ export class Relayer implements RelayerClient { ): Promise => { return this.fetch( this.url('NextGasTankBalanceAdjustmentNonce'), - createHttpRequest(JsonEncode(req, 'NextGasTankBalanceAdjustmentNonceArgs'), headers, signal), + createHttpRequest(JsonEncode(req), headers, signal), ).then( (res) => { return buildResponse(res).then((_data) => { @@ -1237,10 +1406,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('AdjustGasTankBalance'), - createHttpRequest(JsonEncode(req, 'AdjustGasTankBalanceArgs'), headers, signal), - ).then( + return this.fetch(this.url('AdjustGasTankBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'AdjustGasTankBalanceReturn') @@ -1261,7 +1427,7 @@ export class Relayer implements RelayerClient { ): Promise => { return this.fetch( this.url('GetGasTankBalanceAdjustment'), - createHttpRequest(JsonEncode(req, 'GetGasTankBalanceAdjustmentArgs'), headers, signal), + createHttpRequest(JsonEncode(req), headers, signal), ).then( (res) => { return buildResponse(res).then((_data) => { @@ -1283,7 +1449,7 @@ export class Relayer implements RelayerClient { ): Promise => { return this.fetch( this.url('ListGasTankBalanceAdjustments'), - createHttpRequest(JsonEncode(req, 'ListGasTankBalanceAdjustmentsArgs'), headers, signal), + createHttpRequest(JsonEncode(req), headers, signal), ).then( (res) => { return buildResponse(res).then((_data) => { @@ -1303,10 +1469,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('ListGasSponsors'), - createHttpRequest(JsonEncode(req, 'ListGasSponsorsArgs'), headers, signal), - ).then( + return this.fetch(this.url('ListGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'ListGasSponsorsReturn') @@ -1321,10 +1484,7 @@ export class Relayer implements RelayerClient { } getGasSponsor = (req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetGasSponsor'), - createHttpRequest(JsonEncode(req, 'GetGasSponsorArgs'), headers, signal), - ).then( + return this.fetch(this.url('GetGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'GetGasSponsorReturn') @@ -1339,10 +1499,7 @@ export class Relayer implements RelayerClient { } addGasSponsor = (req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('AddGasSponsor'), - createHttpRequest(JsonEncode(req, 'AddGasSponsorArgs'), headers, signal), - ).then( + return this.fetch(this.url('AddGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'AddGasSponsorReturn') @@ -1361,10 +1518,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('UpdateGasSponsor'), - createHttpRequest(JsonEncode(req, 'UpdateGasSponsorArgs'), headers, signal), - ).then( + return this.fetch(this.url('UpdateGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'UpdateGasSponsorReturn') @@ -1383,10 +1537,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('RemoveGasSponsor'), - createHttpRequest(JsonEncode(req, 'RemoveGasSponsorArgs'), headers, signal), - ).then( + return this.fetch(this.url('RemoveGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'RemoveGasSponsorReturn') @@ -1400,15 +1551,107 @@ export class Relayer implements RelayerClient { ) } + listEcosystemGasSponsors = ( + req: ListEcosystemGasSponsorsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListEcosystemGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListEcosystemGasSponsorsReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getEcosystemGasSponsor = ( + req: GetEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + addEcosystemGasSponsor = ( + req: AddEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('AddEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'AddEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + updateEcosystemGasSponsor = ( + req: UpdateEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('UpdateEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'UpdateEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + removeEcosystemGasSponsor = ( + req: RemoveEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('RemoveEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'RemoveEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + addressGasSponsors = ( req: AddressGasSponsorsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('AddressGasSponsors'), - createHttpRequest(JsonEncode(req, 'AddressGasSponsorsArgs'), headers, signal), - ).then( + return this.fetch(this.url('AddressGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'AddressGasSponsorsReturn') @@ -1427,10 +1670,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('GetProjectBalance'), - createHttpRequest(JsonEncode(req, 'GetProjectBalanceArgs'), headers, signal), - ).then( + return this.fetch(this.url('GetProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'GetProjectBalanceReturn') @@ -1449,10 +1689,7 @@ export class Relayer implements RelayerClient { headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch( - this.url('AdjustProjectBalance'), - createHttpRequest(JsonEncode(req, 'AdjustProjectBalanceArgs'), headers, signal), - ).then( + return this.fetch(this.url('AdjustProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return JsonDecode(_data, 'AdjustProjectBalanceReturn') @@ -1468,7 +1705,11 @@ export class Relayer implements RelayerClient { } const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, + } return { method: 'POST', headers: reqHeaders, body, signal } } @@ -1499,44 +1740,11 @@ export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise(obj: T, typ: string = ''): string => { - return JSON.stringify(encodeType(typ, obj)) +// Encode object to JSON with BigInts converted to decimal strings. +export const JsonEncode = (obj: T): string => { + return JSON.stringify(obj, (key, value) => (typeof value === 'bigint' ? value.toString() : value)) } // Decode data (JSON string or already-parsed object) and convert declared BigInt string fields back to BigInt. @@ -1890,7 +2107,7 @@ export class AccessKeyMismatchError extends WebrpcError { this.name = error.name || 'AccessKeyMismatch' this.code = typeof error.code === 'number' ? error.code : 1102 this.message = error.message || `Access key mismatch` - this.status = typeof error.status === 'number' ? error.status : 409 + this.status = typeof error.status === 'number' ? error.status : 403 if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) } @@ -1932,6 +2149,18 @@ export class UnauthorizedUserError extends WebrpcError { } } +export class InvalidChainError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidChain' + this.code = typeof error.code === 'number' ? error.code : 1106 + this.message = error.message || `Network not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidChainError.prototype) + } +} + export class QuotaExceededError extends WebrpcError { constructor(error: WebrpcErrorParams = {}) { super(error) @@ -2114,6 +2343,7 @@ export enum errors { InvalidOrigin = 'InvalidOrigin', InvalidService = 'InvalidService', UnauthorizedUser = 'UnauthorizedUser', + InvalidChain = 'InvalidChain', QuotaExceeded = 'QuotaExceeded', QuotaRateLimit = 'QuotaRateLimit', NoDefaultKey = 'NoDefaultKey', @@ -2155,6 +2385,7 @@ export enum WebrpcErrorCodes { InvalidOrigin = 1103, InvalidService = 1104, UnauthorizedUser = 1105, + InvalidChain = 1106, QuotaExceeded = 1200, QuotaRateLimit = 1201, NoDefaultKey = 1300, @@ -2196,6 +2427,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [1103]: InvalidOriginError, [1104]: InvalidServiceError, [1105]: UnauthorizedUserError, + [1106]: InvalidChainError, [1200]: QuotaExceededError, [1201]: QuotaRateLimitError, [1300]: NoDefaultKeyError, @@ -2217,7 +2449,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = 'Webrpc' -export const WebrpcHeaderValue = 'webrpc@v0.30.2;gen-typescript@v0.22.2;sequence-relayer@v0.4.1' +export const WebrpcHeaderValue = 'webrpc@v0.32.2;gen-typescript@v0.23.1;sequence-relayer@v0.4.1' type WebrpcGenVersions = { WebrpcGenVersion: string diff --git a/packages/services/userdata/CHANGELOG.md b/packages/services/userdata/CHANGELOG.md index 2fad59d85d..51ebad4131 100644 --- a/packages/services/userdata/CHANGELOG.md +++ b/packages/services/userdata/CHANGELOG.md @@ -1,5 +1,56 @@ # @0xsequence/userdata +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 3.0.0 release +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/services/userdata/package.json b/packages/services/userdata/package.json index 60483871d5..38f3c9d5a8 100644 --- a/packages/services/userdata/package.json +++ b/packages/services/userdata/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/userdata", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "userdata sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/userdata", "author": "Sequence Platforms ULC", diff --git a/packages/utils/abi/CHANGELOG.md b/packages/utils/abi/CHANGELOG.md index d7f2586696..8194c38b89 100644 --- a/packages/utils/abi/CHANGELOG.md +++ b/packages/utils/abi/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/abi +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/utils/abi/package.json b/packages/utils/abi/package.json index b5b00a94b1..d70d75cdc6 100644 --- a/packages/utils/abi/package.json +++ b/packages/utils/abi/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/abi", - "version": "3.0.0-beta.19", + "version": "3.0.5", "description": "abi sub-package for Sequence", "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/utils/abi", "author": "Sequence Platforms ULC", diff --git a/packages/wallet/core/CHANGELOG.md b/packages/wallet/core/CHANGELOG.md index 9d4dd02b21..23fc45fe44 100644 --- a/packages/wallet/core/CHANGELOG.md +++ b/packages/wallet/core/CHANGELOG.md @@ -1,5 +1,103 @@ # @0xsequence/wallet-core +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/wallet/core/package.json b/packages/wallet/core/package.json index fec693b90c..4133157861 100644 --- a/packages/wallet/core/package.json +++ b/packages/wallet/core/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-core", - "version": "3.0.0-beta.19", + "version": "3.0.5", "license": "Apache-2.0", "type": "module", "publishConfig": { diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index ef3d81b3a3..6b23cb7a6c 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -18,6 +18,7 @@ import { SessionSigner, SessionSignerInvalidReason, isImplicitSessionSigner, + isIncrementCall, UsageLimit, } from './session/index.js' @@ -130,21 +131,16 @@ export class SessionManager implements SapientSigner { })) } - async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { - // Only use signers that match the topology - const topology = await this.topology - const identitySigners = SessionConfig.getIdentitySigners(topology) - if (identitySigners.length === 0) { - throw new Error('Identity signers not found') - } - - // Prioritize implicit signers - const availableSigners = [...this._implicitSigners, ...this._explicitSigners] - if (availableSigners.length === 0) { - throw new Error('No signers match the topology') - } - - // Find supported signers for each call + /** + * Find one signer per call from the given candidate list (first that supports each call). + */ + private async findSignersForCallsWithCandidates( + wallet: Address.Address, + chainId: number, + calls: Payload.Call[], + topology: SessionConfig.SessionsTopology, + availableSigners: SessionSigner[], + ): Promise { const signers: SessionSigner[] = [] for (const call of calls) { let supported = false @@ -173,9 +169,67 @@ export class SessionManager implements SapientSigner { if (expiredSupportedSigner) { throw new Error(`Signer supporting call is expired: ${expiredSupportedSigner.address}`) } - throw new Error( - `No signer supported for call. ` + `Call: to=${call.to}, data=${call.data}, value=${call.value}, `, - ) + throw new Error(`No signer supported for call. Call: to=${call.to}, data=${call.data}, value=${call.value}, `) + } + } + return signers + } + + async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { + const topology = await this.topology + const identitySigners = SessionConfig.getIdentitySigners(topology) + if (identitySigners.length === 0) { + throw new Error('Identity signers not found') + } + + const availableSigners = [...this._implicitSigners, ...this._explicitSigners] + if (availableSigners.length === 0) { + throw new Error('No signers match the topology') + } + + const nonIncrementCalls: Payload.Call[] = [] + const incrementCalls: Payload.Call[] = [] + for (const call of calls) { + if (isIncrementCall(call, this.address)) { + incrementCalls.push(call) + } else { + nonIncrementCalls.push(call) + } + } + + // Find signers for non-increment calls + const nonIncrementSigners = + nonIncrementCalls.length > 0 + ? await this.findSignersForCallsWithCandidates(wallet, chainId, nonIncrementCalls, topology, availableSigners) + : [] + + let incrementSigners: SessionSigner[] = [] + if (incrementCalls.length > 0) { + // Find signers for increment calls, preferring signers that signed non-increment calls + const incrementCandidates = [ + ...nonIncrementSigners, + ...availableSigners.filter((s) => !nonIncrementSigners.includes(s)), + ] + incrementSigners = await this.findSignersForCallsWithCandidates( + wallet, + chainId, + incrementCalls, + topology, + incrementCandidates, + ) + } + + // Merge back in original call order + const signers: SessionSigner[] = [] + let nonIncrementIndex = 0 + let incrementIndex = 0 + for (const call of calls) { + if (isIncrementCall(call, this.address)) { + signers.push(incrementSigners[incrementIndex]!) + incrementIndex++ + } else { + signers.push(nonIncrementSigners[nonIncrementIndex]!) + nonIncrementIndex++ } } return signers @@ -191,20 +245,23 @@ export class SessionManager implements SapientSigner { } const signers = await this.findSignersForCalls(wallet, chainId, calls) - // Create a map of signers to their associated calls - const signerToCalls = new Map() + // Map each signer to only their non-increment calls + const signerToNonIncrementCalls = new Map() signers.forEach((signer, index) => { const call = calls[index]! - const existingCalls = signerToCalls.get(signer) || [] - signerToCalls.set(signer, [...existingCalls, call]) + if (isIncrementCall(call, this.address)) { + return + } + const existing = signerToNonIncrementCalls.get(signer) || [] + signerToNonIncrementCalls.set(signer, [...existing, call]) }) - // Prepare increments for each explicit signer with their associated calls + // Prepare increments for each explicit signer from their non-increment calls only const increments: UsageLimit[] = ( await Promise.all( - Array.from(signerToCalls.entries()).map(async ([signer, associatedCalls]) => { + Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => { if (isExplicitSessionSigner(signer)) { - return signer.prepareIncrements(wallet, chainId, associatedCalls, this.address, this._provider!) + return signer.prepareIncrements(wallet, chainId, nonIncrementCalls, this.address, this._provider!) } return [] }), diff --git a/packages/wallet/core/src/signers/session/explicit.ts b/packages/wallet/core/src/signers/session/explicit.ts index b7ad087b4f..ef78c554a6 100644 --- a/packages/wallet/core/src/signers/session/explicit.ts +++ b/packages/wallet/core/src/signers/session/explicit.ts @@ -1,7 +1,7 @@ import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox' import { MemoryPkStore, PkStore } from '../pk/index.js' -import { ExplicitSessionSigner, SessionSignerValidity, UsageLimit } from './session.js' +import { ExplicitSessionSigner, isIncrementCall, SessionSignerValidity, UsageLimit } from './session.js' export type ExplicitParams = Omit @@ -208,11 +208,7 @@ export class Explicit implements ExplicitSessionSigner { sessionManagerAddress: Address.Address, provider?: Provider.Provider, ): Promise { - if ( - Address.isEqual(call.to, sessionManagerAddress) && - Hex.size(call.data) > 4 && - Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) - ) { + if (isIncrementCall(call, sessionManagerAddress)) { // Can sign increment usage calls return true } @@ -234,11 +230,7 @@ export class Explicit implements ExplicitSessionSigner { ): Promise { const call = payload.calls[callIdx]! let permissionIndex: number - if ( - Address.isEqual(call.to, sessionManagerAddress) && - Hex.size(call.data) > 4 && - Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) - ) { + if (isIncrementCall(call, sessionManagerAddress)) { // Permission check not required. Use the first permission permissionIndex = 0 } else { diff --git a/packages/wallet/core/src/signers/session/session.ts b/packages/wallet/core/src/signers/session/session.ts index 4bcc5bb771..08b2ade814 100644 --- a/packages/wallet/core/src/signers/session/session.ts +++ b/packages/wallet/core/src/signers/session/session.ts @@ -1,5 +1,5 @@ -import { Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider } from 'ox' +import { Constants, Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' +import { AbiFunction, Address, Hex, Provider } from 'ox' export type SessionSignerInvalidReason = | 'Expired' @@ -68,3 +68,11 @@ export function isExplicitSessionSigner(signer: SessionSigner): signer is Explic export function isImplicitSessionSigner(signer: SessionSigner): signer is ImplicitSessionSigner { return 'identitySigner' in signer } + +export function isIncrementCall(call: Payload.Call, sessionManagerAddress: Address.Address): boolean { + return ( + Address.isEqual(call.to, sessionManagerAddress) && + Hex.size(call.data) >= 4 && + Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) + ) +} diff --git a/packages/wallet/core/src/wallet.ts b/packages/wallet/core/src/wallet.ts index 0377dd336b..d89d5b2b98 100644 --- a/packages/wallet/core/src/wallet.ts +++ b/packages/wallet/core/src/wallet.ts @@ -342,7 +342,7 @@ export class Wallet { if (call.delegateCall) { throw new Error('delegate calls are not allowed in safe mode') } - if (Address.isEqual(call.to, this.address)) { + if (Address.isEqual(call.to, this.address) && call.data !== '0x') { throw new Error('calls to the wallet contract itself are not allowed in safe mode') } } @@ -455,7 +455,7 @@ export class Wallet { if (call.delegateCall) { throw new Error('delegate calls are not allowed in safe mode') } - if (Address.isEqual(call.to, this.address)) { + if (Address.isEqual(call.to, this.address) && call.data !== '0x') { throw new Error('calls to the wallet contract itself are not allowed in safe mode') } } diff --git a/packages/wallet/core/test/constants.ts b/packages/wallet/core/test/constants.ts index 63e862f380..42b51d49b7 100644 --- a/packages/wallet/core/test/constants.ts +++ b/packages/wallet/core/test/constants.ts @@ -5,6 +5,8 @@ import { Abi, AbiEvent, Address } from 'ox' const envFile = process.env.CI ? '.env.test' : '.env.test.local' dotenvConfig({ path: envFile }) +// Contracts are deployed on Arbitrum + // Requires https://example.com redirectUrl export const EMITTER_ADDRESS1: Address.Address = '0xad90eB52BC180Bd9f66f50981E196f3E996278D3' // Requires https://another-example.com redirectUrl diff --git a/packages/wallet/core/test/session-manager.test.ts b/packages/wallet/core/test/session-manager.test.ts index 93a69ed89d..7ba342ebc1 100644 --- a/packages/wallet/core/test/session-manager.test.ts +++ b/packages/wallet/core/test/session-manager.test.ts @@ -1,4 +1,4 @@ -import { Extensions } from '@0xsequence/wallet-primitives' +import { Constants, Extensions } from '@0xsequence/wallet-primitives' import { AbiEvent, AbiFunction, Address, Bytes, Hex, Provider, RpcTransport, Secp256k1 } from 'ox' import { describe, expect, it } from 'vitest' import { Attestation, GenericTree, Payload, Permission, SessionConfig } from '../../primitives/src/index.js' @@ -731,7 +731,7 @@ for (const extension of ALL_EXTENSIONS) { ) it( - 'signs a payload using an explicit session', + 'signs a payload using an explicit session with allowAll and value limit', async () => { const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) const chainId = Number(await provider.request({ method: 'eth_chainId' })) @@ -804,7 +804,7 @@ for (const extension of ALL_EXTENSIONS) { ) it( - 'signs a payload using an explicit session', + 'signs using explicit session with onlyOnce, consumes usage and rejects second call', async () => { const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) const chainId = Number(await provider.request({ method: 'eth_chainId' })) @@ -1357,5 +1357,347 @@ for (const extension of ALL_EXTENSIONS) { }, timeout, ) + + it( + 'two explicit sessions with same value limit: exhaust first then second send uses second session (increment from non-increment calls only)', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const targetAddress = randomAddress() + const valueLimit = 500000000000000000n // 0.5 ETH per session + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(targetAddress).allowAll().build()], + } + + const explicitPrivateKey1 = Secp256k1.randomPrivateKey() + const explicitSigner1 = new Signers.Session.Explicit(explicitPrivateKey1, { + ...sessionPermission, + }) + + const explicitPrivateKey2 = Secp256k1.randomPrivateKey() + const explicitSigner2 = new Signers.Session.Explicit(explicitPrivateKey2, { + ...sessionPermission, + }) + + let sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { + ...sessionPermission, + signer: explicitSigner1.address, + }) + sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { + ...sessionPermission, + signer: explicitSigner2.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], + }, + { stateProvider }, + ) + // Fund wallet with 2 ETH so we can send 0.5 ETH twice (each tx needs value + gas) + await provider.request({ + method: 'anvil_setBalance', + params: [wallet.address, Hex.fromNumber(2n * 1000000000000000000n)], + }) + + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner1, explicitSigner2], + }) + + const call: Payload.Call = { + to: targetAddress, + value: valueLimit, // one full limit + data: '0x' as Hex.Hex, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + + // First send: uses first session (exhausts it) + const increment1 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment1).not.toBeNull() + const calls1 = includeIncrement([call], increment1!, extension.sessions) + const tx1 = await buildAndSignCall(wallet, sessionManager, calls1, provider, chainId) + await simulateTransaction(provider, tx1) + + // Second send: same call. First session is exhausted so findSignersForCalls picks second session. + // Payload is [call, increment] (or [increment, call]). signSapient must derive expectedIncrement + // from non-increment calls only so it matches the increment we built for the second session. + const increment2 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment2).not.toBeNull() + const calls2 = includeIncrement([call], increment2!, extension.sessions) + const tx2 = await buildAndSignCall(wallet, sessionManager, calls2, provider, chainId) + await simulateTransaction(provider, tx2) + }, + timeout, + ) + + describe('increment built from non-increment calls only', () => { + it( + 'prepareIncrement returns null when calls contain only an increment call (no non-increment calls)', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + const sessionTopology = SessionConfig.addExplicitSession( + SessionConfig.emptySessionsTopology(identityAddress), + { + ...sessionPermission, + signer: explicitSigner.address, + }, + ) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + // Only an increment call (no non-increment calls). Contract would reject payload with only self-call; we return null. + const incrementOnlyCall: Payload.Call = { + to: extension.sessions, + data: AbiFunction.encodeData(Constants.INCREMENT_USAGE_LIMIT, [[]]), + value: 0n, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + const result = await sessionManager.prepareIncrement(wallet.address, chainId, [incrementOnlyCall]) + expect(result).toBeNull() + }, + timeout, + ) + + it( + 'prepareIncrement([increment, nonIncrementCall]) produces same increment data as prepareIncrement([nonIncrementCall])', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + const sessionTopology = SessionConfig.addExplicitSession( + SessionConfig.emptySessionsTopology(identityAddress), + { + ...sessionPermission, + signer: explicitSigner.address, + }, + ) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + const nonIncrementCall: Payload.Call = { + to: EMITTER_ADDRESS1, + value: 0n, + data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + + const fromNonIncrementOnly = await sessionManager.prepareIncrement(wallet.address, chainId, [ + nonIncrementCall, + ]) + expect(fromNonIncrementOnly).not.toBeNull() + + // Pass [staleIncrement, nonIncrementCall] — increment is ignored when building; result should match + const withStaleIncrement = await sessionManager.prepareIncrement(wallet.address, chainId, [ + fromNonIncrementOnly!, + nonIncrementCall, + ]) + expect(withStaleIncrement).not.toBeNull() + expect(withStaleIncrement!.data).toEqual(fromNonIncrementOnly!.data) + }, + timeout, + ) + + it( + 'payload with implicit and explicit: increment built only from explicit non-increment call', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const redirectUrl = 'https://example.com' + const implicitSigner = await createImplicitSigner(redirectUrl, identityPrivateKey) + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + + // Topology: identity + blacklist (implicit) + explicit session + let sessionTopology = SessionConfig.emptySessionsTopology(identityAddress) + sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { + ...sessionPermission, + signer: explicitSigner.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + implicitSigners: [implicitSigner], + explicitSigners: [explicitSigner], + }) + + // Explicit call only (onlyOnce produces an increment; implicit does not match this target) + const call: Payload.Call = { + to: EMITTER_ADDRESS1, + value: 0n, + data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment).not.toBeNull() + const calls = includeIncrement([call], increment!, extension.sessions) + const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) + await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0]) + }, + timeout, + ) + + it('signSapient handles selector-only self increment call consistently', async () => { + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), { + chainId: 0, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], + }) + + const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { + ...explicitSigner.sessionPermissions, + signer: explicitSigner.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + const payload: Payload.Parented = { + type: 'call', + nonce: 0n, + space: 0n, + calls: [ + { + to: extension.sessions, + data: AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT), + value: 0n, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + }, + ], + parentWallets: [wallet.address], + } + + const signature = await sessionManager.signSapient(wallet.address, 0, payload, imageHash) + expect(signature.type).toBe('sapient') + }) + }) }) } diff --git a/packages/wallet/dapp-client/CHANGELOG.md b/packages/wallet/dapp-client/CHANGELOG.md index fd18e0bf49..2039d0c2d2 100644 --- a/packages/wallet/dapp-client/CHANGELOG.md +++ b/packages/wallet/dapp-client/CHANGELOG.md @@ -1,5 +1,109 @@ # @0xsequence/dapp-client +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-core@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-core@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-core@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-core@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-core@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-core@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/wallet/dapp-client/package.json b/packages/wallet/dapp-client/package.json index 9f24471020..7ed1867e74 100644 --- a/packages/wallet/dapp-client/package.json +++ b/packages/wallet/dapp-client/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/dapp-client", - "version": "3.0.0-beta.19", + "version": "3.0.5", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -27,7 +27,7 @@ "@vitest/coverage-v8": "^4.0.18", "dotenv": "^17.3.1", "fake-indexeddb": "^6.2.5", - "happy-dom": "^20.7.0", + "happy-dom": "^20.8.9", "typescript": "^5.9.3", "vitest": "^4.0.18" }, diff --git a/packages/wallet/primitives/CHANGELOG.md b/packages/wallet/primitives/CHANGELOG.md index 8ecc6df9b2..7e5a4cdbf7 100644 --- a/packages/wallet/primitives/CHANGELOG.md +++ b/packages/wallet/primitives/CHANGELOG.md @@ -1,5 +1,60 @@ # @0xsequence/wallet-primitives +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/wallet/primitives/package.json b/packages/wallet/primitives/package.json index 71fcf20cb5..e0e283de61 100644 --- a/packages/wallet/primitives/package.json +++ b/packages/wallet/primitives/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-primitives", - "version": "3.0.0-beta.19", + "version": "3.0.5", "license": "Apache-2.0", "type": "module", "publishConfig": { diff --git a/packages/wallet/primitives/src/network.ts b/packages/wallet/primitives/src/network.ts index b176db084d..69f40ae4f4 100644 --- a/packages/wallet/primitives/src/network.ts +++ b/packages/wallet/primitives/src/network.ts @@ -128,6 +128,7 @@ export const ChainId = { SOMNIA: 5031, // INCENTIV + INCENTIV: 24101, INCENTIV_TESTNET_V2: 28802, // KATANA @@ -457,7 +458,7 @@ export const ALL: Network[] = [ chainId: ChainId.BASE, type: NetworkType.MAINNET, name: 'base', - title: 'Base (Coinbase)', + title: 'Base', rpcUrl: getRpcUrl('base'), logoUrl: getLogoUrl(ChainId.BASE), blockExplorer: { @@ -1036,6 +1037,27 @@ export const ALL: Network[] = [ }, }, + { + chainId: ChainId.INCENTIV, + type: NetworkType.MAINNET, + name: 'incentiv', + title: 'Incentiv', + rpcUrl: getRpcUrl('incentiv'), + logoUrl: getLogoUrl(ChainId.INCENTIV), + blockExplorer: { + name: 'Incentiv Explorer', + url: 'https://explorer.incentiv.io/', + }, + nativeCurrency: { + symbol: 'CENT', + name: 'CENT', + decimals: 18, + }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, + }, + { chainId: ChainId.INCENTIV_TESTNET_V2, type: NetworkType.TESTNET, @@ -1173,7 +1195,6 @@ export const ALL: Network[] = [ decimals: 18, }, }, - ] function getRpcUrl(networkName: string): string { diff --git a/packages/wallet/wdk/CHANGELOG.md b/packages/wallet/wdk/CHANGELOG.md index 2d4d5eaa56..5101c28c5a 100644 --- a/packages/wallet/wdk/CHANGELOG.md +++ b/packages/wallet/wdk/CHANGELOG.md @@ -1,5 +1,115 @@ # @0xsequence/wallet-wdk +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/identity-instrument@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-core@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/identity-instrument@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-core@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/identity-instrument@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-core@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/identity-instrument@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-core@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/identity-instrument@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-core@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/identity-instrument@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-core@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + ## 3.0.0-beta.19 ### Patch Changes diff --git a/packages/wallet/wdk/package.json b/packages/wallet/wdk/package.json index 44997822ab..2d62c27953 100644 --- a/packages/wallet/wdk/package.json +++ b/packages/wallet/wdk/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-wdk", - "version": "3.0.0-beta.19", + "version": "3.0.5", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -30,7 +30,7 @@ "@vitest/coverage-v8": "^4.0.18", "dotenv": "^17.3.1", "fake-indexeddb": "^6.2.5", - "happy-dom": "^20.7.0", + "happy-dom": "^20.8.9", "typescript": "^5.9.3", "vitest": "^4.0.18" }, diff --git a/packages/wallet/wdk/src/dbs/auth-commitments.ts b/packages/wallet/wdk/src/dbs/auth-commitments.ts index 721d73619c..613d4bd574 100644 --- a/packages/wallet/wdk/src/dbs/auth-commitments.ts +++ b/packages/wallet/wdk/src/dbs/auth-commitments.ts @@ -3,6 +3,11 @@ import { IDBPDatabase, IDBPTransaction } from 'idb' const TABLE_NAME = 'auth-commitments' +export type CommitAuthArgs = + | { type: 'auth'; state?: string } + | { type: 'reauth'; state: string; signer: string } + | { type: 'add-signer'; wallet: string; state?: string } + export type AuthCommitment = { id: string kind: 'google-pkce' | 'apple' | `custom-${string}` @@ -10,9 +15,7 @@ export type AuthCommitment = { verifier?: string challenge?: string target: string - isSignUp: boolean - signer?: string -} +} & ({ type: 'auth' } | { type: 'reauth'; signer: string } | { type: 'add-signer'; wallet: string }) export class AuthCommitments extends Generic { constructor(dbName: string = 'sequence-auth-commitments') { diff --git a/packages/wallet/wdk/src/dbs/index.ts b/packages/wallet/wdk/src/dbs/index.ts index 797997b944..1cf7f30f03 100644 --- a/packages/wallet/wdk/src/dbs/index.ts +++ b/packages/wallet/wdk/src/dbs/index.ts @@ -1,4 +1,4 @@ -export type { AuthCommitment } from './auth-commitments.js' +export type { AuthCommitment, CommitAuthArgs } from './auth-commitments.js' export { AuthCommitments } from './auth-commitments.js' export type { AuthKey } from './auth-keys.js' diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts index f8f86d5afc..308e2809fa 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts @@ -6,6 +6,7 @@ import * as Identity from '@0xsequence/identity-instrument' import { IdentitySigner } from '../../identity/signer.js' import { AuthCodeHandler } from './authcode.js' import type { WdkEnv } from '../../env.js' +import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { constructor( @@ -22,25 +23,30 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys, env) } - public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) { + public async commitAuth(target: string, args: CommitAuthArgs) { let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) - if (signer) { - challenge = challenge.withSigner({ address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) + if (args.type === 'reauth') { + challenge = challenge.withSigner({ address: args.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) - if (!state) { - state = Hex.fromBytes(Bytes.random(32)) - } + const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - await this.commitments.set({ + const base = { id: state, - kind: this.signupKind, + kind: this.signupKind as Db.AuthCommitment['kind'], verifier, challenge: codeChallenge, target, metadata: {}, - isSignUp, - }) + } + + if (args.type === 'reauth') { + await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) + } else if (args.type === 'add-signer') { + await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) + } else { + await this.commitments.set({ ...base, type: 'auth' }) + } const searchParams = this.serializeQuery({ code_challenge: codeChallenge, diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode.ts b/packages/wallet/wdk/src/sequence/handlers/authcode.ts index 8e98745e87..4b5d5922b5 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode.ts @@ -6,7 +6,9 @@ import * as Identity from '@0xsequence/identity-instrument' import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js' import { IdentitySigner } from '../../identity/signer.js' import { IdentityHandler } from './identity.js' +import { Kinds } from '../types/signer.js' import type { NavigationLike, WdkEnv } from '../../env.js' +import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' export class AuthCodeHandler extends IdentityHandler implements Handler { protected redirectUri: string = '' @@ -26,6 +28,11 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { } public get kind() { + if (this.signupKind === 'google-pkce') { + // Keep Google PKCE on the canonical kind so Google signers created before + // canonicalization still resolve as `login-google`. + return Kinds.LoginGoogle + } return 'login-' + this.signupKind } @@ -33,19 +40,23 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { this.redirectUri = redirectUri } - public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) { - if (!state) { - state = Hex.fromBytes(Bytes.random(32)) - } + public async commitAuth(target: string, args: CommitAuthArgs) { + const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - await this.commitments.set({ + const base = { id: state, - kind: this.signupKind, - signer, + kind: this.signupKind as Db.AuthCommitment['kind'], target, metadata: {}, - isSignUp, - }) + } + + if (args.type === 'reauth') { + await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) + } else if (args.type === 'add-signer') { + await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) + } else { + await this.commitments.set({ ...base, type: 'auth' }) + } const searchParams = this.serializeQuery({ client_id: this.audience, @@ -63,7 +74,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { code: string, ): Promise<[IdentitySigner, { [key: string]: string }]> { let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code) - if (commitment.signer) { + if (commitment.type === 'reauth') { challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } await this.nitroCommitVerifier(challenge) @@ -97,7 +108,11 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { message: 'request-redirect', handle: async () => { const navigation = this.getNavigation() - const url = await this.commitAuth(navigation.getPathname(), false, request.id, address) + const url = await this.commitAuth(navigation.getPathname(), { + type: 'reauth', + state: request.id, + signer: address, + }) navigation.redirect(url) return true }, diff --git a/packages/wallet/wdk/src/sequence/handlers/identity.ts b/packages/wallet/wdk/src/sequence/handlers/identity.ts index f03876a5be..a991e88636 100644 --- a/packages/wallet/wdk/src/sequence/handlers/identity.ts +++ b/packages/wallet/wdk/src/sequence/handlers/identity.ts @@ -81,6 +81,10 @@ export class IdentityHandler { return new IdentitySigner(this.nitro, authKey, this.env.crypto) } + protected async clearAuthKeySigner(address: string): Promise { + await this.authKeys.delBySigner(address) + } + private async getAuthKey(signer: string): Promise { let authKey = await this.authKeys.getBySigner(signer) if (!signer && !authKey) { diff --git a/packages/wallet/wdk/src/sequence/handlers/idtoken.ts b/packages/wallet/wdk/src/sequence/handlers/idtoken.ts new file mode 100644 index 0000000000..58d5b5df9f --- /dev/null +++ b/packages/wallet/wdk/src/sequence/handlers/idtoken.ts @@ -0,0 +1,146 @@ +import { Address, Hex } from 'ox' +import { Signers } from '@0xsequence/wallet-core' +import { Handler } from './handler.js' +import * as Identity from '@0xsequence/identity-instrument' +import * as Db from '../../dbs/index.js' +import { Signatures } from '../signatures.js' +import { SignerActionable, SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/signature-request.js' +import { IdentitySigner } from '../../identity/signer.js' +import { IdentityHandler } from './identity.js' +import { Kinds } from '../types/signer.js' +import type { WdkEnv } from '../../env.js' + +type RespondFn = (idToken: string) => Promise + +export type PromptIdTokenHandler = ( + kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, + respond: RespondFn, +) => Promise + +export class IdTokenHandler extends IdentityHandler implements Handler { + private onPromptIdToken: undefined | PromptIdTokenHandler + + constructor( + public readonly signupKind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, + public readonly issuer: string, + public readonly audience: string, + nitro: Identity.IdentityInstrument, + signatures: Signatures, + authKeys: Db.AuthKeys, + env?: WdkEnv, + ) { + super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env) + } + + public get kind() { + if (this.signupKind === 'google-id-token') { + return Kinds.LoginGoogle + } + if (this.signupKind === 'apple-id-token') { + return Kinds.LoginApple + } + return 'login-' + this.signupKind + } + + public registerUI(onPromptIdToken: PromptIdTokenHandler) { + this.onPromptIdToken = onPromptIdToken + return () => { + this.onPromptIdToken = undefined + } + } + + public unregisterUI() { + this.onPromptIdToken = undefined + } + + public async completeAuth(idToken: string): Promise<[IdentitySigner, { [key: string]: string }]> { + const challenge = new Identity.IdTokenChallenge(this.issuer, this.audience, idToken) + await this.nitroCommitVerifier(challenge) + const { signer: identitySigner, email } = await this.nitroCompleteAuth(challenge) + + return [identitySigner, { email }] + } + + public async getSigner(): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { + const onPromptIdToken = this.onPromptIdToken + if (!onPromptIdToken) { + throw new Error('id-token-handler-ui-not-registered') + } + + return await this.handleAuth(onPromptIdToken) + } + + async status( + address: Address.Address, + _imageHash: Hex.Hex | undefined, + request: BaseSignatureRequest, + ): Promise { + const signer = await this.getAuthKeySigner(address) + if (signer) { + return { + address, + handler: this, + status: 'ready', + handle: async () => { + await this.sign(signer, request) + return true + }, + } + } + + const onPromptIdToken = this.onPromptIdToken + if (!onPromptIdToken) { + return { + address, + handler: this, + reason: 'ui-not-registered', + status: 'unavailable', + } + } + + return { + address, + handler: this, + status: 'actionable', + message: 'request-id-token', + handle: async () => { + try { + const { signer } = await this.handleAuth(onPromptIdToken) + const signerAddress = (await signer.address) as Address.Address + if (!Address.isEqual(signerAddress, address)) { + // ID-token auth prompts are keyed by provider kind, not the requested signer address. + // For example, a user can pick a different Google account in the account picker and + // return a token for a different identity than this request expects. + await this.clearAuthKeySigner(signerAddress) + throw new Error('id-token-signer-mismatch') + } + return true + } catch { + return false + } + }, + } + } + + private handleAuth( + onPromptIdToken: PromptIdTokenHandler, + ): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { + const respond: RespondFn = async (idToken) => { + try { + const [signer, metadata] = await this.completeAuth(idToken) + resolve({ signer, email: metadata.email || '' }) + } catch (error) { + reject(error) + } + } + + await onPromptIdToken(this.signupKind, respond) + } catch (error) { + reject(error) + } + }) + } +} diff --git a/packages/wallet/wdk/src/sequence/handlers/index.ts b/packages/wallet/wdk/src/sequence/handlers/index.ts index 0cac943b9e..9c1b1f0f57 100644 --- a/packages/wallet/wdk/src/sequence/handlers/index.ts +++ b/packages/wallet/wdk/src/sequence/handlers/index.ts @@ -3,4 +3,5 @@ export { DevicesHandler } from './devices.js' export { PasskeysHandler } from './passkeys.js' export { OtpHandler } from './otp.js' export { AuthCodePkceHandler } from './authcode-pkce.js' +export { IdTokenHandler } from './idtoken.js' export { MnemonicHandler } from './mnemonic.js' diff --git a/packages/wallet/wdk/src/sequence/index.ts b/packages/wallet/wdk/src/sequence/index.ts index ed2f9e440a..d2afe15a37 100644 --- a/packages/wallet/wdk/src/sequence/index.ts +++ b/packages/wallet/wdk/src/sequence/index.ts @@ -9,12 +9,15 @@ export { Sessions } from './sessions.js' export { Signatures } from './signatures.js' export type { StartSignUpWithRedirectArgs, + StartAddLoginSignerWithRedirectArgs, CommonSignupArgs, PasskeySignupArgs, MnemonicSignupArgs, EmailOtpSignupArgs, CompleteRedirectArgs, SignupArgs, + AddLoginSignerArgs, + RemoveLoginSignerArgs, LoginToWalletArgs, LoginToMnemonicArgs, LoginToPasskeyArgs, diff --git a/packages/wallet/wdk/src/sequence/manager.ts b/packages/wallet/wdk/src/sequence/manager.ts index 5418dd65bb..c7eecf3294 100644 --- a/packages/wallet/wdk/src/sequence/manager.ts +++ b/packages/wallet/wdk/src/sequence/manager.ts @@ -14,6 +14,7 @@ import { AuthCodePkceHandler, DevicesHandler, Handler, + IdTokenHandler, MnemonicHandler, OtpHandler, PasskeysHandler, @@ -31,9 +32,25 @@ import { Wallets, WalletsInterface } from './wallets.js' import { GuardHandler, PromptCodeHandler } from './handlers/guard.js' import { PasskeyCredential } from '../dbs/index.js' import { PromptMnemonicHandler } from './handlers/mnemonic.js' +import { PromptIdTokenHandler } from './handlers/idtoken.js' import { PromptOtpHandler } from './handlers/otp.js' import { defaultPasskeyProvider, type PasskeyProvider } from './passkeys-provider.js' +type CustomIdentityProvider = + | { + kind: `custom-${string}` + authMethod: 'id-token' + issuer: string + clientId: string + } + | { + kind: `custom-${string}` + authMethod: 'authcode' | 'authcode-pkce' + issuer: string + oauthUrl: string + clientId: string + } + export type ManagerOptions = { verbose?: boolean @@ -85,18 +102,14 @@ export type ManagerOptions = { google?: { enabled: boolean clientId: string + authMethod?: 'authcode-pkce' | 'id-token' } apple?: { enabled: boolean clientId: string + authMethod?: 'authcode' | 'id-token' } - customProviders?: { - kind: `custom-${string}` - authMethod: 'id-token' | 'authcode' | 'authcode-pkce' - issuer: string - oauthUrl: string - clientId: string - }[] + customProviders?: CustomIdentityProvider[] } } @@ -112,18 +125,14 @@ export type ResolvedIdentityOptions = { google: { enabled: boolean clientId: string + authMethod: 'authcode-pkce' | 'id-token' } apple: { enabled: boolean clientId: string + authMethod: 'authcode' | 'id-token' } - customProviders?: { - kind: `custom-${string}` - authMethod: 'id-token' | 'authcode' | 'authcode-pkce' - issuer: string - oauthUrl: string - clientId: string - }[] + customProviders?: CustomIdentityProvider[] } export type ResolvedManagerOptions = { @@ -248,10 +257,12 @@ export const ManagerOptionsDefaults = { google: { enabled: false, clientId: '', + authMethod: 'authcode-pkce' as const, }, apple: { enabled: false, clientId: '', + authMethod: 'authcode' as const, }, }, } @@ -677,42 +688,84 @@ export class Manager { shared.handlers.set(Kinds.LoginEmailOtp, this.otpHandler) } if (ops.identity.google?.enabled) { - shared.handlers.set( - Kinds.LoginGooglePkce, - new AuthCodePkceHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - ops.identity.google.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) + if (ops.identity.google.authMethod === 'id-token') { + shared.handlers.set( + Kinds.LoginGoogle, + new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + ops.identity.google.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + } else { + shared.handlers.set( + Kinds.LoginGoogle, + new AuthCodePkceHandler( + 'google-pkce', + 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', + ops.identity.google.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + } } if (ops.identity.apple?.enabled) { - shared.handlers.set( - Kinds.LoginApple, - new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - ops.identity.apple.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) + if (ops.identity.apple.authMethod === 'id-token') { + shared.handlers.set( + Kinds.LoginApple, + new IdTokenHandler( + 'apple-id-token', + 'https://appleid.apple.com', + ops.identity.apple.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + } else { + shared.handlers.set( + Kinds.LoginApple, + new AuthCodeHandler( + 'apple', + 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', + ops.identity.apple.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + } } if (ops.identity.customProviders?.length) { for (const provider of ops.identity.customProviders) { switch (provider.authMethod) { case 'id-token': - throw new Error('id-token is not supported yet') + shared.handlers.set( + provider.kind, + new IdTokenHandler( + provider.kind, + provider.issuer, + provider.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + break case 'authcode': shared.handlers.set( provider.kind, @@ -770,6 +823,20 @@ export class Manager { return this.otpHandler?.registerUI(onPromptOtp) || (() => {}) } + public registerIdTokenUI(onPromptIdToken: PromptIdTokenHandler) { + const unregisters: (() => void)[] = [] + + this.shared.handlers.forEach((handler) => { + if (handler instanceof IdTokenHandler) { + unregisters.push(handler.registerUI(onPromptIdToken)) + } + }) + + return () => { + unregisters.forEach((unregister) => unregister()) + } + } + public registerGuardUI(onPromptCode: PromptCodeHandler) { return this.guardHandler?.registerUI(onPromptCode) || (() => {}) } diff --git a/packages/wallet/wdk/src/sequence/sessions.ts b/packages/wallet/wdk/src/sequence/sessions.ts index 70c2d85ed3..198258279f 100644 --- a/packages/wallet/wdk/src/sequence/sessions.ts +++ b/packages/wallet/wdk/src/sequence/sessions.ts @@ -9,7 +9,9 @@ import { SessionConfig, } from '@0xsequence/wallet-primitives' import { Address, Bytes, Hash, Hex } from 'ox' +import { AuthCodeHandler } from './handlers/authcode.js' import { AuthCodePkceHandler } from './handlers/authcode-pkce.js' +import { IdTokenHandler } from './handlers/idtoken.js' import { IdentityHandler, identityTypeToHex } from './handlers/identity.js' import { Handler } from './handlers/index.js' import { ManagerOptionsDefaults, Shared } from './manager.js' @@ -362,7 +364,11 @@ export class Sessions implements SessionsInterface { let audienceHash: Hex.Hex = '0x' if (handler instanceof IdentityHandler) { identityType = handler.identityType - if (handler instanceof AuthCodePkceHandler) { + if ( + handler instanceof AuthCodeHandler || + handler instanceof AuthCodePkceHandler || + handler instanceof IdTokenHandler + ) { issuerHash = Hash.keccak256(Hex.fromString(handler.issuer)) audienceHash = Hash.keccak256(Hex.fromString(handler.audience)) } diff --git a/packages/wallet/wdk/src/sequence/signers.ts b/packages/wallet/wdk/src/sequence/signers.ts index 64f9ff6690..ecb43cab76 100644 --- a/packages/wallet/wdk/src/sequence/signers.ts +++ b/packages/wallet/wdk/src/sequence/signers.ts @@ -12,6 +12,11 @@ function toKnownKind(kind: string): Kind { return kind as Kind } + if (kind === 'login-google-pkce') { + // Normalize legacy Google PKCE witnesses while the canonical signer kind is `login-google`. + return Kinds.LoginGoogle + } + if (Object.values(Kinds).includes(kind as (typeof Kinds)[keyof typeof Kinds])) { return kind as Kind } diff --git a/packages/wallet/wdk/src/sequence/types/signature-request.ts b/packages/wallet/wdk/src/sequence/types/signature-request.ts index cbce933da8..e3cb1b7875 100644 --- a/packages/wallet/wdk/src/sequence/types/signature-request.ts +++ b/packages/wallet/wdk/src/sequence/types/signature-request.ts @@ -13,6 +13,8 @@ export type ActionToPayload = { [Actions.Recovery]: Payload.Recovery [Actions.AddRecoverySigner]: Payload.ConfigUpdate [Actions.RemoveRecoverySigner]: Payload.ConfigUpdate + [Actions.AddLoginSigner]: Payload.ConfigUpdate + [Actions.RemoveLoginSigner]: Payload.ConfigUpdate [Actions.SessionImplicitAuthorize]: Payload.SessionImplicitAuthorize } @@ -26,6 +28,8 @@ export const Actions = { Recovery: 'recovery', AddRecoverySigner: 'add-recovery-signer', RemoveRecoverySigner: 'remove-recovery-signer', + AddLoginSigner: 'add-login-signer', + RemoveLoginSigner: 'remove-login-signer', SessionImplicitAuthorize: 'session-implicit-authorize', } as const diff --git a/packages/wallet/wdk/src/sequence/types/signer.ts b/packages/wallet/wdk/src/sequence/types/signer.ts index 30a2a50731..eb46db52fd 100644 --- a/packages/wallet/wdk/src/sequence/types/signer.ts +++ b/packages/wallet/wdk/src/sequence/types/signer.ts @@ -5,7 +5,7 @@ export const Kinds = { LoginPasskey: 'login-passkey', LoginMnemonic: 'login-mnemonic', // Todo: do not name it login-mnemonic, just mnemonic LoginEmailOtp: 'login-email-otp', - LoginGooglePkce: 'login-google-pkce', + LoginGoogle: 'login-google', LoginApple: 'login-apple', Recovery: 'recovery-extension', Guard: 'guard-extension', diff --git a/packages/wallet/wdk/src/sequence/types/wallet.ts b/packages/wallet/wdk/src/sequence/types/wallet.ts index dd754a05b6..c1e12a990c 100644 --- a/packages/wallet/wdk/src/sequence/types/wallet.ts +++ b/packages/wallet/wdk/src/sequence/types/wallet.ts @@ -36,7 +36,7 @@ export interface Wallet { /** * A string identifier for the authentication method used for this session. - * Examples: 'login-mnemonic', 'login-passkey', 'login-google-pkce'. + * Examples: 'login-mnemonic', 'login-passkey', 'login-google'. * @property */ loginType: string diff --git a/packages/wallet/wdk/src/sequence/wallets.ts b/packages/wallet/wdk/src/sequence/wallets.ts index 6cc4f94923..5746f7a832 100644 --- a/packages/wallet/wdk/src/sequence/wallets.ts +++ b/packages/wallet/wdk/src/sequence/wallets.ts @@ -3,22 +3,66 @@ import { Config, Constants, Payload } from '@0xsequence/wallet-primitives' import { Address, Hex, Provider, RpcTransport } from 'ox' import { AuthCommitment } from '../dbs/auth-commitments.js' import { AuthCodeHandler } from './handlers/authcode.js' +import { IdTokenHandler } from './handlers/idtoken.js' import { MnemonicHandler } from './handlers/mnemonic.js' import { OtpHandler } from './handlers/otp.js' import { Shared } from './manager.js' import { Device } from './types/device.js' -import { Action, Module } from './types/index.js' +import { Action, Actions, Module } from './types/index.js' import { Kinds, SignerWithKind, WitnessExtraSignerKind } from './types/signer.js' import { Wallet, WalletSelectionUiHandler } from './types/wallet.js' import { PasskeysHandler } from './handlers/passkeys.js' import type { PasskeySigner } from './passkeys-provider.js' +function getSignupHandlerKey(kind: SignupArgs['kind'] | StartSignUpWithRedirectArgs['kind'] | AuthCommitment['kind']) { + if (kind === 'google-pkce') { + return Kinds.LoginGoogle + } + if (kind.startsWith('custom-')) { + return kind + } + return 'login-' + kind +} + +function getSignerKindForSignup(kind: SignupArgs['kind'] | AuthCommitment['kind']) { + if (kind === 'google-id-token' || kind === 'google-pkce') { + return Kinds.LoginGoogle + } + if (kind === 'apple-id-token' || kind === 'apple') { + return Kinds.LoginApple + } + if (kind.startsWith('custom-')) { + return kind + } + return ('login-' + kind) as string +} + +function getIdTokenSignupHandler( + shared: Shared, + kind: typeof Kinds.LoginGoogle | typeof Kinds.LoginApple | `custom-${string}`, +): IdTokenHandler { + const handler = shared.handlers.get(kind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof IdTokenHandler)) { + throw new Error('handler-does-not-support-id-token') + } + return handler +} + export type StartSignUpWithRedirectArgs = { kind: 'google-pkce' | 'apple' | `custom-${string}` target: string metadata: { [key: string]: string } } +export type StartAddLoginSignerWithRedirectArgs = { + wallet: Address.Address + kind: 'google-pkce' | 'apple' | `custom-${string}` + target: string +} + export type SignupStatus = | { type: 'login-signer-created'; address: Address.Address } | { type: 'device-signer-created'; address: Address.Address } @@ -49,6 +93,11 @@ export type EmailOtpSignupArgs = CommonSignupArgs & { email: string } +export type IdTokenSignupArgs = CommonSignupArgs & { + kind: 'google-id-token' | 'apple-id-token' | `custom-${string}` + idToken: string +} + export type CompleteRedirectArgs = CommonSignupArgs & { state: string code: string @@ -62,7 +111,25 @@ export type AuthCodeSignupArgs = CommonSignupArgs & { isRedirect: boolean } -export type SignupArgs = PasskeySignupArgs | MnemonicSignupArgs | EmailOtpSignupArgs | AuthCodeSignupArgs +export type SignupArgs = + | PasskeySignupArgs + | MnemonicSignupArgs + | EmailOtpSignupArgs + | IdTokenSignupArgs + | AuthCodeSignupArgs + +export type AddLoginSignerArgs = { + wallet: Address.Address +} & ( + | { kind: 'mnemonic'; mnemonic: string } + | { kind: 'email-otp'; email: string } + | { kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`; idToken: string } +) + +export type RemoveLoginSignerArgs = { + wallet: Address.Address + signerAddress: Address.Address +} export type LoginToWalletArgs = { wallet: Address.Address @@ -180,6 +247,7 @@ export interface WalletsInterface { * - `kind: 'mnemonic'`: Uses a mnemonic phrase as the login credential. * - `kind: 'passkey'`: Prompts the user to create a WebAuthn passkey. * - `kind: 'email-otp'`: Initiates an OTP flow to the user's email. + * - `kind: 'google-id-token' | 'apple-id-token'`: Completes an OIDC ID token flow when the provider is configured with `authMethod: 'id-token'`. * - `kind: 'google-pkce' | 'apple'`: Completes an OAuth redirect flow. * Common options like `noGuard` or `noRecovery` can customize the wallet's security features. * @returns A promise that resolves to the address of the newly created wallet, or `undefined` if the sign-up was aborted. @@ -243,6 +311,66 @@ export interface WalletsInterface { */ completeLogin(requestId: string): Promise + /** + * Adds a new login signer to an existing wallet, enabling account federation. + * + * This allows a user to link a new login method (e.g., Google, email OTP, mnemonic) to a wallet + * that was originally created with a different credential. After federation, the wallet can be + * discovered and accessed via any of its linked login methods. + * + * @param args The arguments specifying the wallet and the new login credential to add. + * @returns A promise that resolves to a `requestId` for the configuration update signature request. + * @see {completeAddLoginSigner} + */ + addLoginSigner(args: AddLoginSignerArgs): Promise + + /** + * Completes the add-login-signer process after the configuration update has been signed. + * + * @param requestId The ID of the completed signature request returned by `addLoginSigner`. + * @returns A promise that resolves when the configuration update has been submitted. + */ + completeAddLoginSigner(requestId: string): Promise + + /** + * Initiates an add-login-signer process that involves an OAuth redirect. + * + * This is the first step for adding a social login signer (e.g., Google, Apple) to an existing wallet + * via a redirect-based OAuth flow. It validates the wallet, generates the necessary challenges and state, + * stores them locally, and returns a URL. Your application should redirect the user to this URL. + * + * After the redirect callback, call `completeRedirect` with the returned state and code. This will + * create a pending `AddLoginSigner` signature request internally. The caller can then discover + * the pending request through the signatures module and call `completeAddLoginSigner` with the requestId + * once it has been signed. + * + * @param args Arguments specifying the wallet, provider (`kind`), and the `target` URL for the redirect callback. + * @returns A promise that resolves to the full OAuth URL to which the user should be redirected. + * @see {completeRedirect} for the second step of this flow. + * @see {completeAddLoginSigner} to finalize after signing. + */ + startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs): Promise + + /** + * Removes a login signer from an existing wallet, enabling account defederation. + * + * This allows a user to unlink a login method from a wallet. A safety guard ensures + * at least one login signer always remains. + * + * @param args The arguments specifying the wallet and the signer address to remove. + * @returns A promise that resolves to a `requestId` for the configuration update signature request. + * @see {completeRemoveLoginSigner} + */ + removeLoginSigner(args: RemoveLoginSignerArgs): Promise + + /** + * Completes the remove-login-signer process after the configuration update has been signed. + * + * @param requestId The ID of the completed signature request returned by `removeLoginSigner`. + * @returns A promise that resolves when the configuration update has been submitted. + */ + completeRemoveLoginSigner(requestId: string): Promise + /** * Logs out from a given wallet, ending the current session. * @@ -361,7 +489,25 @@ export function isLoginToPasskeyArgs(args: LoginArgs): args is LoginToPasskeyArg } export function isAuthCodeArgs(args: SignupArgs): args is AuthCodeSignupArgs { - return 'kind' in args && (args.kind === 'google-pkce' || args.kind === 'apple') + return 'code' in args && 'commitment' in args +} + +export function isIdTokenArgs(args: SignupArgs): args is IdTokenSignupArgs { + return 'idToken' in args +} + +function addLoginSignerToSignupArgs(args: AddLoginSignerArgs): SignupArgs { + switch (args.kind) { + case 'mnemonic': + return { kind: 'mnemonic', mnemonic: args.mnemonic } + case 'email-otp': + return { kind: 'email-otp', email: args.email } + default: { + // google-id-token, apple-id-token, custom-* + const _args = args as { kind: string; idToken: string } + return { kind: _args.kind as IdTokenSignupArgs['kind'], idToken: _args.idToken } + } + } } function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology { @@ -674,9 +820,28 @@ export class Wallets implements WalletsInterface { } } + case 'google-id-token': + case 'apple-id-token': { + const handler = getIdTokenSignupHandler( + this.shared, + args.kind === 'google-id-token' ? Kinds.LoginGoogle : Kinds.LoginApple, + ) + const [signer, metadata] = await handler.completeAuth(args.idToken) + const loginEmail = metadata.email + this.shared.modules.logger.log('Created new id token signer:', signer.address) + + return { + signer, + extra: { + signerKind: getSignerKindForSignup(args.kind), + }, + loginEmail, + } + } + case 'google-pkce': case 'apple': { - const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler + const handler = this.shared.handlers.get(getSignupHandlerKey(args.kind)) as AuthCodeHandler if (!handler) { throw new Error('handler-not-registered') } @@ -688,7 +853,7 @@ export class Wallets implements WalletsInterface { return { signer, extra: { - signerKind: 'login-' + args.kind, + signerKind: getSignerKindForSignup(args.kind), }, loginEmail, } @@ -696,7 +861,18 @@ export class Wallets implements WalletsInterface { } if (args.kind.startsWith('custom-')) { - // TODO: support other custom auth methods (e.g. id-token) + if (isIdTokenArgs(args)) { + const handler = getIdTokenSignupHandler(this.shared, args.kind) + const [signer, metadata] = await handler.completeAuth(args.idToken) + return { + signer, + extra: { + signerKind: args.kind, + }, + loginEmail: metadata.email, + } + } + const handler = this.shared.handlers.get(args.kind) as AuthCodeHandler if (!handler) { throw new Error('handler-not-registered') @@ -716,12 +892,35 @@ export class Wallets implements WalletsInterface { } async startSignUpWithRedirect(args: StartSignUpWithRedirectArgs) { - const kind = args.kind.startsWith('custom-') ? args.kind : 'login-' + args.kind - const handler = this.shared.handlers.get(kind) as AuthCodeHandler + const kind = getSignupHandlerKey(args.kind) + const handler = this.shared.handlers.get(kind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + return handler.commitAuth(args.target, { type: 'auth' }) + } + + async startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs) { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const kind = getSignupHandlerKey(args.kind) + const handler = this.shared.handlers.get(kind) if (!handler) { throw new Error('handler-not-registered') } - return handler.commitAuth(args.target, true) + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + return handler.commitAuth(args.target, { type: 'add-signer', wallet: args.wallet }) } async completeRedirect(args: CompleteRedirectArgs): Promise { @@ -730,25 +929,62 @@ export class Wallets implements WalletsInterface { throw new Error('invalid-state') } - // commitment.isSignUp and signUp also mean 'signIn' from wallet's perspective - if (commitment.isSignUp) { - await this.signUp({ - kind: commitment.kind, - commitment, - code: args.code, - noGuard: args.noGuard, - target: commitment.target, - isRedirect: true, - use4337: args.use4337, - }) - } else { - const kind = commitment.kind.startsWith('custom-') ? commitment.kind : 'login-' + commitment.kind - const handler = this.shared.handlers.get(kind) as AuthCodeHandler - if (!handler) { - throw new Error('handler-not-registered') + switch (commitment.type) { + case 'add-signer': { + const handlerKind = getSignupHandlerKey(commitment.kind) + const handler = this.shared.handlers.get(handlerKind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + + const walletAddress = commitment.wallet as Address.Address + const walletEntry = await this.get(walletAddress) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const [signer] = await handler.completeAuth(commitment, args.code) + const signerKind = getSignerKindForSignup(commitment.kind) + + await this.addLoginSignerFromPrepared(walletAddress, { + signer, + extra: { signerKind }, + }) + break + } + + case 'auth': { + await this.signUp({ + kind: commitment.kind, + commitment, + code: args.code, + noGuard: args.noGuard, + target: commitment.target, + isRedirect: true, + use4337: args.use4337, + }) + break } - await handler.completeAuth(commitment, args.code) + case 'reauth': { + const handlerKind = getSignupHandlerKey(commitment.kind) + const handler = this.shared.handlers.get(handlerKind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + + await handler.completeAuth(commitment, args.code) + break + } } if (!commitment.target) { @@ -1184,6 +1420,87 @@ export class Wallets implements WalletsInterface { }) } + async addLoginSigner(args: AddLoginSignerArgs): Promise { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const signupArgs = addLoginSignerToSignupArgs(args) + const loginSigner = await this.prepareSignUp(signupArgs) + return this.addLoginSignerFromPrepared(args.wallet, loginSigner) + } + + async completeAddLoginSigner(requestId: string): Promise { + const request = await this.shared.modules.signatures.get(requestId) + if (request.action !== Actions.AddLoginSigner) { + throw new Error('invalid-request-action') + } + await this.completeConfigurationUpdate(requestId) + } + + async removeLoginSigner(args: RemoveLoginSignerArgs): Promise { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const { loginTopology, modules } = await this.getConfigurationParts(args.wallet) + + const existingSigners = Config.getSigners(loginTopology) + const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] + + if (!allExistingAddresses.some((addr) => Address.isEqual(addr, args.signerAddress))) { + throw new Error('signer-not-found') + } + + const remainingMembers = [ + ...existingSigners.signers + .filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, args.signerAddress)) + .map((x) => ({ address: x })), + ...existingSigners.sapientSigners + .filter((x) => !Address.isEqual(x.address, args.signerAddress)) + .map((x) => ({ address: x.address, imageHash: x.imageHash })), + ] + + if (remainingMembers.length < 1) { + throw new Error('cannot-remove-last-login-signer') + } + + const nextLoginTopology = buildCappedTree(remainingMembers) + + if (this.shared.modules.sessions.hasSessionModule(modules)) { + await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, args.signerAddress) + } + + if (this.shared.modules.recovery.hasRecoveryModule(modules)) { + await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, args.signerAddress) + } + + const requestId = await this.requestConfigurationUpdate( + args.wallet, + { loginTopology: nextLoginTopology, modules }, + Actions.RemoveLoginSigner, + 'wallet-webapp', + ) + + return requestId + } + + async completeRemoveLoginSigner(requestId: string): Promise { + const request = await this.shared.modules.signatures.get(requestId) + if (request.action !== Actions.RemoveLoginSigner) { + throw new Error('invalid-request-action') + } + await this.completeConfigurationUpdate(requestId) + } + async logout( wallet: Address.Address, options?: T, @@ -1414,4 +1731,54 @@ export class Wallets implements WalletsInterface { return requestId } + + private async addLoginSignerFromPrepared( + wallet: Address.Address, + loginSigner: { + signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable + extra: WitnessExtraSignerKind + }, + ): Promise { + const newSignerAddress = await loginSigner.signer.address + + const { loginTopology, modules } = await this.getConfigurationParts(wallet) + + // Check for duplicate signer + const existingSigners = Config.getSigners(loginTopology) + const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] + if (allExistingAddresses.some((addr) => Address.isEqual(addr, newSignerAddress))) { + throw new Error('signer-already-exists') + } + + // Build new login topology with the additional signer + const existingMembers = [ + ...existingSigners.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })), + ...existingSigners.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })), + ] + const newMember = { + address: newSignerAddress, + imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined, + } + const nextLoginTopology = buildCappedTree([...existingMembers, newMember]) + + // Add non-sapient login signer to sessions module identity signers + if (!Signers.isSapientSigner(loginSigner.signer) && this.shared.modules.sessions.hasSessionModule(modules)) { + await this.shared.modules.sessions.addIdentitySignerToModules(modules, newSignerAddress) + } + + // Add to recovery module if present + if (this.shared.modules.recovery.hasRecoveryModule(modules)) { + await this.shared.modules.recovery.addRecoverySignerToModules(modules, newSignerAddress) + } + + // Witness so the wallet becomes discoverable via the new credential + await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet, loginSigner.extra) + + return this.requestConfigurationUpdate( + wallet, + { loginTopology: nextLoginTopology, modules }, + Actions.AddLoginSigner, + 'wallet-webapp', + ) + } } diff --git a/packages/wallet/wdk/test/authcode-pkce.test.ts b/packages/wallet/wdk/test/authcode-pkce.test.ts index 99e197c7ff..d9b46bd87b 100644 --- a/packages/wallet/wdk/test/authcode-pkce.test.ts +++ b/packages/wallet/wdk/test/authcode-pkce.test.ts @@ -90,9 +90,8 @@ describe('AuthCodePkceHandler', () => { describe('commitAuth', () => { it('Should create Google PKCE auth commitment and return OAuth URL', async () => { const target = 'https://example.com/success' - const isSignUp = true - const result = await handler.commitAuth(target, isSignUp) + const result = await handler.commitAuth(target, { type: 'auth' }) // Verify nitroCommitVerifier was called with correct challenge expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( @@ -110,7 +109,7 @@ describe('AuthCodePkceHandler', () => { challenge: 'mock-challenge-hash', target, metadata: {}, - isSignUp, + type: 'auth', }) // Verify OAuth URL is constructed correctly @@ -127,10 +126,13 @@ describe('AuthCodePkceHandler', () => { it('Should use provided state instead of generating random one', async () => { const target = 'https://example.com/success' - const isSignUp = false const customState = 'custom-state-123' - const result = await handler.commitAuth(target, isSignUp, customState) + const result = await handler.commitAuth(target, { + type: 'reauth', + state: customState, + signer: '0x1234567890123456789012345678901234567890', + }) // Verify commitment was saved with custom state expect(mockCommitments.set).toHaveBeenCalledWith({ @@ -140,7 +142,8 @@ describe('AuthCodePkceHandler', () => { challenge: 'mock-challenge-hash', target, metadata: {}, - isSignUp, + type: 'reauth', + signer: '0x1234567890123456789012345678901234567890', }) // Verify URL contains custom state @@ -149,10 +152,9 @@ describe('AuthCodePkceHandler', () => { it('Should include signer in challenge when provided', async () => { const target = 'https://example.com/success' - const isSignUp = true const signer = '0x9876543210987654321098765432109876543210' - await handler.commitAuth(target, isSignUp, undefined, signer) + await handler.commitAuth(target, { type: 'reauth', state: 'test-state', signer }) // Verify nitroCommitVerifier was called with signer in challenge expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( @@ -164,9 +166,8 @@ describe('AuthCodePkceHandler', () => { it('Should generate random state when not provided', async () => { const target = 'https://example.com/success' - const isSignUp = true - const result = await handler.commitAuth(target, isSignUp) + const result = await handler.commitAuth(target, { type: 'auth' }) // Verify that a state parameter is present and looks like a hex string expect(result).toMatch(/state=0x[a-f0-9]+/) @@ -181,18 +182,22 @@ describe('AuthCodePkceHandler', () => { const target = 'https://example.com/success' // Test signup - await handler.commitAuth(target, true) + await handler.commitAuth(target, { type: 'auth' }) expect(mockCommitments.set).toHaveBeenLastCalledWith( expect.objectContaining({ - isSignUp: true, + type: 'auth', }), ) // Test login - await handler.commitAuth(target, false) + await handler.commitAuth(target, { + type: 'reauth', + state: 'test-state', + signer: '0x1234567890123456789012345678901234567890', + }) expect(mockCommitments.set).toHaveBeenLastCalledWith( expect.objectContaining({ - isSignUp: false, + type: 'reauth', }), ) }) @@ -200,13 +205,17 @@ describe('AuthCodePkceHandler', () => { it('Should handle errors from nitroCommitVerifier', async () => { vi.spyOn(handler as any, 'nitroCommitVerifier').mockRejectedValue(new Error('Nitro service unavailable')) - await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Nitro service unavailable') + await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( + 'Nitro service unavailable', + ) }) it('Should handle database errors during commitment storage', async () => { vi.mocked(mockCommitments.set).mockRejectedValue(new Error('Database write failed')) - await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Database write failed') + await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( + 'Database write failed', + ) }) }) @@ -221,7 +230,7 @@ describe('AuthCodePkceHandler', () => { challenge: 'test-challenge-hash', target: 'https://example.com/success', metadata: { scope: 'openid profile email' }, - isSignUp: true, + type: 'auth', } }) @@ -326,14 +335,14 @@ describe('AuthCodePkceHandler', () => { describe('Integration and Edge Cases', () => { it('Should have correct kind property', () => { - expect(handler.kind).toBe('login-google-pkce') + expect(handler.kind).toBe('login-google') }) it('Should handle redirect URI configuration', () => { const newRedirectUri = 'https://newdomain.com/callback' handler.setRedirectUri(newRedirectUri) - return handler.commitAuth('https://example.com/success', true).then((result) => { + return handler.commitAuth('https://example.com/success', { type: 'auth' }).then((result) => { expect(result).toContain(`redirect_uri=${encodeURIComponent(newRedirectUri)}`) }) }) diff --git a/packages/wallet/wdk/test/authcode.test.ts b/packages/wallet/wdk/test/authcode.test.ts index a1c3be0e8e..f673f8d42e 100644 --- a/packages/wallet/wdk/test/authcode.test.ts +++ b/packages/wallet/wdk/test/authcode.test.ts @@ -113,7 +113,7 @@ describe('AuthCodeHandler', () => { kind: 'google-pkce', metadata: {}, target: '/test-target', - isSignUp: false, + type: 'reauth', signer: testWallet, } @@ -188,7 +188,7 @@ describe('AuthCodeHandler', () => { // === KIND GETTER === describe('kind getter', () => { - it('Should return login-google-pkce for Google PKCE handler', () => { + it('Should return login-google for Google PKCE handler', () => { const googleHandler = new AuthCodeHandler( 'google-pkce', 'https://accounts.google.com', @@ -200,7 +200,7 @@ describe('AuthCodeHandler', () => { mockAuthKeys, ) - expect(googleHandler.kind).toBe('login-google-pkce') + expect(googleHandler.kind).toBe('login-google') }) it('Should return login-apple for Apple handler', () => { @@ -247,20 +247,17 @@ describe('AuthCodeHandler', () => { it('Should create auth commitment and return OAuth URL', async () => { const target = '/test-target' - const isSignUp = true - const signer = testWallet - const result = await authCodeHandler.commitAuth(target, isSignUp, undefined, signer) + const result = await authCodeHandler.commitAuth(target, { type: 'auth' }) // Verify commitment was saved expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce() const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.kind).toBe('google-pkce') - expect(commitmentCall.signer).toBe(signer) expect(commitmentCall.target).toBe(target) expect(commitmentCall.metadata).toEqual({}) - expect(commitmentCall.isSignUp).toBe(isSignUp) + expect(commitmentCall.type).toBe('auth') expect(commitmentCall.id).toBeDefined() expect(typeof commitmentCall.id).toBe('string') @@ -276,7 +273,11 @@ describe('AuthCodeHandler', () => { it('Should use provided state parameter', async () => { const customState = 'custom-state-123' - const result = await authCodeHandler.commitAuth('/target', false, customState) + const result = await authCodeHandler.commitAuth('/target', { + type: 'reauth', + state: customState, + signer: testWallet, + }) // Verify commitment uses custom state const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! @@ -285,7 +286,7 @@ describe('AuthCodeHandler', () => { }) it('Should generate random state when not provided', async () => { - await authCodeHandler.commitAuth('/target', false) + await authCodeHandler.commitAuth('/target', { type: 'auth' }) const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.id).toBeDefined() expect(typeof commitmentCall.id).toBe('string') @@ -306,7 +307,7 @@ describe('AuthCodeHandler', () => { ) appleHandler.setRedirectUri('https://example.com/callback') - const result = await appleHandler.commitAuth('/target', false) + const result = await appleHandler.commitAuth('/target', { type: 'auth' }) expect(result).toContain('https://appleid.apple.com/auth/authorize?') expect(result).toContain('client_id=apple-client-id') @@ -315,10 +316,10 @@ describe('AuthCodeHandler', () => { }) it('Should create commitment without signer', async () => { - await authCodeHandler.commitAuth('/target', true) + await authCodeHandler.commitAuth('/target', { type: 'auth' }) const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.signer).toBeUndefined() - expect(commitmentCall.isSignUp).toBe(true) + expect(commitmentCall.type).toBe('auth') }) }) @@ -492,7 +493,7 @@ describe('AuthCodeHandler', () => { const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.target).toBe(window.location.pathname) - expect(commitmentCall.isSignUp).toBe(false) + expect(commitmentCall.type).toBe('reauth') expect(commitmentCall.signer).toBe(testWallet) }) }) @@ -654,7 +655,7 @@ describe('AuthCodeHandler', () => { it('Should handle auth commitments database errors', async () => { mockAuthCommitmentsSet.mockRejectedValueOnce(new Error('Database error')) - await expect(authCodeHandler.commitAuth('/target', false)).rejects.toThrow('Database error') + await expect(authCodeHandler.commitAuth('/target', { type: 'auth' })).rejects.toThrow('Database error') }) it('Should handle auth keys database errors', async () => { @@ -671,7 +672,11 @@ describe('AuthCodeHandler', () => { authCodeHandler.setRedirectUri('https://example.com/callback') // Step 1: Commit auth - const commitUrl = await authCodeHandler.commitAuth('/test-target', false, 'test-state', testWallet) + const commitUrl = await authCodeHandler.commitAuth('/test-target', { + type: 'reauth', + state: 'test-state', + signer: testWallet, + }) expect(commitUrl).toContain('state=test-state') expect(mockAuthCommitmentsSet).toHaveBeenCalledWith( @@ -679,7 +684,7 @@ describe('AuthCodeHandler', () => { id: 'test-state', kind: 'google-pkce', target: '/test-target', - isSignUp: false, + type: 'reauth', signer: testWallet, }), ) @@ -709,17 +714,17 @@ describe('AuthCodeHandler', () => { authCodeHandler.setRedirectUri('https://example.com/callback') // Test signup flow - await authCodeHandler.commitAuth('/signup-target', true, 'signup-state') + await authCodeHandler.commitAuth('/signup-target', { type: 'auth', state: 'signup-state' }) const signupCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(signupCall.isSignUp).toBe(true) + expect(signupCall.type).toBe('auth') expect(signupCall.target).toBe('/signup-target') // Test login flow - await authCodeHandler.commitAuth('/login-target', false, 'login-state') + await authCodeHandler.commitAuth('/login-target', { type: 'reauth', state: 'login-state', signer: testWallet }) const loginCall = mockAuthCommitmentsSet.mock.calls[1]![0]! - expect(loginCall.isSignUp).toBe(false) + expect(loginCall.type).toBe('reauth') expect(loginCall.target).toBe('/login-target') }) }) diff --git a/packages/wallet/wdk/test/identity-auth-dbs.test.ts b/packages/wallet/wdk/test/identity-auth-dbs.test.ts index eccc8b885d..bba408ef36 100644 --- a/packages/wallet/wdk/test/identity-auth-dbs.test.ts +++ b/packages/wallet/wdk/test/identity-auth-dbs.test.ts @@ -34,7 +34,7 @@ describe('Identity Authentication Databases', () => { verifier: 'test-verifier-code', challenge: 'test-challenge-hash', target: 'test-target-url', - isSignUp: true, + type: 'reauth', signer: '0x1234567890123456789012345678901234567890', } @@ -66,7 +66,7 @@ describe('Identity Authentication Databases', () => { response_mode: 'form_post', }, target: 'apple-redirect-url', - isSignUp: false, + type: 'auth', } await authCommitmentsDb.set(appleCommitment) @@ -74,7 +74,7 @@ describe('Identity Authentication Databases', () => { expect(retrieved).toBeDefined() expect(retrieved!.kind).toBe('apple') - expect(retrieved!.isSignUp).toBe(false) + expect(retrieved!.type).toBe('auth') expect(retrieved!.metadata.response_type).toBe('code id_token') }) @@ -85,21 +85,22 @@ describe('Identity Authentication Databases', () => { kind: 'google-pkce', metadata: {}, target: 'target-1', - isSignUp: true, + type: 'auth', }, { id: 'commit-2', kind: 'apple', metadata: {}, target: 'target-2', - isSignUp: false, + type: 'reauth', + signer: '0x1234567890123456789012345678901234567890', }, { id: 'commit-3', kind: 'google-pkce', metadata: {}, target: 'target-3', - isSignUp: true, + type: 'auth', }, ] @@ -129,7 +130,7 @@ describe('Identity Authentication Databases', () => { kind: 'google-pkce', metadata: {}, target: 'init-target', - isSignUp: true, + type: 'auth', } await freshDb.set(testCommitment) @@ -351,9 +352,84 @@ describe('Identity Authentication Databases', () => { }, }) - // Verify that Google handler is registered and uses our databases + // Verify that Google is registered under the canonical signer kind while + // still using the PKCE flow by default. const handlers = (manager as any).shared.handlers - expect(handlers.has('login-google-pkce')).toBe(true) + expect(handlers.has('login-google')).toBe(true) + expect(handlers.has('login-google-pkce')).toBe(false) + }) + + it('Should register the Google ID token handler when configured explicitly', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-google-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('login-google-id-token')).toBe(false) + expect(handlers.has('login-google')).toBe(true) + expect(handlers.has('login-google-pkce')).toBe(false) + }) + + it('Should register the Apple ID token handler when configured explicitly', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-apple-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('login-apple-id-token')).toBe(false) + expect(handlers.has('login-apple')).toBe(true) }) it('Should use auth databases when email authentication is enabled', async () => { @@ -424,5 +500,50 @@ describe('Identity Authentication Databases', () => { const handlers = (manager as any).shared.handlers expect(handlers.has('login-apple')).toBe(true) }) + + it('Should register custom ID token providers without enabling redirect flow for them', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-custom-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + customProviders: [ + { + kind: 'custom-google-native', + authMethod: 'id-token', + issuer: 'https://accounts.google.com', + clientId: 'test-google-client-id', + }, + ], + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('custom-google-native')).toBe(true) + await expect( + manager.wallets.startSignUpWithRedirect({ + kind: 'custom-google-native', + target: '/home', + metadata: {}, + }), + ).rejects.toThrow('handler-does-not-support-redirect') + }) }) }) diff --git a/packages/wallet/wdk/test/identity-signer.test.ts b/packages/wallet/wdk/test/identity-signer.test.ts index 29403b028c..526177aa8e 100644 --- a/packages/wallet/wdk/test/identity-signer.test.ts +++ b/packages/wallet/wdk/test/identity-signer.test.ts @@ -13,7 +13,7 @@ const mockCryptoSubtle = { exportKey: vi.fn(), } -Object.defineProperty(global, 'window', { +Object.defineProperty(globalThis, 'window', { value: { crypto: { subtle: mockCryptoSubtle, diff --git a/packages/wallet/wdk/test/idtoken.test.ts b/packages/wallet/wdk/test/idtoken.test.ts new file mode 100644 index 0000000000..7badcb89a3 --- /dev/null +++ b/packages/wallet/wdk/test/idtoken.test.ts @@ -0,0 +1,343 @@ +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' +import { Address, Hex } from 'ox' +import { Network, Payload } from '@0xsequence/wallet-primitives' +import { IdentityInstrument, IdentityType } from '@0xsequence/identity-instrument' +import { IdTokenHandler, PromptIdTokenHandler } from '../src/sequence/handlers/idtoken.js' +import { Signatures } from '../src/sequence/signatures.js' +import * as Db from '../src/dbs/index.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' +import { Kinds } from '../src/sequence/types/signer.js' + +describe('IdTokenHandler', () => { + let idTokenHandler: IdTokenHandler + let mockNitroInstrument: IdentityInstrument + let mockSignatures: Signatures + let mockAuthKeys: Db.AuthKeys + let mockIdentitySigner: IdentitySigner + let testWallet: Address.Address + let testRequest: BaseSignatureRequest + let mockPromptIdToken: Mock + + beforeEach(() => { + vi.clearAllMocks() + + testWallet = '0x1234567890123456789012345678901234567890' as Address.Address + + mockNitroInstrument = { + commitVerifier: vi.fn(), + completeAuth: vi.fn(), + } as unknown as IdentityInstrument + + mockSignatures = { + addSignature: vi.fn(), + } as unknown as Signatures + + mockAuthKeys = { + set: vi.fn(), + get: vi.fn(), + del: vi.fn(), + delBySigner: vi.fn(), + getBySigner: vi.fn(), + addListener: vi.fn(), + } as unknown as Db.AuthKeys + + mockIdentitySigner = { + address: testWallet, + sign: vi.fn(), + } as unknown as IdentitySigner + + testRequest = { + id: 'test-request-id', + envelope: { + wallet: testWallet, + chainId: Network.ChainId.ARBITRUM, + payload: Payload.fromMessage(Hex.fromString('Test message')), + }, + } as BaseSignatureRequest + + mockPromptIdToken = vi.fn() + + idTokenHandler = new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + 'test-google-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + vi.spyOn(idTokenHandler as any, 'nitroCommitVerifier').mockResolvedValue({ + verifier: 'unused-verifier', + loginHint: '', + challenge: '', + }) + + vi.spyOn(idTokenHandler as any, 'nitroCompleteAuth').mockResolvedValue({ + signer: mockIdentitySigner, + email: 'user@example.com', + }) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + describe('Constructor', () => { + it('Should create IdTokenHandler with correct properties', () => { + const handler = new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + 'test-google-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + expect(handler.signupKind).toBe('google-id-token') + expect(handler.issuer).toBe('https://accounts.google.com') + expect(handler.audience).toBe('test-google-client-id') + expect(handler.identityType).toBe(IdentityType.OIDC) + expect(handler.kind).toBe(Kinds.LoginGoogle) + }) + + it('Should normalize apple-id-token handlers to login-apple', () => { + const handler = new IdTokenHandler( + 'apple-id-token', + 'https://appleid.apple.com', + 'test-apple-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + expect(handler.signupKind).toBe('apple-id-token') + expect(handler.issuer).toBe('https://appleid.apple.com') + expect(handler.audience).toBe('test-apple-client-id') + expect(handler.kind).toBe(Kinds.LoginApple) + }) + + it('Should initialize without a registered UI callback', () => { + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + }) + + describe('UI Registration', () => { + it('Should register ID token UI callback', () => { + const unregister = idTokenHandler.registerUI(mockPromptIdToken) + + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + expect(typeof unregister).toBe('function') + }) + + it('Should unregister UI callback when returned function is called', () => { + const unregister = idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + unregister() + + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + + it('Should unregister UI callback directly', () => { + idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + idTokenHandler.unregisterUI() + + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + + it('Should allow multiple registrations by overwriting the previous callback', () => { + const secondCallback = vi.fn() + + idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + idTokenHandler.registerUI(secondCallback) + + expect(idTokenHandler['onPromptIdToken']).toBe(secondCallback) + }) + }) + + describe('completeAuth()', () => { + it('Should complete auth using an OIDC ID token challenge', async () => { + const idToken = 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.' + + const [signer, metadata] = await idTokenHandler.completeAuth(idToken) + + expect(idTokenHandler['nitroCommitVerifier']).toHaveBeenCalledWith( + expect.objectContaining({ + issuer: 'https://accounts.google.com', + audience: 'test-google-client-id', + idToken, + }), + ) + expect(idTokenHandler['nitroCompleteAuth']).toHaveBeenCalledWith( + expect.objectContaining({ + issuer: 'https://accounts.google.com', + audience: 'test-google-client-id', + idToken, + }), + ) + expect(signer).toBe(mockIdentitySigner) + expect(metadata).toEqual({ email: 'user@example.com' }) + }) + }) + + describe('getSigner()', () => { + it('Should throw when UI is not registered', async () => { + await expect(idTokenHandler.getSigner()).rejects.toThrow('id-token-handler-ui-not-registered') + }) + + it('Should acquire a signer by prompting for a fresh ID token', async () => { + const idToken = 'header.payload.signature' + const completeAuthSpy = vi + .spyOn(idTokenHandler, 'completeAuth') + .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) + + mockPromptIdToken.mockImplementation(async (kind, respond) => { + expect(kind).toBe('google-id-token') + await respond(idToken) + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const result = await idTokenHandler.getSigner() + + expect(result).toEqual({ + signer: mockIdentitySigner, + email: 'user@example.com', + }) + expect(completeAuthSpy).toHaveBeenCalledWith(idToken) + expect(mockPromptIdToken).toHaveBeenCalledWith('google-id-token', expect.any(Function)) + }) + + it('Should surface authentication failures from completeAuth', async () => { + const error = new Error('Authentication failed') + vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(error) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + await expect(idTokenHandler.getSigner()).rejects.toThrow('Authentication failed') + }) + + it('Should surface UI callback errors', async () => { + mockPromptIdToken.mockRejectedValue(new Error('UI callback failed')) + idTokenHandler.registerUI(mockPromptIdToken) + + await expect(idTokenHandler.getSigner()).rejects.toThrow('UI callback failed') + }) + }) + + describe('status()', () => { + it('Should return ready status when an auth key signer is available', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status.status).toBe('ready') + expect(status.handler).toBe(idTokenHandler) + }) + + it('Should sign the request when ready handle is invoked', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) + const signSpy = vi.spyOn(idTokenHandler as any, 'sign').mockResolvedValue(undefined) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(true) + expect(signSpy).toHaveBeenCalledWith(mockIdentitySigner, testRequest) + }) + + it('Should return unavailable when no auth key signer exists and UI is not registered', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status).toMatchObject({ + address: testWallet, + handler: idTokenHandler, + status: 'unavailable', + reason: 'ui-not-registered', + }) + }) + + it('Should return actionable when no auth key signer exists and UI is registered', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status.status).toBe('actionable') + expect(status.address).toBe(testWallet) + expect(status.handler).toBe(idTokenHandler) + expect((status as any).message).toBe('request-id-token') + expect(typeof (status as any).handle).toBe('function') + }) + + it('Should reacquire the signer when actionable handle is invoked', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + const completeAuthSpy = vi + .spyOn(idTokenHandler, 'completeAuth') + .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(true) + expect(completeAuthSpy).toHaveBeenCalledWith('header.payload.signature') + }) + + it('Should return false when actionable handle authentication fails', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(new Error('Authentication failed')) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(false) + }) + + it('Should return false when actionable handle authenticates the wrong signer', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + const wrongSigner = '0x9999999999999999999999999999999999999999' as Address.Address + vi.spyOn(idTokenHandler, 'completeAuth').mockResolvedValue([ + { + ...mockIdentitySigner, + address: wrongSigner, + } as unknown as IdentitySigner, + { email: 'other-user@example.com' }, + ]) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(false) + expect(mockAuthKeys.delBySigner).toHaveBeenCalledWith(wrongSigner) + }) + }) +}) diff --git a/packages/wallet/wdk/test/sessions-idtoken.test.ts b/packages/wallet/wdk/test/sessions-idtoken.test.ts new file mode 100644 index 0000000000..7c080af5c2 --- /dev/null +++ b/packages/wallet/wdk/test/sessions-idtoken.test.ts @@ -0,0 +1,98 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { Hash, Hex, Mnemonic, Secp256k1, Address as OxAddress } from 'ox' +import { Payload } from '@0xsequence/wallet-primitives' +import { newManager } from './constants.js' +import { Manager } from '../src/sequence/index.js' +import { Kinds } from '../src/sequence/types/signer.js' + +describe('Sessions ID token attestation', () => { + let manager: Manager | undefined + + afterEach(async () => { + await manager?.stop() + }) + + it('Should include issuer and audience hashes for google-id-token implicit session authorization', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const signersModule = (manager as any).shared.modules.signers + vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginGoogle) + + const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { + target: 'https://example.com', + applicationData: '0x1234', + }) + + const request = await manager.signatures.get(requestId) + expect(request.action).toBe('session-implicit-authorize') + expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) + + if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { + throw new Error('Expected session implicit authorize payload') + } + + const attestation = request.envelope.payload.attestation + expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://accounts.google.com'))) + expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-google-client-id'))) + expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') + expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') + }) + + it('Should include issuer and audience hashes for apple implicit session authorization', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const signersModule = (manager as any).shared.modules.signers + vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginApple) + + const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { + target: 'https://example.com', + applicationData: '0x1234', + }) + + const request = await manager.signatures.get(requestId) + expect(request.action).toBe('session-implicit-authorize') + expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) + + if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { + throw new Error('Expected session implicit authorize payload') + } + + const attestation = request.envelope.payload.attestation + expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://appleid.apple.com'))) + expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-apple-client-id'))) + expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') + expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') + }) +}) diff --git a/packages/wallet/wdk/test/signers-kindof.test.ts b/packages/wallet/wdk/test/signers-kindof.test.ts index 4e5f83aad5..06b01cb780 100644 --- a/packages/wallet/wdk/test/signers-kindof.test.ts +++ b/packages/wallet/wdk/test/signers-kindof.test.ts @@ -37,4 +37,26 @@ describe('Signers.kindOf', () => { await signers.kindOf(wallet, '0x2222222222222222222222222222222222222222') expect(getWitnessFor).toHaveBeenCalledTimes(1) }) + + it('normalizes legacy Google PKCE signer kind to the canonical Google signer kind', async () => { + const getWitnessFor = vi.fn().mockResolvedValue({ + payload: { + type: 'message', + message: '0x' + Buffer.from(JSON.stringify({ signerKind: 'login-google-pkce' }), 'utf8').toString('hex'), + }, + }) + + const manager = newManager({ + stateProvider: { + getWitnessFor, + getWitnessForSapient: vi.fn(), + } as any, + }) + + const signers = (manager as any).shared.modules.signers + const wallet = '0x1111111111111111111111111111111111111111' + const signer = '0x2222222222222222222222222222222222222222' + + await expect(signers.kindOf(wallet, signer)).resolves.toBe(Kinds.LoginGoogle) + }) }) diff --git a/packages/wallet/wdk/test/transactions.test.ts b/packages/wallet/wdk/test/transactions.test.ts index cf31c973c4..295d00d014 100644 --- a/packages/wallet/wdk/test/transactions.test.ts +++ b/packages/wallet/wdk/test/transactions.test.ts @@ -481,12 +481,31 @@ describe('Transactions', () => { const txId1 = manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ { to: wallet!, + data: '0x1234', }, ]) await expect(txId1).rejects.toThrow() }) + it('Should allow native token transfer to self in safe mode', async () => { + const manager = newManager() + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + + const txId1 = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ + { + to: wallet!, + value: 1n, + }, + ]) + + expect(txId1).toBeDefined() + }) + it('Should allow transactions to self in unsafe mode', async () => { const manager = newManager() const wallet = await manager.wallets.signUp({ diff --git a/packages/wallet/wdk/test/wallets.test.ts b/packages/wallet/wdk/test/wallets.test.ts index a5b26843c0..d686ac257d 100644 --- a/packages/wallet/wdk/test/wallets.test.ts +++ b/packages/wallet/wdk/test/wallets.test.ts @@ -1,8 +1,13 @@ -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { Manager, SignerActionable, SignerReady } from '../src/sequence/index.js' import { Mnemonic, Address } from 'ox' import { newManager } from './constants.js' import { Config, Constants, Network } from '@0xsequence/wallet-primitives' +import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce.js' +import { IdTokenHandler } from '../src/sequence/handlers/idtoken.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { MnemonicHandler } from '../src/sequence/handlers/mnemonic.js' +import { Kinds } from '../src/sequence/types/signer.js' describe('Wallets', () => { let manager: Manager | undefined @@ -24,6 +29,202 @@ describe('Wallets', () => { await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() }) + it('Should create a new wallet using google-id-token when Google ID token auth is enabled', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler + const loginMnemonic = Mnemonic.random(Mnemonic.english) + const loginSigner = MnemonicHandler.toSigner(loginMnemonic) + if (!loginSigner) { + throw new Error('Failed to create login signer for test') + } + + const completeAuthSpy = vi + .spyOn(handler, 'completeAuth') + .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'google-user@example.com' }]) + + const wallet = await manager.wallets.signUp({ + kind: 'google-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }) + + expect(wallet).toBeDefined() + expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') + await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() + + const walletEntry = await manager.wallets.get(wallet!) + expect(walletEntry).toBeDefined() + expect(walletEntry!.loginType).toBe(Kinds.LoginGoogle) + expect(walletEntry!.loginEmail).toBe('google-user@example.com') + + const configuration = await manager.wallets.getConfiguration(wallet!) + expect(configuration.login).toHaveLength(1) + expect(configuration.login[0]!.kind).toBe(Kinds.LoginGoogle) + }) + + it('Should create a new wallet using apple-id-token when Apple ID token auth is enabled', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginApple) as IdTokenHandler + const loginMnemonic = Mnemonic.random(Mnemonic.english) + const loginSigner = MnemonicHandler.toSigner(loginMnemonic) + if (!loginSigner) { + throw new Error('Failed to create login signer for test') + } + + const completeAuthSpy = vi + .spyOn(handler, 'completeAuth') + .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'apple-user@example.com' }]) + + const wallet = await manager.wallets.signUp({ + kind: 'apple-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }) + + expect(wallet).toBeDefined() + expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') + await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() + + const walletEntry = await manager.wallets.get(wallet!) + expect(walletEntry).toBeDefined() + expect(walletEntry!.loginType).toBe(Kinds.LoginApple) + expect(walletEntry!.loginEmail).toBe('apple-user@example.com') + + const configuration = await manager.wallets.getConfiguration(wallet!) + expect(configuration.login).toHaveLength(1) + expect(configuration.login[0]!.kind).toBe(Kinds.LoginApple) + }) + + it('Should register and unregister Google ID token UI callbacks through the manager', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler + const promptIdToken = vi.fn() + + const unregister = manager.registerIdTokenUI(promptIdToken) + + expect(handler['onPromptIdToken']).toBe(promptIdToken) + + unregister() + + expect(handler['onPromptIdToken']).toBeUndefined() + }) + + it('Should keep Google PKCE redirect flow as the default when authMethod is not specified', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as AuthCodePkceHandler + expect(handler).toBeInstanceOf(AuthCodePkceHandler) + + const commitAuthSpy = vi + .spyOn(handler, 'commitAuth') + .mockResolvedValue('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') + + const url = await manager.wallets.startSignUpWithRedirect({ + kind: 'google-pkce', + target: '/auth/return', + metadata: {}, + }) + + expect(url).toBe('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') + expect(commitAuthSpy).toHaveBeenCalledWith('/auth/return', { type: 'auth' }) + }) + + it('Should reject google-id-token signup when Google is configured for redirect auth', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'google-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + + it('Should reject apple-id-token signup when Apple is configured for redirect auth', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + }, + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'apple-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + + it('Should reject custom ID token signup when the provider uses redirect auth', async () => { + manager = newManager({ + identity: { + customProviders: [ + { + kind: 'custom-oidc', + authMethod: 'authcode', + issuer: 'https://issuer.example.com', + oauthUrl: 'https://issuer.example.com/oauth/authorize', + clientId: 'test-custom-client-id', + }, + ], + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'custom-oidc', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + it('Should get a specific wallet by address', async () => { manager = newManager() const mnemonic = Mnemonic.random(Mnemonic.english) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3a0e98430..54ad63e327 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: workspace:^ version: link:../../repo/ui next: - specifier: ^15.5.10 - version: 15.5.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^15.5.14 + version: 15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: ^19.2.3 version: 19.2.3 @@ -76,8 +76,8 @@ importers: specifier: workspace:^ version: link:../../repo/ui next: - specifier: ^15.5.10 - version: 15.5.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^15.5.14 + version: 15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: ^19.2.3 version: 19.2.3 @@ -157,7 +157,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/services/identity-instrument: dependencies: @@ -185,7 +185,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/services/indexer: devDependencies: @@ -261,7 +261,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/services/userdata: devDependencies: @@ -325,7 +325,7 @@ importers: version: 25.3.0 '@vitest/coverage-v8': specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0)) + version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) dotenv: specifier: ^17.3.1 version: 17.3.1 @@ -337,7 +337,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/wallet/dapp-client: dependencies: @@ -368,7 +368,7 @@ importers: version: 25.3.0 '@vitest/coverage-v8': specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0)) + version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) dotenv: specifier: ^17.3.1 version: 17.3.1 @@ -376,14 +376,14 @@ importers: specifier: ^6.2.5 version: 6.2.5 happy-dom: - specifier: ^20.7.0 - version: 20.7.0 + specifier: ^20.8.9 + version: 20.8.9 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/wallet/primitives: dependencies: @@ -399,13 +399,13 @@ importers: version: link:../../../repo/typescript-config '@vitest/coverage-v8': specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0)) + version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) packages/wallet/primitives-cli: dependencies: @@ -488,7 +488,7 @@ importers: version: 25.3.0 '@vitest/coverage-v8': specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0)) + version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) dotenv: specifier: ^17.3.1 version: 17.3.1 @@ -496,14 +496,14 @@ importers: specifier: ^6.2.5 version: 6.2.5 happy-dom: - specifier: ^20.7.0 - version: 20.7.0 + specifier: ^20.8.9 + version: 20.8.9 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) repo/eslint-config: devDependencies: @@ -530,7 +530,7 @@ importers: version: 7.0.1(eslint@9.39.2) eslint-plugin-turbo: specifier: ^2.6.3 - version: 2.6.3(eslint@9.39.2)(turbo@2.8.10) + version: 2.6.3(eslint@9.39.2)(turbo@2.8.21) globals: specifier: ^16.5.0 version: 16.5.0 @@ -720,8 +720,8 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} @@ -933,8 +933,8 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} '@img/sharp-darwin-arm64@0.34.5': @@ -1104,56 +1104,56 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@next/env@15.5.10': - resolution: {integrity: sha512-plg+9A/KoZcTS26fe15LHg+QxReTazrIOoKKUC3Uz4leGGeNPgLHdevVraAAOX0snnUs3WkRx3eUQpj9mreG6A==} + '@next/env@15.5.14': + resolution: {integrity: sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==} '@next/eslint-plugin-next@15.5.9': resolution: {integrity: sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==} - '@next/swc-darwin-arm64@15.5.7': - resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} + '@next/swc-darwin-arm64@15.5.14': + resolution: {integrity: sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.7': - resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==} + '@next/swc-darwin-x64@15.5.14': + resolution: {integrity: sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.7': - resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==} + '@next/swc-linux-arm64-gnu@15.5.14': + resolution: {integrity: sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.5.7': - resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} + '@next/swc-linux-arm64-musl@15.5.14': + resolution: {integrity: sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.5.7': - resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} + '@next/swc-linux-x64-gnu@15.5.14': + resolution: {integrity: sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.5.7': - resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} + '@next/swc-linux-x64-musl@15.5.14': + resolution: {integrity: sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.5.7': - resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} + '@next/swc-win32-arm64-msvc@15.5.14': + resolution: {integrity: sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.7': - resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==} + '@next/swc-win32-x64-msvc@15.5.14': + resolution: {integrity: sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1326,10 +1326,40 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@turbo/darwin-64@2.8.21': + resolution: {integrity: sha512-kfGoM0Iw8ZNZpbds+4IzOe0hjvHldqJwUPRAjXJi3KBxg/QOZL95N893SRoMtf2aJ+jJ3dk32yPkp8rvcIjP9g==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.8.21': + resolution: {integrity: sha512-o9HEflxUEyr987x0cTUzZBhDOyL6u95JmdmlkH2VyxAw7zq2sdtM5e72y9ufv2N5SIoOBw1fVn9UES5VY5H6vQ==} + cpu: [arm64] + os: [darwin] + '@turbo/gen@1.13.4': resolution: {integrity: sha512-PK38N1fHhDUyjLi0mUjv0RbX0xXGwDLQeRSGsIlLcVpP1B5fwodSIwIYXc9vJok26Yne94BX5AGjueYsUT3uUw==} hasBin: true + '@turbo/linux-64@2.8.21': + resolution: {integrity: sha512-uTxlCcXWy5h1fSSymP8XSJ+AudzEHMDV3IDfKX7+DGB8kgJ+SLoTUAH7z4OFA7I/l2sznz0upPdbNNZs91YMag==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.8.21': + resolution: {integrity: sha512-cdHIcxNcihHHkCHp0Y4Zb60K4Qz+CK4xw1gb6s/t/9o4SMeMj+hTBCtoW6QpPnl9xPYmxuTou8Zw6+cylTnREg==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.8.21': + resolution: {integrity: sha512-/iBj4OzbqEY8CX+eaeKbBTMZv2CLXNrt0692F7HnK7LcyYwyDecaAiSET6ZzL4opT7sbwkKvzAC/fhqT3Quu1A==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.8.21': + resolution: {integrity: sha512-95tMA/ZbIidJFUUtkmqioQ1gf3n3I1YbRP3ZgVdWTVn2qVbkodcIdGXBKRHHrIbRsLRl99SiHi/L7IxhpZDagQ==} + cpu: [arm64] + os: [win32] + '@turbo/workspaces@1.13.4': resolution: {integrity: sha512-3uYg2b5TWCiupetbDFMbBFMHl33xQTvp5DNg0fZSYal73Z9AlFH9yWabHWMYw6ywmwM1evkYRpTVA2n7GgqT5A==} hasBin: true @@ -1645,6 +1675,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.0, please upgrade better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -1702,8 +1733,8 @@ packages: camel-case@3.0.0: resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001781: + resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} cbor2@1.12.0: resolution: {integrity: sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==} @@ -2255,8 +2286,8 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@20.7.0: - resolution: {integrity: sha512-hR/uLYQdngTyEfxnOoa+e6KTcfBFyc1hgFj/Cc144A5JJUuHFYqIEBDcD4FeGqUeKLRZqJ9eN9u7/GDjYEgS1g==} + happy-dom@20.8.9: + resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} engines: {node: '>=20.0.0'} has-bigints@1.1.0: @@ -2802,8 +2833,8 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - next@15.5.10: - resolution: {integrity: sha512-r0X65PNwyDDyOrWNKpQoZvOatw7BcsTPRKdwEqtc9cj3wv7mbBIk9tKed4klRaFXJdX0rugpuMTHslDrAU1bBg==} + next@15.5.14: + resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -3027,8 +3058,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3444,6 +3475,7 @@ packages: syncpack@14.0.0: resolution: {integrity: sha512-OfAa3Oip5YC9Ad1jEs92Hw08Wy4JfdpdeequIwaJGsQG0tJtb8gpQfCdLuBefsk6n8WiUdt/5qmzcW+BDXtzbQ==} engines: {node: '>=14.17.0'} + deprecated: 'pnpm users: upgrade to 14.0.2 or newer' hasBin: true term-size@2.2.1: @@ -3553,6 +3585,10 @@ packages: resolution: {integrity: sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ==} hasBin: true + turbo@2.8.21: + resolution: {integrity: sha512-FlJ8OD5Qcp0jTAM7E4a/RhUzRNds2GzKlyxHKA6N247VLy628rrxAGlMpIXSz6VB430+TiQDJ/SMl6PL1lu6wQ==} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3793,6 +3829,18 @@ packages: utf-8-validate: optional: true + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -4098,7 +4146,7 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/runtime@1.8.1': + '@emnapi/runtime@1.9.1': dependencies: tslib: 2.8.1 optional: true @@ -4238,7 +4286,7 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.0.0': + '@img/colour@1.1.0': optional: true '@img/sharp-darwin-arm64@0.34.5': @@ -4323,7 +4371,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.8.1 + '@emnapi/runtime': 1.9.1 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -4382,34 +4430,34 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@next/env@15.5.10': {} + '@next/env@15.5.14': {} '@next/eslint-plugin-next@15.5.9': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.5.7': + '@next/swc-darwin-arm64@15.5.14': optional: true - '@next/swc-darwin-x64@15.5.7': + '@next/swc-darwin-x64@15.5.14': optional: true - '@next/swc-linux-arm64-gnu@15.5.7': + '@next/swc-linux-arm64-gnu@15.5.14': optional: true - '@next/swc-linux-arm64-musl@15.5.7': + '@next/swc-linux-arm64-musl@15.5.14': optional: true - '@next/swc-linux-x64-gnu@15.5.7': + '@next/swc-linux-x64-gnu@15.5.14': optional: true - '@next/swc-linux-x64-musl@15.5.7': + '@next/swc-linux-x64-musl@15.5.14': optional: true - '@next/swc-win32-arm64-msvc@15.5.7': + '@next/swc-win32-arm64-msvc@15.5.14': optional: true - '@next/swc-win32-x64-msvc@15.5.7': + '@next/swc-win32-x64-msvc@15.5.14': optional: true '@noble/ciphers@1.3.0': {} @@ -4529,6 +4577,12 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@turbo/darwin-64@2.8.21': + optional: true + + '@turbo/darwin-arm64@2.8.21': + optional: true + '@turbo/gen@1.13.4(@types/node@25.3.0)(typescript@5.9.3)': dependencies: '@turbo/workspaces': 1.13.4(@types/node@25.3.0) @@ -4549,6 +4603,18 @@ snapshots: - supports-color - typescript + '@turbo/linux-64@2.8.21': + optional: true + + '@turbo/linux-arm64@2.8.21': + optional: true + + '@turbo/windows-64@2.8.21': + optional: true + + '@turbo/windows-arm64@2.8.21': + optional: true + '@turbo/workspaces@1.13.4(@types/node@25.3.0)': dependencies: chalk: 2.4.2 @@ -4714,7 +4780,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0))': + '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.18 @@ -4726,7 +4792,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.3.0)(happy-dom@20.7.0) + vitest: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) '@vitest/expect@4.0.18': dependencies: @@ -4959,7 +5025,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.7 - caniuse-lite: 1.0.30001769 + caniuse-lite: 1.0.30001781 electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) @@ -4995,7 +5061,7 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001781: {} cbor2@1.12.0: {} @@ -5416,11 +5482,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.6.3(eslint@9.39.2)(turbo@2.8.10): + eslint-plugin-turbo@2.6.3(eslint@9.39.2)(turbo@2.8.21): dependencies: dotenv: 16.0.3 eslint: 9.39.2 - turbo: 2.8.10 + turbo: 2.8.21 eslint-scope@8.4.0: dependencies: @@ -5731,14 +5797,14 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@20.7.0: + happy-dom@20.8.9: dependencies: '@types/node': 25.3.0 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.18.3 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -6254,24 +6320,24 @@ snapshots: netmask@2.0.2: {} - next@15.5.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@next/env': 15.5.10 + '@next/env': 15.5.14 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001769 + caniuse-lite: 1.0.30001781 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.7 - '@next/swc-darwin-x64': 15.5.7 - '@next/swc-linux-arm64-gnu': 15.5.7 - '@next/swc-linux-arm64-musl': 15.5.7 - '@next/swc-linux-x64-gnu': 15.5.7 - '@next/swc-linux-x64-musl': 15.5.7 - '@next/swc-win32-arm64-msvc': 15.5.7 - '@next/swc-win32-x64-msvc': 15.5.7 + '@next/swc-darwin-arm64': 15.5.14 + '@next/swc-darwin-x64': 15.5.14 + '@next/swc-linux-arm64-gnu': 15.5.14 + '@next/swc-linux-arm64-musl': 15.5.14 + '@next/swc-linux-x64-gnu': 15.5.14 + '@next/swc-linux-x64-musl': 15.5.14 + '@next/swc-win32-arm64-msvc': 15.5.14 + '@next/swc-win32-x64-msvc': 15.5.14 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -6530,7 +6596,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.6: + postcss@8.5.8: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -6775,7 +6841,7 @@ snapshots: sharp@0.34.5: dependencies: - '@img/colour': 1.0.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.7.4 optionalDependencies: @@ -7119,6 +7185,15 @@ snapshots: turbo-windows-64: 2.8.10 turbo-windows-arm64: 2.8.10 + turbo@2.8.21: + optionalDependencies: + '@turbo/darwin-64': 2.8.21 + '@turbo/darwin-arm64': 2.8.21 + '@turbo/linux-64': 2.8.21 + '@turbo/linux-arm64': 2.8.21 + '@turbo/windows-64': 2.8.21 + '@turbo/windows-arm64': 2.8.21 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -7240,14 +7315,14 @@ snapshots: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - postcss: 8.5.6 + postcss: 8.5.8 rollup: 4.53.4 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.3.0 fsevents: 2.3.3 - vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.7.0): + vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@25.3.0)) @@ -7271,7 +7346,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.3.0 - happy-dom: 20.7.0 + happy-dom: 20.8.9 transitivePeerDependencies: - jiti - less @@ -7367,6 +7442,8 @@ snapshots: ws@8.18.3: {} + ws@8.20.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/repo/eslint-config/CHANGELOG.md b/repo/eslint-config/CHANGELOG.md index ddc6085a88..2df72f2e7a 100644 --- a/repo/eslint-config/CHANGELOG.md +++ b/repo/eslint-config/CHANGELOG.md @@ -1,5 +1,12 @@ # @repo/eslint-config +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + ## 0.0.1-beta.1 ### Patch Changes diff --git a/repo/eslint-config/package.json b/repo/eslint-config/package.json index 5625bc6999..9c38b69880 100644 --- a/repo/eslint-config/package.json +++ b/repo/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/eslint-config", - "version": "0.0.1-beta.1", + "version": "0.0.1", "type": "module", "private": true, "exports": { diff --git a/repo/typescript-config/CHANGELOG.md b/repo/typescript-config/CHANGELOG.md index 611dc70b2d..3e5ecbabf4 100644 --- a/repo/typescript-config/CHANGELOG.md +++ b/repo/typescript-config/CHANGELOG.md @@ -1,5 +1,12 @@ # @repo/typescript-config +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + ## 0.0.1-beta.1 ### Patch Changes diff --git a/repo/typescript-config/package.json b/repo/typescript-config/package.json index cb34e9260e..ed931bce61 100644 --- a/repo/typescript-config/package.json +++ b/repo/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/typescript-config", - "version": "0.0.1-beta.1", + "version": "0.0.1", "private": true, "license": "MIT", "publishConfig": { diff --git a/repo/ui/CHANGELOG.md b/repo/ui/CHANGELOG.md index 8994e02948..232f9accb7 100644 --- a/repo/ui/CHANGELOG.md +++ b/repo/ui/CHANGELOG.md @@ -1,5 +1,12 @@ # @repo/ui +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + ## 0.0.1-beta.1 ### Patch Changes diff --git a/repo/ui/package.json b/repo/ui/package.json index 670a816352..f0045e54d0 100644 --- a/repo/ui/package.json +++ b/repo/ui/package.json @@ -1,6 +1,6 @@ { "name": "@repo/ui", - "version": "0.0.1-beta.1", + "version": "0.0.1", "private": true, "type": "module", "exports": {