diff --git a/packages/keplr/__tests__/versions.test.ts b/packages/keplr/__tests__/versions.test.ts new file mode 100644 index 000000000..bea8bef5d --- /dev/null +++ b/packages/keplr/__tests__/versions.test.ts @@ -0,0 +1,73 @@ +import { extractVersion } from '../src'; + +const versions = [ + '0.47.20', + '0.47.20-titan.3', + 'axelarnetwork/cosmos-sdk v0.47.20-0.20230904150332-37fb903a6c62', + 'celestiaorg/cosmos-sdk v0.47.20-sdk-v1.2.3', + 'cheqd/cosmos-sdk v0.47.20-cheqd-tag', + 'cheqd/cosmos-sdk v0.47.20-height-mismatch', + 'cosmos/cosmos-sdk v0.47.20', + 'desmos-labs/cosmos-sdk v0.47.20-desmos', + 'dydxprotocol/cosmos-sdk v0.47.20-0.20231011192538-b95c66dedbd5', + 'evmos/cosmos-sdk v0.47.20-evmos.2', + 'evmos/cosmos-sdk v0.47.20-evmos', + 'github.com/Finschia/finschia-sdk@v0.47.20', + 'github.com/wormhole-foundation/cosmos-sdk@v0.47.20-wormhole-2', + 'neutron-org/cosmos-sdk v0.47.20-neutron', + 'nolus-protocol/cosmos-sdk v0.47.20-nolus', + 'onomyprotocol/onomy-sdk v0.47.20-0.20221103153534-77ffa1c3fab2', + 'onomyprotocol/onomy-sdk v0.47.20-onomy-dev', + 'osmosis-labs/cosmos-sdk v0.47.20-v25-osmo-1', + 'osmosis-labs/cosmos-sdk v0.47.20', + 'persistenceOne/cosmos-sdk v0.47.20-lsm-rc0', + 'persistenceOne/cosmos-sdk v0.47.20-lsm5', + 'rust-ninja/cosmos-sdk v0.47.20-patch-validators-trim-tag', + 'sei-cosmos v0.47.20', + 'sge-network/cosmos-sdk v0.47.20-0.20240223100624-2a2661276cb4', + 'Stride-Labs/cosmos-sdk v0.47.20-stride-distribution-fix-0', + 'terra-money/cosmos-sdk v0.47.20-terra.0', + 'ununifi/cosmos-sdk v0.47.20-custom-bank-1', + 'v0.47.20', + 'v0.47.20-ics', + 'v0.47.20-ics-lsm', + 'v0.47.20-0.20220523154235-2921a1c3c918', + 'v0.47.20-ledger.3', + 'v0.47.20-0.20231114190313-b9164dd660b6', + 'v0.47.20-ledger', + 'v0.47.20-bank-rc2', + 'v0.47.20-0.20240417094812-f556fd956fb1', + 'v0.47.20-ics-lsm', + 'v0.47.20-evmos.2', + 'xpladev/cosmos-sdk v0.47.20-xpla', +]; + +describe('extractVersion', () => { + it('parse all sorts of versions', () => { + for (let version of versions) { + expect(extractVersion(version)).toEqual('0.47.20'); + } + }); + it('should extract the version number when input matches the expected format', () => { + expect(extractVersion('0.47')).toEqual('0.47.0'); + expect(extractVersion('github.com/lavanet/cosmos-sdk@v0.47.x-lava')).toEqual('0.47.0'); + expect(extractVersion('service/api v1.2.3-beta')).toEqual('1.2.3'); + expect(extractVersion('product/module v10.0.1-alpha')).toEqual('10.0.1'); + expect(extractVersion('serviceapi v1.2.3')).toEqual('1.2.3'); + expect(extractVersion('service/api/1.2.3')).toEqual('1.2.3'); + expect(extractVersion('123')).toEqual('123.0.0'); + }); + + it('should return null for empty or undefined inputs', () => { + expect(extractVersion('')).toBeNull(); + // @ts-ignore + expect(extractVersion(null)).toBeNull(); + // @ts-ignore + expect(extractVersion(undefined)).toBeNull(); + }); + + it('should handle edge cases with version extraction', () => { + expect(extractVersion('name/endpoint v0.0.0-xyz')).toEqual('0.0.0'); + expect(extractVersion('another/try v999.999.999-final')).toEqual('999.999.999'); + }); +}); diff --git a/packages/keplr/src/index.ts b/packages/keplr/src/index.ts index b3e42662d..b2686d2be 100644 --- a/packages/keplr/src/index.ts +++ b/packages/keplr/src/index.ts @@ -7,27 +7,53 @@ const getRpc = (chain: Chain): string => chain.apis?.rpc?.[0]?.address ?? ''; const getRest = (chain: Chain): string => chain.apis?.rest?.[0]?.address ?? ''; const getExplr = (chain: Chain): string => chain.explorers?.[0]?.url ?? ''; -const cleanVer = (ver: string) => { - if (!semver.valid(ver)) { - // try to split by @ for repo@version - ver = ver.split('@').pop(); - if (semver.valid(ver)) return ver; - - const spaces = ver.split('.').length; - switch (spaces) { - case 1: - return ver + '.0.0'; - case 2: - return ver + '.0'; - case 3: - default: - throw new Error( - 'contact @chain-registry/keplr maintainers: bad version' - ); - } - } +const versionPatterns = { + fullSemver: /v?(\d+\.\d+\.\d+)(?=-[\w.-]+|$)/, // Matches complete semver patterns like '0.47.20' or 'v0.47.20' + partialSemver: /v?(\d+\.\d+)(?=(?:\.\d+)?(?=-[\w.-]+|$))/, // Matches partial semver patterns like '0.47' or 'v0.47' + basicVersion: /v?(\d+)(?=(?:\.\d+)?(?:\.\d+)?(?=-[\w.-]+|$))/, // Matches basic versions like '0' or 'v0' + tagged: /@v(\d+\.\d+)(?:\.x)?(?=-[\w.-]+|$)/, // Specific for tagged formats + embedded: /\/v(\d+\.\d+\.\d+)(?=-[\w.-]+|$)/, // For versions embedded in paths + simple: /v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/, // General simple versions + complexEmbedded: /[\w-]+\/[\w-]+ v(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-\d+[\w.-]*)/, // Complex formats with namespaces + taggedVersion: /[\w-]+\/[\w-]+ v(\d+)(?:\.(\d+))?(?:\.(\d+))?(?=-[\w.-]+|$)/ // Versions with descriptive tags }; +export function extractVersion(input: string): string | null { + let version = null; + + // Check each pattern in turn + if (versionPatterns.fullSemver.test(input)) { + version = input.match(versionPatterns.fullSemver)?.[1]; + } else if (versionPatterns.partialSemver.test(input)) { + version = input.match(versionPatterns.partialSemver)?.[1]; + } else if (versionPatterns.basicVersion.test(input)) { + version = input.match(versionPatterns.basicVersion)?.[1]; + } else if (versionPatterns.taggedVersion.test(input)) { + version = input.match(versionPatterns.taggedVersion)?.[1]; + } else if (versionPatterns.complexEmbedded.test(input)) { + version = input.match(versionPatterns.complexEmbedded)?.[1]; + } else if (versionPatterns.simple.test(input)) { + version = input.match(versionPatterns.simple)?.[1]; + } else if (versionPatterns.tagged.test(input)) { + version = input.match(versionPatterns.tagged)?.[1]; + } else if (versionPatterns.embedded.test(input)) { + version = input.match(versionPatterns.embedded)?.[1]; + } + + return version ? normalizeVersion(version) : null; +} + +function normalizeVersion(version: string): string { + // Ensures the version is normalized to the 'x.y.z' format, if applicable + const parts = version.split('.'); + while (parts.length < 3) { + parts.push('0'); + } + return parts.join('.'); +} + + + export const chainRegistryChainToKeplr = ( chain: Chain, assets: AssetList[], @@ -48,7 +74,7 @@ export const chainRegistryChainToKeplr = ( const features = []; // if NOT specified, we assume stargate, sorry not sorry const sdkVer = chain.codebase?.cosmos_sdk_version - ? cleanVer(chain.codebase?.cosmos_sdk_version) + ? extractVersion(chain.codebase?.cosmos_sdk_version) : '0.40'; // stargate @@ -63,7 +89,7 @@ export const chainRegistryChainToKeplr = ( if (chain.codebase?.cosmwasm_enabled) { features.push('cosmwasm'); - const wasmVer = cleanVer(chain.codebase.cosmwasm_version ?? '0.24'); + const wasmVer = extractVersion(chain.codebase.cosmwasm_version ?? '0.24'); if (semver.satisfies(wasmVer, '>=0.24')) features.push('wasmd_0.24+'); }