diff --git a/.changeset/large-ravens-lay.md b/.changeset/large-ravens-lay.md new file mode 100644 index 000000000..7b55f5215 --- /dev/null +++ b/.changeset/large-ravens-lay.md @@ -0,0 +1,7 @@ +--- +'@xchainjs/xchain-thorchain-query': patch +'@xchainjs/xchain-solana': patch +'@xchainjs/xchain-util': patch +--- + +fix assetFromString util function diff --git a/.changeset/ninety-toys-shake.md b/.changeset/ninety-toys-shake.md new file mode 100644 index 000000000..1cfdb5ece --- /dev/null +++ b/.changeset/ninety-toys-shake.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-util': patch +--- + +Fix broken assetFromString() diff --git a/packages/xchain-solana/__tests__/client.test.ts b/packages/xchain-solana/__tests__/client.test.ts index 36a0ac216..98262c8e8 100644 --- a/packages/xchain-solana/__tests__/client.test.ts +++ b/packages/xchain-solana/__tests__/client.test.ts @@ -100,6 +100,10 @@ describe('Solana client', () => { }) describe('Addresses', () => { + let client: Client + beforeAll(() => { + client = new Client() + }) it('Should not get address without phrase', () => { expect(async () => await client.getAddressAsync()).rejects.toThrowError('Phrase must be provided') }) diff --git a/packages/xchain-solana/package.json b/packages/xchain-solana/package.json index 20098be05..56ccf32fa 100644 --- a/packages/xchain-solana/package.json +++ b/packages/xchain-solana/package.json @@ -35,7 +35,7 @@ "@metaplex-foundation/mpl-token-metadata": "3.2.1", "@metaplex-foundation/umi": "0.9.2", "@metaplex-foundation/umi-bundle-defaults": "0.9.2", - "@solana/addresses": "2.0.0-rc.1", + "@solana/addresses": "2.0.0", "@solana/spl-token": "0.4.8", "@solana/web3.js": "1.95.2", "@xchainjs/xchain-client": "workspace:*", diff --git a/packages/xchain-thorchain-query/src/thorchain-query.ts b/packages/xchain-thorchain-query/src/thorchain-query.ts index 33600245b..98179d1f8 100644 --- a/packages/xchain-thorchain-query/src/thorchain-query.ts +++ b/packages/xchain-thorchain-query/src/thorchain-query.ts @@ -6,6 +6,7 @@ import { AssetCryptoAmount, Chain, CryptoAmount, + SECURED_ASSET_DELIMITER, SYNTH_ASSET_DELIMITER, SecuredAsset, SynthAsset, @@ -1614,6 +1615,7 @@ export class ThorchainQuery { ['c', 'BCH.BCH'], ['a', 'AVAX.AVAX'], ['s', 'BSC.BNB'], + ['f', 'BASE.ETH'], ]) const nativeAsset = nativeAlias.get(alias.toLowerCase()) @@ -1625,6 +1627,8 @@ export class ThorchainQuery { delimiter = TRADE_ASSET_DELIMITER } else if (alias.includes(SYNTH_ASSET_DELIMITER)) { delimiter = SYNTH_ASSET_DELIMITER + } else if (alias.includes(SECURED_ASSET_DELIMITER)) { + delimiter = SECURED_ASSET_DELIMITER } const splitedAlias = alias.split(delimiter) diff --git a/packages/xchain-thorchain/__e2e__/keystore-client.e2e.ts b/packages/xchain-thorchain/__e2e__/keystore-client.e2e.ts index ade68bc8c..13f6f3997 100644 --- a/packages/xchain-thorchain/__e2e__/keystore-client.e2e.ts +++ b/packages/xchain-thorchain/__e2e__/keystore-client.e2e.ts @@ -1,5 +1,13 @@ import { Tx } from '@xchainjs/xchain-client' -import { assetAmount, assetFromStringEx, assetToBase, assetToString, baseToAsset } from '@xchainjs/xchain-util' +import { + AssetType, + SecuredAsset, + assetAmount, + assetFromStringEx, + assetToBase, + assetToString, + baseToAsset, +} from '@xchainjs/xchain-util' import { AssetRuneNative as AssetRune, Client, DepositTx } from '../src' @@ -47,6 +55,12 @@ const getPrintableDepositTx = (depositTx: DepositTx) => { }), } } +const BTCSECURED: SecuredAsset = { + chain: 'BTC', + ticker: 'BTC', + symbol: 'BTC', + type: AssetType.SECURED, +} describe('Thorchain Keystore', () => { let client: Client @@ -143,6 +157,28 @@ describe('Thorchain Keystore', () => { } }) + it('Should make secured Asset swap', async () => { + try { + /** + * MAKE SURE TO TEST THIS FUNCTION WITH YOUR ADDRESS BNB, OTHERWISE, YOU COULD LOSE FUNDS + */ + const address: string = 'thor1rkpukrhljr72sxww2t0nwvng84zegp59805e03' || 'TO_BE_DEFINED' + if (address === 'TO_BE_DEFINED') throw Error('Set an address to try the deposit e2e function') + const memo = `=:AVAX-AVAX:${address}` + + const hash = await client.deposit({ + walletIndex: 0, + amount: assetToBase(assetAmount(0.00024005, 8)), + asset: BTCSECURED, + memo, + }) + console.log(hash) + } catch (error) { + console.log(error) + throw error + } + }) + it('Should transfer offline', async () => { const txRaw = await client.transferOffline({ walletIndex: 0, diff --git a/packages/xchain-util/__tests__/asset.test.ts b/packages/xchain-util/__tests__/asset.test.ts index 6f25c0ffc..16f4800b5 100755 --- a/packages/xchain-util/__tests__/asset.test.ts +++ b/packages/xchain-util/__tests__/asset.test.ts @@ -343,15 +343,35 @@ describe('asset', () => { expect(result).toEqual({ chain: 'ETH', symbol: 'ETH', ticker: 'ETH', type: AssetType.TRADE }) }) - it('trade BTC-BTC', () => { + it('secured BTC-BTC', () => { const result = assetFromString('BTC-BTC') expect(result).toEqual({ chain: 'BTC', symbol: 'BTC', ticker: 'BTC', type: AssetType.SECURED }) }) - it('trade ETH-ETH', () => { + it('secured ETH-ETH', () => { const result = assetFromString('ETH-ETH') expect(result).toEqual({ chain: 'ETH', symbol: 'ETH', ticker: 'ETH', type: AssetType.SECURED }) }) + it('secured AVAX-SOL-0XFE6B19286885A4F7F55ADAD09C3CD1F906D2478', () => { + const result = assetFromString('AVAX-SOL-0XFE6B19286885A4F7F55ADAD09C3CD1F906D2478') + expect(result).toEqual({ + chain: 'AVAX', + symbol: 'SOL-0XFE6B19286885A4F7F55ADAD09C3CD1F906D2478', + ticker: 'SOL', + type: AssetType.SECURED, + }) + }) + + it('synth ETH/USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48', () => { + const result = assetFromString('ETH/USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48') + expect(result).toEqual({ + chain: 'ETH', + symbol: 'USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48', + ticker: 'USDC', + type: AssetType.SYNTH, + }) + }) + it('KUJI.USK', () => { const result = assetFromString('KUJI.USK') expect(result).toEqual({ chain: 'KUJI', symbol: 'USK', ticker: 'USK', type: AssetType.TOKEN }) @@ -398,10 +418,6 @@ describe('asset', () => { const result = assetFromString('.BNB.BNB') expect(result).toBeNull() }) - it('null for invalid chain', () => { - const result = assetFromString('invalid.BNB.BNB') - expect(result).toEqual({ chain: 'invalid', symbol: 'BNB', type: AssetType.NATIVE, ticker: 'BNB' }) - }) }) describe('assetToString', () => { @@ -413,6 +429,15 @@ describe('asset', () => { const asset: Asset = { chain: 'ETH', symbol: 'ETH', ticker: 'ETH', type: AssetType.NATIVE } expect(assetToString(asset)).toEqual('ETH.ETH') }) + it('DAI string test', () => { + const asset: TokenAsset = { + chain: 'ETH', + symbol: 'DAI-0X6B175474E89094C44DA98B954EEDEAC495271D0F', + ticker: 'DAI', + type: AssetType.TOKEN, + } + expect(assetToString(asset)).toEqual('ETH.DAI-0X6B175474E89094C44DA98B954EEDEAC495271D0F') + }) it('ETH/ETH', () => { const asset: SynthAsset = { chain: 'ETH', symbol: 'ETH', ticker: 'ETH', type: AssetType.SYNTH } expect(assetToString(asset)).toEqual('ETH/ETH') diff --git a/packages/xchain-util/src/asset.ts b/packages/xchain-util/src/asset.ts index dbc753154..48e8719b6 100755 --- a/packages/xchain-util/src/asset.ts +++ b/packages/xchain-util/src/asset.ts @@ -278,41 +278,59 @@ const assetConfigs = new Map([ ['RUNE', { chain: 'THOR', symbol: 'RUNE', ticker: 'RUNE', type: AssetType.NATIVE }], ]) -const createAsset = (chain: string, symbol: string, ticker: string, type: AssetType) => { +// Helper function to create an asset from its components +const createAsset = (chain: string, symbol: string, ticker: string, type: AssetType): AnyAsset => { return { chain, symbol, ticker, type } } export const assetFromString = (s: string): AnyAsset | null => { + if (!s || s.trim() === '') return null // Handle empty strings + + // Check if the asset is directly in the assetConfigs const directAsset = assetConfigs.get(s) if (directAsset) return directAsset - // Define possible delimiters and their associated asset types - const delimiters: Record<'.' | '/' | '~' | '-', AssetType> = { + // Define asset delimiters for the first delimiter + const assetDelimiters: Record = { '.': AssetType.NATIVE, '/': AssetType.SYNTH, '~': AssetType.TRADE, '-': AssetType.SECURED, } - // Identify the first delimiter - const delimiter = Object.keys(delimiters).find((delim) => s.includes(delim)) as '.' | '/' | '~' | '-' | undefined - if (!delimiter) return null + // Identify the first delimiter in the string + const firstDelimiter = Object.keys(assetDelimiters).find((delim) => s.includes(delim)) as + | keyof typeof assetDelimiters + | undefined + if (!firstDelimiter) return null - // Split the string by the first delimiter - const [chain, symbol] = s.split(delimiter) - if (!chain || !symbol) return null + // Split the string into chain and symbol part based on the first delimiter + const [chain, ...restParts] = s.split(firstDelimiter) - // Determine initial type based on delimiter - let type = delimiters[delimiter] + if (!chain || restParts.length === 0) return null // Invalid format or empty parts - // Additional checks for token classification - if (delimiter === '.' && symbol.includes('-')) { - type = AssetType.TOKEN - } + // Handle secured and trade assets which may have further splits + const symbol = restParts.join(firstDelimiter) + let ticker = symbol.split('-')[0] - // Extract the ticker from the symbol (first part before `-` if it exists) - const ticker = symbol.split('-')[0] + // For secured and trade assets, handle contract address as part of the symbol + const type = assetDelimiters[firstDelimiter] + // Check if symbol is empty (e.g., BNB. or AVAX~ cases) + if (symbol === '') return null + // Handle trade and secured assets + if (firstDelimiter === '~' || firstDelimiter === '-' || firstDelimiter === '/') { + return createAsset(chain.trim(), symbol.trim(), ticker.trim(), type) + } + // Handle token assets: if the symbol has more than one part (split by `-`) + if (symbol.includes('-')) { + const [primaryTicker] = symbol.split('-') + ticker = primaryTicker + + // For token assets, use the contract address + return createAsset(chain.trim(), symbol.trim(), ticker.trim(), AssetType.TOKEN) + } + // For native, synth, or other types of assets return createAsset(chain.trim(), symbol.trim(), ticker.trim(), type) } diff --git a/yarn.lock b/yarn.lock index 85e8218f2..94443ee83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3305,28 +3305,28 @@ __metadata: languageName: node linkType: hard -"@solana/addresses@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/addresses@npm:2.0.0-rc.1" - dependencies: - "@solana/assertions": "npm:2.0.0-rc.1" - "@solana/codecs-core": "npm:2.0.0-rc.1" - "@solana/codecs-strings": "npm:2.0.0-rc.1" - "@solana/errors": "npm:2.0.0-rc.1" +"@solana/addresses@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/addresses@npm:2.0.0" + dependencies: + "@solana/assertions": "npm:2.0.0" + "@solana/codecs-core": "npm:2.0.0" + "@solana/codecs-strings": "npm:2.0.0" + "@solana/errors": "npm:2.0.0" peerDependencies: typescript: ">=5" - checksum: 10c0/3aabe755fd7fb7dee2edfd73faa15f83d97860d4f852bdbf6f90bec08909b4df7f623b1f714ff6951b05b01ed091bbffb7b68a76da0ccea238047b0610efe084 + checksum: 10c0/b4dc51c5818a36668d653f4162f4aba0356d7212ae0271483da992272bcb95b5626b6a7305c175e1d7b39b28c91ebe2768728b67ab557ac601637a0465537b87 languageName: node linkType: hard -"@solana/assertions@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/assertions@npm:2.0.0-rc.1" +"@solana/assertions@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/assertions@npm:2.0.0" dependencies: - "@solana/errors": "npm:2.0.0-rc.1" + "@solana/errors": "npm:2.0.0" peerDependencies: typescript: ">=5" - checksum: 10c0/2c9b6881e80671813a58723441c4aee325255017602e1195f48bc9ba4b609da965b51e56835eb5897a7d1978a4e09749eb4a2aafddcf2b47a5bf949f4d906ac7 + checksum: 10c0/a04e9def2a43b80b6299e83c631743ecc5e5e8ba062ec4576d8f49c297b9b96c2b0bc101e160353f3883b54907415d5815ad8e7f81bde5f0431c86462913121c languageName: node linkType: hard @@ -3351,6 +3351,17 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-core@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/codecs-core@npm:2.0.0" + dependencies: + "@solana/errors": "npm:2.0.0" + peerDependencies: + typescript: ">=5" + checksum: 10c0/31d20a17dc5388671fb7bd2ba924fae3a60d45e88571443d888210c28a05b913e884f49a425ac4e07a0c5c53352ff748cd882b8146ed46d2701f6fac926d9764 + languageName: node + linkType: hard + "@solana/codecs-core@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/codecs-core@npm:2.0.0-preview.2" @@ -3371,17 +3382,6 @@ __metadata: languageName: node linkType: hard -"@solana/codecs-core@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/codecs-core@npm:2.0.0-rc.1" - dependencies: - "@solana/errors": "npm:2.0.0-rc.1" - peerDependencies: - typescript: ">=5" - checksum: 10c0/3b1fd09727bf850d191292b14e1afb64cda4e57f898c06483f40d0402c4f07f1d4df555f028f664701e647834c74924818857443666d039f4e44c8c01f31f427 - languageName: node - linkType: hard - "@solana/codecs-data-structures@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/codecs-data-structures@npm:2.0.0-preview.2" @@ -3406,6 +3406,18 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-numbers@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/codecs-numbers@npm:2.0.0" + dependencies: + "@solana/codecs-core": "npm:2.0.0" + "@solana/errors": "npm:2.0.0" + peerDependencies: + typescript: ">=5" + checksum: 10c0/b424c5495dc34af03529c01ae90c8d4cc855986c47f05a2a3728a29ff14455dc10e082040d717dbba67e9ae84afb6718ffd89086fb1d58763fe4fe8d80810faa + languageName: node + linkType: hard + "@solana/codecs-numbers@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/codecs-numbers@npm:2.0.0-preview.2" @@ -3428,15 +3440,17 @@ __metadata: languageName: node linkType: hard -"@solana/codecs-numbers@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/codecs-numbers@npm:2.0.0-rc.1" +"@solana/codecs-strings@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/codecs-strings@npm:2.0.0" dependencies: - "@solana/codecs-core": "npm:2.0.0-rc.1" - "@solana/errors": "npm:2.0.0-rc.1" + "@solana/codecs-core": "npm:2.0.0" + "@solana/codecs-numbers": "npm:2.0.0" + "@solana/errors": "npm:2.0.0" peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 typescript: ">=5" - checksum: 10c0/baf888bbd9c9ed2420207329c735def60a2b3d94d4a0dd1a92703f4de165a96dfd5b66e4fe954d6a7fae12b6b95c41da500499f100b6d5cfad6420d4bfe71b50 + checksum: 10c0/330577f6b898d7f721f6326e41a27882740e4531e8f4717d3b8d521afcc9648c874f63d86bdf34d0a08bbd4bed0996a93a7cf131df452ab3221a5e7a621f292d languageName: node linkType: hard @@ -3467,20 +3481,6 @@ __metadata: languageName: node linkType: hard -"@solana/codecs-strings@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/codecs-strings@npm:2.0.0-rc.1" - dependencies: - "@solana/codecs-core": "npm:2.0.0-rc.1" - "@solana/codecs-numbers": "npm:2.0.0-rc.1" - "@solana/errors": "npm:2.0.0-rc.1" - peerDependencies: - fastestsmallesttextencoderdecoder: ^1.0.22 - typescript: ">=5" - checksum: 10c0/7f3483407de7e324075a85f2f8c91103021d6b8f38cfd4cf78603cbd7b00ea8b828a0cb9b61fb2b0db6d3e733fdf358006de23278cf3b103af1f1de4f3f66233 - languageName: node - linkType: hard - "@solana/codecs@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/codecs@npm:2.0.0-preview.2" @@ -3509,6 +3509,20 @@ __metadata: languageName: node linkType: hard +"@solana/errors@npm:2.0.0": + version: 2.0.0 + resolution: "@solana/errors@npm:2.0.0" + dependencies: + chalk: "npm:^5.3.0" + commander: "npm:^12.1.0" + peerDependencies: + typescript: ">=5" + bin: + errors: bin/cli.mjs + checksum: 10c0/f56fdc5263d99cfa65a7ee6213e5156383c58f1f3d86c540a05fddb5021d4a824e84f9ae545914acea534e9ee264cfc3001790ae4f90270d2bb3fb288e9fa49b + languageName: node + linkType: hard + "@solana/errors@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/errors@npm:2.0.0-preview.2" @@ -3535,20 +3549,6 @@ __metadata: languageName: node linkType: hard -"@solana/errors@npm:2.0.0-rc.1": - version: 2.0.0-rc.1 - resolution: "@solana/errors@npm:2.0.0-rc.1" - dependencies: - chalk: "npm:^5.3.0" - commander: "npm:^12.1.0" - peerDependencies: - typescript: ">=5" - bin: - errors: bin/cli.mjs - checksum: 10c0/26b9edb43b4ba86b36aefb020a6e47706554ce57a95a357a55879c570ffd000417b1d9567b94120d114dfd38051e8362c18ee082b58cc34690c4c00f1040423c - languageName: node - linkType: hard - "@solana/options@npm:2.0.0-preview.2": version: 2.0.0-preview.2 resolution: "@solana/options@npm:2.0.0-preview.2" @@ -4884,7 +4884,7 @@ __metadata: "@metaplex-foundation/mpl-token-metadata": "npm:3.2.1" "@metaplex-foundation/umi": "npm:0.9.2" "@metaplex-foundation/umi-bundle-defaults": "npm:0.9.2" - "@solana/addresses": "npm:2.0.0-rc.1" + "@solana/addresses": "npm:2.0.0" "@solana/spl-token": "npm:0.4.8" "@solana/web3.js": "npm:1.95.2" "@xchainjs/xchain-client": "workspace:*"