diff --git a/src/AssetsTransferApi.spec.ts b/src/AssetsTransferApi.spec.ts index b8538814..ddc99e25 100644 --- a/src/AssetsTransferApi.spec.ts +++ b/src/AssetsTransferApi.spec.ts @@ -90,7 +90,6 @@ describe('AssetTransferAPI', () => { describe('SystemToRelay', () => { it('Should corectly return Native', () => { const assetType = systemAssetsApi['fetchAssetType']( - 'statemint', '0', ['DOT'], Direction.SystemToRelay @@ -102,7 +101,6 @@ describe('AssetTransferAPI', () => { describe('RelayToSystem', () => { it('Should correctly return Native', () => { const assetType = systemAssetsApi['fetchAssetType']( - 'polkadot', '1000', ['DOT'], Direction.RelayToSystem @@ -114,7 +112,6 @@ describe('AssetTransferAPI', () => { describe('SystemToPara', () => { it('Should correctly return Foreign', () => { const assetType = systemAssetsApi['fetchAssetType']( - 'statemint', '2000', ['1'], Direction.SystemToPara diff --git a/src/AssetsTransferApi.ts b/src/AssetsTransferApi.ts index 53c74be8..6e106ff0 100644 --- a/src/AssetsTransferApi.ts +++ b/src/AssetsTransferApi.ts @@ -29,9 +29,7 @@ import { checkXcmTxInputs, checkXcmVersion, } from './errors'; -import { findRelayChain } from './registry/findRelayChain'; -import { parseRegistry } from './registry/parseRegistry'; -import type { ChainInfoRegistry } from './registry/types'; +import { Registry } from './registry'; import { sanitizeAddress } from './sanitize/sanitizeAddress'; import { AssetsTransferApiOpts, @@ -59,7 +57,7 @@ export class AssetsTransferApi { readonly _opts: AssetsTransferApiOpts; readonly _specName: string; readonly _safeXcmVersion: number; - readonly _registry: ChainInfoRegistry; + readonly registry: Registry; constructor( api: ApiPromise, @@ -71,7 +69,7 @@ export class AssetsTransferApi { this._opts = opts; this._specName = specName; this._safeXcmVersion = safeXcmVersion; - this._registry = parseRegistry(opts); + this.registry = new Registry(specName, this._opts); } /** @@ -97,7 +95,7 @@ export class AssetsTransferApi { */ checkBaseInputTypes(destChainId, destAddr, assetIds, amounts); - const { _api, _specName, _safeXcmVersion, _registry } = this; + const { _api, _specName, _safeXcmVersion, registry } = this; const isOriginSystemParachain = SYSTEM_PARACHAINS_NAMES.includes( _specName.toLowerCase() ); @@ -120,12 +118,7 @@ export class AssetsTransferApi { * This will throw a BaseError if the inputs are incorrect and don't * fit the constraints for creating a local asset transfer. */ - const localAssetType = checkLocalTxInput( - assetIds, - amounts, - _specName, - _registry - ); // Throws an error when any of the inputs are incorrect. + const localAssetType = checkLocalTxInput(assetIds, amounts, registry); // Throws an error when any of the inputs are incorrect. const method = opts?.keepAlive ? 'transferKeepAlive' : 'transfer'; if (isLocalSystemTx) { let tx: SubmittableExtrinsic<'promise', ISubmittableResult>; @@ -182,15 +175,10 @@ export class AssetsTransferApi { xcmDirection, destChainId, _specName, - _registry + registry ); - const assetType = this.fetchAssetType( - _specName, - destChainId, - assetIds, - xcmDirection - ); + const assetType = this.fetchAssetType(destChainId, assetIds, xcmDirection); let txMethod: Methods; let transaction: SubmittableExtrinsic<'promise', ISubmittableResult>; @@ -402,7 +390,6 @@ export class AssetsTransferApi { } private fetchAssetType( - specName: string, destChainId: string, assets: string[], xcmDirection: Direction @@ -415,8 +402,7 @@ export class AssetsTransferApi { return AssetType.Foreign; } - const relayChainName = findRelayChain(specName, this._registry); - const relayChainInfo = this._registry[relayChainName]; + const relayChainInfo = this.registry.currentRelayRegistry; /** * We can assume all the assets in `assets` are either foreign or native since we check diff --git a/src/createXcmTypes/types.ts b/src/createXcmTypes/types.ts index e3ceeefb..63bc9e5a 100644 --- a/src/createXcmTypes/types.ts +++ b/src/createXcmTypes/types.ts @@ -8,14 +8,7 @@ import type { WeightLimitV2, } from '@polkadot/types/interfaces'; -type RequireOnlyOne = Pick< - T, - Exclude -> & - { - [K in Keys]-?: Required> & - Partial, undefined>>; - }[Keys]; +import type { RequireOnlyOne } from '../types'; export interface ICreateXcmType { createBeneficiary: ( diff --git a/src/errors/checkLocalTxInputs.spec.ts b/src/errors/checkLocalTxInputs.spec.ts index d1104e35..465c6276 100644 --- a/src/errors/checkLocalTxInputs.spec.ts +++ b/src/errors/checkLocalTxInputs.spec.ts @@ -1,47 +1,43 @@ // Copyright 2023 Parity Technologies (UK) Ltd. -import { parseRegistry } from '../registry/parseRegistry'; +import { Registry } from '../registry'; import { checkLocalTxInput } from './checkLocalTxInputs'; describe('checkLocalTxInput', () => { - const registry = parseRegistry({}); + const registry = new Registry('statemine', {}); it('Should correctly return Balances with an empty assetIds', () => { - const res = checkLocalTxInput([], ['10000'], 'statemine', registry); + const res = checkLocalTxInput([], ['10000'], registry); expect(res).toEqual('Balances'); }); it('Should correctly return Balances with a native token', () => { - const res = checkLocalTxInput(['KSM'], ['10000'], 'statemine', registry); + const res = checkLocalTxInput(['KSM'], ['10000'], registry); expect(res).toEqual('Balances'); }); it('Should correctly return Assets with a valid assetId', () => { - const res = checkLocalTxInput(['1984'], ['10000'], 'statemine', registry); + const res = checkLocalTxInput(['1984'], ['10000'], registry); expect(res).toEqual('Assets'); }); it('Should correctly throw an error for incorrect length on `assetIds`', () => { - const err = () => - checkLocalTxInput(['1', '2'], ['10000'], 'statemine', registry); + const err = () => checkLocalTxInput(['1', '2'], ['10000'], registry); expect(err).toThrowError( 'Local transactions must have the `assetIds` input be a length of 1 or 0, and the `amounts` input be a length of 1' ); }); it('Should correctly throw an error for incorrect length on `amounts`', () => { - const err = () => - checkLocalTxInput(['1'], ['10000', '20000'], 'statemine', registry); + const err = () => checkLocalTxInput(['1'], ['10000', '20000'], registry); expect(err).toThrowError( 'Local transactions must have the `assetIds` input be a length of 1 or 0, and the `amounts` input be a length of 1' ); }); it('Should correctly throw an error with an incorrect assetId', () => { - const err = () => - checkLocalTxInput(['TST'], ['10000'], 'statemine', registry); + const err = () => checkLocalTxInput(['TST'], ['10000'], registry); expect(err).toThrowError( 'The assetId passed in is not a valid number: TST' ); }); it("Should correctly throw an error when the assetId doesn't exist", () => { - const err = () => - checkLocalTxInput(['9876111'], ['10000'], 'statemine', registry); + const err = () => checkLocalTxInput(['9876111'], ['10000'], registry); expect(err).toThrowError( 'The assetId 9876111 does not exist in the registry.' ); diff --git a/src/errors/checkLocalTxInputs.ts b/src/errors/checkLocalTxInputs.ts index 483a5464..4ce4605c 100644 --- a/src/errors/checkLocalTxInputs.ts +++ b/src/errors/checkLocalTxInputs.ts @@ -1,8 +1,7 @@ // Copyright 2023 Parity Technologies (UK) Ltd. import { SYSTEM_PARACHAINS_IDS } from '../consts'; -import { findRelayChain } from '../registry/findRelayChain'; -import type { ChainInfoRegistry } from '../registry/types'; +import { Registry } from '../registry'; import { BaseError } from './BaseError'; enum LocalTxType { @@ -20,11 +19,9 @@ enum LocalTxType { export const checkLocalTxInput = ( assetIds: string[], amounts: string[], - specName: string, - registry: ChainInfoRegistry + registry: Registry ): LocalTxType => { - const relayChain = findRelayChain(specName, registry); - const relayChainInfo = registry[relayChain]; + const relayChainInfo = registry.currentRelayRegistry; const systemParachainInfo = relayChainInfo[SYSTEM_PARACHAINS_IDS[0]]; // Ensure the lengths in assetIds and amounts is correct diff --git a/src/errors/checkXcmTxInputs.spec.ts b/src/errors/checkXcmTxInputs.spec.ts index 5c3c671c..2f78e284 100644 --- a/src/errors/checkXcmTxInputs.spec.ts +++ b/src/errors/checkXcmTxInputs.spec.ts @@ -1,7 +1,6 @@ -import { ChainInfoRegistry } from 'src/registry/types'; +// Copyright 2023 Parity Technologies (UK) Ltd. -import { findRelayChain } from '../registry/findRelayChain'; -import { parseRegistry } from '../registry/parseRegistry'; +import { Registry } from '../registry'; import { Direction } from '../types'; import { checkAssetIdInput, @@ -10,11 +9,11 @@ import { checkRelayAssetIdLength, } from './checkXcmTxInputs'; -const runTests = (tests: Test[], registry: ChainInfoRegistry) => { +const runTests = (tests: Test[]) => { for (const test of tests) { const [destChainId, specName, testInputs, direction, errorMessage] = test; - const relayChainName = findRelayChain(specName, registry); - const currentRegistry = registry[relayChainName]; + const registry = new Registry(specName, {}); + const currentRegistry = registry.currentRelayRegistry; const err = () => checkAssetIdInput( @@ -67,8 +66,6 @@ type Test = [ describe('checkAssetIds', () => { it('Should error when an assetId is found that is empty or a blank space', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '1000', @@ -86,12 +83,10 @@ describe('checkAssetIds', () => { ], ]; - runTests(tests, registry); + runTests(tests); }); it('Should error when direction is RelayToSystem and assetId does not match relay chains native token', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '1000', @@ -116,12 +111,10 @@ describe('checkAssetIds', () => { ], ]; - runTests(tests, registry); + runTests(tests); }); it('Should error when direction is RelayToPara and assetId does not match relay chains native token', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '2004', @@ -139,12 +132,10 @@ describe('checkAssetIds', () => { ], ]; - runTests(tests, registry); + runTests(tests); }); it('Should error when direction is SystemToRelay and an assetId is not native to the relay chain', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '0', @@ -169,12 +160,10 @@ describe('checkAssetIds', () => { ], ]; - runTests(tests, registry); + runTests(tests); }); it('Should error when direction is SystemToPara and integer assetId is not found in system parachains assets', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '2004', @@ -201,8 +190,8 @@ describe('checkAssetIds', () => { for (const test of tests) { const [destChainId, specName, testInputs, direction, errorMessage] = test; - const relayChainName = findRelayChain(specName, registry); - const currentRegistry = registry[relayChainName]; + const registry = new Registry(specName, {}); + const currentRegistry = registry.currentRelayRegistry; const err = () => checkAssetIdInput( @@ -217,8 +206,6 @@ describe('checkAssetIds', () => { }); it('Should error when direction is SystemToPara and the string assetId is not found in the system parachains tokens or assets', () => { - const registry = parseRegistry({}); - const tests: Test[] = [ [ '2004', @@ -245,8 +232,8 @@ describe('checkAssetIds', () => { for (const test of tests) { const [destChainId, specName, testInputs, direction, errorMessage] = test; - const relayChainName = findRelayChain(specName, registry); - const currentRegistry = registry[relayChainName]; + const registry = new Registry(specName, {}); + const currentRegistry = registry.currentRelayRegistry; const err = () => checkAssetIdInput( diff --git a/src/errors/checkXcmTxInputs.ts b/src/errors/checkXcmTxInputs.ts index 0afe236d..18ecc936 100644 --- a/src/errors/checkXcmTxInputs.ts +++ b/src/errors/checkXcmTxInputs.ts @@ -1,6 +1,6 @@ import { RELAY_CHAIN_IDS, SYSTEM_PARACHAINS_IDS } from '../consts'; -import { findRelayChain } from '../registry/findRelayChain'; -import type { ChainInfo, ChainInfoRegistry } from '../registry/types'; +import { Registry } from '../registry'; +import type { ChainInfo } from '../registry/types'; import { Direction } from '../types'; import { BaseError } from './BaseError'; @@ -277,10 +277,9 @@ export const checkXcmTxInputs = ( xcmDirection: Direction, destChainId: string, specName: string, - registry: ChainInfoRegistry + registry: Registry ) => { - const relayChainName = findRelayChain(specName, registry); - const relayChainInfo: ChainInfo = registry[relayChainName]; + const relayChainInfo = registry.currentRelayRegistry; /** * Checks to ensure that assetId's are either valid integer numbers or native asset token symbols */ diff --git a/src/registry/Registry.spec.ts b/src/registry/Registry.spec.ts new file mode 100644 index 00000000..a237f918 --- /dev/null +++ b/src/registry/Registry.spec.ts @@ -0,0 +1,82 @@ +import { Registry } from './Registry'; + +describe('Registry', () => { + const registry = new Registry('polkadot', {}); + describe('lookupTokenSymbol', () => { + it('Should return the correct result', () => { + const res = registry.lookupTokenSymbol('GLMR'); + const expected = [ + { + tokens: ['GLMR'], + assetsInfo: {}, + specName: 'moonbeam', + chainId: '2004', + }, + ]; + expect(res).toEqual(expected); + }); + }); + describe('lookupAssetId', () => { + it('Should return the correct result', () => { + const res = registry.lookupAssetId('1984'); + const expected = [ + { + tokens: ['DOT'], + assetsInfo: { + '1': 'no1', + '2': 'BTC', + '3': 'DOT', + '4': 'EFI', + '5': 'PLX', + '6': 'LPHP', + '7': 'lucky7', + '8': 'JOE', + '9': 'PINT', + '10': 'BEAST', + '11': 'web3', + '21': 'WBTC', + '77': 'TRQ', + '100': 'WETH', + '101': 'DOTMA', + '123': '123', + '666': 'DANGER', + '777': '777', + '999': 'gold', + '1000': 'BRZ', + '1337': 'USDC', + '1984': 'USDt', + '862812': 'CUBO', + '868367': 'VSC', + '20090103': 'BTC', + }, + specName: 'statemint', + chainId: '1000', + }, + ]; + expect(res).toEqual(expected); + }); + }); + describe('lookupParachainId', () => { + it('Should return the correct result', () => { + const res1 = registry.lookupParachainId('1000'); + const res2 = registry.lookupParachainId('999999'); + + expect(res1).toEqual(true); + expect(res2).toEqual(false); + }); + }); + describe('lookupParachainInfo', () => { + it('Should return the correct result', () => { + const res = registry.lookupParachainInfo('2000'); + const expected = [ + { + tokens: ['ACA', 'AUSD', 'DOT', 'LDOT'], + assetsInfo: {}, + specName: 'acala', + chainId: '2000', + }, + ]; + expect(res).toEqual(expected); + }); + }); +}); diff --git a/src/registry/Registry.ts b/src/registry/Registry.ts new file mode 100644 index 00000000..f242d6f1 --- /dev/null +++ b/src/registry/Registry.ts @@ -0,0 +1,112 @@ +import type { AssetsTransferApiOpts } from '../types'; +import { findRelayChain, parseRegistry } from './'; +import type { + ChainInfo, + ChainInfoRegistry, + ExpandedChainInfoKeys, + RelayChains, +} from './types'; + +export class Registry { + readonly specName: string; + readonly registry: ChainInfoRegistry; + readonly relayChain: RelayChains; + readonly currentRelayRegistry: ChainInfo; + + constructor(specName: string, opts: AssetsTransferApiOpts) { + this.specName = specName; + this.registry = parseRegistry(opts); + this.relayChain = findRelayChain(this.specName, this.registry); + this.currentRelayRegistry = this.registry[this.relayChain]; + } + + /** + * Getter for the complete registry. + */ + public get getRegistry() { + return this.registry; + } + + /** + * Getter for the name of the relay chain for this network. + */ + public get getRelayChain() { + return this.relayChain; + } + + /** + * Getter for the registry associated with this networks relay chain. + */ + public get getRelaysRegistry() { + return this.currentRelayRegistry; + } + + /** + * Lookup all chains that have the following token symbol. It will return an array + * with all the chains that have the following token symbols. Note this will only + * be searched in the respective relay chains registry. + * + * @param symbol Token symbol to lookup + */ + public lookupTokenSymbol(symbol: string): ExpandedChainInfoKeys[] { + const chainIds = Object.keys(this.currentRelayRegistry); + const result = []; + + for (let i = 0; i < chainIds.length; i++) { + const chainInfo = this.currentRelayRegistry[chainIds[i]]; + if (chainInfo.tokens.includes(symbol)) { + result.push(Object.assign({}, chainInfo, { chainId: chainIds[i] })); + } + } + + return result; + } + + /** + * Lookup all chains that have the following assetId. It will return an array + * with all the chains that have the following AssetId. Note this will only + * be searched in the respective relay chains registry. + * + * @param id AssetId to lookup + */ + public lookupAssetId(id: string) { + const chainIds = Object.keys(this.currentRelayRegistry); + const result = []; + + for (let i = 0; i < chainIds.length; i++) { + const chainInfo = this.currentRelayRegistry[chainIds[i]]; + if (Object.keys(chainInfo.assetsInfo).includes(id)) { + result.push(Object.assign({}, chainInfo, { chainId: chainIds[i] })); + } + } + + return result; + } + + /** + * Check whether a parachain id exists within the relay chains registry. + * + * @param id Id of the parachain + */ + public lookupParachainId(id: string): boolean { + const chainIds = Object.keys(this.currentRelayRegistry); + if (chainIds.includes(id)) return true; + + return false; + } + + /** + * Return the info for a parachain within a relay chains registry. + * + * @param id + */ + public lookupParachainInfo(id: string): ExpandedChainInfoKeys[] { + const chainIds = Object.keys(this.currentRelayRegistry); + if (chainIds.includes(id)) { + return [ + Object.assign({}, this.currentRelayRegistry[id], { chainId: id }), + ]; + } + return []; + } +} diff --git a/src/registry/index.ts b/src/registry/index.ts index 6d7a3106..27222eed 100644 --- a/src/registry/index.ts +++ b/src/registry/index.ts @@ -1,2 +1,3 @@ export { findRelayChain } from './findRelayChain'; export { parseRegistry } from './parseRegistry'; +export { Registry } from './Registry'; diff --git a/src/registry/types.ts b/src/registry/types.ts index b605c29c..c6fd8453 100644 --- a/src/registry/types.ts +++ b/src/registry/types.ts @@ -1,15 +1,19 @@ // Copyright 2023 Parity Technologies (UK) Ltd. -interface AssetsInfo { +export interface AssetsInfo { [key: string]: string; } +export interface ChainInfoKeys { + specName: string; + tokens: string[]; + assetsInfo: AssetsInfo; +} + +export type ExpandedChainInfoKeys = { chainId: string } & ChainInfoKeys; + export type ChainInfo = { - [x: string]: { - specName: string; - tokens: string[]; - assetsInfo: AssetsInfo; - }; + [x: string]: ChainInfoKeys; }; export type ChainInfoRegistry = { diff --git a/src/types.ts b/src/types.ts index 3841a0ce..f9a9627d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,15 @@ import type { ISubmittableResult } from '@polkadot/types/types'; import type { ChainInfoRegistry } from './registry/types'; +export type RequireOnlyOne = Pick< + T, + Exclude +> & + { + [K in Keys]-?: Required> & + Partial, undefined>>; + }[Keys]; + export type RequireAtLeastOne = Pick< T, Exclude