viem
Version:
282 lines (264 loc) • 8.76 kB
text/typescript
import type { Address } from 'abitype'
import type { Account } from '../../accounts/types.js'
import {
type ParseAccountErrorType,
parseAccount,
} from '../../accounts/utils/parseAccount.js'
import {
type EstimateFeesPerGasErrorType,
internal_estimateFeesPerGas,
} from '../../actions/public/estimateFeesPerGas.js'
import {
type EstimateGasErrorType,
type EstimateGasParameters,
estimateGas,
} from '../../actions/public/estimateGas.js'
import {
type GetBlockErrorType,
getBlock,
} from '../../actions/public/getBlock.js'
import {
type GetTransactionCountErrorType,
getTransactionCount,
} from '../../actions/public/getTransactionCount.js'
import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import { type AccountNotFoundErrorType } from '../../errors/account.js'
import {
Eip1559FeesNotSupportedError,
MaxFeePerGasTooLowError,
} from '../../errors/fee.js'
import type { Block } from '../../index.js'
import type { DeriveAccount, GetAccountParameter } from '../../types/account.js'
import type { Chain, DeriveChain } from '../../types/chain.js'
import type { GetChainParameter } from '../../types/chain.js'
import type { TransactionSerializable } from '../../types/transaction.js'
import type { UnionOmit, UnionRequiredBy } from '../../types/utils.js'
import type { FormattedTransactionRequest } from '../../utils/formatters/transactionRequest.js'
import { getAction } from '../../utils/getAction.js'
import type {
AssertRequestErrorType,
AssertRequestParameters,
} from '../../utils/transaction/assertRequest.js'
import { assertRequest } from '../../utils/transaction/assertRequest.js'
import { getTransactionType } from '../../utils/transaction/getTransactionType.js'
export type PrepareTransactionRequestParameterType =
| 'fees'
| 'gas'
| 'nonce'
| 'type'
type ParameterTypeToParameters<
TParameterType extends PrepareTransactionRequestParameterType,
> = TParameterType extends 'fees'
? 'maxFeePerGas' | 'maxPriorityFeePerGas' | 'gasPrice'
: TParameterType
export type PrepareTransactionRequestParameters<
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined,
TChainOverride extends Chain | undefined = Chain | undefined,
TAccountOverride extends Account | Address | undefined =
| Account
| Address
| undefined,
TParameterType extends
PrepareTransactionRequestParameterType = PrepareTransactionRequestParameterType,
///
derivedChain extends Chain | undefined = DeriveChain<TChain, TChainOverride>,
> = UnionOmit<FormattedTransactionRequest<derivedChain>, 'from'> &
GetAccountParameter<TAccount, TAccountOverride, false> &
GetChainParameter<TChain, TChainOverride> & {
parameters?: TParameterType[]
}
export type PrepareTransactionRequestReturnType<
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined,
TChainOverride extends Chain | undefined = Chain | undefined,
TAccountOverride extends Account | Address | undefined =
| Account
| Address
| undefined,
TParameterType extends
PrepareTransactionRequestParameterType = PrepareTransactionRequestParameterType,
///
derivedAccount extends Account | Address | undefined = DeriveAccount<
TAccount,
TAccountOverride
>,
derivedChain extends Chain | undefined = DeriveChain<TChain, TChainOverride>,
> = UnionRequiredBy<
UnionOmit<FormattedTransactionRequest<derivedChain>, 'from'>,
ParameterTypeToParameters<TParameterType>
> &
GetChainParameter<TChain, TChainOverride> &
(derivedAccount extends Account
? { account: derivedAccount; from: Address }
: { account?: undefined; from?: undefined })
export type PrepareTransactionRequestErrorType =
| AccountNotFoundErrorType
| AssertRequestErrorType
| ParseAccountErrorType
| GetBlockErrorType
| GetTransactionCountErrorType
| EstimateGasErrorType
| EstimateFeesPerGasErrorType
/**
* Prepares a transaction request for signing.
*
* - Docs: https://viem.sh/docs/actions/wallet/prepareTransactionRequest.html
*
* @param args - {@link PrepareTransactionRequestParameters}
* @returns The transaction request. {@link PrepareTransactionRequestReturnType}
*
* @example
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { prepareTransactionRequest } from 'viem/actions'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* })
* const request = await prepareTransactionRequest(client, {
* account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* to: '0x0000000000000000000000000000000000000000',
* value: 1n,
* })
*
* @example
* // Account Hoisting
* import { createWalletClient, http } from 'viem'
* import { privateKeyToAccount } from 'viem/accounts'
* import { mainnet } from 'viem/chains'
* import { prepareTransactionRequest } from 'viem/actions'
*
* const client = createWalletClient({
* account: privateKeyToAccount('0x…'),
* chain: mainnet,
* transport: custom(window.ethereum),
* })
* const request = await prepareTransactionRequest(client, {
* to: '0x0000000000000000000000000000000000000000',
* value: 1n,
* })
*/
export async function prepareTransactionRequest<
TChain extends Chain | undefined,
TAccount extends Account | undefined,
TParameterType extends PrepareTransactionRequestParameterType,
TAccountOverride extends Account | Address | undefined = undefined,
TChainOverride extends Chain | undefined = undefined,
>(
client: Client<Transport, TChain, TAccount>,
args: PrepareTransactionRequestParameters<
TChain,
TAccount,
TChainOverride,
TAccountOverride,
TParameterType
>,
): Promise<
PrepareTransactionRequestReturnType<
TChain,
TAccount,
TChainOverride,
TAccountOverride,
TParameterType
>
> {
const {
account: account_ = client.account,
chain,
gas,
nonce,
parameters = ['fees', 'gas', 'nonce', 'type'],
type,
} = args
const account = account_ ? parseAccount(account_) : undefined
const block = await getAction(
client,
getBlock,
'getBlock',
)({ blockTag: 'latest' })
const request = { ...args, ...(account ? { from: account?.address } : {}) }
if (parameters.includes('nonce') && typeof nonce === 'undefined' && account)
request.nonce = await getAction(
client,
getTransactionCount,
'getTransactionCount',
)({
address: account.address,
blockTag: 'pending',
})
if (
(parameters.includes('fees') || parameters.includes('type')) &&
typeof type === 'undefined'
) {
try {
request.type = getTransactionType(
request as TransactionSerializable,
) as any
} catch {
// infer type from block
request.type =
typeof block.baseFeePerGas === 'bigint' ? 'eip1559' : 'legacy'
}
}
if (parameters.includes('fees')) {
if (request.type === 'eip1559') {
// EIP-1559 fees
const { maxFeePerGas, maxPriorityFeePerGas } =
await internal_estimateFeesPerGas(client, {
block: block as Block,
chain,
request: request as PrepareTransactionRequestParameters,
})
if (
typeof args.maxPriorityFeePerGas === 'undefined' &&
args.maxFeePerGas &&
args.maxFeePerGas < maxPriorityFeePerGas
)
throw new MaxFeePerGasTooLowError({
maxPriorityFeePerGas,
})
request.maxPriorityFeePerGas = maxPriorityFeePerGas
request.maxFeePerGas = maxFeePerGas
} else {
// Legacy fees
if (
typeof args.maxFeePerGas !== 'undefined' ||
typeof args.maxPriorityFeePerGas !== 'undefined'
)
throw new Eip1559FeesNotSupportedError()
const { gasPrice: gasPrice_ } = await internal_estimateFeesPerGas(
client,
{
block: block as Block,
chain,
request: request as PrepareTransactionRequestParameters,
type: 'legacy',
},
)
request.gasPrice = gasPrice_
}
}
if (parameters.includes('gas') && typeof gas === 'undefined')
request.gas = await getAction(
client,
estimateGas,
'estimateGas',
)({
...request,
account: account
? { address: account.address, type: 'json-rpc' }
: undefined,
} as EstimateGasParameters)
assertRequest(request as AssertRequestParameters)
delete request.parameters
return request as unknown as PrepareTransactionRequestReturnType<
TChain,
TAccount,
TChainOverride,
TAccountOverride,
TParameterType
>
}