fix(transaction): use Legacy (Type 0) transaction format for KAVA
KAVA EVM does not support EIP-1559 dynamic fee transactions. Changed from EIP-1559 (Type 2) to Legacy (Type 0) format: - prepareTransaction: Use [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0] - finalizeTransaction: Use EIP-155 v calculation (chainId * 2 + 35 + recoveryId) - Remove type prefix (0x02) as Legacy transactions don't need it - Update Home.tsx and CoSignSession.tsx to use gasPrice instead of maxFeePerGas 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d18733deb1
commit
0f8e9cf228
|
|
@ -130,11 +130,10 @@ export default function CoSignSession() {
|
|||
if (storedTxInfo) {
|
||||
try {
|
||||
const parsed = JSON.parse(storedTxInfo);
|
||||
// 恢复 bigint 类型
|
||||
// 恢复 bigint 类型 (Legacy 交易使用 gasPrice)
|
||||
if (parsed.preparedTx) {
|
||||
parsed.preparedTx.gasLimit = BigInt(parsed.preparedTx.gasLimit);
|
||||
parsed.preparedTx.maxFeePerGas = BigInt(parsed.preparedTx.maxFeePerGas);
|
||||
parsed.preparedTx.maxPriorityFeePerGas = BigInt(parsed.preparedTx.maxPriorityFeePerGas);
|
||||
parsed.preparedTx.gasPrice = BigInt(parsed.preparedTx.gasPrice);
|
||||
parsed.preparedTx.value = BigInt(parsed.preparedTx.value);
|
||||
}
|
||||
setTxInfo(parsed);
|
||||
|
|
|
|||
|
|
@ -293,8 +293,7 @@ export default function Home() {
|
|||
preparedTx: {
|
||||
...preparedTx,
|
||||
gasLimit: preparedTx.gasLimit.toString(),
|
||||
maxFeePerGas: preparedTx.maxFeePerGas.toString(),
|
||||
maxPriorityFeePerGas: preparedTx.maxPriorityFeePerGas.toString(),
|
||||
gasPrice: preparedTx.gasPrice.toString(),
|
||||
value: preparedTx.value.toString(),
|
||||
},
|
||||
to: transferTo,
|
||||
|
|
|
|||
|
|
@ -50,21 +50,24 @@ export interface TransactionParams {
|
|||
|
||||
/**
|
||||
* 准备好的交易 (包含所有必要字段)
|
||||
* 使用 Legacy 交易格式 (Type 0),因为 KAVA 不支持 EIP-1559
|
||||
*/
|
||||
export interface PreparedTransaction {
|
||||
chainId: number;
|
||||
nonce: number;
|
||||
maxPriorityFeePerGas: bigint;
|
||||
maxFeePerGas: bigint;
|
||||
gasPrice: bigint; // Legacy 使用 gasPrice 而不是 maxFeePerGas
|
||||
gasLimit: bigint;
|
||||
to: string;
|
||||
value: bigint;
|
||||
data: string;
|
||||
accessList: unknown[];
|
||||
// 用于签名的哈希
|
||||
signHash: string;
|
||||
// 原始交易数据 (用于签名后广播)
|
||||
rawTxForSigning: string;
|
||||
// 为了兼容性保留这些字段
|
||||
maxPriorityFeePerGas?: bigint;
|
||||
maxFeePerGas?: bigint;
|
||||
accessList?: unknown[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -392,7 +395,8 @@ export async function estimateGas(params: { from: string; to: string; value: str
|
|||
}
|
||||
|
||||
/**
|
||||
* 准备 EIP-1559 交易
|
||||
* 准备 Legacy 交易 (Type 0)
|
||||
* KAVA 不支持 EIP-1559,所以使用 Legacy 格式
|
||||
* 返回交易数据和待签名的哈希
|
||||
*/
|
||||
export async function prepareTransaction(params: TransactionParams): Promise<PreparedTransaction> {
|
||||
|
|
@ -400,9 +404,8 @@ export async function prepareTransaction(params: TransactionParams): Promise<Pre
|
|||
|
||||
// 获取或使用提供的参数
|
||||
const nonce = params.nonce ?? await getNonce(params.from);
|
||||
const gasPrice = await getGasPrice();
|
||||
const maxFeePerGas = params.maxFeePerGas ?? gasPrice.maxFeePerGas;
|
||||
const maxPriorityFeePerGas = params.maxPriorityFeePerGas ?? gasPrice.maxPriorityFeePerGas;
|
||||
const gasPriceData = await getGasPrice();
|
||||
const gasPrice = params.maxFeePerGas ?? gasPriceData.maxFeePerGas;
|
||||
const gasLimit = params.gasLimit ?? await estimateGas({
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
|
|
@ -412,81 +415,72 @@ export async function prepareTransaction(params: TransactionParams): Promise<Pre
|
|||
|
||||
const value = kavaToWei(params.value);
|
||||
const data = params.data || '0x';
|
||||
const accessList: unknown[] = [];
|
||||
|
||||
// EIP-1559 交易字段顺序
|
||||
// [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]
|
||||
// Legacy 交易字段顺序 (EIP-155)
|
||||
// [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]
|
||||
// 最后三个字段用于 EIP-155 replay protection
|
||||
const txFields = [
|
||||
chainId,
|
||||
nonce,
|
||||
maxPriorityFeePerGas,
|
||||
maxFeePerGas,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
params.to.toLowerCase(),
|
||||
value,
|
||||
data,
|
||||
accessList,
|
||||
chainId,
|
||||
0, // EIP-155: v placeholder
|
||||
0, // EIP-155: r placeholder
|
||||
];
|
||||
|
||||
// RLP 编码交易字段
|
||||
const encodedTx = rlpEncode(txFields);
|
||||
|
||||
// EIP-1559 交易类型前缀: 0x02
|
||||
const txWithType = new Uint8Array(1 + encodedTx.length);
|
||||
txWithType[0] = 0x02;
|
||||
txWithType.set(encodedTx, 1);
|
||||
|
||||
// 计算签名哈希
|
||||
const signHashBytes = await keccak256(txWithType);
|
||||
// 计算签名哈希 (不需要类型前缀,Legacy 交易直接哈希)
|
||||
const signHashBytes = await keccak256(encodedTx);
|
||||
const signHash = bytesToHex(signHashBytes);
|
||||
|
||||
return {
|
||||
chainId,
|
||||
nonce,
|
||||
maxPriorityFeePerGas,
|
||||
maxFeePerGas,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
to: params.to.toLowerCase(),
|
||||
value,
|
||||
data,
|
||||
accessList,
|
||||
signHash,
|
||||
rawTxForSigning: bytesToHex(txWithType),
|
||||
rawTxForSigning: bytesToHex(encodedTx),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用签名完成交易并返回可广播的原始交易
|
||||
* 使用 Legacy 交易格式 (Type 0)
|
||||
*/
|
||||
export function finalizeTransaction(
|
||||
preparedTx: PreparedTransaction,
|
||||
signature: { r: string; s: string; v: number }
|
||||
): string {
|
||||
// EIP-1559 签名后的交易字段
|
||||
// [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, v, r, s]
|
||||
// EIP-155: v = chainId * 2 + 35 + recovery_id
|
||||
// recovery_id 是 0 或 1
|
||||
const v = preparedTx.chainId * 2 + 35 + signature.v;
|
||||
|
||||
// Legacy 签名后的交易字段
|
||||
// [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
|
||||
const signedTxFields = [
|
||||
preparedTx.chainId,
|
||||
preparedTx.nonce,
|
||||
preparedTx.maxPriorityFeePerGas,
|
||||
preparedTx.maxFeePerGas,
|
||||
preparedTx.gasPrice,
|
||||
preparedTx.gasLimit,
|
||||
preparedTx.to,
|
||||
preparedTx.value,
|
||||
preparedTx.data,
|
||||
preparedTx.accessList,
|
||||
signature.v, // recovery id (0 or 1 for EIP-1559)
|
||||
v,
|
||||
'0x' + signature.r,
|
||||
'0x' + signature.s,
|
||||
];
|
||||
|
||||
const encodedSignedTx = rlpEncode(signedTxFields);
|
||||
|
||||
// 添加类型前缀
|
||||
const signedTxWithType = new Uint8Array(1 + encodedSignedTx.length);
|
||||
signedTxWithType[0] = 0x02;
|
||||
signedTxWithType.set(encodedSignedTx, 1);
|
||||
|
||||
return '0x' + bytesToHex(signedTxWithType);
|
||||
// Legacy 交易不需要类型前缀
|
||||
return '0x' + bytesToHex(encodedSignedTx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue