diff --git a/apps/vc-api/src/api/filters/vc.api.filters.ts b/apps/vc-api/src/api/filters/vc.api.filters.ts index 901c5ed..cfb504c 100644 --- a/apps/vc-api/src/api/filters/vc.api.filters.ts +++ b/apps/vc-api/src/api/filters/vc.api.filters.ts @@ -5,6 +5,7 @@ import { SocialResolverNotFoundExceptionFilter } from './credentials/social-reso import { CredentialsExceptionFilter } from './credentials/credentials.filter'; import { AuthenticationExceptionFilter } from './auth/authentication.filter'; import { JustaNameInitializerExceptionFilter } from './auth/justaName-intializer.filter'; +import { Web3ProviderExceptionFilter } from './web3-provider/web3-provider.filter'; export const VCManagementApiFilters = [ OTPExceptionFilter, @@ -14,4 +15,5 @@ export const VCManagementApiFilters = [ ChainIdInvalidExceptionFilter, CredentialsExceptionFilter, SocialResolverNotFoundExceptionFilter, + Web3ProviderExceptionFilter, ]; diff --git a/apps/vc-api/src/api/filters/web3-provider/web3-provider.filter.ts b/apps/vc-api/src/api/filters/web3-provider/web3-provider.filter.ts new file mode 100644 index 0000000..40912bf --- /dev/null +++ b/apps/vc-api/src/api/filters/web3-provider/web3-provider.filter.ts @@ -0,0 +1,16 @@ +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { Web3ProviderException } from '../../../core/domain/exceptions/Web3Provider.exception'; + +@Catch(Web3ProviderException) +export class Web3ProviderExceptionFilter extends BaseExceptionFilter { + catch(exception: Web3ProviderException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_REQUEST; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} \ No newline at end of file diff --git a/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts index 156e935..e2a2b25 100644 --- a/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts +++ b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts @@ -13,8 +13,8 @@ export class VerifyRecordsControllerMapper implements IVerifyRecordsControllerMa verifyRecordsApiRequest: VerifyRecordsApiRequest, ): VerifyRecordsRequest { return { + providerUrl: verifyRecordsApiRequest.providerUrl, ens: verifyRecordsApiRequest.ens, - chainId: verifyRecordsApiRequest.chainId, credentials: verifyRecordsApiRequest.credentials, issuer: verifyRecordsApiRequest.issuer, matchStandard: verifyRecordsApiRequest.matchStandard diff --git a/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts index 9d89bf9..98431c2 100644 --- a/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts +++ b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts @@ -1,21 +1,19 @@ -import { IsArray, IsInt, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Transform, Type } from 'class-transformer'; -import { ChainId } from '@justaname.id/sdk'; +import { Transform } from 'class-transformer'; import { Credentials } from '../../../core/domain/entities/credentials'; export class VerifyRecordsApiRequest { + @ApiProperty() + @IsString() + providerUrl: string; + @ApiProperty() @IsArray() @IsString({ each: true }) @Transform(({ value }) => (Array.isArray(value) ? value : [value])) ens: string[]; - @ApiProperty() - @Type(() => Number) - @IsInt() - chainId: ChainId; - @ApiProperty() @IsArray() @IsString({ each: true }) diff --git a/apps/vc-api/src/core/applications/provider-services/ifetch-chain-id.service.ts b/apps/vc-api/src/core/applications/provider-services/ifetch-chain-id.service.ts new file mode 100644 index 0000000..12e9ebc --- /dev/null +++ b/apps/vc-api/src/core/applications/provider-services/ifetch-chain-id.service.ts @@ -0,0 +1,5 @@ +export const FETCH_CHAIN_ID_SERVICE = 'FETCH_CHAIN_ID_SERVICE'; + +export interface IFetchChainIdService { + getChainId(providerUrl: string): Promise; +} \ No newline at end of file diff --git a/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts index bf25a1c..0155b6e 100644 --- a/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts +++ b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts @@ -4,6 +4,6 @@ import { Subname } from '../../domain/entities/subname'; export const SUBNAME_RECORDS_FETCHER = 'SUBNAME_RECORDS_FETCHER'; export interface ISubnameRecordsFetcher { - fetchRecords(subname: string, chainId: ChainId, texts?: string[]): Promise; - fetchRecordsFromManySubnames(subnames: string[], chainId: ChainId, texts?: string[]): Promise + fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts?: string[]): Promise; + fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts?: string[]): Promise } diff --git a/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts b/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts index 9685e15..eb6aad1 100644 --- a/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts +++ b/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts @@ -1,7 +1,7 @@ export class VerifyRecordsRequest { + providerUrl: string; ens: string[]; issuer: string; - chainId: number; credentials: string[]; matchStandard?: boolean; } diff --git a/apps/vc-api/src/core/applications/verify-records/response/record-verifier-checker.response.ts b/apps/vc-api/src/core/applications/verify-records/response/record-verifier-checker.response.ts new file mode 100644 index 0000000..9e21ca3 --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/response/record-verifier-checker.response.ts @@ -0,0 +1,3 @@ +export class RecordVerifierCheckerResponse { + [key: string]: boolean; +} diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts index 4ae7ddb..ccb9fe7 100644 --- a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -12,6 +12,8 @@ import {TelegramCredential} from "../../domain/credentials/telegram.credential"; import {EmailCredential} from "../../domain/credentials/email.credential"; import {DiscordCredential} from "../../domain/credentials/discord.credential"; import { ChainIdInvalidException } from '../../domain/exceptions/ChainIdInvalid.exception'; +import { FETCH_CHAIN_ID_SERVICE, IFetchChainIdService } from '../provider-services/ifetch-chain-id.service'; +import { RecordVerifierCheckerResponse } from './response/record-verifier-checker.response'; @Injectable() export class VerifyRecordsService implements IVerifyRecordsService { @@ -25,20 +27,23 @@ export class VerifyRecordsService implements IVerifyRecordsService { @Inject(SUBNAME_RECORDS_FETCHER) private readonly subnameRecordsFetcher: ISubnameRecordsFetcher, @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, + @Inject(FETCH_CHAIN_ID_SERVICE) private readonly fetchChainIdService: IFetchChainIdService, ) { this.domain = this.environmentGetter.getEnsDomain(); } async verifyRecords(verifyRecordsRequest: VerifyRecordsRequest): Promise { - const { ens, chainId, credentials, issuer } = verifyRecordsRequest; + const { ens, credentials, issuer, providerUrl } = verifyRecordsRequest; const validIssuer = issuer ? issuer : this.domain; + const chainId = await this.fetchChainIdService.getChainId(providerUrl); + if (chainId !== 1 && chainId !== 11155111) { throw ChainIdInvalidException.withId(chainId); } - const subnameRecords = await this.subnameRecordsFetcher.fetchRecordsFromManySubnames(ens, chainId, [...credentials, ...credentials.map((record) => `${record}_${validIssuer}`)]); + const subnameRecords = await this.subnameRecordsFetcher.fetchRecordsFromManySubnames(providerUrl, ens, chainId, [...credentials, ...credentials.map((record) => `${record}_${validIssuer}`)]); const responses: VerifyRecordsResponse[] = []; for (const subnameRecord of subnameRecords) { @@ -56,7 +61,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { return responses; } - private _recordVerifier(record: string, subnameRecords: Subname, chainId: number, issuer: string, matchStandard: boolean): { [key: string]: boolean } { + private _recordVerifier(record: string, subnameRecords: Subname, chainId: number, issuer: string, matchStandard: boolean): RecordVerifierCheckerResponse { // 1) check if record_issuer exists in subnameRecords, if not return false const foundRecordIssuer = subnameRecords.metadata.textRecords.find((item) => item.key === `${record}_${issuer}`); if (!foundRecordIssuer) { diff --git a/apps/vc-api/src/core/domain/exceptions/Web3Provider.exception.ts b/apps/vc-api/src/core/domain/exceptions/Web3Provider.exception.ts new file mode 100644 index 0000000..0cd1f58 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/Web3Provider.exception.ts @@ -0,0 +1,10 @@ +export class Web3ProviderException extends Error { + private constructor(message: string) { + super(message); + } + + static withMessage(providerUrl: string): Web3ProviderException { + const message = `Web3ProviderException: Provider Url ${providerUrl} threw an error`; + return new Web3ProviderException(message); + } + } \ No newline at end of file diff --git a/apps/vc-api/src/external/provider-services/fetch-chain-id.service.ts b/apps/vc-api/src/external/provider-services/fetch-chain-id.service.ts new file mode 100644 index 0000000..db7bfc8 --- /dev/null +++ b/apps/vc-api/src/external/provider-services/fetch-chain-id.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; +import { IFetchChainIdService } from '../../core/applications/provider-services/ifetch-chain-id.service'; +import { Web3ProviderException } from '../../core/domain/exceptions/Web3Provider.exception'; + +@Injectable() +export class FetchChainIdService implements IFetchChainIdService { + providerChainIdMap: Map = new Map(); + + + async getChainId(providerUrl: string): Promise { + try { + if (this.providerChainIdMap.has(providerUrl)) { + return this.providerChainIdMap.get(providerUrl) as number; + } + + const provider = new ethers.JsonRpcProvider(providerUrl); + const network = await provider.getNetwork(); + + this.providerChainIdMap.set(providerUrl, Number(network.chainId)); + + return Number(network.chainId) + } catch (e) { + throw Web3ProviderException.withMessage(providerUrl); + } + } +} \ No newline at end of file diff --git a/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts index bcbdcbd..ce3d01e 100644 --- a/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts +++ b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts @@ -16,10 +16,8 @@ export class SubnameRecordsFetcher implements ISubnameRecordsFetcher { @Inject(ENS_MANAGER_SERVICE) private readonly ensManagerService: IEnsManagerService ) {} - async fetchRecords(subname: string, chainId: ChainId, texts?: string[]): Promise { - + async fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts?: string[]): Promise { let records: GetRecordsResponse; - const providerUrl = (chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() if(texts) { const client = createClient({ @@ -48,10 +46,8 @@ export class SubnameRecordsFetcher implements ISubnameRecordsFetcher { return this.mapSubnameRecordsResponseToSubname(subname, records); } - async fetchRecordsFromManySubnames(subnames: string[], chainId: ChainId, texts?: string[]): Promise { - + async fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts?: string[]): Promise { let records: GetRecordsResponse[]; - const providerUrl = (chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() if(texts) { const client = createClient({ diff --git a/apps/vc-api/src/vc-management.module.ts b/apps/vc-api/src/vc-management.module.ts index 8052cf6..b2c962f 100644 --- a/apps/vc-api/src/vc-management.module.ts +++ b/apps/vc-api/src/vc-management.module.ts @@ -56,6 +56,8 @@ import {EmailResolver} from "./core/applications/credentials/facade/email-resolv import {EMAIL_RESOLVER} from "./core/applications/credentials/facade/email-resolver/iemail.resolver"; import { VCManagementApiFilters } from './api/filters/vc.api.filters'; import { APP_FILTER } from '@nestjs/core'; +import { FetchChainIdService } from './external/provider-services/fetch-chain-id.service'; +import { FETCH_CHAIN_ID_SERVICE } from './core/applications/provider-services/ifetch-chain-id.service'; const dynamicImport = async (packageName: string) => new Function(`return import('${packageName}')`)(); @@ -81,6 +83,10 @@ const dynamicImport = async (packageName: string) => provide: APP_FILTER, useClass: filter, })), + { + useClass: FetchChainIdService, + provide: FETCH_CHAIN_ID_SERVICE + }, { useClass: CredentialsControllerMapper, provide: AUTH_CONTROLLER_MAPPER diff --git a/apps/vc-api/test/src/api/verify-records/mapper/verify-records.controller.mapper.spec.ts b/apps/vc-api/test/src/api/verify-records/mapper/verify-records.controller.mapper.spec.ts index d7f8bd4..ab27ed6 100644 --- a/apps/vc-api/test/src/api/verify-records/mapper/verify-records.controller.mapper.spec.ts +++ b/apps/vc-api/test/src/api/verify-records/mapper/verify-records.controller.mapper.spec.ts @@ -7,7 +7,6 @@ import { VerifyRecordsRequest } from "../../../../../src/core/applications/verif import { VerifyRecordsResponse } from "../../../../../src/core/applications/verify-records/response/verify-records.response"; import { VerifyRecordsApiResponse } from "../../../../../src/api/verify-records/responses/verify-records.api.response"; const ENS = 'ENS'; -const CHAIN_ID = 1; const CREDENTIALS = 'github'; const ISSUER = 'ISSUER'; const MATCH_STANDARD = true; @@ -15,24 +14,25 @@ const SUBNAME = 'SUBNAME'; const RECORDS = { 'RECORD_1': true, }; +const PROVIDER_URL = 'PROVIDER_URL'; const getVerifyRecordsApiRequest = (): VerifyRecordsApiRequest => { return { ens: [ENS], - chainId: CHAIN_ID, credentials: [CREDENTIALS], issuer: ISSUER, - matchStandard: MATCH_STANDARD + matchStandard: MATCH_STANDARD, + providerUrl: PROVIDER_URL }; }; const getVerifyRecordsRequest = (): VerifyRecordsRequest => { return { ens: [ENS], - chainId: CHAIN_ID, credentials: [CREDENTIALS], issuer: ISSUER, - matchStandard: MATCH_STANDARD + matchStandard: MATCH_STANDARD, + providerUrl: PROVIDER_URL }; }; diff --git a/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts b/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts index 2200a5c..a7e5a1b 100644 --- a/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts +++ b/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts @@ -16,6 +16,7 @@ import { IVerifyRecordsService, VERIFY_RECORDS_SERVICE, } from '../../../../src/core/applications/verify-records/iverify-records.service'; +import { FETCH_CHAIN_ID_SERVICE, IFetchChainIdService } from '../../../../src/core/applications/provider-services/ifetch-chain-id.service'; const ENS = 'ENS'; const ERROR_MESSAGE = 'ERROR_MESSAGE'; @@ -23,22 +24,20 @@ const SOCIAL_CREDENTIAL: SocialCredentials = 'github'; const CHAIN_ID = 11155111; const CHAIN_ID_2 = 31337; const ISSUER = 'ISSUER'; - +const PROVIDER_URL = 'PROVIDER_URL'; const RECORD_KEY = 'RECORD_KEY'; const RECORD_VALUE = true; const getVerifyRecordsApiRequest = (): VerifyRecordsApiRequest => ({ ens: [ENS], - chainId: CHAIN_ID, credentials: [SOCIAL_CREDENTIAL], issuer: ISSUER, + providerUrl: PROVIDER_URL, }); -const getVerifyRecordsRequest = ( - chainId: number = CHAIN_ID -): VerifyRecordsRequest => ({ +const getVerifyRecordsRequest = (): VerifyRecordsRequest => ({ ens: [ENS], - chainId, + providerUrl: PROVIDER_URL, credentials: [SOCIAL_CREDENTIAL], issuer: ISSUER, }); @@ -61,6 +60,7 @@ describe('Verify records controller integration tests', () => { let app: INestApplication; let verifyRecordsService: DeepMocked; let verifyRecordsControllerMapper: DeepMocked; + let fetchChainIdService: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -70,6 +70,10 @@ describe('Verify records controller integration tests', () => { provide: APP_FILTER, useClass: filter, })), + { + provide: FETCH_CHAIN_ID_SERVICE, + useValue: createMock(), + }, { provide: VERIFY_RECORDS_SERVICE, useValue: createMock(), @@ -89,9 +93,14 @@ describe('Verify records controller integration tests', () => { DeepMocked >('VERIFY_RECORDS_CONTROLLER_MAPPER'); + fetchChainIdService = module.get>( + FETCH_CHAIN_ID_SERVICE + ); + verifyRecordsControllerMapper.mapVerifyRecordsApiRequestToVerifyRecordsRequest.mockReturnValue(getVerifyRecordsRequest()); verifyRecordsControllerMapper.mapVerifyRecordsResponsesToVerifyRecordsApiResponses.mockReturnValue([getVerifyRecordsApiResponse()]) + fetchChainIdService.getChainId.mockResolvedValueOnce(CHAIN_ID); app = module.createNestApplication(); await app.init(); diff --git a/package.json b/package.json index cc212fe..c9e10aa 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "crypto": "^1.0.1", "crypto-js": "^4.2.0", "ens-did-resolver": "^1.0.4", - "ethers": "^6.13.2", + "ethers": "^6.13.4", "ethr-did-resolver": "^10.1.10", "express-session": "^1.18.0", "moment": "^2.30.1", diff --git a/yarn.lock b/yarn.lock index dc9c8af..f46f833 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10850,7 +10850,7 @@ ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: "@scure/bip32" "1.4.0" "@scure/bip39" "1.3.0" -ethers@^6.11.1, ethers@^6.13.2, ethers@^6.8.1: +ethers@^6.11.1, ethers@^6.13.4, ethers@^6.8.1: version "6.13.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.4.tgz#bd3e1c3dc1e7dc8ce10f9ffb4ee40967a651b53c" integrity sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA== @@ -18264,16 +18264,7 @@ string-to-template-literal@^2.0.0: resolved "https://registry.yarnpkg.com/string-to-template-literal/-/string-to-template-literal-2.0.0.tgz#0ce56bf42052d53049243e11dbf045b9ad687f9e" integrity sha512-AbTUWHXMyoRlTFP9qe013dfGTFq1XbcBLUoLC7PcumbJewtUwNXCvnko5cH2gZkUFC7kD2Fwxiv4YIndkU0xHA== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18351,14 +18342,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -19951,7 +19935,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -19969,15 +19953,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"