diff --git a/wormhole-connect/src/routes/cctpManual/chains/evm.ts b/wormhole-connect/src/routes/cctpManual/chains/evm.ts index 9cbba0bb9..a1bf14ae4 100644 --- a/wormhole-connect/src/routes/cctpManual/chains/evm.ts +++ b/wormhole-connect/src/routes/cctpManual/chains/evm.ts @@ -10,7 +10,13 @@ import { import { BigNumber, ethers, providers, utils } from 'ethers'; import { getNativeVersionOfToken } from 'store/transferInput'; import { getTokenById, getTokenDecimals } from 'utils'; -import { PayloadType, isEvmChain, solanaContext, wh } from 'utils/sdk'; +import { + PayloadType, + isEvmChain, + solanaContext, + toChainId, + toChainName, +} from 'utils/sdk'; import { ManualCCTPMessage, SignedMessage, @@ -23,6 +29,7 @@ import { getNonce, } from '../utils'; import ManualCCTP from './abstract'; +import config from 'config'; export default class ManualCCTPEvmImpl implements ManualCCTP @@ -35,8 +42,8 @@ export default class ManualCCTPEvmImpl recipientChain: ChainName | ChainId, recipientAddress: string, ): Promise { - const fromChainId = wh.toChainId(sendingChain); - const fromChainName = wh.toChainName(sendingChain); + const fromChainId = toChainId(sendingChain); + const fromChainName = toChainName(sendingChain); const decimals = getTokenDecimals(fromChainId, token); const parsedAmt = utils.parseUnits(amount, decimals); @@ -44,14 +51,15 @@ export default class ManualCCTPEvmImpl if (!isEvmChain(sendingChain)) { throw new Error('No support for non EVM cctp currently'); } - const chainContext = wh.getContext( + const chainContext = config.wh.getContext( sendingChain, ) as EthContext; const tokenMessenger = - wh.mustGetContracts(sendingChain).cctpContracts?.cctpTokenMessenger; + config.wh.mustGetContracts(sendingChain).cctpContracts + ?.cctpTokenMessenger; const circleTokenMessenger = await TokenMessenger__factory.connect( tokenMessenger!, - wh.getSigner(fromChainId)!, + config.wh.getSigner(fromChainId)!, ); const tokenAddr = (token as TokenId).address; // approve @@ -61,8 +69,8 @@ export default class ManualCCTPEvmImpl tokenAddr, parsedAmt, ); - const recipientChainName = wh.toChainName(recipientChain); - const destinationDomain = wh.conf.chains[recipientChainName]?.cctpDomain; + const recipientChainName = toChainName(recipientChain); + const destinationDomain = config.chains[recipientChainName]?.cctpDomain; if (destinationDomain === undefined) throw new Error(`No CCTP on ${recipientChainName}`); const tx = await circleTokenMessenger.populateTransaction.depositForBurn( @@ -72,7 +80,9 @@ export default class ManualCCTPEvmImpl chainContext.context.parseAddress(tokenAddr, sendingChain), ); - const sentTx = await wh.getSigner(fromChainName)?.sendTransaction(tx); + const sentTx = await config.wh + .getSigner(fromChainName) + ?.sendTransaction(tx); const rx = await sentTx?.wait(); if (!rx) throw new Error("Transaction didn't go through"); return rx; @@ -85,11 +95,11 @@ export default class ManualCCTPEvmImpl ): Promise { if (!isSignedCCTPMessage(message)) throw new Error('Signed message is not for CCTP'); - const context: any = wh.getContext(destChain); + const context: any = config.wh.getContext(destChain); const circleMessageTransmitter = context.contracts.mustGetContracts(destChain).cctpContracts ?.cctpMessageTransmitter; - const connection = wh.mustGetSigner(destChain); + const connection = config.wh.mustGetSigner(destChain); const contract = MessageTransmitter__factory.connect( circleMessageTransmitter, connection, @@ -110,21 +120,21 @@ export default class ManualCCTPEvmImpl ): Promise { // use this as reference // https://goerli.etherscan.io/tx/0xe4984775c76b8fe7c2b09cd56fb26830f6e5c5c6b540eb97d37d41f47f33faca#eventlog - const provider = wh.mustGetProvider(chain); + const provider = config.wh.mustGetProvider(chain); const receipt = await provider.getTransactionReceipt(tx); if (!receipt) throw new Error(`No receipt for ${tx} on ${chain}`); // Get the CCTP log const cctpLog = receipt.logs.filter( - (log) => log.topics[0] === CCTP_LOG_TokenMessenger_DepositForBurn, + (log: any) => log.topics[0] === CCTP_LOG_TokenMessenger_DepositForBurn, )[0]; const parsedCCTPLog = TokenMessenger__factory.createInterface().parseLog(cctpLog); const messageLog = receipt.logs.filter( - (log) => log.topics[0] === CCTP_LOG_MessageSent, + (log: any) => log.topics[0] === CCTP_LOG_MessageSent, )[0]; const message = @@ -132,18 +142,18 @@ export default class ManualCCTPEvmImpl .message; const toChain = getChainNameCCTP(parsedCCTPLog.args.destinationDomain); - const destContext = wh.getContext(toChain); + const destContext = config.wh.getContext(toChain); let recipient = destContext.parseAddress(parsedCCTPLog.args.mintRecipient); if (toChain === 'solana') { recipient = await solanaContext().getTokenAccountOwner(recipient); } - const fromChain = wh.toChainName(chain); + const fromChain = toChainName(chain); const tokenId: TokenId = { chain: fromChain, address: parsedCCTPLog.args.burnToken, }; const token = getTokenById(tokenId); - const decimals = await wh.fetchTokenDecimals(tokenId, fromChain); + const decimals = await config.wh.fetchTokenDecimals(tokenId, fromChain); return { sendTx: receipt.transactionHash, sender: receipt.from, @@ -177,7 +187,7 @@ export default class ManualCCTPEvmImpl recipientAddress: string, routeOptions?: any, ): Promise { - const provider = wh.mustGetProvider(sendingChain); + const provider = config.wh.mustGetProvider(sendingChain); const { gasPrice } = await provider.getFeeData(); if (!gasPrice) throw new Error('gas price not available, cannot estimate fees'); @@ -186,22 +196,23 @@ export default class ManualCCTPEvmImpl if (!isEvmChain(sendingChain)) { throw new Error('No support for non EVM cctp currently'); } - const chainContext = wh.getContext( + const chainContext = config.wh.getContext( sendingChain, ) as EthContext; const tokenMessenger = - wh.mustGetContracts(sendingChain).cctpContracts?.cctpTokenMessenger; + config.wh.mustGetContracts(sendingChain).cctpContracts + ?.cctpTokenMessenger; const circleSender = TokenMessenger__factory.connect( tokenMessenger!, - wh.getSigner(sendingChain)!, + config.wh.getSigner(sendingChain)!, ); const tokenAddr = (token as TokenId).address; - const toChainName = wh.toChainName(recipientChain)!; - const decimals = getTokenDecimals(wh.toChainId(sendingChain), token); + const toChain = toChainName(recipientChain)!; + const decimals = getTokenDecimals(toChainId(sendingChain), token); const parsedAmt = utils.parseUnits(`${amount}`, decimals); - const destinationDomain = wh.conf.chains[toChainName]?.cctpDomain; + const destinationDomain = config.chains[toChain]?.cctpDomain; if (destinationDomain === undefined) - throw new Error(`CCTP not supported on ${toChainName}`); + throw new Error(`CCTP not supported on ${toChain}`); const tx = await circleSender.populateTransaction.depositForBurn( parsedAmt, destinationDomain, @@ -221,11 +232,11 @@ export default class ManualCCTPEvmImpl if (!isSignedCCTPMessage(messageInfo)) throw new Error('Signed message is not for CCTP'); const nonce = getNonce(messageInfo.message); - const context: any = wh.getContext(destChain); + const context: any = config.wh.getContext(destChain); const circleMessageTransmitter = context.contracts.mustGetContracts(destChain).cctpContracts ?.cctpMessageTransmitter; - const connection = wh.mustGetProvider(destChain); + const connection = config.wh.mustGetProvider(destChain); const iface = new utils.Interface([ 'function usedNonces(bytes32 domainNonceHash) view returns (uint256)', ]); @@ -235,7 +246,7 @@ export default class ManualCCTPEvmImpl connection, ); - const cctpDomain = wh.conf.chains[messageInfo.fromChain]?.cctpDomain; + const cctpDomain = config.chains[messageInfo.fromChain]?.cctpDomain; if (cctpDomain === undefined) throw new Error(`CCTP not supported on ${messageInfo.fromChain}`); diff --git a/wormhole-connect/src/routes/cctpManual/chains/solana.ts b/wormhole-connect/src/routes/cctpManual/chains/solana.ts index 54f9a3ac2..52d5dc998 100644 --- a/wormhole-connect/src/routes/cctpManual/chains/solana.ts +++ b/wormhole-connect/src/routes/cctpManual/chains/solana.ts @@ -16,8 +16,7 @@ import { WormholeContext, } from '@wormhole-foundation/wormhole-connect-sdk'; import { BigNumber, utils as ethUtils } from 'ethers'; -import { PayloadType, solanaContext, wh } from 'utils/sdk'; -import { TOKENS } from '../../../config'; +import { PayloadType, solanaContext, toChainId, toChainName } from 'utils/sdk'; import { getNativeVersionOfToken } from '../../../store/transferInput'; import { getTokenById, getTokenDecimals } from '../../../utils'; import { @@ -34,6 +33,7 @@ import ManualCCTP from './abstract'; import { getChainNameCCTP, getDomainCCTP } from '../utils/chains'; import { CircleBridge } from '@wormhole-foundation/sdk-definitions'; import { hexlify } from 'ethers/lib/utils'; +import config from 'config'; const CCTP_NONCE_OFFSET = 12; const MAX_NONCES_PER_ACCOUNT = 6400n; @@ -67,7 +67,7 @@ const findProgramAddress = ( }; function getMessageTransmitter(): Program { - const context = wh.getContext( + const context = config.wh.getContext( CHAIN_ID_SOLANA, ) as SolanaContext; const connection = context.connection; @@ -84,7 +84,7 @@ function getMessageTransmitter(): Program { } function getTokenMessenger(): Program { - const context = wh.getContext( + const context = config.wh.getContext( CHAIN_ID_SOLANA, ) as SolanaContext; const connection = context.connection; @@ -109,15 +109,15 @@ export class ManualCCTPSolanaImpl implements ManualCCTP { recipientChain: ChainName | ChainId, recipientAddress: string, ): Promise { - const fromChainId = wh.toChainId(sendingChain); + const fromChainId = toChainId(sendingChain); const decimals = getTokenDecimals(fromChainId, token); const parsedAmt = ethUtils.parseUnits(amount, decimals); if (token === 'native') throw new Error('Native not supported by cctp routes'); - const recipientChainName = wh.toChainName(recipientChain); - const destinationDomain = wh.conf.chains[recipientChainName]?.cctpDomain; + const recipientChainName = toChainName(recipientChain); + const destinationDomain = config.chains[recipientChainName]?.cctpDomain; if (destinationDomain === undefined) throw new Error(`No CCTP on ${recipientChainName}`); @@ -163,7 +163,7 @@ export class ManualCCTPSolanaImpl implements ManualCCTP { new PublicKey(senderAddress), ); - const destContext = wh.getContext(recipientChain); + const destContext = config.wh.getContext(recipientChain); const recipient = destContext.formatAddress(recipientAddress); const messageSentKeypair = Keypair.generate(); @@ -213,7 +213,7 @@ export class ManualCCTPSolanaImpl implements ManualCCTP { const messageTransmitterProgram = getMessageTransmitter(); const tokenMessengerMinterProgram = getTokenMessenger(); - const fromContext = wh.getContext(message.fromChain); + const fromContext = config.wh.getContext(message.fromChain); const messageBytes = Buffer.from(message.message.replace('0x', ''), 'hex'); const attestationBytes = Buffer.from( @@ -224,7 +224,7 @@ export class ManualCCTPSolanaImpl implements ManualCCTP { const remoteDomain = getDomainCCTP(message.fromChain); const tokenKey = getNativeVersionOfToken('USDC', 'solana'); - const tokenConfig = TOKENS[tokenKey]; + const tokenConfig = config.tokens[tokenKey]; if (!tokenConfig || !tokenConfig.tokenId) throw new Error('Invalid USDC token'); const solanaUsdcAddress = new PublicKey(tokenConfig.tokenId.address); @@ -390,16 +390,15 @@ export class ManualCCTPSolanaImpl implements ManualCCTP { if (!data) throw new Error('No message sent data'); // TODO: why 44? const circleMsgArray = new Uint8Array(data.data.slice(44)); - const [circleMsg, hash] = CircleBridge.deserialize(circleMsgArray); - console.log(circleMsg, hash); + const [circleMsg] = CircleBridge.deserialize(circleMsgArray); const tokenAddress = await context.parseAssetAddress( circleMsg.payload.burnToken.toString(), ); const tokenId: TokenId = { address: tokenAddress, chain: 'solana' }; const token = getTokenById(tokenId); - const decimals = await wh.fetchTokenDecimals(tokenId, 'solana'); + const decimals = await config.wh.fetchTokenDecimals(tokenId, 'solana'); const toChain = getChainNameCCTP(circleMsg.destinationDomain); - const destContext = wh.getContext(toChain); + const destContext = config.wh.getContext(toChain); const recipient = destContext.parseAddress( circleMsg.payload.mintRecipient.toString(), ); diff --git a/wormhole-connect/src/routes/cctpManual/utils/chains.ts b/wormhole-connect/src/routes/cctpManual/utils/chains.ts index 473754258..90ac1043e 100644 --- a/wormhole-connect/src/routes/cctpManual/utils/chains.ts +++ b/wormhole-connect/src/routes/cctpManual/utils/chains.ts @@ -29,6 +29,8 @@ export const CCTPDomains: Partial> = { arbitrumgoerli: 3, basegoerli: 6, solana: 5, + polygon: 7, + mumbai: 7, }; export function getChainNameCCTP(domain: number): ChainName { diff --git a/wormhole-connect/src/routes/operator.ts b/wormhole-connect/src/routes/operator.ts index 46b71ffb3..141b317fa 100644 --- a/wormhole-connect/src/routes/operator.ts +++ b/wormhole-connect/src/routes/operator.ts @@ -99,7 +99,7 @@ export class Operator { } if (chain === 'solana') { - const { connection, contracts } = wh.getContext( + const { connection, contracts } = config.wh.getContext( chain, ) as SolanaContext; if (!connection) throw new Error('No connection for Solana'); diff --git a/wormhole-connect/src/views/Bridge/Inputs/TokenWarnings.tsx b/wormhole-connect/src/views/Bridge/Inputs/TokenWarnings.tsx index 86300ffa0..e30c9efae 100644 --- a/wormhole-connect/src/views/Bridge/Inputs/TokenWarnings.tsx +++ b/wormhole-connect/src/views/Bridge/Inputs/TokenWarnings.tsx @@ -8,7 +8,7 @@ import { setForeignAsset, } from 'store/transferInput'; import config from 'config'; -import { getWrappedTokenId } from 'utils'; +import { getTokenById, getWrappedTokenId } from 'utils'; import { TransferWallet, signAndSendTransaction } from 'utils/wallet'; import { joinClass } from 'utils/style'; import { solanaContext } from 'utils/sdk'; @@ -157,7 +157,9 @@ function TokenWarnings() { setShowErrors(false); return false; } - const tokenId = getWrappedTokenId(tokenConfig); + const tokenId = + getTokenById({ chain: 'solana', address: foreignAsset })?.tokenId || + getWrappedTokenId(tokenConfig); const account = await solanaContext().getAssociatedTokenAccount( tokenId, receiving.address, @@ -183,7 +185,9 @@ function TokenWarnings() { throw new Error( 'The token must be registered on Solana before an associated token account can be created', ); - const tokenId = getWrappedTokenId(tokenConfig); + const tokenId = + getTokenById({ chain: 'solana', address: foreignAsset })?.tokenId || + getWrappedTokenId(tokenConfig); const tx = await solanaContext().createAssociatedTokenAccount( tokenId, receiving.address, diff --git a/wormhole-connect/src/views/Redeem/SendTo.tsx b/wormhole-connect/src/views/Redeem/SendTo.tsx index 28d9c4c11..1cf77e8e1 100644 --- a/wormhole-connect/src/views/Redeem/SendTo.tsx +++ b/wormhole-connect/src/views/Redeem/SendTo.tsx @@ -41,7 +41,11 @@ function AssociatedTokenAlert() { const wallet = useSelector((state: RootState) => state.wallet.receiving); const createAssociatedTokenAccount = useCallback(async () => { - const token = getTokenById(txData.tokenId); + const receivedToken = config.tokens[txData.receivedTokenKey]; + const token = + receivedToken?.nativeChain === 'solana' + ? receivedToken + : getTokenById(txData.tokenId); if (!token) return; const tokenId = getWrappedTokenId(token); const tx = await solanaContext().createAssociatedTokenAccount(