Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/core/config/registry/hft/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ use HFT contracts on EVM and `pallet-hft` on Substrate.
type: "wrapped-hft",
underlying: "0x...", // canonical ERC20
weth: true, // only if the contract's isWeth() is true:
// sends spend native via msg.value, balances read native
// sends spend native via msg.value; the app shows the
// native balance and symbol (e.g. tBNB) in the form
},
{
chainId: Base.chainId,
Expand Down
12 changes: 9 additions & 3 deletions src/core/config/wagmi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NetworkConfig } from "@hyperbridge-fe/shared"
import { EVM_NETWORKS, VIEM_CHAIN_MAP } from "@hyperbridge-fe/shared/config"
import { getDefaultConfig } from "connectkit"
import type { HttpTransport } from "viem"
import { fallback, type Transport } from "viem"
import { createConfig, http } from "wagmi"
import { coinbaseWallet, injected, walletConnect } from "wagmi/connectors"
import { APP_NAME, APP_URL, NETWORK_ENV, WALLET_CONNECT_ID } from "@/config/constants"
Expand Down Expand Up @@ -52,10 +52,16 @@ function createWagmiConfig(): any {
return createConfig(getDefaultConfig(config as any))
}

// Fall back through every configured RPC so one flaky endpoint doesn't
// zero out balances or block sends for the whole chain.
const networkToTransport = (
network: NetworkConfig,
): [number, HttpTransport] => {
return [Number(network.chainId), http(network.rpcUrls[0])]
): [number, Transport] => {
const transports = network.rpcUrls.map((url) => http(url))
return [
Number(network.chainId),
transports.length > 1 ? fallback(transports) : transports[0],
]
}

export type WagmiChainId = number
Expand Down
4 changes: 3 additions & 1 deletion src/core/lib/balance-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ export const BalanceHelper = {
chainId,
})

return BalanceImpl.create(balance.value, decimals, symbol)
// Users spend the native token, so show its symbol (e.g. tBNB), not the
// wrapper's registry symbol.
return BalanceImpl.create(balance.value, balance.decimals, balance.symbol)
}

const underlying =
Expand Down
4 changes: 4 additions & 0 deletions src/core/lib/fee-token-preparation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TokenFaucetABI } from "@/abis/TokenFaucet"
import { gatewayConfig } from "@/config/services/gateway-config"
import { WagmiConfig } from "@/config/wagmi"
import { rootLogger } from "@/lib/logger"
import { evmFeeOverrides } from "@/lib/utils/gas"

const logger = rootLogger.withTag("FeeTokenPrep")

Expand Down Expand Up @@ -134,13 +135,16 @@ export async function requestFeeTokenDrip(params: {

await switchChain(WagmiConfig, { chainId: params.source.chainId })

const feeOverrides = await evmFeeOverrides(params.source.chainId)

const hash = await writeContract(WagmiConfig, {
abi: TokenFaucetABI,
address: faucet,
functionName: "drip",
args: [params.feeTokenAddress],
chainId: params.source.chainId,
account: params.owner,
...feeOverrides,
})

await waitForTransactionReceipt(WagmiConfig, {
Expand Down
5 changes: 5 additions & 0 deletions src/core/lib/transactions/bridge-hft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
isHftToken,
} from "@/lib/hft/hyper-fungible-token"
import { rootLogger } from "@/lib/logger"
import { evmFeeOverrides } from "@/lib/utils/gas"
import type { EVMToken } from "@/types"
import type { TxCreationEvents } from "@/types/tx"
import { WagmiConfig, type WagmiChainId } from "@/config/wagmi"
Expand Down Expand Up @@ -90,6 +91,8 @@ export class HftBridgeTx implements BridgeTxExecutor {
throw new Error("Wallet not connected")
}

const feeOverrides = await evmFeeOverrides(source.chainId)

const gen = hft.bridge({
token: token.address,
from: account.address,
Expand All @@ -113,6 +116,7 @@ export class HftBridgeTx implements BridgeTxExecutor {
to: step.tx.to,
data: step.tx.data,
chainId: source.chainId as WagmiChainId,
...feeOverrides,
})
await waitForTransactionReceipt(WagmiConfig, {
hash,
Expand All @@ -129,6 +133,7 @@ export class HftBridgeTx implements BridgeTxExecutor {
data: step.tx.data,
value: step.tx.value,
chainId: source.chainId as WagmiChainId,
...feeOverrides,
})

yield {
Expand Down
40 changes: 40 additions & 0 deletions src/core/lib/utils/__tests__/gas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { parseGwei } from "viem"
import { describe, expect, it } from "vitest"
import { clampToMinPriorityFee } from "../gas"

describe("clampToMinPriorityFee", () => {
const floor = parseGwei("25")

it("raises an undershooting tip to the floor and keeps base-fee headroom", () => {
const result = clampToMinPriorityFee(
{
maxPriorityFeePerGas: parseGwei("1.5"),
maxFeePerGas: parseGwei("2"), // 0.5 gwei of base-fee headroom
},
floor,
)

expect(result.maxPriorityFeePerGas).toBe(floor)
expect(result.maxFeePerGas).toBe(floor + parseGwei("0.5"))
})

it("leaves estimates above the floor untouched", () => {
const fees = {
maxPriorityFeePerGas: parseGwei("30"),
maxFeePerGas: parseGwei("80"),
}

expect(clampToMinPriorityFee(fees, floor)).toEqual(fees)
})

it("never returns maxFeePerGas below the tip", () => {
const result = clampToMinPriorityFee(
{ maxPriorityFeePerGas: parseGwei("1"), maxFeePerGas: parseGwei("1") },
floor,
)

expect(result.maxFeePerGas).toBeGreaterThanOrEqual(
result.maxPriorityFeePerGas,
)
})
})
52 changes: 52 additions & 0 deletions src/core/lib/utils/gas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { estimateFeesPerGas } from "@wagmi/core"
import { parseGwei } from "viem"
import { WagmiConfig } from "@/config/wagmi"
import { rootLogger } from "@/lib/logger"

const logger = rootLogger.withTag("Gas")

/**
* Bor (Polygon) chains reject transactions with a priority fee below 25 gwei,
* and wallet estimators regularly undershoot it (MetaMask suggests 1.5 gwei),
* failing broadcasts with "transaction gas price below minimum".
*/
const MIN_PRIORITY_FEE: Record<number, bigint> = {
137: parseGwei("25"),
80002: parseGwei("25"),
}

export type FeeOverrides = {
maxFeePerGas?: bigint
maxPriorityFeePerGas?: bigint
}

/** Raises the priority fee to the floor, keeping the base-fee headroom intact. */
export function clampToMinPriorityFee(
fees: { maxFeePerGas: bigint; maxPriorityFeePerGas: bigint },
minPriorityFee: bigint,
): Required<FeeOverrides> {
const tip =
fees.maxPriorityFeePerGas >= minPriorityFee
? fees.maxPriorityFeePerGas
: minPriorityFee
const basePortion = fees.maxFeePerGas - fees.maxPriorityFeePerGas

return { maxPriorityFeePerGas: tip, maxFeePerGas: basePortion + tip }
}

/**
* Explicit fee fields for chains whose minimum priority fee wallets undershoot.
* Returns {} elsewhere so the wallet keeps estimating on its own.
*/
export async function evmFeeOverrides(chainId: number): Promise<FeeOverrides> {
const floor = MIN_PRIORITY_FEE[chainId]
if (!floor) return {}

try {
const fees = await estimateFeesPerGas(WagmiConfig, { chainId })
return clampToMinPriorityFee(fees, floor)
} catch (err) {
logger.warn(`Fee estimation failed for Chain(${chainId})`, err)
return {}
}
}
3 changes: 2 additions & 1 deletion src/shared/config/registry/evm-networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ export const BscTestnet: EVMChainConfig = {
chainId: 97,
stateMachineId: "EVM-97",
rpcUrls: [
"https://bsc-testnet-rpc.publicnode.com",
"https://data-seed-prebsc-1-s1.bnbchain.org:8545",
`https://bnb-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
"https://bsc-testnet-rpc.publicnode.com",
"https://bsc-geth-testnet-rpc.blockops.network",
],
networkType: "testnet",
Expand Down
Loading