diff --git a/packages/passport/sdk-sample-app/get-transaction.ts b/packages/passport/sdk-sample-app/get-transaction.ts new file mode 100644 index 0000000000..50f40ab99e --- /dev/null +++ b/packages/passport/sdk-sample-app/get-transaction.ts @@ -0,0 +1,245 @@ +import * as ethers from "ethers"; + +const erc20Abi = [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] + +async function main() { + const provider = new ethers.JsonRpcProvider("https://sepolia-rollup.arbitrum.io/rpc"); + + const wallet = ethers.Wallet.fromPhrase("attract chase stool practice orphan version bargain spin crisp often chuckle auction save own exclude", provider); + console.log(wallet.address); + + const sender = "0x1d11a5c0adf049a5ab706cbaae78d681e25bbe62"; + const usdc = new ethers.Contract("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", erc20Abi, provider); + + console.log(await usdc.balanceOf(sender)); + + console.log(await usdc.approve.estimateGas("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", 100, { from: sender })) + + const transferTx = usdc.interface.encodeFunctionData("approve", ["0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", 100]); + + console.log({ transferTx }); +} + +main(); + diff --git a/packages/passport/sdk-sample-app/src/components/arbitrum/ArbitrumWorkflow.tsx b/packages/passport/sdk-sample-app/src/components/arbitrum/ArbitrumWorkflow.tsx new file mode 100644 index 0000000000..fc5ac89538 --- /dev/null +++ b/packages/passport/sdk-sample-app/src/components/arbitrum/ArbitrumWorkflow.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { Stack } from 'react-bootstrap'; +import { usePassportProvider } from '@/context/PassportProvider'; +import Request from '@/components/arbitrum/Request'; +import CardStack from '@/components/CardStack'; +import { useStatusProvider } from '@/context/StatusProvider'; +import WorkflowButton from '@/components/WorkflowButton'; + +function ArbitrumWorkflow() { + const [showRequest, setShowRequest] = useState(false); + + const { isLoading } = useStatusProvider(); + const { connectArbitrum, arbitrumProvider } = usePassportProvider(); + + const handleRequest = () => { + setShowRequest(true); + }; + + return ( + + + {arbitrumProvider && ( + <> + + request + + {showRequest && ( + + )} + + )} + {!arbitrumProvider && ( + + Connect Arbitrum + + )} + + + ); +} + +export default ArbitrumWorkflow; diff --git a/packages/passport/sdk-sample-app/src/components/arbitrum/Request.tsx b/packages/passport/sdk-sample-app/src/components/arbitrum/Request.tsx new file mode 100644 index 0000000000..95457e36a6 --- /dev/null +++ b/packages/passport/sdk-sample-app/src/components/arbitrum/Request.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { + Form, Offcanvas, Spinner, Stack, +} from 'react-bootstrap'; +import { Heading } from '@biom3/react'; +import { ModalProps } from '@/types'; +import { useStatusProvider } from '@/context/StatusProvider'; +import { usePassportProvider } from '@/context/PassportProvider'; +import { RequestArguments } from '@imtbl/passport'; +import WorkflowButton from '@/components/WorkflowButton'; + +interface EthereumMethod { + name: string; +} + +const EthereumMethods: EthereumMethod[] = [ + { name: 'eth_requestAccounts' }, + { name: 'eth_accounts' }, + { name: 'eth_chainId' }, +]; + +function Request({ showModal, setShowModal }: ModalProps) { + const [selectedEthMethod, setSelectedEthMethod] = useState(EthereumMethods[0]); + const [loadingRequest, setLoadingRequest] = useState(false); + const [isInvalid, setInvalid] = useState(undefined); + + const { addMessage } = useStatusProvider(); + const { arbitrumProvider } = usePassportProvider(); + + const resetForm = () => { + setInvalid(false); + }; + + const handleClose = () => { + resetForm(); + setShowModal(false); + }; + + const performRequest = async (request: RequestArguments) => { + setInvalid(false); + setLoadingRequest(true); + try { + if (!arbitrumProvider) { + addMessage('Request', 'No Arbitrum provider connected. Connect via "Connect Arbitrum" first.'); + setLoadingRequest(false); + setInvalid(true); + return; + } + const result = await arbitrumProvider.request(request); + setLoadingRequest(false); + addMessage(request.method, result); + handleClose(); + } catch (err) { + addMessage('Request', err); + handleClose(); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const form = e.currentTarget; + if (form.checkValidity()) { + await performRequest({ + method: selectedEthMethod?.name || '', + }); + } else { + setInvalid(true); + } + }; + + const handleSetEthMethod = (e: React.ChangeEvent) => { + resetForm(); + const ethMethod = EthereumMethods.find((method) => method.name === e.target.value); + if (!ethMethod) { + console.error('Invalid eth method'); + } else { + setSelectedEthMethod(ethMethod); + } + }; + + return ( + + + Arbitrum Request + + +
+ + Ethereum Method + + { + EthereumMethods.map((method) => ( + + )) + } + + + + + Submit + + { loadingRequest && } + +
+
+
+ ); +} + +export default Request; diff --git a/packages/passport/sdk-sample-app/src/components/zkevm/ZkEvmWorkflow.tsx b/packages/passport/sdk-sample-app/src/components/zkevm/ZkEvmWorkflow.tsx index 0eb66d66ec..34025b371f 100644 --- a/packages/passport/sdk-sample-app/src/components/zkevm/ZkEvmWorkflow.tsx +++ b/packages/passport/sdk-sample-app/src/components/zkevm/ZkEvmWorkflow.tsx @@ -5,7 +5,7 @@ import React, { useState, } from 'react'; import { Stack } from 'react-bootstrap'; -import { connectWallet } from '@imtbl/wallet'; +import { connectWallet, ZkEvmProvider } from '@imtbl/wallet'; import { usePassportProvider } from '@/context/PassportProvider'; import Request from '@/components/zkevm/Request'; import CardStack from '@/components/CardStack'; @@ -22,7 +22,6 @@ function ZkEvmWorkflow() { const { isLoading, addMessage, setIsLoading } = useStatusProvider(); const { connectZkEvm, - zkEvmProvider, defaultWalletProvider, activeZkEvmProvider, setDefaultWalletProvider, @@ -47,7 +46,7 @@ function ZkEvmWorkflow() { } setIsLoading(true); try { - const provider = await connectWallet(); + const provider = await connectWallet() as ZkEvmProvider; if (provider) { setDefaultWalletProvider(provider); addMessage( diff --git a/packages/passport/sdk-sample-app/src/context/PassportProvider.tsx b/packages/passport/sdk-sample-app/src/context/PassportProvider.tsx index d3ae83240a..e2ac6ac3e8 100644 --- a/packages/passport/sdk-sample-app/src/context/PassportProvider.tsx +++ b/packages/passport/sdk-sample-app/src/context/PassportProvider.tsx @@ -3,22 +3,25 @@ import React, { } from 'react'; import { IMXProvider } from '@imtbl/x-provider'; import { - LinkedWallet, LinkWalletParams, Provider, UserProfile, MarketingConsentStatus, + LinkedWallet, LinkWalletParams, UserProfile, MarketingConsentStatus, EvmChain, } from '@imtbl/passport'; import { useImmutableProvider } from '@/context/ImmutableProvider'; import { useStatusProvider } from '@/context/StatusProvider'; import { EnvironmentNames } from '@/types'; +import { SequenceProvider, ZkEvmProvider } from '@imtbl/wallet'; const PassportContext = createContext<{ imxProvider: IMXProvider | undefined; - zkEvmProvider: Provider | undefined; - defaultWalletProvider: Provider | undefined; - activeZkEvmProvider: Provider | undefined; + zkEvmProvider: ZkEvmProvider | undefined; + arbitrumProvider: SequenceProvider | undefined; + defaultWalletProvider: ZkEvmProvider | undefined; + activeZkEvmProvider: ZkEvmProvider | undefined; activeZkEvmAccount: string; isSandboxEnvironment: boolean; - setDefaultWalletProvider: (provider?: Provider) => void; + setDefaultWalletProvider: (provider?: ZkEvmProvider) => void; connectImx:() => void; connectZkEvm: () => void; + connectArbitrum: () => void; logout: () => void; login: () => void; popupRedirect: () => void; @@ -36,6 +39,7 @@ const PassportContext = createContext<{ }>({ imxProvider: undefined, zkEvmProvider: undefined, + arbitrumProvider: undefined, defaultWalletProvider: undefined, activeZkEvmProvider: undefined, activeZkEvmAccount: '', @@ -43,6 +47,7 @@ const PassportContext = createContext<{ isSandboxEnvironment: false, connectImx: () => undefined, connectZkEvm: () => undefined, + connectArbitrum: () => undefined, logout: () => undefined, login: () => Promise.resolve(undefined), popupRedirect: () => Promise.resolve(undefined), @@ -63,8 +68,9 @@ export function PassportProvider({ children, }: { children: JSX.Element | JSX.Element[] }) { const [imxProvider, setImxProvider] = useState(); - const [zkEvmProvider, setZkEvmProvider] = useState(); - const [defaultWalletProvider, setDefaultWalletProvider] = useState(); + const [zkEvmProvider, setZkEvmProvider] = useState(); + const [arbitrumProvider, setArbitrumProvider] = useState(); + const [defaultWalletProvider, setDefaultWalletProvider] = useState(); const [activeZkEvmAccount, setActiveZkEvmAccount] = useState(''); const { addMessage, setIsLoading } = useStatusProvider(); @@ -97,7 +103,7 @@ export function PassportProvider({ const connectZkEvm = useCallback(async () => { setIsLoading(true); - const provider = await passportClient.connectEvm(); + const provider = await passportClient.connectEvm() as ZkEvmProvider; if (provider) { setZkEvmProvider(provider); addMessage('ConnectZkEvm', 'Connected'); @@ -107,6 +113,26 @@ export function PassportProvider({ setIsLoading(false); }, [passportClient, setIsLoading, addMessage]); + const connectArbitrum = useCallback(async () => { + setIsLoading(true); + try { + const provider = await passportClient.connectEvm({ + chain: EvmChain.ARBITRUM_ONE, + announceProvider: true + }) as SequenceProvider; + if (provider) { + setArbitrumProvider(provider); + addMessage('ConnectArbitrum', 'Connected'); + } else { + addMessage('ConnectArbitrum', 'Failed to connect'); + } + } catch (err) { + addMessage('ConnectArbitrum', err); + } finally { + setIsLoading(false); + } + }, [passportClient, setIsLoading, addMessage]); + const getIdToken = useCallback(async () => { setIsLoading(true); const idToken = await passportClient.getIdToken(); @@ -162,6 +188,7 @@ export function PassportProvider({ await passportClient.logout(); setImxProvider(undefined); setZkEvmProvider(undefined); + setArbitrumProvider(undefined); setDefaultWalletProvider(undefined); } catch (err) { addMessage('Logout', err); @@ -362,12 +389,14 @@ export function PassportProvider({ const providerValues = useMemo(() => ({ imxProvider, zkEvmProvider, + arbitrumProvider, defaultWalletProvider, activeZkEvmProvider, activeZkEvmAccount, setDefaultWalletProvider, connectImx, connectZkEvm, + connectArbitrum, logout, popupRedirect, popupRedirectGoogle, @@ -386,12 +415,14 @@ export function PassportProvider({ }), [ imxProvider, zkEvmProvider, + arbitrumProvider, defaultWalletProvider, activeZkEvmProvider, activeZkEvmAccount, isSandboxEnvironment, connectImx, connectZkEvm, + connectArbitrum, logout, popupRedirect, popupRedirectGoogle, @@ -420,6 +451,7 @@ export function usePassportProvider() { const { imxProvider, zkEvmProvider, + arbitrumProvider, defaultWalletProvider, activeZkEvmProvider, activeZkEvmAccount, @@ -427,6 +459,7 @@ export function usePassportProvider() { setDefaultWalletProvider, connectImx, connectZkEvm, + connectArbitrum, logout, popupRedirect, popupRedirectGoogle, @@ -445,6 +478,7 @@ export function usePassportProvider() { return { imxProvider, zkEvmProvider, + arbitrumProvider, defaultWalletProvider, activeZkEvmProvider, activeZkEvmAccount, @@ -452,6 +486,7 @@ export function usePassportProvider() { setDefaultWalletProvider, connectImx, connectZkEvm, + connectArbitrum, logout, popupRedirect, popupRedirectGoogle, diff --git a/packages/passport/sdk-sample-app/src/pages/index.tsx b/packages/passport/sdk-sample-app/src/pages/index.tsx index ac20f5886f..9b33e84e63 100644 --- a/packages/passport/sdk-sample-app/src/pages/index.tsx +++ b/packages/passport/sdk-sample-app/src/pages/index.tsx @@ -10,10 +10,13 @@ import { useStatusProvider } from '@/context/StatusProvider'; import { BASE_PATH } from '@/config'; import PassportMethods from '@/components/PassportMethods'; import ZkEvmWorkflow from '@/components/zkevm/ZkEvmWorkflow'; +import ArbitrumWorkflow from '@/components/arbitrum/ArbitrumWorkflow'; export default function Home() { const { isLoading } = useStatusProvider(); - const { imxProvider, zkEvmProvider, defaultWalletProvider } = usePassportProvider(); + const { + imxProvider, zkEvmProvider, arbitrumProvider, defaultWalletProvider, + } = usePassportProvider(); return ( <> @@ -26,7 +29,10 @@ export default function Home() {
- + @@ -37,6 +43,9 @@ export default function Home() { + + + diff --git a/packages/passport/sdk/src/Passport.test.ts b/packages/passport/sdk/src/Passport.test.ts index 3608ae4506..94d61d584f 100644 --- a/packages/passport/sdk/src/Passport.test.ts +++ b/packages/passport/sdk/src/Passport.test.ts @@ -34,7 +34,6 @@ jest.mock('@imtbl/auth', () => { }); jest.mock('@imtbl/wallet', () => { - const actual = jest.requireActual('@imtbl/wallet'); const connectWalletMock = jest.fn(); return { @@ -44,8 +43,17 @@ jest.mock('@imtbl/wallet', () => { MagicTEESigner: jest.fn(), WalletConfiguration: jest.fn(), ConfirmationScreen: jest.fn(), - EvmChain: actual.EvmChain, - getChainConfig: actual.getChainConfig, + EvmChain: { + ZKEVM: 'zkevm', + ARBITRUM_ONE: 'arbitrum_one', + }, + getChainConfig: jest.fn().mockReturnValue({ + chainId: 42161, + rpcUrl: 'https://arb1.arbitrum.io/rpc', + relayerUrl: 'https://relayer.sequence.app', + apiUrl: 'https://api.immutable.com', + name: 'Arbitrum One', + }), __mocked: { connectWalletMock, }, @@ -235,11 +243,11 @@ describe('Passport', () => { }); it('throws error for non-zkEVM chains (not yet implemented)', async () => { - const { EvmChain: actualEvmChain } = jest.requireActual('@imtbl/wallet'); + const { EvmChain: evmChain } = jest.requireMock('@imtbl/wallet'); const passport = createPassport(); await expect( - passport.connectEvm({ announceProvider: true, chain: actualEvmChain.ARBITRUM_ONE }), + passport.connectEvm({ announceProvider: true, chain: evmChain.ARBITRUM_ONE }), ).rejects.toThrow('Chain arbitrum_one is not yet supported. Only ZKEVM is currently available.'); }); }); diff --git a/packages/passport/sdk/src/starkEx/passportImxProvider.test.ts b/packages/passport/sdk/src/starkEx/passportImxProvider.test.ts index fdb84f87f1..1d71afe54e 100644 --- a/packages/passport/sdk/src/starkEx/passportImxProvider.test.ts +++ b/packages/passport/sdk/src/starkEx/passportImxProvider.test.ts @@ -1,13 +1,18 @@ +import type { GuardianClient, MagicTEESigner } from '@imtbl/wallet'; import { IMXClient } from '@imtbl/x-client'; import { ImxApiClients, imx } from '@imtbl/generated-clients'; import { Auth, User } from '@imtbl/auth'; -import { GuardianClient, MagicTEESigner } from '@imtbl/wallet'; import { PassportImxProvider } from './passportImxProvider'; import { ImxGuardianClient } from './imxGuardianClient'; import registerOffchain from './workflows/registerOffchain'; import { getStarkSigner } from './getStarkSigner'; import { PassportError, PassportErrorType } from '../errors/passportError'; +jest.mock('@imtbl/wallet', () => ({ + GuardianClient: jest.fn(), + MagicTEESigner: jest.fn(), +})); + jest.mock('./workflows/registerOffchain'); jest.mock('./getStarkSigner'); diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 20a2cc692d..b2acbdb7f2 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -40,6 +40,10 @@ "viem": "~2.18.0" }, "devDependencies": { + "@0xsequence/identity-instrument": "3.0.0-beta.10", + "@0xsequence/wallet-wdk": "3.0.0-beta.10", + "@0xsequence/wallet-core": "3.0.0-beta.10", + "@0xsequence/wallet-primitives": "3.0.0-beta.10", "@swc/core": "^1.3.36", "@swc/jest": "^0.2.37", "@types/jest": "^29.5.12", @@ -47,6 +51,7 @@ "@jest/test-sequencer": "^29.7.0", "jest": "^29.4.3", "jest-environment-jsdom": "^29.4.3", + "ox": "^0.9.17", "ts-node": "^10.9.1", "tsup": "^8.3.0", "typescript": "^5.6.2" diff --git a/packages/wallet/src/connectWallet.test.ts b/packages/wallet/src/connectWallet.test.ts index 6efd022c19..f6b830bf9d 100644 --- a/packages/wallet/src/connectWallet.test.ts +++ b/packages/wallet/src/connectWallet.test.ts @@ -46,6 +46,14 @@ jest.mock('./sequence/sequenceProvider', () => ({ SequenceProvider: jest.fn(), })); +jest.mock('./sequence/signer', () => ({ + createSequenceSigner: jest.fn().mockReturnValue({ + getAddress: jest.fn().mockResolvedValue('0x1234'), + signPayload: jest.fn(), + signMessage: jest.fn(), + }), +})); + jest.mock('./provider/eip6963', () => ({ announceProvider: jest.fn(), passportProviderInfo: { name: 'passport', rdns: 'com.immutable.passport', icon: '' }, diff --git a/packages/wallet/src/connectWallet.ts b/packages/wallet/src/connectWallet.ts index ae4144aa4b..ce3b5ab794 100644 --- a/packages/wallet/src/connectWallet.ts +++ b/packages/wallet/src/connectWallet.ts @@ -24,6 +24,7 @@ import { IMMUTABLE_ZKEVM_TESTNET_CHAIN_ID, } from './constants'; import { ChainId } from './network/chains'; +import { createSequenceSigner } from './sequence/signer'; /** * Type guard to check if chainId is a valid key for MAGIC_CONFIG @@ -289,10 +290,19 @@ export async function connectWallet( sessionActivityApiUrl, }); } else { + // Create Sequence signer based on environment + const sequenceSigner = createSequenceSigner(auth, authConfig, { + identityInstrumentEndpoint: initialChain.sequenceIdentityInstrumentEndpoint, + }); + // Non-zkEVM chain - use SequenceProvider provider = new SequenceProvider({ + auth, chainConfig: initialChain, + multiRollupApiClients, guardianClient, + ethSigner: sequenceSigner, + passportEventEmitter, }); } diff --git a/packages/wallet/src/network/presets.ts b/packages/wallet/src/network/presets.ts index d44c4b8a62..e05fe869fb 100644 --- a/packages/wallet/src/network/presets.ts +++ b/packages/wallet/src/network/presets.ts @@ -49,6 +49,7 @@ export const ARBITRUM_ONE_CHAIN: ChainConfig = { apiUrl: 'https://api.immutable.com', passportDomain: 'https://passport.immutable.com', feeTokenSymbol: 'ETH', + sequenceIdentityInstrumentEndpoint: 'https://next-identity.sequence.app', }; /** @@ -63,6 +64,7 @@ export const ARBITRUM_SEPOLIA_CHAIN: ChainConfig = { apiUrl: 'https://api.sandbox.immutable.com', passportDomain: 'https://passport.sandbox.immutable.com', feeTokenSymbol: 'ETH', + sequenceIdentityInstrumentEndpoint: 'https://next-identity.sequence-dev.app', }; /** diff --git a/packages/wallet/src/sequence/sequenceProvider.ts b/packages/wallet/src/sequence/sequenceProvider.ts index bb92d3a6ea..fe6a4d6365 100644 --- a/packages/wallet/src/sequence/sequenceProvider.ts +++ b/packages/wallet/src/sequence/sequenceProvider.ts @@ -4,52 +4,169 @@ import { toHex, type PublicClient, } from 'viem'; -import { TypedEventEmitter } from '@imtbl/auth'; +import { MultiRollupApiClients } from '@imtbl/generated-clients'; +import { Auth, TypedEventEmitter, User } from '@imtbl/auth'; +import { trackFlow, trackError, identify } from '@imtbl/metrics'; import { Provider, + ProviderEvent, ProviderEventMap, RequestArguments, ChainConfig, + EvmChain, } from '../types'; import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from '../zkEvm/JsonRpcError'; import GuardianClient from '../guardian'; +import { SequenceSigner } from './signer'; +import { registerUser } from './user'; +import { getEvmChainFromChainId } from '../network/chainRegistry'; export type SequenceProviderInput = { + auth: Auth; chainConfig: ChainConfig; + multiRollupApiClients: MultiRollupApiClients; guardianClient: GuardianClient; + ethSigner: SequenceSigner; + passportEventEmitter: TypedEventEmitter; }; +/** Non-zkEVM chain type */ +type SequenceChain = Exclude; + +/** + * Check if user is registered for a non-zkEVM chain. + * The chain data is stored as user[chainName] (e.g., user.arbitrum_one). + */ +function isUserRegisteredForChain(user: User, chain: SequenceChain): boolean { + return chain in user && !!(user as any)[chain]?.ethAddress; +} + +/** + * Get the user's eth address for a non-zkEVM chain. + */ +function getUserChainAddress(user: User, chain: SequenceChain): string | undefined { + const chainData = (user as any)[chain]; + return chainData?.ethAddress; +} + export class SequenceProvider implements Provider { + readonly #auth: Auth; + readonly #chainConfig: ChainConfig; + readonly #multiRollupApiClients: MultiRollupApiClients; + readonly #rpcProvider: PublicClient; readonly #providerEventEmitter: TypedEventEmitter; readonly #guardianClient: GuardianClient; + readonly #ethSigner: SequenceSigner; + + readonly #evmChain: SequenceChain; + public readonly isPassport: boolean = true; - constructor({ chainConfig, guardianClient }: SequenceProviderInput) { + constructor({ + auth, + chainConfig, + multiRollupApiClients, + guardianClient, + ethSigner, + passportEventEmitter, + }: SequenceProviderInput) { + // Validate this is not a zkEVM chain + const evmChain = getEvmChainFromChainId(chainConfig.chainId); + if (evmChain === EvmChain.ZKEVM) { + throw new Error('SequenceProvider cannot be used for zkEVM chains. Use ZkEvmProvider instead.'); + } + this.#evmChain = evmChain; + + this.#auth = auth; this.#chainConfig = chainConfig; + this.#multiRollupApiClients = multiRollupApiClients; this.#guardianClient = guardianClient; + this.#ethSigner = ethSigner; + this.#providerEventEmitter = passportEventEmitter; // Create PublicClient for reading from the chain using viem this.#rpcProvider = createPublicClient({ transport: http(this.#chainConfig.rpcUrl), }); + } - this.#providerEventEmitter = new TypedEventEmitter(); + /** + * Get the user's address for this chain if already registered. + */ + async #getChainAddress(): Promise { + const user = await this.#auth.getUser(); + if (user && isUserRegisteredForChain(user, this.#evmChain)) { + return getUserChainAddress(user, this.#evmChain); + } + return undefined; } async #performRequest(request: RequestArguments): Promise { switch (request.method) { - // TODO: Implement eth_requestAccounts case 'eth_requestAccounts': { - throw new JsonRpcError( - ProviderErrorCode.UNSUPPORTED_METHOD, - 'eth_requestAccounts not yet implemented for this chain', - ); + // Check if already registered + const existingAddress = await this.#getChainAddress(); + if (existingAddress) return [existingAddress]; + + const flow = trackFlow('passport', 'ethRequestAccounts'); + + try { + const user = await this.#auth.getUserOrLogin(); + flow.addEvent('endGetUserOrLogin'); + + let userEthAddress: string | undefined; + + if (!isUserRegisteredForChain(user, this.#evmChain)) { + flow.addEvent('startUserRegistration'); + + userEthAddress = await registerUser({ + auth: this.#auth, + ethSigner: this.#ethSigner, + multiRollupApiClients: this.#multiRollupApiClients, + accessToken: user.accessToken, + rpcProvider: this.#rpcProvider, + flow, + }); + flow.addEvent('endUserRegistration'); + } else { + userEthAddress = getUserChainAddress(user, this.#evmChain); + } + + if (!userEthAddress) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + 'Failed to get user address after registration', + ); + } + + this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [ + userEthAddress, + ]); + identify({ + passportId: user.profile.sub, + }); + return [userEthAddress]; + } catch (error) { + if (error instanceof Error) { + trackError('passport', 'ethRequestAccounts', error, { flowId: flow.details.flowId }); + } else { + flow.addEvent('errored'); + } + throw error; + } finally { + flow.addEvent('End'); + } + } + + case 'eth_accounts': { + const address = await this.#getChainAddress(); + return address ? [address] : []; } // TODO: Implement eth_sendTransaction @@ -60,12 +177,6 @@ export class SequenceProvider implements Provider { ); } - // TODO: Implement eth_accounts - case 'eth_accounts': { - // Return empty array until eth_requestAccounts is implemented - return []; - } - // TODO: Implement personal_sign case 'personal_sign': { throw new JsonRpcError( diff --git a/packages/wallet/src/sequence/signer/identityInstrumentSigner.ts b/packages/wallet/src/sequence/signer/identityInstrumentSigner.ts new file mode 100644 index 0000000000..abea5d0e2c --- /dev/null +++ b/packages/wallet/src/sequence/signer/identityInstrumentSigner.ts @@ -0,0 +1,193 @@ +import { hashMessage, toHex, concat } from 'viem'; +import { Identity } from '@0xsequence/wallet-wdk'; +import { IdentityInstrument, IdTokenChallenge } from '@0xsequence/identity-instrument'; +import { WalletError, WalletErrorType } from '../../errors'; +import { Auth, User, decodeJwtPayload } from '@imtbl/auth'; +import { Hex, Address } from 'ox'; +import { + Payload, + Signature as SequenceSignature, +} from '@0xsequence/wallet-primitives'; +import { SequenceSigner } from './types'; + +interface IdTokenPayload { + iss: string; + aud: string; + sub: string; +} + +interface AuthKey { + address: string; + privateKey: CryptoKey; + identitySigner: string; + expiresAt: Date; +} + +interface UserWallet { + userIdentifier: string; + signerAddress: string; + authKey: AuthKey; + identityInstrument: IdentityInstrument; +} + +export interface IdentityInstrumentSignerConfig { + /** Sequence Identity Instrument endpoint URL */ + identityInstrumentEndpoint: string; +} + +export class IdentityInstrumentSigner implements SequenceSigner { + readonly #auth: Auth; + + readonly #config: IdentityInstrumentSignerConfig; + + #userWallet: UserWallet | null = null; + + #createWalletPromise: Promise | null = null; + + constructor(auth: Auth, config: IdentityInstrumentSignerConfig) { + this.#auth = auth; + this.#config = config; + } + + async #getUserOrThrow(): Promise { + const user = await this.#auth.getUser(); + if (!user) { + throw new WalletError( + 'User not authenticated', + WalletErrorType.NOT_LOGGED_IN_ERROR, + ); + } + return user; + } + + async #getUserWallet(): Promise { + let userWallet = this.#userWallet; + if (!userWallet) { + userWallet = await this.#createWallet(); + } + + const user = await this.#getUserOrThrow(); + if (user.profile.sub !== userWallet.userIdentifier) { + userWallet = await this.#createWallet(user); + } + + return userWallet; + } + + async #createWallet(user?: User): Promise { + if (this.#createWalletPromise) return this.#createWalletPromise; + + this.#createWalletPromise = (async () => { + try { + this.#userWallet = null; + await this.#auth.forceUserRefresh(); // TODO shouldn't have to refresh all the time + + const authenticatedUser = user || await this.#getUserOrThrow(); + + if (!authenticatedUser.idToken) { + throw new WalletError( + 'User idToken not available', + WalletErrorType.NOT_LOGGED_IN_ERROR, + ); + } + + const { idToken } = authenticatedUser; + const decoded = decodeJwtPayload(idToken); + const issuer = decoded.iss; + const audience = decoded.aud; + + const keyPair = await window.crypto.subtle.generateKey( + { name: 'ECDSA', namedCurve: 'P-256' }, + false, + ['sign', 'verify'], + ); + + const publicKey = await window.crypto.subtle.exportKey('raw', keyPair.publicKey); + const authKey: AuthKey = { + address: Hex.fromBytes(new Uint8Array(publicKey)), + privateKey: keyPair.privateKey, + identitySigner: '', + expiresAt: new Date(Date.now() + 3600000), + }; + + const identityInstrument = new IdentityInstrument( + this.#config.identityInstrumentEndpoint, + '@14:test', + ); + const challenge = new IdTokenChallenge(issuer, audience, idToken); + + await identityInstrument.commitVerifier( + Identity.toIdentityAuthKey(authKey), + challenge, + ); + + const result = await identityInstrument.completeAuth( + Identity.toIdentityAuthKey(authKey), + challenge, + ); + + const signerAddress = result.signer.address; + authKey.identitySigner = signerAddress; + + this.#userWallet = { + userIdentifier: authenticatedUser.profile.sub, + signerAddress, + authKey, + identityInstrument, + }; + + return this.#userWallet; + } catch (error) { + const errorMessage = `Identity Instrument: Failed to create signer: ${(error as Error).message}`; + throw new WalletError(errorMessage, WalletErrorType.WALLET_CONNECTION_ERROR); + } finally { + this.#createWalletPromise = null; + } + })(); + + return this.#createWalletPromise; + } + + async getAddress(): Promise { + const wallet = await this.#getUserWallet(); + return wallet.signerAddress; + } + + async signPayload( + walletAddress: Address.Address, + chainId: number, + payload: Payload.Parented, + ): Promise { + const wallet = await this.#getUserWallet(); + + const signer = new Identity.IdentitySigner( + wallet.identityInstrument, + wallet.authKey, + ); + + return signer.sign(walletAddress, chainId, payload); + } + + async signMessage(message: string | Uint8Array): Promise { + const wallet = await this.#getUserWallet(); + + const signer = new Identity.IdentitySigner( + wallet.identityInstrument, + wallet.authKey, + ); + + const messageBytes = typeof message === 'string' + ? new TextEncoder().encode(message) + : message; + const digest = hashMessage({ raw: messageBytes }); + + const signature = await signer.signDigest(Hex.toBytes(digest)); + + // Format signature: r (32 bytes) + s (32 bytes) + v (1 byte) + const r = toHex(signature.r, { size: 32 }); + const s = toHex(signature.s, { size: 32 }); + const v = toHex(signature.yParity + 27, { size: 1 }); + + return concat([r, s, v]); + } +} diff --git a/packages/wallet/src/sequence/signer/index.ts b/packages/wallet/src/sequence/signer/index.ts new file mode 100644 index 0000000000..f34ae6c31e --- /dev/null +++ b/packages/wallet/src/sequence/signer/index.ts @@ -0,0 +1,45 @@ +import { Auth, IAuthConfiguration } from '@imtbl/auth'; +import { SequenceSigner } from './types'; +import { IdentityInstrumentSigner } from './identityInstrumentSigner'; +import { PrivateKeySigner } from './privateKeySigner'; + +export type { SequenceSigner } from './types'; +export { IdentityInstrumentSigner } from './identityInstrumentSigner'; +export type { IdentityInstrumentSignerConfig } from './identityInstrumentSigner'; +export { PrivateKeySigner } from './privateKeySigner'; + +const DEV_AUTH_DOMAIN = 'https://auth.dev.immutable.com'; + +export interface CreateSequenceSignerConfig { + /** Identity Instrument endpoint (required for prod/sandbox) */ + identityInstrumentEndpoint?: string; +} + +/** + * Create the appropriate signer based on environment. + * - Dev environment (behind VPN): uses PrivateKeySigner + * - Prod/Sandbox: uses IdentityInstrumentSigner + * + * @param auth - Auth instance + * @param authConfig - Auth configuration (to determine environment) + * @param config - Signer configuration + */ +export function createSequenceSigner( + auth: Auth, + authConfig: IAuthConfiguration, + config: CreateSequenceSignerConfig = {}, +): SequenceSigner { + const isDevEnvironment = authConfig.authenticationDomain === DEV_AUTH_DOMAIN; + + if (isDevEnvironment) { + return new PrivateKeySigner(auth); + } + + if (!config.identityInstrumentEndpoint) { + throw new Error('identityInstrumentEndpoint is required for non-dev environments'); + } + + return new IdentityInstrumentSigner(auth, { + identityInstrumentEndpoint: config.identityInstrumentEndpoint, + }); +} diff --git a/packages/wallet/src/sequence/signer/privateKeySigner.ts b/packages/wallet/src/sequence/signer/privateKeySigner.ts new file mode 100644 index 0000000000..8d184c014f --- /dev/null +++ b/packages/wallet/src/sequence/signer/privateKeySigner.ts @@ -0,0 +1,111 @@ +import { keccak256, toBytes } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { WalletError, WalletErrorType } from '../../errors'; +import { Auth, User } from '@imtbl/auth'; +import { Signers } from '@0xsequence/wallet-core'; +import { + Payload, + Signature as SequenceSignature, +} from '@0xsequence/wallet-primitives'; +import { SequenceSigner } from './types'; +import { Address } from 'ox'; + +interface PrivateKeyWallet { + userIdentifier: string; + signerAddress: string; + signer: Signers.Pk.Pk; + privateKey: `0x${string}`; +} + +/** + * Private key signer for dev environments (behind VPN). + * Uses a deterministic private key derived from the user's sub. + */ +export class PrivateKeySigner implements SequenceSigner { + readonly #auth: Auth; + + #privateKeyWallet: PrivateKeyWallet | null = null; + + #createWalletPromise: Promise | null = null; + + constructor(auth: Auth) { + this.#auth = auth; + } + + async #getUserOrThrow(): Promise { + const user = await this.#auth.getUser(); + if (!user) { + throw new WalletError('User not authenticated', WalletErrorType.NOT_LOGGED_IN_ERROR); + } + return user; + } + + async #getWalletInstance(): Promise { + let privateKeyWallet = this.#privateKeyWallet; + if (!privateKeyWallet) { + privateKeyWallet = await this.#createWallet(); + } + + const user = await this.#getUserOrThrow(); + if (user.profile.sub !== privateKeyWallet.userIdentifier) { + privateKeyWallet = await this.#createWallet(user); + } + + return privateKeyWallet; + } + + async #createWallet(user?: User): Promise { + if (this.#createWalletPromise) return this.#createWalletPromise; + + this.#createWalletPromise = (async () => { + try { + this.#privateKeyWallet = null; + const authenticatedUser = user || (await this.#getUserOrThrow()); + + const privateKeyHash = keccak256(toBytes(`${authenticatedUser.profile.sub}-sequence`)); + const signer = new Signers.Pk.Pk(privateKeyHash); + const signerAddress = signer.address; + + this.#privateKeyWallet = { + userIdentifier: authenticatedUser.profile.sub, + signerAddress, + signer, + privateKey: privateKeyHash, + }; + + return this.#privateKeyWallet; + } catch (error) { + const errorMessage = `Failed to create private key wallet: ${(error as Error).message}`; + throw new WalletError(errorMessage, WalletErrorType.WALLET_CONNECTION_ERROR); + } finally { + this.#createWalletPromise = null; + } + })(); + + return this.#createWalletPromise; + } + + async getAddress(): Promise { + const wallet = await this.#getWalletInstance(); + return wallet.signerAddress; + } + + async signPayload( + walletAddress: Address.Address, + chainId: number, + payload: Payload.Parented, + ): Promise { + const pkWallet = await this.#getWalletInstance(); + return pkWallet.signer.sign(walletAddress, chainId, payload); + } + + async signMessage(message: string | Uint8Array): Promise { + const pkWallet = await this.#getWalletInstance(); + + // Use viem's account to sign + const account = privateKeyToAccount(pkWallet.privateKey); + const messageToSign = typeof message === 'string' ? message : { raw: message }; + + return await account.signMessage({ message: messageToSign }); + } +} diff --git a/packages/wallet/src/sequence/signer/types.ts b/packages/wallet/src/sequence/signer/types.ts new file mode 100644 index 0000000000..5e77709aba --- /dev/null +++ b/packages/wallet/src/sequence/signer/types.ts @@ -0,0 +1,24 @@ +import { Address } from 'ox'; +import { + Payload, + Signature as SequenceSignature, +} from '@0xsequence/wallet-primitives'; + +/** + * Signer interface for Sequence wallet operations. + * Used by non-zkEVM chains (e.g., Arbitrum). + */ +export interface SequenceSigner { + /** Get the signer's address */ + getAddress(): Promise; + + /** Sign a Sequence payload (for transactions) */ + signPayload( + walletAddress: Address.Address, + chainId: number, + payload: Payload.Parented, + ): Promise; + + /** Sign a message (EIP-191 personal_sign) */ + signMessage(message: string | Uint8Array): Promise; +} diff --git a/packages/wallet/src/sequence/user/index.ts b/packages/wallet/src/sequence/user/index.ts new file mode 100644 index 0000000000..d50728984e --- /dev/null +++ b/packages/wallet/src/sequence/user/index.ts @@ -0,0 +1,2 @@ +export { registerUser } from './registerUser'; +export type { RegisterUserInput } from './registerUser'; diff --git a/packages/wallet/src/sequence/user/registerUser.ts b/packages/wallet/src/sequence/user/registerUser.ts new file mode 100644 index 0000000000..33d8637294 --- /dev/null +++ b/packages/wallet/src/sequence/user/registerUser.ts @@ -0,0 +1,100 @@ +import { MultiRollupApiClients } from '@imtbl/generated-clients'; +import { Flow } from '@imtbl/metrics'; +import type { PublicClient } from 'viem'; +import { getEip155ChainId } from '../../zkEvm/walletHelpers'; +import { Auth } from '@imtbl/auth'; +import { JsonRpcError, RpcErrorCode } from '../../zkEvm/JsonRpcError'; +import { SequenceSigner } from '../signer'; + +export type RegisterUserInput = { + auth: Auth; + ethSigner: SequenceSigner; + multiRollupApiClients: MultiRollupApiClients; + accessToken: string; + rpcProvider: PublicClient; + flow: Flow; +}; + +const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport'; + +/** + * Format the signature for the registration API. + * Converts v value from 27/28 format to 0/1 recovery param format. + */ +function formatSignature(signature: string): string { + const sig = signature.startsWith('0x') ? signature.slice(2) : signature; + const r = sig.substring(0, 64); + const s = sig.substring(64, 128); + const v = sig.substring(128, 130); + + const vNum = parseInt(v, 16); + const recoveryParam = vNum >= 27 ? vNum - 27 : vNum; + const vHex = recoveryParam.toString(16).padStart(2, '0'); + + return `0x${r}${s}${vHex}`; +} + +/** + * Register a user for a non-zkEVM chain (e.g., Arbitrum). + * Creates a counterfactual address for the user on the specified chain. + */ +export async function registerUser({ + auth, + ethSigner, + multiRollupApiClients, + accessToken, + rpcProvider, + flow, +}: RegisterUserInput): Promise { + // Parallelize the operations that can happen concurrently + const getAddressPromise = ethSigner.getAddress(); + getAddressPromise.then(() => flow.addEvent('endGetAddress')); + + const signMessagePromise = ethSigner.signMessage(MESSAGE_TO_SIGN).then(formatSignature); + signMessagePromise.then(() => flow.addEvent('endSignRaw')); + + const detectNetworkPromise = rpcProvider.getChainId(); + detectNetworkPromise.then(() => flow.addEvent('endDetectNetwork')); + + const listChainsPromise = multiRollupApiClients.chainsApi.listChains(); + listChainsPromise.then(() => flow.addEvent('endListChains')); + + const [ethereumAddress, ethereumSignature, chainId, chainListResponse] = await Promise.all([ + getAddressPromise, + signMessagePromise, + detectNetworkPromise, + listChainsPromise, + ]); + + const eipChainId = getEip155ChainId(Number(chainId)); + const chainName = chainListResponse.data?.result?.find((chain) => chain.id === eipChainId)?.name; + if (!chainName) { + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + `Chain name does not exist for chain id ${chainId}`, + ); + } + + try { + const registrationResponse = await multiRollupApiClients.passportApi.createCounterfactualAddressV2({ + chainName, + createCounterfactualAddressRequest: { + ethereum_address: ethereumAddress, + ethereum_signature: ethereumSignature, + }, + }, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + flow.addEvent('endCreateCounterfactualAddress'); + + auth.forceUserRefreshInBackground(); + + return registrationResponse.data.counterfactual_address; + } catch (error) { + flow.addEvent('errorRegisteringUser'); + throw new JsonRpcError( + RpcErrorCode.INTERNAL_ERROR, + `Failed to create counterfactual address: ${error}`, + ); + } +} diff --git a/packages/wallet/src/types.ts b/packages/wallet/src/types.ts index 863487f465..c4f85acbf3 100644 --- a/packages/wallet/src/types.ts +++ b/packages/wallet/src/types.ts @@ -210,6 +210,11 @@ export interface ChainConfig { /** Sequence RPC node URL TODO: check if this can be removed and only use rpcUrl */ nodeUrl?: string; + + /** + * Sequence Identity Instrument endpoint (for non-zkEVM chains in prod/sandbox) + */ + sequenceIdentityInstrumentEndpoint?: string; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50f6441917..57de7b5ad1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1360,7 +1360,7 @@ importers: version: 5.0.7(rollup@4.28.0) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.28.0)(tslib@2.7.0)(typescript@5.6.2) + version: 11.1.6(rollup@4.28.0)(tslib@2.8.1)(typescript@5.6.2) '@segment/analytics-next': specifier: ^1.53.2 version: 1.54.0(encoding@0.1.13) @@ -2316,6 +2316,18 @@ importers: specifier: ~2.18.0 version: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) devDependencies: + '@0xsequence/identity-instrument': + specifier: 3.0.0-beta.10 + version: 3.0.0-beta.10(typescript@5.6.2) + '@0xsequence/wallet-core': + specifier: 3.0.0-beta.10 + version: 3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@0xsequence/wallet-primitives': + specifier: 3.0.0-beta.10 + version: 3.0.0-beta.10(typescript@5.6.2) + '@0xsequence/wallet-wdk': + specifier: 3.0.0-beta.10 + version: 3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) '@jest/test-sequencer': specifier: ^29.7.0 version: 29.7.0 @@ -2337,6 +2349,9 @@ importers: jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ox: + specifier: ^0.9.17 + version: 0.9.17(typescript@5.6.2) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@18.15.13)(typescript@5.6.2) @@ -2754,11 +2769,32 @@ packages: peerDependencies: ethers: '>=6' + '@0xsequence/guard@3.0.0-beta.10': + resolution: {integrity: sha512-CNf3+ivEiUT7op8WKhrqWnDNtuEpF5NLYiV4X1sl+CkDawzxBlLgsmNrK8isPF75x5HwQGy4fH4F/U3O80crEQ==} + + '@0xsequence/identity-instrument@3.0.0-beta.10': + resolution: {integrity: sha512-Dm11v2vuK3hiKzl8PYvGqCz5Y+6/bVViswo30w5J16LVey+AzhltoI6ezqUe/h9SpLi/AT7ZQTVJipSOae6Prw==} + + '@0xsequence/relayer@3.0.0-beta.10': + resolution: {integrity: sha512-d4lw9o6rgVNHTpHPqv7u8/JBJ0MfmoOUbJE9cMRDqNKGv63/DMl3UNX9hZiqKvR32xhYpoO6d0DBzp26TUHwZg==} + + '@0xsequence/tee-verifier@0.1.2': + resolution: {integrity: sha512-7sKr8/T4newknx6LAukjlRI3siGiGhBnZohz2Z3jX0zb0EBQdKUq0L//A7CPSckHFPxTg/QvQU2v8e9x9GfkDw==} + '@0xsequence/utils@2.3.38': resolution: {integrity: sha512-XCe17omFbLjQnDW7HNhNzTqcpeeiXeSCc5ttFjOYex+GO8v9imPt3qbcn4N2v4dlylfkSfpdh4DcnKlcAPAtFw==} peerDependencies: ethers: '>=6' + '@0xsequence/wallet-core@3.0.0-beta.10': + resolution: {integrity: sha512-tOZCnwInFgk61za5VCRblmA+ur94DFiREW4TCGTMvTWHEl4Ins6xoaiywTRdnMvoWFHITPkGraA+f1xB+XRvmA==} + + '@0xsequence/wallet-primitives@3.0.0-beta.10': + resolution: {integrity: sha512-yE+4+afIiPh369U9b3GLhQ2R0giirF6JSo5LlBJD9nBAFqsgtJwdwD1wYIDXYvCYc8/1HFvuAQ4oa+uc7OXRZw==} + + '@0xsequence/wallet-wdk@3.0.0-beta.10': + resolution: {integrity: sha512-F+gh8W4TW5YN1xEiqerzWfsq085LpwYFHoKclk4Ngv6AzQdh34DEsiO0CEWlPVFlUR43N3c9PC5xXciw1fuRww==} + '@0xsquid/sdk@2.8.25': resolution: {integrity: sha512-fSMKVdKIX8G3qFpoTf3WfcyjhGdc9hE0uSu1bs1gsh4+iG19ILguDdrY8g87dUknt9PCKBb6TIt1QeYEgbXjdA==} @@ -8305,6 +8341,10 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + asn1js@3.0.7: + resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} + engines: {node: '>=12.0.0'} + assert@2.0.0: resolution: {integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==} @@ -8790,6 +8830,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + bytestreamjs@2.0.1: + resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} + engines: {node: '>=6.0.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -8868,6 +8912,10 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + cbor2@1.12.0: + resolution: {integrity: sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==} + engines: {node: '>=18.7'} + cbor@8.1.0: resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} engines: {node: '>=12.19'} @@ -11510,6 +11558,9 @@ packages: idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + idb@8.0.3: + resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} + identity-obj-proxy@3.0.0: resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} engines: {node: '>=4'} @@ -12484,6 +12535,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-canonicalize@2.0.0: + resolution: {integrity: sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg==} + json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} @@ -13691,6 +13745,14 @@ packages: outvariant@1.4.0: resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + ox@0.9.17: + resolution: {integrity: sha512-rKAnhzhRU3Xh3hiko+i1ZxywZ55eWQzeS/Q4HRKLx2PqfHOolisZHErSsJVipGlmQKHW5qwOED/GighEw9dbLg==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + ox@0.9.6: resolution: {integrity: sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==} peerDependencies: @@ -14014,6 +14076,10 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + pkijs@3.3.3: + resolution: {integrity: sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==} + engines: {node: '>=16.0.0'} + playwright-core@1.45.3: resolution: {integrity: sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==} engines: {node: '>=18'} @@ -14665,6 +14731,13 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} @@ -16391,6 +16464,9 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsort@0.0.1: resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} @@ -16796,6 +16872,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -17396,11 +17476,83 @@ snapshots: '@0xsequence/utils': 2.3.38(ethers@6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)) ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@0xsequence/guard@3.0.0-beta.10(typescript@5.6.2)': + dependencies: + ox: 0.9.17(typescript@5.6.2) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/identity-instrument@3.0.0-beta.10(typescript@5.6.2)': + dependencies: + json-canonicalize: 2.0.0 + jwt-decode: 4.0.0 + ox: 0.9.17(typescript@5.6.2) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/relayer@3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': + dependencies: + '@0xsequence/wallet-primitives': 3.0.0-beta.10(typescript@5.6.2) + mipd: 0.0.7(typescript@5.6.2) + ox: 0.9.17(typescript@5.6.2) + viem: 2.41.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/tee-verifier@0.1.2': + dependencies: + cbor2: 1.12.0 + pkijs: 3.3.3 + '@0xsequence/utils@2.3.38(ethers@6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) js-base64: 3.7.8 + '@0xsequence/wallet-core@3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': + dependencies: + '@0xsequence/guard': 3.0.0-beta.10(typescript@5.6.2) + '@0xsequence/relayer': 3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@0xsequence/wallet-primitives': 3.0.0-beta.10(typescript@5.6.2) + mipd: 0.0.7(typescript@5.6.2) + ox: 0.9.17(typescript@5.6.2) + viem: 2.41.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/wallet-primitives@3.0.0-beta.10(typescript@5.6.2)': + dependencies: + ox: 0.9.17(typescript@5.6.2) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/wallet-wdk@3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': + dependencies: + '@0xsequence/guard': 3.0.0-beta.10(typescript@5.6.2) + '@0xsequence/identity-instrument': 3.0.0-beta.10(typescript@5.6.2) + '@0xsequence/relayer': 3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@0xsequence/tee-verifier': 0.1.2 + '@0xsequence/wallet-core': 3.0.0-beta.10(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@0xsequence/wallet-primitives': 3.0.0-beta.10(typescript@5.6.2) + idb: 8.0.3 + jwt-decode: 4.0.0 + ox: 0.9.17(typescript@5.6.2) + uuid: 13.0.0 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + '@0xsquid/sdk@2.8.25(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@cosmjs/cosmwasm-stargate': 0.32.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -19369,7 +19521,7 @@ snapshots: '@cosmjs/encoding': 0.31.3 '@cosmjs/math': 0.31.3 '@cosmjs/utils': 0.31.3 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.8.0 bn.js: 5.2.1 elliptic: 6.6.1 libsodium-wrappers-sumo: 0.7.15 @@ -21146,7 +21298,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.2 - '@jest/test-sequencer@26.6.3(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(utf-8-validate@5.0.10)': + '@jest/test-sequencer@26.6.3': dependencies: '@jest/test-result': 26.6.2 graceful-fs: 4.2.11 @@ -21154,11 +21306,7 @@ snapshots: jest-runner: 26.6.3(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(utf-8-validate@5.0.10) jest-runtime: 26.6.3(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(utf-8-validate@5.0.10) transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - ts-node - - utf-8-validate '@jest/test-sequencer@27.5.1': dependencies: @@ -21513,7 +21661,7 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.8.0 '@scure/base': 1.1.7 '@types/debug': 4.1.8 debug: 4.3.7(supports-color@8.1.1) @@ -21527,7 +21675,7 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.8.0 '@scure/base': 1.1.7 '@types/debug': 4.1.8 debug: 4.3.7(supports-color@8.1.1) @@ -23277,14 +23425,14 @@ snapshots: optionalDependencies: rollup: 4.28.0 - '@rollup/plugin-typescript@11.1.6(rollup@4.28.0)(tslib@2.7.0)(typescript@5.6.2)': + '@rollup/plugin-typescript@11.1.6(rollup@4.28.0)(tslib@2.8.1)(typescript@5.6.2)': dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.28.0) resolve: 1.22.8 typescript: 5.6.2 optionalDependencies: rollup: 4.28.0 - tslib: 2.7.0 + tslib: 2.8.1 '@rollup/pluginutils@3.1.0(rollup@2.79.1)': dependencies: @@ -26143,6 +26291,12 @@ snapshots: dependencies: safer-buffer: 2.1.2 + asn1js@3.0.7: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + assert@2.0.0: dependencies: es6-object-assign: 1.1.0 @@ -26158,7 +26312,7 @@ snapshots: ast-types@0.15.2: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 astral-regex@1.0.0: {} @@ -26170,7 +26324,7 @@ snapshots: async-mutex@0.2.6: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 async@1.5.2: {} @@ -26875,6 +27029,8 @@ snapshots: bytes@3.1.2: {} + bytestreamjs@2.0.1: {} + cac@6.7.14: {} cacache@17.1.3: @@ -26968,6 +27124,8 @@ snapshots: caseless@0.12.0: {} + cbor2@1.12.0: {} + cbor@8.1.0: dependencies: nofilter: 3.1.0 @@ -30830,6 +30988,8 @@ snapshots: idb@7.1.1: {} + idb@8.0.3: {} + identity-obj-proxy@3.0.0: dependencies: harmony-reflect: 1.6.2 @@ -31570,7 +31730,7 @@ snapshots: jest-config@26.6.3(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.26.10 - '@jest/test-sequencer': 26.6.3(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.13))(@types/node@20.14.13)(typescript@5.6.2))(utf-8-validate@5.0.10) + '@jest/test-sequencer': 26.6.3 '@jest/types': 26.6.2 babel-jest: 26.6.3(@babel/core@7.26.10) chalk: 4.1.2 @@ -33059,6 +33219,8 @@ snapshots: json-buffer@3.0.1: {} + json-canonicalize@2.0.0: {} + json-parse-better-errors@1.0.2: {} json-parse-even-better-errors@2.3.1: {} @@ -34592,6 +34754,21 @@ snapshots: outvariant@1.4.0: {} + ox@0.9.17(typescript@5.6.2): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.1.0(typescript@5.6.2) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - zod + ox@0.9.6(typescript@5.6.2): dependencies: '@adraffy/ens-normalize': 1.11.1 @@ -34947,6 +35124,15 @@ snapshots: dependencies: find-up: 3.0.0 + pkijs@3.3.3: + dependencies: + '@noble/hashes': 1.4.0 + asn1js: 3.0.7 + bytestreamjs: 2.0.1 + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + playwright-core@1.45.3: {} playwright@1.45.3: @@ -35620,6 +35806,12 @@ snapshots: pure-rand@6.1.0: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.5: {} + q@1.5.1: {} qr-code-styling@1.6.0-rc.1: @@ -36259,7 +36451,7 @@ snapshots: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.7.0 + tslib: 2.8.1 rechoir@0.6.2: dependencies: @@ -38241,6 +38433,8 @@ snapshots: tslib@2.7.0: {} + tslib@2.8.1: {} + tsort@0.0.1: {} tsup@8.3.0(@swc/core@1.15.3(@swc/helpers@0.5.13))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.6.2)(yaml@2.5.0): @@ -38646,6 +38840,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@13.0.0: {} + uuid@8.3.2: {} uuid@9.0.1: {}