@hyperlane-xyz/widgets
Version:
Common react components for Hyperlane projects
91 lines • 4.51 kB
JavaScript
import { useWallet } from '@solana/wallet-adapter-react';
import { Connection } from '@solana/web3.js';
import { useCallback } from 'react';
import { ProviderType, } from '@hyperlane-xyz/sdk/providers/ProviderType';
import { sleep } from '@hyperlane-xyz/utils';
import { widgetLogger } from '../logger.js';
const logger = widgetLogger.child({ module: 'walletIntegrations/solana' });
export { useSolanaAccount, useSolanaActiveChain, useSolanaConnectFn, useSolanaDisconnectFn, useSolanaWalletDetails, } from './solanaWallet.js';
export function useSolanaSwitchNetwork() {
const onSwitchNetwork = useCallback(async (chainName) => {
logger.warn(`Solana wallet must be connected to origin chain ${chainName}`);
}, []);
return { switchNetwork: onSwitchNetwork };
}
export function useSolanaWatchAsset(_multiProvider) {
const onAddAsset = useCallback(async (_token, _activeChainName) => {
throw new Error('Watch asset not available for solana');
}, []);
return { addAsset: onAddAsset };
}
export function useSolanaTransactionFns(multiProvider) {
const { sendTransaction: sendSolTransaction } = useWallet();
const { switchNetwork } = useSolanaSwitchNetwork();
const onSendTx = useCallback(async ({ tx, chainName, activeChainName, }) => {
if (tx.type !== ProviderType.SolanaWeb3)
throw new Error(`Unsupported tx type: ${tx.type}`);
if (activeChainName && activeChainName !== chainName)
await switchNetwork(chainName);
const rpcUrl = multiProvider.getRpcUrl(chainName);
const connection = new Connection(rpcUrl, 'confirmed');
const { context: { slot: minContextSlot }, value: { lastValidBlockHeight }, } = await connection.getLatestBlockhashAndContext();
logger.debug(`Sending tx on chain ${chainName}`);
const signature = await sendSolTransaction(tx.transaction, connection, {
minContextSlot,
});
const confirm = async () => {
// Poll via HTTP instead of connection.confirmTransaction which
// relies on signatureSubscribe (WebSocket) — many RPC providers
// (e.g. Alchemy) don't support that method.
const POLL_INTERVAL_MS = 2000;
while (true) {
try {
const { value } = await connection.getSignatureStatuses([
signature,
]);
const status = value?.[0];
if (status?.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
}
if (status?.confirmationStatus === 'confirmed' ||
status?.confirmationStatus === 'finalized') {
break;
}
const blockHeight = await connection.getBlockHeight();
if (blockHeight > lastValidBlockHeight) {
throw new Error('Transaction expired: block height exceeded');
}
}
catch (error) {
// Re-throw definitive failures (tx error, expiry)
if (error instanceof Error &&
(error.message.startsWith('Transaction failed') ||
error.message.startsWith('Transaction expired'))) {
throw error;
}
// Tolerate transient RPC errors (timeouts, rate limits)
logger.warn('Transient error polling tx confirmation', error);
}
await sleep(POLL_INTERVAL_MS);
}
const tx = await connection.getTransaction(signature, {
commitment: 'confirmed',
maxSupportedTransactionVersion: 0,
});
if (!tx) {
throw new Error(`Transaction ${signature} confirmed but not found`);
}
return { type: ProviderType.SolanaWeb3, receipt: tx };
};
return { hash: signature, confirm };
}, [switchNetwork, sendSolTransaction, multiProvider]);
const onMultiSendTx = useCallback(async ({ txs: _, chainName: __, activeChainName: ___, }) => {
throw new Error('Multi Transactions not supported on Solana');
}, []);
return {
sendTransaction: onSendTx,
sendMultiTransaction: onMultiSendTx,
switchNetwork,
};
}
//# sourceMappingURL=solana.js.map