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) {
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue