166 lines
9.3 KiB
JavaScript
166 lines
9.3 KiB
JavaScript
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
|
|
const PRIVATE_KEY = '886ea4cffe76c386fecf3ff321ac9ae913737c46c17bc6ce2413752144668a2a';
|
|
const RPC_URL = 'https://evm.kava.io';
|
|
const CHAIN_ID = 2222;
|
|
|
|
// Contract bytecode (compiled with paris EVM, no PUSH0)
|
|
const BYTECODE = '0x608060405234801561001057600080fd5b5033600081815260208181526040808320670de0b6b3a76400009081905590519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36106fb8061006d6000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c8063313ce56711610066578063313ce5671461012b57806370a082311461014557806395d89b411461016e578063a9059cbb14610192578063dd62ed3e146101a557600080fd5b806306fdde0314610098578063095ea7b3146100d857806318160ddd146100fb57806323b872dd14610118575b600080fd5b6100c26040518060400160405280600b81526020016a111d5c9a585b881554d11560aa1b81525081565b6040516100cf91906105a0565b60405180910390f35b6100eb6100e636600461060a565b6101de565b60405190151581526020016100cf565b61010a670de0b6b3a764000081565b6040519081526020016100cf565b6100eb610126366004610634565b6102a0565b610133600681565b60405160ff90911681526020016100cf565b61010a610153366004610670565b6001600160a01b031660009081526020819052604090205490565b6100c260405180604001604052806005815260200164191554d11560da1b81525081565b6100eb6101a036600461060a565b61049a565b61010a6101b3366004610692565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60006001600160a01b03831661023b5760405162461bcd60e51b815260206004820152601760248201527f417070726f766520746f207a65726f206164647265737300000000000000000060448201526064015b60405180910390fd5b3360008181526001602090815260408083206001600160a01b03881680855290835292819020869055518581529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a350600192915050565b60006001600160a01b0384166102f85760405162461bcd60e51b815260206004820152601a60248201527f5472616e736665722066726f6d207a65726f20616464726573730000000000006044820152606401610232565b6001600160a01b0383166103495760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b6044820152606401610232565b6001600160a01b0384166000908152602081905260409020548211156103a85760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610232565b6001600160a01b03841660009081526001602090815260408083203384529091529020548211156104145760405162461bcd60e51b8152602060048201526016602482015275496e73756666696369656e7420616c6c6f77616e636560501b6044820152606401610232565b6001600160a01b03848116600081815260208181526040808320805488900390559387168083528483208054880190558383526001825284832033845282529184902080548790039055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35060019392505050565b60006001600160a01b0383166104ed5760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b6044820152606401610232565b336000908152602081905260409020548211156105435760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610232565b33600081815260208181526040808320805487900390556001600160a01b03871680845292819020805487019055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910161028f565b600060208083528351808285015260005b818110156105cd578581018301518582016040015282016105b1565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461060557600080fd5b919050565b6000806040838503121561061d57600080fd5b610626836105ee565b946020939093013593505050565b60008060006060848603121561064957600080fd5b610652846105ee565b9250610660602085016105ee565b9150604084013590509250925092565b60006020828403121561068257600080fd5b61068b826105ee565b9392505050565b600080604083850312156106a557600080fd5b6106ae836105ee565b91506106bc602084016105ee565b9050925092905056fea264697066735822122028c97073f6e7db0ad943d101cb6873b31c3eb19bcea3eda83148447ab676a5ee64736f6c63430008130033';
|
|
|
|
// RLP encode helper
|
|
function rlpEncodeLength(len, offset) {
|
|
if (len < 56) {
|
|
return Buffer.from([len + offset]);
|
|
}
|
|
const hexLen = len.toString(16);
|
|
const lenBytes = Buffer.from(hexLen.length % 2 ? '0' + hexLen : hexLen, 'hex');
|
|
return Buffer.concat([Buffer.from([offset + 55 + lenBytes.length]), lenBytes]);
|
|
}
|
|
|
|
function rlpEncodeItem(data) {
|
|
if (typeof data === 'string') {
|
|
data = data.startsWith('0x') ? data.slice(2) : data;
|
|
if (data.length === 0) return Buffer.from([0x80]);
|
|
const bytes = Buffer.from(data, 'hex');
|
|
if (bytes.length === 1 && bytes[0] < 0x80) return bytes;
|
|
return Buffer.concat([rlpEncodeLength(bytes.length, 0x80), bytes]);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function rlpEncodeList(items) {
|
|
const encodedItems = items.map(rlpEncodeItem);
|
|
const totalLen = encodedItems.reduce((sum, b) => sum + b.length, 0);
|
|
return Buffer.concat([rlpEncodeLength(totalLen, 0xc0), ...encodedItems]);
|
|
}
|
|
|
|
async function rpcCall(method, params) {
|
|
const response = await fetch(RPC_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 })
|
|
});
|
|
const json = await response.json();
|
|
if (json.error) throw new Error(json.error.message);
|
|
return json.result;
|
|
}
|
|
|
|
async function deploy() {
|
|
const address = '0x4F7E78d6B7C5FC502Ec7039848690f08c8970F1E';
|
|
|
|
// Get nonce
|
|
const nonceHex = await rpcCall('eth_getTransactionCount', [address, 'latest']);
|
|
const nonce = parseInt(nonceHex, 16);
|
|
console.log('Nonce:', nonce);
|
|
|
|
// Estimate gas
|
|
const gasEstimate = await rpcCall('eth_estimateGas', [{
|
|
from: address,
|
|
data: BYTECODE
|
|
}]);
|
|
console.log('Gas estimate:', parseInt(gasEstimate, 16));
|
|
|
|
// Get gas price
|
|
const gasPrice = await rpcCall('eth_gasPrice', []);
|
|
console.log('Gas price:', parseInt(gasPrice, 16));
|
|
|
|
// Use slightly higher gas for safety
|
|
const gas = Math.ceil(parseInt(gasEstimate, 16) * 1.2);
|
|
const gasPriceValue = parseInt(gasPrice, 16);
|
|
|
|
// Build EIP-155 transaction
|
|
const txFields = [
|
|
nonce === 0 ? '' : nonce.toString(16), // nonce
|
|
gasPriceValue.toString(16), // gasPrice
|
|
gas.toString(16), // gasLimit
|
|
'', // to (empty for contract creation)
|
|
'', // value (0)
|
|
BYTECODE.slice(2), // data (without 0x)
|
|
CHAIN_ID.toString(16), // chainId (2222 = 0x8ae)
|
|
'', // empty for EIP-155
|
|
'' // empty for EIP-155
|
|
];
|
|
|
|
// RLP encode for signing
|
|
const rlpEncoded = rlpEncodeList(txFields);
|
|
const msgHash = keccak_256(rlpEncoded);
|
|
|
|
console.log('Signing transaction...');
|
|
|
|
// Sign
|
|
const privateKeyBytes = hexToBytes(PRIVATE_KEY);
|
|
const sigBytes = secp256k1.sign(msgHash, privateKeyBytes, { lowS: true });
|
|
|
|
// Get signature components (64-byte compact signature)
|
|
const r = bytesToHex(sigBytes.slice(0, 32));
|
|
const s = bytesToHex(sigBytes.slice(32, 64));
|
|
|
|
// Get public key for recovery check
|
|
const expectedPubKey = secp256k1.getPublicKey(privateKeyBytes, true);
|
|
|
|
// Try both recovery values to find the correct one
|
|
let recovery = 0;
|
|
for (let rec = 0; rec <= 1; rec++) {
|
|
try {
|
|
// Build 65-byte signature with recovery id
|
|
const sigWithRec = new Uint8Array(65);
|
|
sigWithRec.set(sigBytes, 0);
|
|
sigWithRec[64] = rec;
|
|
|
|
const recovered = secp256k1.recoverPublicKey(msgHash, sigWithRec, rec);
|
|
if (bytesToHex(recovered) === bytesToHex(expectedPubKey)) {
|
|
recovery = rec;
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
// Continue trying
|
|
}
|
|
}
|
|
console.log('Recovery:', recovery);
|
|
|
|
const v = CHAIN_ID * 2 + 35 + recovery;
|
|
|
|
// Build signed transaction
|
|
const signedTxFields = [
|
|
nonce === 0 ? '' : nonce.toString(16),
|
|
gasPriceValue.toString(16),
|
|
gas.toString(16),
|
|
'',
|
|
'',
|
|
BYTECODE.slice(2),
|
|
v.toString(16),
|
|
r,
|
|
s
|
|
];
|
|
|
|
const signedTx = '0x' + rlpEncodeList(signedTxFields).toString('hex');
|
|
console.log('Signed tx length:', signedTx.length);
|
|
|
|
// Send transaction
|
|
console.log('Sending transaction...');
|
|
const txHash = await rpcCall('eth_sendRawTransaction', [signedTx]);
|
|
console.log('Transaction hash:', txHash);
|
|
|
|
// Wait for receipt
|
|
console.log('Waiting for confirmation...');
|
|
let receipt = null;
|
|
for (let i = 0; i < 60; i++) {
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
receipt = await rpcCall('eth_getTransactionReceipt', [txHash]);
|
|
if (receipt) break;
|
|
process.stdout.write('.');
|
|
}
|
|
|
|
if (receipt) {
|
|
console.log('\nContract deployed!');
|
|
console.log('Contract address:', receipt.contractAddress);
|
|
console.log('Block number:', parseInt(receipt.blockNumber, 16));
|
|
console.log('Gas used:', parseInt(receipt.gasUsed, 16));
|
|
console.log('Status:', receipt.status === '0x1' ? 'Success' : 'Failed');
|
|
}
|
|
}
|
|
|
|
deploy().catch(console.error);
|