diff --git a/packages/swapper/src/swappers/ThorchainSwapper/utils/poolAssetHelpers/poolAssetHelpers.ts b/packages/swapper/src/swappers/ThorchainSwapper/utils/poolAssetHelpers/poolAssetHelpers.ts index 0175c89e135..f31433872f3 100644 --- a/packages/swapper/src/swappers/ThorchainSwapper/utils/poolAssetHelpers/poolAssetHelpers.ts +++ b/packages/swapper/src/swappers/ThorchainSwapper/utils/poolAssetHelpers/poolAssetHelpers.ts @@ -5,10 +5,16 @@ import generatedTradableAssetMap from '../../generated/generatedTradableAssetMap const thorPoolIdAssetIdSymbolMap = generatedTradableAssetMap as Record -export const assetIdToThorPoolAssetIdMap = invert(thorPoolIdAssetIdSymbolMap) +export const assetIdToThorPoolAssetIdMap: Record = invert( + thorPoolIdAssetIdSymbolMap, +) + +const assetIdToThorPoolAssetIdMapLower: Record = Object.fromEntries( + Object.entries(assetIdToThorPoolAssetIdMap).map(([k, v]) => [k.toLowerCase(), v]), +) export const thorPoolAssetIdToAssetId = (id: string): AssetId | undefined => thorPoolIdAssetIdSymbolMap[id.toUpperCase()] export const assetIdToThorPoolAssetId = ({ assetId }: { assetId: AssetId }): string | undefined => - assetIdToThorPoolAssetIdMap[assetId] + assetIdToThorPoolAssetIdMapLower[assetId.toLowerCase()] diff --git a/packages/swapper/src/thorchain-utils/solana/constants.ts b/packages/swapper/src/thorchain-utils/solana/constants.ts new file mode 100644 index 00000000000..793406146df --- /dev/null +++ b/packages/swapper/src/thorchain-utils/solana/constants.ts @@ -0,0 +1,3 @@ +// Solana Memo Program v2 +// https://spl.solana.com/memo +export const MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' diff --git a/src/components/Modals/Send/utils.ts b/src/components/Modals/Send/utils.ts index 2b2ef38e06b..3abb01d58a6 100644 --- a/src/components/Modals/Send/utils.ts +++ b/src/components/Modals/Send/utils.ts @@ -16,12 +16,13 @@ import type { GetFeeDataInput, } from '@shapeshiftoss/chain-adapters' import { utxoChainIds } from '@shapeshiftoss/chain-adapters' -import type { HDWallet } from '@shapeshiftoss/hdwallet-core' +import type { HDWallet, SolanaTxInstruction } from '@shapeshiftoss/hdwallet-core' import { isGridPlus, supportsETH, supportsSolana } from '@shapeshiftoss/hdwallet-core/wallet' import { isLedger } from '@shapeshiftoss/hdwallet-ledger' import { isTrezor } from '@shapeshiftoss/hdwallet-trezor' import type { CosmosSdkChainId, EvmChainId, KnownChainIds, UtxoChainId } from '@shapeshiftoss/types' import { contractAddressOrUndefined } from '@shapeshiftoss/utils' +import { PublicKey, TransactionInstruction } from '@solana/web3.js' import type { SendInput } from './Form' @@ -59,6 +60,8 @@ export type EstimateFeesInput = { contractAddress: string | undefined } +const SOLANA_MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' + export const estimateFees = async ({ amountCryptoPrecision, assetId, @@ -112,8 +115,17 @@ export const estimateFees = async ({ case CHAIN_NAMESPACE.Solana: { const adapter = assertGetSolanaChainAdapter(asset.chainId) + const memoInstruction: TransactionInstruction | undefined = memo + ? new TransactionInstruction({ + keys: [], + programId: new PublicKey(SOLANA_MEMO_PROGRAM_ID), + data: Buffer.from(memo, 'utf8'), + }) + : undefined + // For SPL transfers, build complete instruction set including compute budget - // For SOL transfers (pure sends i.e not e.g a Jup swap), pass no instructions to get 0 count (avoids blind signing) + // For SOL transfers with memo (e.g. THORChain), pass memo instruction for accurate fee estimation + // For pure SOL transfers, pass no instructions to get 0 count (avoids blind signing) const instructions = contractAddress ? await adapter.buildEstimationInstructions({ from: account, @@ -121,6 +133,8 @@ export const estimateFees = async ({ tokenId: contractAddress, value, }) + : memoInstruction + ? [memoInstruction] : undefined const getFeeDataInput: GetFeeDataInput = { @@ -376,29 +390,36 @@ export const handleSendWithMetadata = async ({ const solanaAdapter = assertGetSolanaChainAdapter(chainId) const { account } = fromAccountId(sendInput.accountId) - const instructions = await solanaAdapter.buildEstimationInstructions({ + + const memoInstruction: SolanaTxInstruction | undefined = memo + ? { keys: [], programId: SOLANA_MEMO_PROGRAM_ID, data: Buffer.from(memo, 'utf8') } + : undefined + + const estimationInstructions = await solanaAdapter.buildEstimationInstructions({ from: account, to, tokenId: contractAddress, value, }) + const shouldAddComputeBudget = estimationInstructions.length > 1 || Boolean(memoInstruction) + const input: BuildSendTxInput = { to, value, wallet, accountNumber: bip44Params.accountNumber, pubKey: skipDeviceDerivation ? fromAccountId(sendInput.accountId).account : undefined, - chainSpecific: - instructions.length <= 1 - ? { - tokenId: contractAddress, - } - : { - tokenId: contractAddress, - computeUnitLimit: fees.chainSpecific.computeUnits, - computeUnitPrice: fees.chainSpecific.priorityFee, - }, + chainSpecific: shouldAddComputeBudget + ? { + tokenId: contractAddress, + computeUnitLimit: fees.chainSpecific.computeUnits, + computeUnitPrice: fees.chainSpecific.priorityFee, + instructions: memoInstruction ? [memoInstruction] : undefined, + } + : { + tokenId: contractAddress, + }, } return solanaAdapter.buildSendTransaction(input) diff --git a/src/lib/utils/thorchain/index.ts b/src/lib/utils/thorchain/index.ts index 1f770627430..c3c497e8e0a 100644 --- a/src/lib/utils/thorchain/index.ts +++ b/src/lib/utils/thorchain/index.ts @@ -5,6 +5,7 @@ import { fromAccountId, fromAssetId, rujiAssetId, + solanaChainId, tcyAssetId, thorchainChainId, tronChainId, @@ -308,7 +309,12 @@ export const getThorchainTransactionType = (chainId: ChainId) => { if (supportedEvmChainIds.includes(chainId as KnownChainIds)) { return 'EvmCustomTx' } - if (isUtxoChainId(chainId) || chainId === cosmosChainId || chainId === tronChainId) { + if ( + isUtxoChainId(chainId) || + chainId === cosmosChainId || + chainId === tronChainId || + chainId === solanaChainId + ) { return 'Send' } diff --git a/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx b/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx index 4f841700b14..40f9a6635a4 100644 --- a/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx +++ b/src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx @@ -1673,6 +1673,7 @@ export const AddLiquidityInput: React.FC = ({ {maybeOpportunityNotSupportedExplainer} {maybeAlert} = ({ assetIds }) => { ) } -const TypeRadio: React.FC = props => { - const { getInputProps, getRadioProps } = useRadio(props) +const TypeRadio: React.FC = props => { + const { 'data-testid': dataTestId, ...radioProps } = props + const { getInputProps, getRadioProps } = useRadio(radioProps) const input = getInputProps() const checkbox = getRadioProps() return ( - + + + {radioOptions} )