Skip to content

Commit

Permalink
feat: add celo fee.estimateFeePerGas (#2041)
Browse files Browse the repository at this point in the history
* Add celo fee.estimateFeePerGas (#10)

* missed changeset (#12)

* chore: tweak

* chore: tweak

* Update nervous-cows-agree.md

---------

Co-authored-by: Aaron DeRuvo <aaron.deruvo@gmail.com>
Co-authored-by: jxom <jakemoxey@gmail.com>
  • Loading branch information
3 people authored Mar 30, 2024
1 parent ea6e0f5 commit 29eea54
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/nervous-cows-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added custom Celo fees estimation function for cases when feeCurrency is used to send a transaction.
23 changes: 23 additions & 0 deletions src/actions/public/estimateFeesPerGas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createPublicClient } from '../../clients/createPublicClient.js'
import { http } from '../../clients/transports/http.js'

import { localHttpUrl } from '~test/src/constants.js'
import { createTestClient } from '~viem/index.js'
import {
estimateFeesPerGas,
internal_estimateFeesPerGas,
Expand All @@ -31,6 +32,28 @@ test('legacy', async () => {
expect(gasPrice).toBe((gasPrice_! * 120n) / 100n)
})

test('args: chain `estimateFeesPerGas` override (when null returned)', async () => {
const client = createTestClient({
transport: http(localHttpUrl),
mode: 'anvil',
})

const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFeesPerGas(
client,
{
chain: {
...anvilChain,
fees: {
estimateFeesPerGas: async () => null,
},
},
},
)

expect(maxFeePerGas).toBeTypeOf('bigint')
expect(maxPriorityFeePerGas).toBeTypeOf('bigint')
})

test('args: chain `estimateFeesPerGas` override', async () => {
const client = createPublicClient({
transport: http(localHttpUrl),
Expand Down
11 changes: 7 additions & 4 deletions src/actions/public/estimateFeesPerGas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import type {
Chain,
ChainEstimateFeesPerGasFnParameters,
ChainFeesFnParameters,
GetChainParameter,
} from '../../types/chain.js'
import type { GetChainParameter } from '../../types/chain.js'
import type {
FeeValuesEIP1559,
FeeValuesLegacy,
Expand Down Expand Up @@ -130,14 +130,17 @@ export async function internal_estimateFeesPerGas<
? block_
: await getAction(client, getBlock, 'getBlock')({})

if (typeof chain?.fees?.estimateFeesPerGas === 'function')
return chain.fees.estimateFeesPerGas({
if (typeof chain?.fees?.estimateFeesPerGas === 'function') {
const fees = (await chain.fees.estimateFeesPerGas({
block: block_ as Block,
client,
multiply,
request,
type,
} as ChainEstimateFeesPerGasFnParameters) as unknown as EstimateFeesPerGasReturnType<type>
} as ChainEstimateFeesPerGasFnParameters)) as unknown as EstimateFeesPerGasReturnType<type>

if (fees !== null) return fees
}

if (type === 'eip1559') {
if (typeof block.baseFeePerGas !== 'bigint')
Expand Down
2 changes: 2 additions & 0 deletions src/celo/chainConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { fees } from './fees.js'
import { formatters } from './formatters.js'
import { serializers } from './serializers.js'

export const chainConfig = {
formatters,
serializers,
fees,
} as const
65 changes: 65 additions & 0 deletions src/celo/fees.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, test, vi } from 'vitest'
import { celo } from '~viem/chains/index.js'
import { http, createTestClient } from '~viem/index.js'
import type { ChainEstimateFeesPerGasFn } from '~viem/types/chain.js'

const client = createTestClient({
transport: http(),
chain: celo,
mode: 'anvil',
})

describe('celo/fees', () => {
const celoestimateFeesPerGasFn = celo.fees
.estimateFeesPerGas as ChainEstimateFeesPerGasFn

test("doesn't call the client when feeCurrency is not provided", async () => {
const requestMock = vi.spyOn(client, 'request')

expect(celo.fees.estimateFeesPerGas).toBeTypeOf('function')

const fees = await celoestimateFeesPerGasFn({
client,
request: {},
} as any)

expect(fees).toBeNull()
expect(requestMock).not.toHaveBeenCalled()
})

test('calls the client when feeCurrency is provided', async () => {
const requestMock = vi.spyOn(client, 'request')
requestMock.mockImplementation((request) => {
switch (request.method) {
case 'eth_gasPrice':
return '11619349802'
case 'eth_maxPriorityFeePerGas':
return '2323869960'
}
})

expect(celo.fees.estimateFeesPerGas).toBeTypeOf('function')

const fees = await celoestimateFeesPerGasFn({
client,
request: {
feeCurrency: '0xfee',
},
} as any)

expect(fees).toMatchInlineSnapshot(`
{
"maxFeePerGas": 11619349802n,
"maxPriorityFeePerGas": 2323869960n,
}
`)
expect(requestMock).toHaveBeenCalledWith({
method: 'eth_maxPriorityFeePerGas',
params: ['0xfee'],
})
expect(requestMock).toHaveBeenCalledWith({
method: 'eth_gasPrice',
params: ['0xfee'],
})
})
})
92 changes: 92 additions & 0 deletions src/celo/fees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { Client } from '../clients/createClient.js'
import {
type Address,
type ChainEstimateFeesPerGasFnParameters,
type ChainFees,
type Hex,
} from '../index.js'

import { formatters } from './formatters.js'

export const fees: ChainFees<typeof formatters> = {
/*
* Estimates the fees per gas for a transaction.
* If the transaction is to be paid in a token (feeCurrency is present) then the fees
* are estimated in the value of the token. Otherwise falls back to the default
* estimation by returning null.
*
* @param params fee estimation function parameters
*/
estimateFeesPerGas: async (
params: ChainEstimateFeesPerGasFnParameters<typeof formatters>,
) => {
if (!params.request?.feeCurrency) return null

const [maxFeePerGas, maxPriorityFeePerGas] = await Promise.all([
estimateFeePerGasInFeeCurrency(params.client, params.request.feeCurrency),
estimateMaxPriorityFeePerGasInFeeCurrency(
params.client,
params.request.feeCurrency,
),
])

return {
maxFeePerGas,
maxPriorityFeePerGas,
}
},
}

type RequestGasPriceInFeeCurrencyParams = {
Method: 'eth_gasPrice'
Parameters: [Address]
ReturnType: Hex
}

/*
* Estimate the fee per gas in the value of the fee token
*
* @param client - Client to use
* @param feeCurrency - Address of a whitelisted fee token
* @returns The fee per gas in wei in the value of the fee token
*
*/
async function estimateFeePerGasInFeeCurrency(
client: Client,
feeCurrency: Address,
) {
const fee = await client.request<RequestGasPriceInFeeCurrencyParams>({
method: 'eth_gasPrice',
params: [feeCurrency],
})
return BigInt(fee)
}

type RequestMaxGasPriceInFeeCurrencyParams = {
Method: 'eth_maxPriorityFeePerGas'
Parameters: [Address]
ReturnType: Hex
}

/*
* Estimate the max priority fee per gas in the value of the fee token
*
* @param client - Client to use
* @param feeCurrency - Address of a whitelisted fee token
* @returns The fee per gas in wei in the value of the fee token
*
*/
async function estimateMaxPriorityFeePerGasInFeeCurrency(
client: Client,
feeCurrency: Address,
) {
const feesPerGas =
await client.request<RequestMaxGasPriceInFeeCurrencyParams>({
method: 'eth_maxPriorityFeePerGas',
params: [feeCurrency],
})
return BigInt(feesPerGas)
}
9 changes: 8 additions & 1 deletion src/celo/sendTransaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ describe('sendTransaction()', () => {
return 1n
}

if (
request.method === 'eth_gasPrice' &&
(request.params as string[])[0] === feeCurrencyAddress
) {
return 2n
}

if (request.method === 'eth_estimateGas') {
return 1n
}
Expand Down Expand Up @@ -92,7 +99,7 @@ describe('sendTransaction()', () => {
expect(transportRequestMock).toHaveBeenLastCalledWith({
method: 'eth_sendRawTransaction',
params: [
'0x7cf89282a4ec8001850165a0bc0101940000000000000000000000000000000000000fee9400000000000000000000000000000000000000017b94f39fd6e51aad88f6f4ce6ab8827279cfffb922660180c080a004389976320970e0227b20df6f79f2f35a2832d18b9732cb017d15db9f80fb44a0735b9abf965b7f38d1c659527cc93a9fc37b3a3b7bd5910d0c7db4b740be860f',
'0x7cf88d82a4ec80010201940000000000000000000000000000000000000fee9400000000000000000000000000000000000000017b94f39fd6e51aad88f6f4ce6ab8827279cfffb922660180c080a049b4b40c685a0bf9e3d1cca92a9175382bfa3a5e1bbd65610abcb0bd28b4ad90a009b40f809939683763bec0943101c7a7c79ea60239fd0d73975f555e6777ee1d',
],
})
})
Expand Down
10 changes: 7 additions & 3 deletions src/types/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,17 @@ export type ChainFees<
* Overrides the return value in the [`estimateFeesPerGas` Action](/docs/actions/public/estimateFeesPerGas).
*/
estimateFeesPerGas?:
| ((
args: ChainEstimateFeesPerGasFnParameters<formatters>,
) => Promise<EstimateFeesPerGasReturnType>)
| ChainEstimateFeesPerGasFn<formatters>
| bigint
| undefined
}

export type ChainEstimateFeesPerGasFn<
formatters extends ChainFormatters | undefined = ChainFormatters | undefined,
> = (
args: ChainEstimateFeesPerGasFnParameters<formatters>,
) => Promise<EstimateFeesPerGasReturnType | null>

export type ChainFormatters = {
/** Modifies how the Block structure is formatted & typed. */
block?: ChainFormatter<'block'> | undefined
Expand Down

0 comments on commit 29eea54

Please sign in to comment.