> For the complete documentation index, see [llms.txt](/llms.txt).

# Send EVM and Solana transactions

This guide shows you how to send transactions on both EVM networks and Solana from a single multichain session. No network switching is required.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

- Follow Step 1 of the [quickstart](/metamask-connect/multichain/quickstart/javascript/) to install the multichain client.
- To build Solana transactions, install `@solana/web3.js`:

- npm
- Yarn
- pnpm
- Bun

```
npm install @solana/web3.js

```

```
yarn add @solana/web3.js

```

```
pnpm add @solana/web3.js

```

```
bun add @solana/web3.js

```

## Initialize and connect[​](#initialize-and-connect "Direct link to Initialize and connect")

Initialize a multichain client using [createMultichainClient](/metamask-connect/multichain/reference/methods/#createmultichainclient), and connect to both ecosystems using [connect](/metamask-connect/multichain/reference/methods/#connect):

```
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain'

const client = await createMultichainClient({
  dapp: {
    name: 'Multichain Demo',
    url: window.location.href,
  },
  api: {
    supportedNetworks: {
      ...getInfuraRpcUrls({ infuraApiKey: 'YOUR_INFURA_API_KEY' }),
    },
  },
})

await client.connect(
  ['eip155:1', 'eip155:137', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], // Ethereum, Polygon, Solana
  []
)

```

## Understand RPC routing[​](#understand-rpc-routing "Direct link to Understand RPC routing")

The multichain client routes EVM methods based on type:

| Route        | Methods                                                                                                         | Transport                                      |
| ------------ | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| **RPC node** | eth_call, eth_getBalance, eth_blockNumber, eth_getTransactionReceipt, eth_estimateGas, eth_getCode, eth_getLogs | Infura / custom RPC URL from supportedNetworks |
| **Wallet**   | eth_sendTransaction, personal_sign, eth_signTypedData_v4, wallet_switchEthereumChain, wallet_addEthereumChain   | MetaMask (extension or mobile)                 |

All Solana methods route through the MetaMask wallet. There is no RPC node fallback for Solana.

## Send an EVM transaction[​](#send-an-evm-transaction "Direct link to Send an EVM transaction")

Use [invokeMethod](/metamask-connect/multichain/reference/methods/#invokemethod) with [eth_sendTransaction](/metamask-connect/evm/reference/json-rpc-api/eth%5FsendTransaction/) to send a transaction on any EVM chain in the session:

```
const txHash = await client.invokeMethod({
  scope: 'eip155:1', // Ethereum Mainnet
  request: {
    method: 'eth_sendTransaction',
    params: [
      {
        from: '0xYourAddress',
        to: '0xRecipientAddress',
        value: '0x2386F26FC10000', // 0.01 ETH in hex wei
        gas: '0x5208', // 21000 gas (optional)
      },
    ],
  },
})
console.log('ETH tx hash:', txHash)

```

Target a different chain by changing the `scope`; for example, `eip155:137` for Polygon:

```
const txHash = await client.invokeMethod({
  scope: 'eip155:137',
  request: {
    method: 'eth_sendTransaction',
    params: [
      {
        from: '0xYourAddress',
        to: '0xRecipientAddress',
        value: '0x2386F26FC10000', // 0.01 POL in hex wei
      },
    ],
  },
})
console.log('POL tx hash:', txHash)

```

## Estimate gas[​](#estimate-gas "Direct link to Estimate gas")

Use [invokeMethod](/metamask-connect/multichain/reference/methods/#invokemethod) with [eth_estimateGas](/metamask-connect/evm/reference/json-rpc-api/eth%5FestimateGas/) to estimate the gas cost before sending. This routes to the RPC node and does not prompt the user:

```
const gasEstimate = await client.invokeMethod({
  scope: 'eip155:1',
  request: {
    method: 'eth_estimateGas',
    params: [
      {
        from: '0xYourAddress',
        to: '0xRecipientAddress',
        value: '0x2386F26FC10000',
      },
    ],
  },
})
console.log('Estimated gas:', gasEstimate)

```

## Build and send a Solana transaction[​](#build-and-send-a-solana-transaction "Direct link to Build and send a Solana transaction")

Build a transaction with `@solana/web3.js`, serialize it to base64, then send it with `solana_signAndSendTransaction`. This signs and broadcasts the transaction in one step:

```
import { Connection, PublicKey, SystemProgram, Transaction } from '@solana/web3.js'

const connection = new Connection('https://solana-mainnet.infura.io/v3/YOUR_INFURA_API_KEY')
const fromPubkey = new PublicKey('YourSolanaPublicKey')
const toPubkey = new PublicKey('RecipientSolanaPublicKey')

const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey,
    toPubkey,
    lamports: 1_000_000, // 0.001 SOL
  })
)

transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
transaction.feePayer = fromPubkey

const serialized = transaction.serialize({
  requireAllSignatures: false,
  verifySignatures: false,
})
const base64Transaction = Buffer.from(serialized).toString('base64')

const result = await client.invokeMethod({
  scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
  request: {
    method: 'solana_signAndSendTransaction',
    params: {
      transaction: base64Transaction,
    },
  },
})
console.log('SOL tx signature:', result.signature)

```

## Sign a Solana transaction without sending[​](#sign-a-solana-transaction-without-sending "Direct link to Sign a Solana transaction without sending")

Use `solana_signTransaction` to get the signed transaction back without broadcasting it. This is useful when you need to inspect or modify the signed output before submitting:

```
const signResult = await client.invokeMethod({
  scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
  request: {
    method: 'solana_signTransaction',
    params: {
      transaction: base64Transaction,
    },
  },
})

// Broadcast the signed transaction yourself
const signedBuffer = Buffer.from(signResult.transaction, 'base64')
const txId = await connection.sendRawTransaction(signedBuffer)
console.log('Transaction ID:', txId)

```

## Error handling[​](#error-handling "Direct link to Error handling")

| Error code | Description               | Action                                                     |
| ---------- | ------------------------- | ---------------------------------------------------------- |
| 4001       | User rejected the request | Show a retry option. Do not treat as an application error. |
| -32002     | Request already pending   | Wait for the user to respond in MetaMask before retrying.  |

  
```
try {
  const txHash = await client.invokeMethod({
    scope: 'eip155:1',
    request: {
      method: 'eth_sendTransaction',
      params: [
        {
          from: '0xYourAddress',
          to: '0xRecipientAddress',
          value: '0x2386F26FC10000',
        },
      ],
    },
  })
} catch (err) {
  if (err.code === 4001) {
    console.log('User rejected the transaction')
    return
  }
  if (err.code === -32002) {
    console.log('A transaction request is already pending')
    return
  }
  throw err
}

```

## Next steps[​](#next-steps "Direct link to Next steps")

- [Sign messages on EVM and Solana.](/metamask-connect/multichain/guides/sign-transactions/)
- See the [Multichain SDK methods reference](/metamask-connect/multichain/reference/methods/).
