diff --git a/contracts/.gitignore b/contracts/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/contracts/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/contracts/DurianUSDT.sol b/contracts/DurianUSDT.sol new file mode 100644 index 00000000..7b19e1e7 --- /dev/null +++ b/contracts/DurianUSDT.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +/** + * @title DurianUSDT + * @dev Fixed supply ERC-20 token - NO MINTING CAPABILITY + * Total Supply: 1,000,000,000,000 (1 Trillion) tokens with 6 decimals (matching USDT) + * + * IMPORTANT: This contract has NO mint function and NO way to increase supply. + * All tokens are minted to the deployer at construction time. + */ +contract DurianUSDT { + string public constant name = "Durian USDT"; + string public constant symbol = "dUSDT"; + uint8 public constant decimals = 6; + + // Fixed total supply: 1 trillion tokens (1,000,000,000,000 * 10^6) + uint256 public constant totalSupply = 1_000_000_000_000 * 10**6; + + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Constructor - mints entire fixed supply to deployer + * No mint function exists - supply is permanently fixed + */ + constructor() { + _balances[msg.sender] = totalSupply; + emit Transfer(address(0), msg.sender, totalSupply); + } + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) public returns (bool) { + require(to != address(0), "Transfer to zero address"); + require(_balances[msg.sender] >= amount, "Insufficient balance"); + + unchecked { + _balances[msg.sender] -= amount; + _balances[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public returns (bool) { + require(spender != address(0), "Approve to zero address"); + _allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + require(from != address(0), "Transfer from zero address"); + require(to != address(0), "Transfer to zero address"); + require(_balances[from] >= amount, "Insufficient balance"); + require(_allowances[from][msg.sender] >= amount, "Insufficient allowance"); + + unchecked { + _balances[from] -= amount; + _balances[to] += amount; + _allowances[from][msg.sender] -= amount; + } + + emit Transfer(from, to, amount); + return true; + } +} diff --git a/contracts/build/DurianUSDT.abi b/contracts/build/DurianUSDT.abi new file mode 100644 index 00000000..b284e91e --- /dev/null +++ b/contracts/build/DurianUSDT.abi @@ -0,0 +1,229 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/build/DurianUSDT.bin b/contracts/build/DurianUSDT.bin new file mode 100644 index 00000000..c364e59f --- /dev/null +++ b/contracts/build/DurianUSDT.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5033600081815260208181526040808320670de0b6b3a76400009081905590519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36106fb8061006d6000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c8063313ce56711610066578063313ce5671461012b57806370a082311461014557806395d89b411461016e578063a9059cbb14610192578063dd62ed3e146101a557600080fd5b806306fdde0314610098578063095ea7b3146100d857806318160ddd146100fb57806323b872dd14610118575b600080fd5b6100c26040518060400160405280600b81526020016a111d5c9a585b881554d11560aa1b81525081565b6040516100cf91906105a0565b60405180910390f35b6100eb6100e636600461060a565b6101de565b60405190151581526020016100cf565b61010a670de0b6b3a764000081565b6040519081526020016100cf565b6100eb610126366004610634565b6102a0565b610133600681565b60405160ff90911681526020016100cf565b61010a610153366004610670565b6001600160a01b031660009081526020819052604090205490565b6100c260405180604001604052806005815260200164191554d11560da1b81525081565b6100eb6101a036600461060a565b61049a565b61010a6101b3366004610692565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60006001600160a01b03831661023b5760405162461bcd60e51b815260206004820152601760248201527f417070726f766520746f207a65726f206164647265737300000000000000000060448201526064015b60405180910390fd5b3360008181526001602090815260408083206001600160a01b03881680855290835292819020869055518581529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a350600192915050565b60006001600160a01b0384166102f85760405162461bcd60e51b815260206004820152601a60248201527f5472616e736665722066726f6d207a65726f20616464726573730000000000006044820152606401610232565b6001600160a01b0383166103495760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b6044820152606401610232565b6001600160a01b0384166000908152602081905260409020548211156103a85760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610232565b6001600160a01b03841660009081526001602090815260408083203384529091529020548211156104145760405162461bcd60e51b8152602060048201526016602482015275496e73756666696369656e7420616c6c6f77616e636560501b6044820152606401610232565b6001600160a01b03848116600081815260208181526040808320805488900390559387168083528483208054880190558383526001825284832033845282529184902080548790039055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35060019392505050565b60006001600160a01b0383166104ed5760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b6044820152606401610232565b336000908152602081905260409020548211156105435760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610232565b33600081815260208181526040808320805487900390556001600160a01b03871680845292819020805487019055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910161028f565b600060208083528351808285015260005b818110156105cd578581018301518582016040015282016105b1565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461060557600080fd5b919050565b6000806040838503121561061d57600080fd5b610626836105ee565b946020939093013593505050565b60008060006060848603121561064957600080fd5b610652846105ee565b9250610660602085016105ee565b9150604084013590509250925092565b60006020828403121561068257600080fd5b61068b826105ee565b9392505050565b600080604083850312156106a557600080fd5b6106ae836105ee565b91506106bc602084016105ee565b9050925092905056fea264697066735822122028c97073f6e7db0ad943d101cb6873b31c3eb19bcea3eda83148447ab676a5ee64736f6c63430008130033 \ No newline at end of file diff --git a/contracts/build/DurianUSDT_sol_DurianUSDT.abi b/contracts/build/DurianUSDT_sol_DurianUSDT.abi new file mode 100644 index 00000000..c640994f --- /dev/null +++ b/contracts/build/DurianUSDT_sol_DurianUSDT.abi @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/build/DurianUSDT_sol_DurianUSDT.bin b/contracts/build/DurianUSDT_sol_DurianUSDT.bin new file mode 100644 index 00000000..9ad43331 --- /dev/null +++ b/contracts/build/DurianUSDT_sol_DurianUSDT.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b50335f81815260208181526040808320670de0b6b3a76400009081905590519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36106ca806100685f395ff3fe608060405234801561000f575f5ffd5b5060043610610090575f3560e01c8063313ce56711610063578063313ce5671461012757806370a082311461014157806395d89b4114610169578063a9059cbb1461018d578063dd62ed3e146101a0575f5ffd5b806306fdde0314610094578063095ea7b3146100d457806318160ddd146100f757806323b872dd14610114575b5f5ffd5b6100be6040518060400160405280600b81526020016a111d5c9a585b881554d11560aa1b81525081565b6040516100cb9190610591565b60405180910390f35b6100e76100e23660046105e1565b6101d8565b60405190151581526020016100cb565b610106670de0b6b3a764000081565b6040519081526020016100cb565b6100e7610122366004610609565b610298565b61012f600681565b60405160ff90911681526020016100cb565b61010661014f366004610643565b6001600160a01b03165f9081526020819052604090205490565b6100be60405180604001604052806005815260200164191554d11560da1b81525081565b6100e761019b3660046105e1565b61048e565b6101066101ae366004610663565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b5f6001600160a01b0383166102345760405162461bcd60e51b815260206004820152601760248201527f417070726f766520746f207a65726f206164647265737300000000000000000060448201526064015b60405180910390fd5b335f8181526001602090815260408083206001600160a01b03881680855290835292819020869055518581529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a350600192915050565b5f6001600160a01b0384166102ef5760405162461bcd60e51b815260206004820152601a60248201527f5472616e736665722066726f6d207a65726f2061646472657373000000000000604482015260640161022b565b6001600160a01b0383166103405760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b604482015260640161022b565b6001600160a01b0384165f9081526020819052604090205482111561039e5760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b604482015260640161022b565b6001600160a01b0384165f9081526001602090815260408083203384529091529020548211156104095760405162461bcd60e51b8152602060048201526016602482015275496e73756666696369656e7420616c6c6f77616e636560501b604482015260640161022b565b6001600160a01b038481165f81815260208181526040808320805488900390559387168083528483208054880190558383526001825284832033845282529184902080548790039055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35060019392505050565b5f6001600160a01b0383166104e05760405162461bcd60e51b81526020600482015260186024820152775472616e7366657220746f207a65726f206164647265737360401b604482015260640161022b565b335f908152602081905260409020548211156105355760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b604482015260640161022b565b335f81815260208181526040808320805487900390556001600160a01b03871680845292819020805487019055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610287565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b03811681146105dc575f5ffd5b919050565b5f5f604083850312156105f2575f5ffd5b6105fb836105c6565b946020939093013593505050565b5f5f5f6060848603121561061b575f5ffd5b610624846105c6565b9250610632602085016105c6565b929592945050506040919091013590565b5f60208284031215610653575f5ffd5b61065c826105c6565b9392505050565b5f5f60408385031215610674575f5ffd5b61067d836105c6565b915061068b602084016105c6565b9050925092905056fea2646970667358221220401cdf325cd267d7b8721fd26fa71e8965baaf38de318523730aa20166b0c98664736f6c63430008210033 \ No newline at end of file diff --git a/contracts/compile.mjs b/contracts/compile.mjs new file mode 100644 index 00000000..979d8cca --- /dev/null +++ b/contracts/compile.mjs @@ -0,0 +1,45 @@ +import solc from 'solc'; +import fs from 'fs'; + +const source = fs.readFileSync('DurianUSDT.sol', 'utf8'); + +const input = { + language: 'Solidity', + sources: { + 'DurianUSDT.sol': { + content: source + } + }, + settings: { + optimizer: { + enabled: true, + runs: 200 + }, + evmVersion: 'paris', // Use paris to avoid PUSH0 + outputSelection: { + '*': { + '*': ['abi', 'evm.bytecode'] + } + } + } +}; + +const output = JSON.parse(solc.compile(JSON.stringify(input))); + +if (output.errors) { + output.errors.forEach(err => { + console.log(err.formattedMessage); + }); +} + +const contract = output.contracts['DurianUSDT.sol']['DurianUSDT']; +const bytecode = contract.evm.bytecode.object; +const abi = contract.abi; + +fs.mkdirSync('build', { recursive: true }); +fs.writeFileSync('build/DurianUSDT.bin', bytecode); +fs.writeFileSync('build/DurianUSDT.abi', JSON.stringify(abi, null, 2)); + +console.log('Compiled successfully!'); +console.log('Bytecode length:', bytecode.length); +console.log('ABI functions:', abi.filter(x => x.type === 'function').map(x => x.name).join(', ')); diff --git a/contracts/deploy-ethers.mjs b/contracts/deploy-ethers.mjs new file mode 100644 index 00000000..6bac8666 --- /dev/null +++ b/contracts/deploy-ethers.mjs @@ -0,0 +1,61 @@ +import { ethers } from 'ethers'; +import fs from 'fs'; + +const PRIVATE_KEY = '0x886ea4cffe76c386fecf3ff321ac9ae913737c46c17bc6ce2413752144668a2a'; +const RPC_URL = 'https://evm.kava.io'; + +// Contract bytecode +const BYTECODE = '0x' + fs.readFileSync('build/DurianUSDT.bin', 'utf8'); +const ABI = JSON.parse(fs.readFileSync('build/DurianUSDT.abi', 'utf8')); + +async function deploy() { + // Connect to Kava mainnet + const provider = new ethers.JsonRpcProvider(RPC_URL); + const wallet = new ethers.Wallet(PRIVATE_KEY, provider); + + console.log('Deployer address:', wallet.address); + + // Check balance + const balance = await provider.getBalance(wallet.address); + console.log('Balance:', ethers.formatEther(balance), 'KAVA'); + + // Get network info + const network = await provider.getNetwork(); + console.log('Chain ID:', network.chainId.toString()); + + // Create contract factory + const factory = new ethers.ContractFactory(ABI, BYTECODE, wallet); + + console.log('Deploying contract...'); + + // Deploy + const contract = await factory.deploy(); + console.log('Transaction hash:', contract.deploymentTransaction().hash); + + // Wait for deployment + console.log('Waiting for confirmation...'); + await contract.waitForDeployment(); + + const contractAddress = await contract.getAddress(); + console.log('Contract deployed at:', contractAddress); + + // Verify deployment + console.log('\nVerifying deployment...'); + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + const totalSupply = await contract.totalSupply(); + const ownerBalance = await contract.balanceOf(wallet.address); + + console.log('Token name:', name); + console.log('Token symbol:', symbol); + console.log('Decimals:', decimals.toString()); + console.log('Total supply:', ethers.formatUnits(totalSupply, 6), 'dUSDT'); + console.log('Owner balance:', ethers.formatUnits(ownerBalance, 6), 'dUSDT'); + + console.log('\n=== DEPLOYMENT COMPLETE ==='); + console.log('Contract Address:', contractAddress); + console.log('Explorer:', `https://kavascan.com/address/${contractAddress}`); +} + +deploy().catch(console.error); diff --git a/contracts/deploy.mjs b/contracts/deploy.mjs new file mode 100644 index 00000000..700eca2d --- /dev/null +++ b/contracts/deploy.mjs @@ -0,0 +1,165 @@ +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); diff --git a/contracts/package-lock.json b/contracts/package-lock.json new file mode 100644 index 00000000..7f064cf3 --- /dev/null +++ b/contracts/package-lock.json @@ -0,0 +1,252 @@ +{ + "name": "contracts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "contracts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "ethers": "^6.16.0", + "solc": "^0.8.19" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ethers": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solc": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.19.tgz", + "integrity": "sha512-yqurS3wzC4LdEvmMobODXqprV4MYJcVtinuxgrp61ac8K2zz40vXA0eSAskSHPgv8dQo7Nux39i3QBsHx4pqyA==", + "license": "MIT", + "dependencies": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solc.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/contracts/package.json b/contracts/package.json new file mode 100644 index 00000000..3162cd6c --- /dev/null +++ b/contracts/package.json @@ -0,0 +1,19 @@ +{ + "name": "contracts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "ethers": "^6.16.0", + "solc": "^0.8.19" + } +} diff --git a/contracts/transfer-all.mjs b/contracts/transfer-all.mjs new file mode 100644 index 00000000..419da529 --- /dev/null +++ b/contracts/transfer-all.mjs @@ -0,0 +1,56 @@ +import { ethers } from 'ethers'; +import fs from 'fs'; + +const PRIVATE_KEY = '0x886ea4cffe76c386fecf3ff321ac9ae913737c46c17bc6ce2413752144668a2a'; +const RPC_URL = 'https://evm.kava.io'; + +// dUSDT Contract +const CONTRACT_ADDRESS = '0xA9F3A35dBa8699c8C681D8db03F0c1A8CEB9D7c3'; +const ABI = JSON.parse(fs.readFileSync('build/DurianUSDT.abi', 'utf8')); + +// Transfer destination +const TO_ADDRESS = '0xdd919d51ec09c0830fd000a0ff6f0e1859b48db2'; + +async function transferAll() { + // Connect to Kava mainnet + const provider = new ethers.JsonRpcProvider(RPC_URL); + const wallet = new ethers.Wallet(PRIVATE_KEY, provider); + + console.log('From address:', wallet.address); + console.log('To address:', TO_ADDRESS); + + // Connect to contract + const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet); + + // Get current balance + const balance = await contract.balanceOf(wallet.address); + console.log('Current balance:', ethers.formatUnits(balance, 6), 'dUSDT'); + + if (balance === 0n) { + console.log('No balance to transfer'); + return; + } + + // Transfer all tokens + console.log('\nTransferring all tokens...'); + const tx = await contract.transfer(TO_ADDRESS, balance); + console.log('Transaction hash:', tx.hash); + + // Wait for confirmation + console.log('Waiting for confirmation...'); + const receipt = await tx.wait(); + console.log('Confirmed in block:', receipt.blockNumber); + console.log('Gas used:', receipt.gasUsed.toString()); + + // Verify balances + console.log('\n=== Transfer Complete ==='); + const newFromBalance = await contract.balanceOf(wallet.address); + const newToBalance = await contract.balanceOf(TO_ADDRESS); + console.log('From balance:', ethers.formatUnits(newFromBalance, 6), 'dUSDT'); + console.log('To balance:', ethers.formatUnits(newToBalance, 6), 'dUSDT'); + + console.log('\nExplorer link:'); + console.log(`https://kavascan.com/tx/${tx.hash}`); +} + +transferAll().catch(console.error); diff --git a/contracts/verify-sig.mjs b/contracts/verify-sig.mjs new file mode 100644 index 00000000..49167cf1 --- /dev/null +++ b/contracts/verify-sig.mjs @@ -0,0 +1,107 @@ +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 CHAIN_ID = 2222; + +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]); +} + +// Expected address +const privateKeyBytes = hexToBytes(PRIVATE_KEY); +const publicKey = secp256k1.getPublicKey(privateKeyBytes, false); +const pubKeyNoPrefix = publicKey.slice(1); +const hashPubKey = keccak_256(pubKeyNoPrefix); +const expectedAddress = '0x' + bytesToHex(hashPubKey.slice(-20)); +console.log('Expected address:', expectedAddress); + +// Build transaction +const nonce = 0; +const gasPrice = 1000000000; +const gas = 557117; + +const txFields = [ + '', // nonce = 0 + gasPrice.toString(16), + gas.toString(16), + '', // to (empty for contract creation) + '', // value (0) + BYTECODE.slice(2), + CHAIN_ID.toString(16), + '', + '' +]; + +console.log('Transaction fields:'); +console.log(' nonce:', txFields[0] || '(empty = 0)'); +console.log(' gasPrice:', txFields[1]); +console.log(' gas:', txFields[2]); +console.log(' to:', txFields[3] || '(empty = contract creation)'); +console.log(' value:', txFields[4] || '(empty = 0)'); +console.log(' data length:', txFields[5].length); +console.log(' chainId:', txFields[6]); + +const rlpEncoded = rlpEncodeList(txFields); +const msgHash = keccak_256(rlpEncoded); +console.log('\nMessage hash:', bytesToHex(msgHash)); + +// Sign +const sigBytes = secp256k1.sign(msgHash, privateKeyBytes, { lowS: true }); +const r = bytesToHex(sigBytes.slice(0, 32)); +const s = bytesToHex(sigBytes.slice(32, 64)); +console.log('Signature r:', r); +console.log('Signature s:', s); + +// Now try to recover address from signature +// We need to try both recovery values +const sig = secp256k1.Signature.fromHex(r + s); + +for (let rec = 0; rec <= 1; rec++) { + try { + const sigWithRec = sig.addRecoveryBit(rec); + const recoveredPoint = sigWithRec.recoverPublicKey(msgHash); + // Get uncompressed public key bytes + const recoveredPubHex = recoveredPoint.toHex ? recoveredPoint.toHex(false) : null; + if (recoveredPubHex) { + const recoveredPubBytes = hexToBytes(recoveredPubHex); + const recoveredPubNoPrefix = recoveredPubBytes.slice(1); + const recoveredHash = keccak_256(recoveredPubNoPrefix); + const recoveredAddress = '0x' + bytesToHex(recoveredHash.slice(-20)); + console.log(`\nRecovery ${rec}:`); + console.log(' Recovered address:', recoveredAddress); + if (recoveredAddress.toLowerCase() === expectedAddress.toLowerCase()) { + console.log(' *** MATCH! Use recovery =', rec); + console.log(' v =', CHAIN_ID * 2 + 35 + rec); + } + } + } catch (e) { + console.log(`Recovery ${rec} failed:`, e.message); + } +}