diff --git a/guides/integrating-the-safe-core-sdk.md b/guides/integrating-the-safe-core-sdk.md index 03927ebe5..d96eeca33 100644 --- a/guides/integrating-the-safe-core-sdk.md +++ b/guides/integrating-the-safe-core-sdk.md @@ -291,7 +291,7 @@ type SafeMultisigTransactionResponse = { transactionHash?: string confirmationType?: string signature: string - signatureType?: string + signatureType?: SignatureType }, // ... ] diff --git a/packages/api-kit/package.json b/packages/api-kit/package.json index 8ec5b808b..78ab62184 100644 --- a/packages/api-kit/package.json +++ b/packages/api-kit/package.json @@ -1,6 +1,6 @@ { "name": "@safe-global/api-kit", - "version": "2.5.4", + "version": "2.5.5", "description": "SDK that facilitates the interaction with the Safe Transaction Service API", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -39,7 +39,7 @@ ], "homepage": "https://github.com/safe-global/safe-core-sdk#readme", "devDependencies": { - "@safe-global/relay-kit": "^3.2.4", + "@safe-global/relay-kit": "^3.3.0", "@safe-global/testing-kit": "^0.1.1", "@types/chai": "^4.3.19", "@types/chai-as-promised": "^7.1.8", @@ -58,8 +58,8 @@ "web3": "^4.12.1" }, "dependencies": { - "@safe-global/protocol-kit": "^5.0.4", - "@safe-global/types-kit": "^1.0.0", + "@safe-global/protocol-kit": "^5.1.0", + "@safe-global/types-kit": "^1.0.1", "node-fetch": "^2.7.0", "viem": "^2.21.8" } diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index be9089339..2067ac103 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -115,20 +115,28 @@ class SafeApiKit { /** * Decodes the specified Safe transaction data. * - * @param data - The Safe transaction data + * @param data - The Safe transaction data. '0x' prefixed hexadecimal string. + * @param to - The address of the receiving contract. If provided, the decoded data will be more accurate, as in case of an ABI collision the Safe Transaction Service would know which ABI to use * @returns The transaction data decoded * @throws "Invalid data" * @throws "Not Found" * @throws "Ensure this field has at least 1 hexadecimal chars (not counting 0x)." */ - async decodeData(data: string): Promise { + async decodeData(data: string, to?: string): Promise { if (data === '') { throw new Error('Invalid data') } + + const dataDecoderRequest: { data: string; to?: string } = { data } + + if (to) { + dataDecoderRequest.to = to + } + return sendRequest({ url: `${this.#txServiceBaseUrl}/v1/data-decoder/`, method: HttpMethod.Post, - body: { data } + body: dataDecoderRequest }) } diff --git a/packages/api-kit/src/types/safeTransactionServiceTypes.ts b/packages/api-kit/src/types/safeTransactionServiceTypes.ts index 5b75d0147..6a044b7ad 100644 --- a/packages/api-kit/src/types/safeTransactionServiceTypes.ts +++ b/packages/api-kit/src/types/safeTransactionServiceTypes.ts @@ -4,7 +4,8 @@ import { SafeTransactionData, UserOperation, SafeOperationResponse, - ListResponse + ListResponse, + SignatureType } from '@safe-global/types-kit' export type ListOptions = { @@ -231,7 +232,7 @@ export type SafeMessageConfirmation = { readonly modified: string readonly owner: string readonly signature: string - readonly signatureType: string + readonly signatureType: SignatureType } export type SafeMessage = { diff --git a/packages/api-kit/tests/e2e/decodeData.test.ts b/packages/api-kit/tests/e2e/decodeData.test.ts index 7d96a5548..436788970 100644 --- a/packages/api-kit/tests/e2e/decodeData.test.ts +++ b/packages/api-kit/tests/e2e/decodeData.test.ts @@ -45,4 +45,22 @@ describe('decodeData', () => { }) ) }) + + it('should decode the data and allow to specify the receiving contract', async () => { + const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1' + const to = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' + const decodedData = await safeApiKit.decodeData(data, to) + chai.expect(JSON.stringify(decodedData)).to.be.equal( + JSON.stringify({ + method: 'enableModule', + parameters: [ + { + name: 'module', + type: 'address', + value: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' + } + ] + }) + ) + }) }) diff --git a/packages/protocol-kit/package.json b/packages/protocol-kit/package.json index 7c26a1ea3..6920649cd 100644 --- a/packages/protocol-kit/package.json +++ b/packages/protocol-kit/package.json @@ -1,6 +1,6 @@ { "name": "@safe-global/protocol-kit", - "version": "5.0.4", + "version": "5.1.0", "description": "SDK that facilitates the interaction with Safe Smart Accounts", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -66,12 +66,15 @@ "web3": "^4.12.1" }, "dependencies": { - "@noble/hashes": "^1.3.3", - "@safe-global/safe-deployments": "^1.37.14", + "@safe-global/safe-deployments": "^1.37.20", "@safe-global/safe-modules-deployments": "^2.2.4", - "@safe-global/types-kit": "^1.0.0", + "@safe-global/types-kit": "^1.0.1", "abitype": "^1.0.2", "semver": "^7.6.3", "viem": "^2.21.8" + }, + "optionalDependencies": { + "@noble/curves": "^1.6.0", + "@peculiar/asn1-schema": "^2.3.13" } } diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index 3b9dd9859..6b39deeb8 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -42,7 +42,8 @@ import { SigningMethodType, SwapOwnerTxParams, SafeModulesPaginated, - RemovePasskeyOwnerTxParams + RemovePasskeyOwnerTxParams, + PasskeyArgType } from './types' import { EthSafeSignature, @@ -59,7 +60,8 @@ import { generateSignature, preimageSafeMessageHash, preimageSafeTransactionHash, - adjustVInSignature + adjustVInSignature, + extractPasskeyData } from './utils' import EthSafeTransaction from './utils/transactions/SafeTransaction' import { SafeTransactionOptionalProps } from './utils/transactions/types' @@ -1698,6 +1700,15 @@ class Safe { }): ContractInfo | undefined => { return getContractInfo(contractAddress) } + + /** + * This method creates a signer to be used with the init method + * @param {Credential} credential - The credential to be used to create the signer. Can be generated in the web with navigator.credentials.create + * @returns {PasskeyArgType} - The signer to be used with the init method + */ + static createPasskeySigner = async (credential: Credential): Promise => { + return extractPasskeyData(credential) + } } export default Safe diff --git a/packages/protocol-kit/src/contracts/safeDeploymentContracts.ts b/packages/protocol-kit/src/contracts/safeDeploymentContracts.ts index 9e2685c42..405d2d595 100644 --- a/packages/protocol-kit/src/contracts/safeDeploymentContracts.ts +++ b/packages/protocol-kit/src/contracts/safeDeploymentContracts.ts @@ -112,13 +112,15 @@ export async function getCompatibilityFallbackHandlerContract({ export async function getMultiSendContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const multiSendContract = await getMultiSendContractInstance( safeVersion, safeProvider, customContracts?.multiSendAddress, - customContracts?.multiSendAbi + customContracts?.multiSendAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed(multiSendContract.getAddress()) @@ -131,13 +133,15 @@ export async function getMultiSendContract({ export async function getMultiSendCallOnlyContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const multiSendCallOnlyContract = await getMultiSendCallOnlyContractInstance( safeVersion, safeProvider, customContracts?.multiSendCallOnlyAddress, - customContracts?.multiSendCallOnlyAbi + customContracts?.multiSendCallOnlyAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed( @@ -152,13 +156,15 @@ export async function getMultiSendCallOnlyContract({ export async function getSignMessageLibContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const signMessageLibContract = await getSignMessageLibContractInstance( safeVersion, safeProvider, customContracts?.signMessageLibAddress, - customContracts?.signMessageLibAbi + customContracts?.signMessageLibAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed( @@ -173,13 +179,15 @@ export async function getSignMessageLibContract({ export async function getCreateCallContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const createCallContract = await getCreateCallContractInstance( safeVersion, safeProvider, customContracts?.createCallAddress, - customContracts?.createCallAbi + customContracts?.createCallAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed(createCallContract.getAddress()) @@ -192,13 +200,15 @@ export async function getCreateCallContract({ export async function getSimulateTxAccessorContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const simulateTxAccessorContract = await getSimulateTxAccessorContractInstance( safeVersion, safeProvider, customContracts?.simulateTxAccessorAddress, - customContracts?.simulateTxAccessorAbi + customContracts?.simulateTxAccessorAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed( @@ -213,13 +223,15 @@ export async function getSimulateTxAccessorContract({ export async function getSafeWebAuthnSignerFactoryContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const safeWebAuthnSignerFactoryContract = await getSafeWebAuthnSignerFactoryContractInstance( safeVersion, safeProvider, customContracts?.safeWebAuthnSignerFactoryAddress, - customContracts?.safeWebAuthnSignerFactoryAbi + customContracts?.safeWebAuthnSignerFactoryAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed( @@ -234,13 +246,15 @@ export async function getSafeWebAuthnSignerFactoryContract({ export async function getSafeWebAuthnSharedSignerContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType }: GetContractInstanceProps): Promise { const safeWebAuthnSharedSignerContract = await getSafeWebAuthnSharedSignerContractInstance( safeVersion, safeProvider, customContracts?.safeWebAuthnSharedSignerAddress, - customContracts?.safeWebAuthnSharedSignerAbi + customContracts?.safeWebAuthnSharedSignerAbi, + deploymentType ) const isContractDeployed = await safeProvider.isContractDeployed( diff --git a/packages/protocol-kit/src/index.ts b/packages/protocol-kit/src/index.ts index cd12b85cf..9c502e2e0 100644 --- a/packages/protocol-kit/src/index.ts +++ b/packages/protocol-kit/src/index.ts @@ -35,7 +35,6 @@ import { estimateTxGas, estimateSafeTxGas, estimateSafeDeploymentGas, - extractPasskeyCoordinates, extractPasskeyData, validateEthereumAddress, validateEip3770Address @@ -74,7 +73,6 @@ export { estimateSafeTxGas, estimateSafeDeploymentGas, extractPasskeyData, - extractPasskeyCoordinates, ContractManager, CreateCallBaseContract, createERC20TokenTransferTransaction, diff --git a/packages/protocol-kit/src/managers/contractManager.ts b/packages/protocol-kit/src/managers/contractManager.ts index f71590c8e..651a66fa1 100644 --- a/packages/protocol-kit/src/managers/contractManager.ts +++ b/packages/protocol-kit/src/managers/contractManager.ts @@ -62,13 +62,15 @@ class ContractManager { this.#multiSendContract = await getMultiSendContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType: predictedSafe?.safeDeploymentConfig?.deploymentType }) this.#multiSendCallOnlyContract = await getMultiSendCallOnlyContract({ safeProvider, safeVersion, - customContracts + customContracts, + deploymentType: predictedSafe?.safeDeploymentConfig?.deploymentType }) } diff --git a/packages/protocol-kit/src/types/passkeys.ts b/packages/protocol-kit/src/types/passkeys.ts index 912c73e5c..e440692d4 100644 --- a/packages/protocol-kit/src/types/passkeys.ts +++ b/packages/protocol-kit/src/types/passkeys.ts @@ -3,8 +3,11 @@ export type PasskeyCoordinates = { y: string } +export type GetPasskeyCredentialFn = (options?: CredentialRequestOptions) => Promise + export type PasskeyArgType = { rawId: string // required to sign data coordinates: PasskeyCoordinates // required to sign data - customVerifierAddress?: string // optional + customVerifierAddress?: string + getFn?: GetPasskeyCredentialFn } diff --git a/packages/protocol-kit/src/types/safeProvider.ts b/packages/protocol-kit/src/types/safeProvider.ts index 6e5c40a6e..d473dae2d 100644 --- a/packages/protocol-kit/src/types/safeProvider.ts +++ b/packages/protocol-kit/src/types/safeProvider.ts @@ -50,7 +50,7 @@ export type HexAddress = string export type PrivateKey = string export type HttpTransport = string export type SocketTransport = string -export type SafeSigner = HexAddress | PrivateKey | PasskeyClient +export type SafeSigner = HexAddress | PrivateKey | PasskeyArgType | PasskeyClient export type SafeProviderConfig = { /** signerOrProvider - Ethers signer or provider */ diff --git a/packages/protocol-kit/src/utils/eip-3770/config.ts b/packages/protocol-kit/src/utils/eip-3770/config.ts index 82dc8ed51..18f7562d2 100644 --- a/packages/protocol-kit/src/utils/eip-3770/config.ts +++ b/packages/protocol-kit/src/utils/eip-3770/config.ts @@ -65,7 +65,7 @@ export const networks: NetworkShortName[] = [ { chainId: 282n, shortName: 'zkTCRO' }, { chainId: 288n, shortName: 'boba' }, { chainId: 291n, shortName: 'orderly' }, - { chainId: 300n, shortName: 'ogn' }, + { chainId: 300n, shortName: 'zksync-sepolia' }, { chainId: 314n, shortName: 'filecoin' }, { chainId: 321n, shortName: 'kcs' }, { chainId: 322n, shortName: 'kcst' }, @@ -112,6 +112,7 @@ export const networks: NetworkShortName[] = [ { chainId: 1337n, shortName: 'geth' }, { chainId: 1442n, shortName: 'testnet-zkEVM-mango' }, { chainId: 1513n, shortName: 'Story' }, + { chainId: 1516n, shortName: 'story-testnet' }, { chainId: 1559n, shortName: 'tenet' }, { chainId: 1625n, shortName: 'gravity' }, { chainId: 1663n, shortName: 'Gobi' }, @@ -120,6 +121,8 @@ export const networks: NetworkShortName[] = [ { chainId: 1811n, shortName: 'lif3-testnet' }, { chainId: 1890n, shortName: 'lightlink_phoenix' }, { chainId: 1891n, shortName: 'lightlink_pegasus' }, + { chainId: 1923n, shortName: 'swell-l2' }, + { chainId: 1924n, shortName: 'swell-l2-testnet' }, { chainId: 1984n, shortName: 'euntest' }, { chainId: 1998n, shortName: 'kyoto-testnet' }, { chainId: 2000n, shortName: 'dc' }, @@ -144,6 +147,7 @@ export const networks: NetworkShortName[] = [ { chainId: 3737n, shortName: 'csb' }, { chainId: 3776n, shortName: 'astrzk' }, { chainId: 4002n, shortName: 'tftm' }, + { chainId: 4061n, shortName: 'Nahmii3Mainnet' }, { chainId: 4062n, shortName: 'Nahmii3Testnet' }, { chainId: 4078n, shortName: 'muster' }, { chainId: 4157n, shortName: 'crossfi-testnet' }, @@ -183,6 +187,7 @@ export const networks: NetworkShortName[] = [ { chainId: 8822n, shortName: 'iotaevm' }, { chainId: 9000n, shortName: 'evmos-testnet' }, { chainId: 9001n, shortName: 'evmos' }, + { chainId: 9700n, shortName: 'MainnetDev' }, { chainId: 9728n, shortName: 'boba-testnet' }, { chainId: 10000n, shortName: 'smartbch' }, { chainId: 10001n, shortName: 'smartbchtest' }, @@ -228,6 +233,7 @@ export const networks: NetworkShortName[] = [ { chainId: 42161n, shortName: 'arb1' }, { chainId: 42170n, shortName: 'arb-nova' }, { chainId: 42220n, shortName: 'celo' }, + { chainId: 42421n, shortName: 'rwa' }, { chainId: 42793n, shortName: 'etlk' }, { chainId: 43113n, shortName: 'fuji' }, { chainId: 43114n, shortName: 'avax' }, @@ -260,6 +266,8 @@ export const networks: NetworkShortName[] = [ { chainId: 84532n, shortName: 'basesep' }, { chainId: 90001n, shortName: 'dhobyghaut' }, { chainId: 97435n, shortName: 'sling' }, + { chainId: 98864n, shortName: 'plume-devnet' }, + { chainId: 98865n, shortName: 'plume' }, { chainId: 103454n, shortName: 'masatest' }, { chainId: 105105n, shortName: 'stratis' }, { chainId: 111188n, shortName: 're-al' }, @@ -274,6 +282,7 @@ export const networks: NetworkShortName[] = [ { chainId: 314159n, shortName: 'filecoin-calibration' }, { chainId: 328527n, shortName: 'nal' }, { chainId: 333999n, shortName: 'olympus' }, + { chainId: 381931n, shortName: 'metal' }, { chainId: 421611n, shortName: 'arb-rinkeby' }, { chainId: 421613n, shortName: 'arb-goerli' }, { chainId: 421614n, shortName: 'arb-sep' }, @@ -287,6 +296,8 @@ export const networks: NetworkShortName[] = [ { chainId: 656476n, shortName: 'open-campus-codex' }, { chainId: 660279n, shortName: 'xai' }, { chainId: 713715n, shortName: 'sei-devnet' }, + { chainId: 743111n, shortName: 'hemi-sep' }, + { chainId: 763373n, shortName: 'inksepolia' }, { chainId: 764984n, shortName: 'lamina1test' }, { chainId: 808813n, shortName: 'bob-sepolia' }, { chainId: 810180n, shortName: 'zklink-nova' }, @@ -318,13 +329,15 @@ export const networks: NetworkShortName[] = [ { chainId: 1313161554n, shortName: 'aurora' }, { chainId: 1313161555n, shortName: 'aurora-testnet' }, { chainId: 1511670449n, shortName: 'GPT' }, + { chainId: 1570754601n, shortName: 'hst-test' }, { chainId: 1666600000n, shortName: 'hmy-s0' }, { chainId: 1666700000n, shortName: 'hmy-b-s0' }, { chainId: 11297108099n, shortName: 'tpalm' }, { chainId: 11297108109n, shortName: 'palm' }, { chainId: 37714555429n, shortName: 'xaitestnet' }, { chainId: 88153591557n, shortName: 'arb-blueberry' }, - { chainId: 123420000220n, shortName: 'fluence-stage' } + { chainId: 123420000220n, shortName: 'fluence-stage' }, + { chainId: 920637907288165n, shortName: 'kkrt-starknet-sepolia' } ] if (process.env.TEST_NETWORK === 'hardhat') { diff --git a/packages/protocol-kit/src/utils/passkeys/PasskeyClient.ts b/packages/protocol-kit/src/utils/passkeys/PasskeyClient.ts index 3307cfd6a..40bec29dd 100644 --- a/packages/protocol-kit/src/utils/passkeys/PasskeyClient.ts +++ b/packages/protocol-kit/src/utils/passkeys/PasskeyClient.ts @@ -22,7 +22,8 @@ import { PasskeyArgType, PasskeyClient, SafeWebAuthnSignerFactoryContractImplementationType, - SafeWebAuthnSharedSignerContractImplementationType + SafeWebAuthnSharedSignerContractImplementationType, + GetPasskeyCredentialFn } from '@safe-global/protocol-kit/types' import { getDefaultFCLP256VerifierAddress } from './extractPasskeyData' import { asHex } from '../types' @@ -31,20 +32,29 @@ import isSharedSigner from './isSharedSigner' export const PASSKEY_CLIENT_KEY = 'passkeyWallet' export const PASSKEY_CLIENT_NAME = 'Passkey Wallet Client' -const sign = async (passkeyRawId: Uint8Array, data: Uint8Array): Promise => { - const assertion = (await navigator.credentials.get({ +const sign = async ( + passkeyRawId: Uint8Array, + data: Uint8Array, + getFn?: GetPasskeyCredentialFn +): Promise => { + // Avoid loosing the context for navigator.credentials.get function that leads to an error + const getCredentials = getFn || navigator.credentials.get.bind(navigator.credentials) + + const assertion = (await getCredentials({ publicKey: { challenge: data, allowCredentials: [{ type: 'public-key', id: passkeyRawId }], userVerification: 'required' } - })) as PublicKeyCredential & { response: AuthenticatorAssertionResponse } + })) as PublicKeyCredential + + const assertionResponse = assertion.response as AuthenticatorAssertionResponse - if (!assertion?.response?.authenticatorData) { + if (!assertionResponse?.authenticatorData) { throw new Error('Failed to sign data with passkey Signer') } - const { authenticatorData, signature, clientDataJSON } = assertion.response + const { authenticatorData, signature, clientDataJSON } = assertionResponse return encodeAbiParameters(parseAbiParameters('bytes, bytes, uint256[2]'), [ toHex(new Uint8Array(authenticatorData)), @@ -104,10 +114,14 @@ export const createPasskeyClient = async ( .extend(() => ({ signMessage({ message }: { message: SignableMessage }) { if (typeof message === 'string') { - return sign(passkeyRawId, toBytes(message)) + return sign(passkeyRawId, toBytes(message), passkey.getFn) } - return sign(passkeyRawId, isHex(message.raw) ? toBytes(message.raw) : message.raw) + return sign( + passkeyRawId, + isHex(message.raw) ? toBytes(message.raw) : message.raw, + passkey.getFn + ) }, signTransaction, signTypedData, @@ -145,6 +159,17 @@ export const createPasskeyClient = async ( })) as PasskeyClient } +function decodeClientDataJSON(clientDataJSON: ArrayBuffer): string { + const uint8Array = new Uint8Array(clientDataJSON) + + let result = '' + for (let i = 0; i < uint8Array.length; i++) { + result += String.fromCharCode(uint8Array[i]) + } + + return result +} + /** * Compute the additional client data JSON fields. This is the fields other than `type` and * `challenge` (including `origin` and any other additional client data fields that may be @@ -157,7 +182,8 @@ export const createPasskeyClient = async ( * @throws {Error} Throws an error if the client data JSON does not contain the expected 'challenge' field pattern. */ function extractClientDataFields(clientDataJSON: ArrayBuffer): Hex { - const decodedClientDataJSON = new TextDecoder('utf-8').decode(clientDataJSON) + const decodedClientDataJSON = decodeClientDataJSON(clientDataJSON) + const match = decodedClientDataJSON.match( /^\{"type":"webauthn.get","challenge":"[A-Za-z0-9\-_]{43}",(.*)\}$/ ) diff --git a/packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts b/packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts index 5760fd4b6..f11807270 100644 --- a/packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts +++ b/packages/protocol-kit/src/utils/passkeys/extractPasskeyData.ts @@ -1,43 +1,119 @@ -import { getFCLP256VerifierDeployment } from '@safe-global/safe-modules-deployments' import { Buffer } from 'buffer' -import { PasskeyCoordinates, PasskeyArgType } from '@safe-global/protocol-kit/types' +import { getFCLP256VerifierDeployment } from '@safe-global/safe-modules-deployments' +import { PasskeyArgType, PasskeyCoordinates } from '@safe-global/protocol-kit/types' /** - * Extracts and returns the passkey data (coordinates and rawId) from a given passkey Credential. + * Converts a Base64 URL-encoded string to a Uint8Array. * - * @param {Credential} passkeyCredential - The passkey credential generated via `navigator.credentials.create()` using correct parameters. - * @returns {Promise} A promise that resolves to an object containing the coordinates and the rawId derived from the passkey. - * @throws {Error} Throws an error if the coordinates could not be extracted + * This function handles Base64 URL variants by replacing URL-safe characters + * with standard Base64 characters, decodes the Base64 string into a binary string, + * and then converts it into a Uint8Array. + * + * @param {string} base64 - The Base64 URL-encoded string to convert. + * @returns {Uint8Array} The resulting Uint8Array from the decoded Base64 string. */ -export async function extractPasskeyData(passkeyCredential: Credential): Promise { - const passkey = passkeyCredential as PublicKeyCredential - const attestationResponse = passkey.response as AuthenticatorAttestationResponse +function base64ToUint8Array(base64: string): Uint8Array { + const base64Fixed = base64.replace(/-/g, '+').replace(/_/g, '/') + const binaryBuffer = Buffer.from(base64Fixed, 'base64') - const publicKey = attestationResponse.getPublicKey() + return new Uint8Array(binaryBuffer) +} - if (!publicKey) { - throw new Error('Failed to generate passkey Coordinates. getPublicKey() failed') +/** + * Dynamic import libraries required for decoding public keys. + */ +async function importLibs() { + const { p256 } = await import('@noble/curves/p256') + + const { AsnParser, AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } = await import( + '@peculiar/asn1-schema' + ) + + @AsnType({ type: AsnTypeTypes.Sequence }) + class AlgorithmIdentifier { + @AsnProp({ type: AsnPropTypes.ObjectIdentifier }) + public id: string = '' + + @AsnProp({ type: AsnPropTypes.ObjectIdentifier, optional: true }) + public curve: string = '' } - const coordinates = await extractPasskeyCoordinates(publicKey) - const rawId = Buffer.from(passkey.rawId).toString('hex') + @AsnType({ type: AsnTypeTypes.Sequence }) + class ECPublicKey { + @AsnProp({ type: AlgorithmIdentifier }) + public algorithm = new AlgorithmIdentifier() + + @AsnProp({ type: AsnPropTypes.BitString }) + public publicKey: ArrayBuffer = new ArrayBuffer(0) + } return { - rawId, - coordinates + p256, + AsnParser, + ECPublicKey } } - /** - * Extracts and returns coordinates from a given passkey public key. + * Decodes a Base64-encoded ECDSA public key for React Native and extracts the x and y coordinates. * - * @param {ArrayBuffer} publicKey - The public key of the passkey from which coordinates will be extracted. - * @returns {Promise} A promise that resolves to an object containing the coordinates derived from the public key of the passkey. - * @throws {Error} Throws an error if the coordinates could not be extracted via `crypto.subtle.exportKey()` + * This function handles both ASN.1 DER-encoded keys and uncompressed keys. It decodes a Base64-encoded + * public key, checks its format, and extracts the x and y coordinates using the `@noble/curves` library. + * The coordinates are returned as hexadecimal strings prefixed with '0x'. + * + * @param {string} publicKey - The Base64-encoded public key to decode. + * @returns {PasskeyCoordinates} An object containing the x and y coordinates of the public key. + * @throws {Error} Throws an error if the key is empty or if the coordinates cannot be extracted. */ -export async function extractPasskeyCoordinates( - publicKey: ArrayBuffer +export async function decodePublicKeyForReactNative( + publicKey: string ): Promise { + const { p256, AsnParser, ECPublicKey } = await importLibs() + + let publicKeyBytes = base64ToUint8Array(publicKey) + + if (publicKeyBytes.length === 0) { + throw new Error('Decoded public key is empty.') + } + + const isAsn1Encoded = publicKeyBytes[0] === 0x30 + const isUncompressedKey = publicKeyBytes.length === 64 + + if (isAsn1Encoded) { + const asn1ParsedKey = AsnParser.parse(publicKeyBytes.buffer, ECPublicKey) + + publicKeyBytes = new Uint8Array(asn1ParsedKey.publicKey) + } else if (isUncompressedKey) { + const uncompressedKey = new Uint8Array(65) + uncompressedKey[0] = 0x04 + uncompressedKey.set(publicKeyBytes, 1) + + publicKeyBytes = uncompressedKey + } + + const point = p256.ProjectivePoint.fromHex(publicKeyBytes) + + const x = point.x.toString(16).padStart(64, '0') + const y = point.y.toString(16).padStart(64, '0') + + return { + x: '0x' + x, + y: '0x' + y + } +} + +/** + * Decodes an ECDSA public key for the web platform and extracts the x and y coordinates. + * + * This function uses the Web Crypto API to import a public key in SPKI format and then + * exports it to a JWK format to retrieve the x and y coordinates. The coordinates are + * returned as hexadecimal strings prefixed with '0x'. + * + * @param {ArrayBuffer} publicKey - The public key in SPKI format to decode. + * @returns {Promise} A promise that resolves to an object containing + * the x and y coordinates of the public key. + * @throws {Error} Throws an error if the key coordinates cannot be extracted. + */ +export async function decodePublicKeyForWeb(publicKey: ArrayBuffer): Promise { const algorithm = { name: 'ECDSA', namedCurve: 'P-256', @@ -60,6 +136,71 @@ export async function extractPasskeyCoordinates( } } +/** + * Decodes the x and y coordinates of the public key from a created public key credential response. + * + * @param {AuthenticatorResponse} response + * @returns {PasskeyCoordinates} Object containing the coordinates derived from the public key of the passkey. + * @throws {Error} Throws an error if the coordinates could not be extracted via `p256.ProjectivePoint.fromHex` + */ +export async function decodePublicKey( + response: AuthenticatorResponse +): Promise { + const publicKeyAuthenticatorResponse = response as AuthenticatorAttestationResponse + const publicKey = publicKeyAuthenticatorResponse.getPublicKey() + + if (!publicKey) { + throw new Error('Failed to generate passkey coordinates. getPublicKey() failed') + } + + if (typeof publicKey === 'string') { + // Public key is base64 encoded + // - React Native platform uses base64 encoded strings + return decodePublicKeyForReactNative(publicKey) + } + + if (publicKey instanceof ArrayBuffer) { + // Public key is an ArrayBuffer + // - Web platform uses ArrayBuffer + return await decodePublicKeyForWeb(publicKey) + } + + throw new Error('Unsupported public key format.') +} + +/** + * Extracts and returns the passkey data (coordinates and rawId) from a given passkey Credential. + * + * @param {Credential} passkeyCredential - The passkey credential generated via `navigator.credentials.create()` or other method in another platforms. + * @returns {Promise} A promise that resolves to an object containing the coordinates and the rawId derived from the passkey. + * This is the important information in the Safe account context and should be stored securely as it is used to verify the passkey and to instantiate the SDK + * as a signer (`Safe.init()) + * @throws {Error} Throws an error if the coordinates could not be extracted + */ +export async function extractPasskeyData(passkeyCredential: Credential): Promise { + const passkeyPublicKeyCredential = passkeyCredential as PublicKeyCredential + + const rawId = Buffer.from(passkeyPublicKeyCredential.rawId).toString('hex') + const coordinates = await decodePublicKey(passkeyPublicKeyCredential.response) + + return { + rawId, + coordinates + } +} + +/** + * Retrieves the default FCLP256 Verifier address for a given blockchain network. + * + * This function fetches the deployment information for the FCLP256 Verifier and + * returns the verifier address associated with the specified chain ID. It ensures + * that the correct version and release status are used. + * + * @param {string} chainId - The ID of the blockchain network to retrieve the verifier address for. + * @returns {string} The FCLP256 Verifier address for the specified chain ID. + * @throws {Error} Throws an error if the deployment information or address cannot be found. + */ + export function getDefaultFCLP256VerifierAddress(chainId: string): string { const FCLP256VerifierDeployment = getFCLP256VerifierDeployment({ version: '0.2.1', diff --git a/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts b/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts index aedf90d3e..c1f02b86d 100644 --- a/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts +++ b/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts @@ -311,7 +311,7 @@ describe('Off-chain signatures', () => { transactionHash: '', confirmationType: '', signature: '0x111111', - signatureType: '' + signatureType: 'EOA' }, { owner: '0x2222222222222222222222222222222222222222', @@ -319,7 +319,7 @@ describe('Off-chain signatures', () => { transactionHash: '', confirmationType: '', signature: '0x222222', - signatureType: '' + signatureType: 'EOA' } ], trusted: true, diff --git a/packages/protocol-kit/tests/e2e/utils/passkeys.ts b/packages/protocol-kit/tests/e2e/utils/passkeys.ts index 2053af959..c23e46489 100644 --- a/packages/protocol-kit/tests/e2e/utils/passkeys.ts +++ b/packages/protocol-kit/tests/e2e/utils/passkeys.ts @@ -1,7 +1,8 @@ -import { PasskeyArgType, PasskeyClient, extractPasskeyCoordinates } from '@safe-global/protocol-kit' +import { PasskeyArgType, PasskeyClient } from '@safe-global/protocol-kit' import { WebAuthnCredentials } from './webauthnShim' import { WalletClient, keccak256, toBytes, Transport, Chain, Account } from 'viem' import { asHex } from '@safe-global/protocol-kit/utils/types' +import { decodePublicKeyForWeb } from '@safe-global/protocol-kit/utils' let singleInstance: WebAuthnCredentials @@ -54,7 +55,7 @@ export async function createMockPasskey( webAuthnCredentials?: WebAuthnCredentials ): Promise { const credentialsInstance = webAuthnCredentials ?? getWebAuthnCredentials() - const passkeyCredential = await credentialsInstance.create({ + const passkeyCredential = credentialsInstance.create({ publicKey: { rp: { name: 'Safe', @@ -85,7 +86,8 @@ export async function createMockPasskey( const exportedPublicKey = await crypto.subtle.exportKey('spki', key) const rawId = Buffer.from(passkeyCredential.rawId).toString('hex') - const coordinates = await extractPasskeyCoordinates(exportedPublicKey) + + const coordinates = await decodePublicKeyForWeb(exportedPublicKey) const passkey: PasskeyArgType = { rawId, diff --git a/packages/relay-kit/package.json b/packages/relay-kit/package.json index 5e5be690d..bee9e4e57 100644 --- a/packages/relay-kit/package.json +++ b/packages/relay-kit/package.json @@ -1,6 +1,6 @@ { "name": "@safe-global/relay-kit", - "version": "3.2.4", + "version": "3.3.0", "description": "SDK for Safe Smart Accounts with support for ERC-4337 and Relay", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -39,9 +39,9 @@ }, "dependencies": { "@gelatonetwork/relay-sdk": "^5.5.0", - "@safe-global/protocol-kit": "^5.0.4", + "@safe-global/protocol-kit": "^5.1.0", "@safe-global/safe-modules-deployments": "^2.2.4", - "@safe-global/types-kit": "^1.0.0", + "@safe-global/types-kit": "^1.0.1", "viem": "^2.21.8" } } diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 41e56cfa0..aa045d232 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -293,7 +293,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ if (isBatch) { const multiSendContract = await getMultiSendContract({ safeProvider, - safeVersion + safeVersion, + deploymentType: options.deploymentType || undefined }) const batchData = encodeFunctionData({ @@ -315,7 +316,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ predictedSafe: { safeDeploymentConfig: { safeVersion, - saltNonce: options.saltNonce || undefined + saltNonce: options.saltNonce || undefined, + deploymentType: options.deploymentType || undefined }, safeAccountConfig: { owners: options.owners, diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts index 706238353..b2304d729 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts @@ -1,3 +1,4 @@ +import { SignatureTypes } from '@safe-global/types-kit' import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from '../constants' export const OWNER_1 = '0xFfAC5578BE8AC1B2B9D13b34cAf4A074B96B8A1b' @@ -109,7 +110,7 @@ export const SAFE_OPERATION_RESPONSE = { owner: '0x3059EfD1BCe33be41eeEfd5fb6D520d7fEd54E43', signature: '0xcb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d', - signatureType: 'EOA' + signatureType: SignatureTypes.EOA } ], preparedSignature: diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 5ddafabcc..a64bda523 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -1,5 +1,5 @@ import { Account, Address, Chain, Hash, Hex, PublicClient, PublicRpcSchema, Transport } from 'viem' -import Safe, { SafeProviderConfig } from '@safe-global/protocol-kit' +import Safe, { DeploymentType, SafeProviderConfig } from '@safe-global/protocol-kit' import { EstimateGasData, MetaTransactionData, @@ -19,6 +19,7 @@ type PredictedSafeOptions = { threshold: number safeVersion?: SafeVersion saltNonce?: string + deploymentType?: DeploymentType } export type SponsoredPaymasterOption = { diff --git a/packages/sdk-starter-kit/package.json b/packages/sdk-starter-kit/package.json index e9443b1b5..35ce340c3 100644 --- a/packages/sdk-starter-kit/package.json +++ b/packages/sdk-starter-kit/package.json @@ -1,6 +1,6 @@ { "name": "@safe-global/sdk-starter-kit", - "version": "1.0.4", + "version": "1.1.0", "description": "SDK that provides the basic tools to interact with the Safe Smart Account.", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -36,10 +36,10 @@ "access": "public" }, "dependencies": { - "@safe-global/api-kit": "^2.5.4", - "@safe-global/protocol-kit": "^5.0.4", - "@safe-global/relay-kit": "^3.2.4", - "@safe-global/types-kit": "^1.0.0", + "@safe-global/api-kit": "^2.5.5", + "@safe-global/protocol-kit": "^5.1.0", + "@safe-global/relay-kit": "^3.3.0", + "@safe-global/types-kit": "^1.0.1", "viem": "^2.21.8" } } diff --git a/packages/sdk-starter-kit/src/BaseClient.ts b/packages/sdk-starter-kit/src/BaseClient.ts index f80425124..e40e819e5 100644 --- a/packages/sdk-starter-kit/src/BaseClient.ts +++ b/packages/sdk-starter-kit/src/BaseClient.ts @@ -1,6 +1,8 @@ import Safe, { AddOwnerTxParams, + AddPasskeyOwnerTxParams, RemoveOwnerTxParams, + RemovePasskeyOwnerTxParams, SwapOwnerTxParams } from '@safe-global/protocol-kit' import SafeApiKit from '@safe-global/api-kit' @@ -85,10 +87,12 @@ export class BaseClient { /** * Encodes the data for adding a new owner to the Safe. * - * @param {AddOwnerTxParams} addOwnerParams - The parameters for adding a new owner + * @param {AddOwnerTxParams | AddPasskeyOwnerTxParams} addOwnerParams - The parameters for adding a new owner * @returns {TransactionBase} The encoded data */ - async createAddOwnerTransaction(addOwnerParams: AddOwnerTxParams): Promise { + async createAddOwnerTransaction( + addOwnerParams: AddOwnerTxParams | AddPasskeyOwnerTxParams + ): Promise { const addOwnerTransaction = await this.protocolKit.createAddOwnerTx(addOwnerParams) return this.#buildTransaction(addOwnerTransaction) @@ -97,11 +101,11 @@ export class BaseClient { /** * Encodes the data for removing an owner from the Safe. * - * @param {RemoveOwnerTxParams} removeOwnerParams - The parameters for removing an owner + * @param {RemoveOwnerTxParams | RemovePasskeyOwnerTxParams} removeOwnerParams - The parameters for removing an owner * @returns {TransactionBase} The encoded data */ async createRemoveOwnerTransaction( - removeOwnerParams: RemoveOwnerTxParams + removeOwnerParams: RemoveOwnerTxParams | RemovePasskeyOwnerTxParams ): Promise { const removeOwnerTransaction = await this.protocolKit.createRemoveOwnerTx(removeOwnerParams) diff --git a/packages/sdk-starter-kit/src/constants.ts b/packages/sdk-starter-kit/src/constants.ts index 20da24056..9d0afda04 100644 --- a/packages/sdk-starter-kit/src/constants.ts +++ b/packages/sdk-starter-kit/src/constants.ts @@ -1,3 +1,7 @@ +import { DeploymentType } from '@safe-global/protocol-kit' + +export const DEFAULT_DEPLOYMENT_TYPE: DeploymentType = 'canonical' + export enum SafeClientTxStatus { DEPLOYED_AND_EXECUTED = 'DEPLOYED_AND_EXECUTED', DEPLOYED_AND_PENDING_SIGNATURES = 'DEPLOYED_AND_PENDING_SIGNATURES', diff --git a/packages/sdk-starter-kit/src/index.ts b/packages/sdk-starter-kit/src/index.ts index 259987af5..28f4cbd50 100644 --- a/packages/sdk-starter-kit/src/index.ts +++ b/packages/sdk-starter-kit/src/index.ts @@ -1,9 +1,10 @@ -import Safe from '@safe-global/protocol-kit' +import Safe, { SafeConfig } from '@safe-global/protocol-kit' import SafeApiKit from '@safe-global/api-kit' import { SafeClient } from '@safe-global/sdk-starter-kit/SafeClient' import { isValidAddress, isValidSafeConfig } from '@safe-global/sdk-starter-kit/utils' import { SdkStarterKitConfig } from '@safe-global/sdk-starter-kit/types' +import { DEFAULT_DEPLOYMENT_TYPE } from './constants' /** * Initializes a Safe client with the given configuration options. @@ -13,7 +14,7 @@ import { SdkStarterKitConfig } from '@safe-global/sdk-starter-kit/types' */ export async function createSafeClient(config: SdkStarterKitConfig): Promise { const protocolKit = await getProtocolKitInstance(config) - const apiKit = await getApiKitInstance(protocolKit) + const apiKit = await getApiKitInstance(protocolKit, config) if (!protocolKit || !apiKit) throw new Error('Failed to create a kit instances') @@ -36,7 +37,8 @@ async function getProtocolKitInstance(config: SdkStarterKitConfig): Promise { +async function getApiKitInstance( + protocolKit: Safe, + config: SdkStarterKitConfig +): Promise { const chainId = await protocolKit.getChainId() - return new SafeApiKit({ chainId }) + return new SafeApiKit({ chainId, txServiceUrl: config.txServiceUrl }) } export * from './types' diff --git a/packages/sdk-starter-kit/src/types.ts b/packages/sdk-starter-kit/src/types.ts index 2fc62862f..839a8ac55 100644 --- a/packages/sdk-starter-kit/src/types.ts +++ b/packages/sdk-starter-kit/src/types.ts @@ -59,6 +59,7 @@ export type PredictedSafeConfig = { export type SdkStarterKitRootConfig = { provider: SafeProvider['provider'] signer?: SafeProvider['signer'] + txServiceUrl?: string } export type SdkStarterKitConfig = SdkStarterKitRootConfig & diff --git a/packages/testing-kit/package.json b/packages/testing-kit/package.json index 0541fb4c8..05b02711c 100644 --- a/packages/testing-kit/package.json +++ b/packages/testing-kit/package.json @@ -40,7 +40,7 @@ "@openzeppelin/contracts": "^2.5.1", "@safe-global/safe-contracts-v1.4.1": "npm:@safe-global/safe-contracts@1.4.1", "@safe-global/safe-passkey": "0.2.0-alpha.1", - "@safe-global/types-kit": "^1.0.0", + "@safe-global/types-kit": "^1.0.1", "@types/semver": "^7.5.8", "hardhat": "^2.19.3", "hardhat-deploy": "^0.12.4", diff --git a/packages/types-kit/package.json b/packages/types-kit/package.json index 6ad1e7c6e..7bbc8811e 100644 --- a/packages/types-kit/package.json +++ b/packages/types-kit/package.json @@ -1,6 +1,6 @@ { "name": "@safe-global/types-kit", - "version": "1.0.0", + "version": "1.0.1", "description": "Types for use with the Safe Core SDK packages", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 839e9635a..cec7c4ca5 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -188,13 +188,22 @@ export interface EIP712TypedData { primaryType?: string } +export const SignatureTypes = { + CONTRACT_SIGNATURE: 'CONTRACT_SIGNATURE', + EOA: 'EOA', + APPROVED_HASH: 'APPROVED_HASH', + ETH_SIGN: 'ETH_SIGN' +} as const + +export type SignatureType = (typeof SignatureTypes)[keyof typeof SignatureTypes] + export type SafeMultisigConfirmationResponse = { readonly owner: string readonly submissionDate: string readonly transactionHash?: string readonly confirmationType?: string readonly signature: string - readonly signatureType?: string + readonly signatureType: SignatureType } export type ListResponse = { @@ -313,7 +322,7 @@ export type SafeOperationConfirmation = { readonly modified: string readonly owner: string readonly signature: string - readonly signatureType: string + readonly signatureType: SignatureType } export type UserOperationResponse = { diff --git a/yarn.lock b/yarn.lock index 904bf6e2a..c3bc3813f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1136,6 +1136,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/curves@~1.4.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" @@ -1168,7 +1175,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -1715,6 +1722,15 @@ resolved "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-2.5.1.tgz" integrity sha512-qIy6tLx8rtybEsIOAlrM4J/85s2q2nPkDqj/Rx46VakBZ0LwtFhXIVub96LXHczQX0vaqmAueDqNPXtbSXSaYQ== +"@peculiar/asn1-schema@^2.3.13": + version "2.3.13" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz#ec8509cdcbc0da3abe73fd7e690556b57a61b8f4" + integrity sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g== + dependencies: + asn1js "^3.0.5" + pvtsutils "^1.3.5" + tslib "^2.6.2" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -1735,10 +1751,10 @@ resolved "https://registry.yarnpkg.com/@safe-global/safe-contracts/-/safe-contracts-1.4.1-build.0.tgz#5d82e2f3fd8430b4589df992b9ee2c71386082fe" integrity sha512-TIpoKJtMqLcLFoid0cvpxo8YTcnRUj95MHvxzwgPbJPRONOckNS6ebgGyBBRDmnpxFh34IBpPUZ7JD+z2Cfbbg== -"@safe-global/safe-deployments@^1.37.14": - version "1.37.14" - resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.14.tgz#31c1d1ff924d94ce639c136bef154de3adf5f75e" - integrity sha512-uHpYizq52j1arwWRxHbEbrZsECD5tG87NwLo/xDViRVw/GIrkRC6HerkzfZwiHVjVrC8gN8o3ApLsknYbxrF4w== +"@safe-global/safe-deployments@^1.37.20": + version "1.37.20" + resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.20.tgz#368c172b19cd34fa77ec5c0df6a16d52d64abdee" + integrity sha512-2T2cYtgpLGzxM0GUPXd5mqZiQ193fgWXB2Yg0Azd/EBpBsAfUEWVmjdux+5D2aNNyVv++16Hq5GqiOv5sU07Tg== dependencies: semver "^7.6.2" @@ -2544,6 +2560,15 @@ arrify@^2.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== +asn1js@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" + integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ== + dependencies: + pvtsutils "^1.3.2" + pvutils "^1.1.3" + tslib "^2.4.0" + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz" @@ -7263,6 +7288,18 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz" integrity sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg== +pvtsutils@^1.3.2, pvtsutils@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.5.tgz#b8705b437b7b134cd7fd858f025a23456f1ce910" + integrity sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA== + dependencies: + tslib "^2.6.1" + +pvutils@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" + integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== + qs@^6.9.4: version "6.11.1" resolved "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz" @@ -8220,6 +8257,11 @@ tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.6.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsort@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz"