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:
hailin 2025-12-31 12:45:38 -08:00
parent d18733deb1
commit 0f8e9cf228
3 changed files with 35 additions and 43 deletions

View File

@ -130,11 +130,10 @@ export default function CoSignSession() {
if (storedTxInfo) { if (storedTxInfo) {
try { try {
const parsed = JSON.parse(storedTxInfo); const parsed = JSON.parse(storedTxInfo);
// 恢复 bigint 类型 // 恢复 bigint 类型 (Legacy 交易使用 gasPrice)
if (parsed.preparedTx) { if (parsed.preparedTx) {
parsed.preparedTx.gasLimit = BigInt(parsed.preparedTx.gasLimit); parsed.preparedTx.gasLimit = BigInt(parsed.preparedTx.gasLimit);
parsed.preparedTx.maxFeePerGas = BigInt(parsed.preparedTx.maxFeePerGas); parsed.preparedTx.gasPrice = BigInt(parsed.preparedTx.gasPrice);
parsed.preparedTx.maxPriorityFeePerGas = BigInt(parsed.preparedTx.maxPriorityFeePerGas);
parsed.preparedTx.value = BigInt(parsed.preparedTx.value); parsed.preparedTx.value = BigInt(parsed.preparedTx.value);
} }
setTxInfo(parsed); setTxInfo(parsed);

View File

@ -293,8 +293,7 @@ export default function Home() {
preparedTx: { preparedTx: {
...preparedTx, ...preparedTx,
gasLimit: preparedTx.gasLimit.toString(), gasLimit: preparedTx.gasLimit.toString(),
maxFeePerGas: preparedTx.maxFeePerGas.toString(), gasPrice: preparedTx.gasPrice.toString(),
maxPriorityFeePerGas: preparedTx.maxPriorityFeePerGas.toString(),
value: preparedTx.value.toString(), value: preparedTx.value.toString(),
}, },
to: transferTo, to: transferTo,

View File

@ -50,21 +50,24 @@ export interface TransactionParams {
/** /**
* () * ()
* 使 Legacy (Type 0) KAVA EIP-1559
*/ */
export interface PreparedTransaction { export interface PreparedTransaction {
chainId: number; chainId: number;
nonce: number; nonce: number;
maxPriorityFeePerGas: bigint; gasPrice: bigint; // Legacy 使用 gasPrice 而不是 maxFeePerGas
maxFeePerGas: bigint;
gasLimit: bigint; gasLimit: bigint;
to: string; to: string;
value: bigint; value: bigint;
data: string; data: string;
accessList: unknown[];
// 用于签名的哈希 // 用于签名的哈希
signHash: string; signHash: string;
// 原始交易数据 (用于签名后广播) // 原始交易数据 (用于签名后广播)
rawTxForSigning: 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> { 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 nonce = params.nonce ?? await getNonce(params.from);
const gasPrice = await getGasPrice(); const gasPriceData = await getGasPrice();
const maxFeePerGas = params.maxFeePerGas ?? gasPrice.maxFeePerGas; const gasPrice = params.maxFeePerGas ?? gasPriceData.maxFeePerGas;
const maxPriorityFeePerGas = params.maxPriorityFeePerGas ?? gasPrice.maxPriorityFeePerGas;
const gasLimit = params.gasLimit ?? await estimateGas({ const gasLimit = params.gasLimit ?? await estimateGas({
from: params.from, from: params.from,
to: params.to, to: params.to,
@ -412,81 +415,72 @@ export async function prepareTransaction(params: TransactionParams): Promise<Pre
const value = kavaToWei(params.value); const value = kavaToWei(params.value);
const data = params.data || '0x'; const data = params.data || '0x';
const accessList: unknown[] = [];
// EIP-1559 交易字段顺序 // Legacy 交易字段顺序 (EIP-155)
// [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList] // [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]
// 最后三个字段用于 EIP-155 replay protection
const txFields = [ const txFields = [
chainId,
nonce, nonce,
maxPriorityFeePerGas, gasPrice,
maxFeePerGas,
gasLimit, gasLimit,
params.to.toLowerCase(), params.to.toLowerCase(),
value, value,
data, data,
accessList, chainId,
0, // EIP-155: v placeholder
0, // EIP-155: r placeholder
]; ];
// RLP 编码交易字段 // RLP 编码交易字段
const encodedTx = rlpEncode(txFields); const encodedTx = rlpEncode(txFields);
// EIP-1559 交易类型前缀: 0x02 // 计算签名哈希 (不需要类型前缀Legacy 交易直接哈希)
const txWithType = new Uint8Array(1 + encodedTx.length); const signHashBytes = await keccak256(encodedTx);
txWithType[0] = 0x02;
txWithType.set(encodedTx, 1);
// 计算签名哈希
const signHashBytes = await keccak256(txWithType);
const signHash = bytesToHex(signHashBytes); const signHash = bytesToHex(signHashBytes);
return { return {
chainId, chainId,
nonce, nonce,
maxPriorityFeePerGas, gasPrice,
maxFeePerGas,
gasLimit, gasLimit,
to: params.to.toLowerCase(), to: params.to.toLowerCase(),
value, value,
data, data,
accessList,
signHash, signHash,
rawTxForSigning: bytesToHex(txWithType), rawTxForSigning: bytesToHex(encodedTx),
}; };
} }
/** /**
* 使广 * 使广
* 使 Legacy (Type 0)
*/ */
export function finalizeTransaction( export function finalizeTransaction(
preparedTx: PreparedTransaction, preparedTx: PreparedTransaction,
signature: { r: string; s: string; v: number } signature: { r: string; s: string; v: number }
): string { ): string {
// EIP-1559 签名后的交易字段 // EIP-155: v = chainId * 2 + 35 + recovery_id
// [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, v, r, s] // recovery_id 是 0 或 1
const v = preparedTx.chainId * 2 + 35 + signature.v;
// Legacy 签名后的交易字段
// [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
const signedTxFields = [ const signedTxFields = [
preparedTx.chainId,
preparedTx.nonce, preparedTx.nonce,
preparedTx.maxPriorityFeePerGas, preparedTx.gasPrice,
preparedTx.maxFeePerGas,
preparedTx.gasLimit, preparedTx.gasLimit,
preparedTx.to, preparedTx.to,
preparedTx.value, preparedTx.value,
preparedTx.data, preparedTx.data,
preparedTx.accessList, v,
signature.v, // recovery id (0 or 1 for EIP-1559)
'0x' + signature.r, '0x' + signature.r,
'0x' + signature.s, '0x' + signature.s,
]; ];
const encodedSignedTx = rlpEncode(signedTxFields); const encodedSignedTx = rlpEncode(signedTxFields);
// 添加类型前缀 // Legacy 交易不需要类型前缀
const signedTxWithType = new Uint8Array(1 + encodedSignedTx.length); return '0x' + bytesToHex(encodedSignedTx);
signedTxWithType[0] = 0x02;
signedTxWithType.set(encodedSignedTx, 1);
return '0x' + bytesToHex(signedTxWithType);
} }
/** /**