안녕하세요 답변이 많이 늦어졌네요
정확한 이슈를 잘모르겠는데 Legacy Tx와 ValueTransfer Tx의 차이가
ValueTransfer는 RLP 인코딩시 포함시키는 인자와 Prefix로 "0x08"이라는 값이 들어갑니다
아래 유틸 라이브러리를 제외하고 직접 순수하게 작성해보았습니다.
import * as sha3 from '@noble/hashes/sha3';
import * as utils from '@noble/hashes/utils';
import * as secp256k1 from '@noble/secp256k1';
import RLP from 'rlp';
import fetch from 'cross-fetch';
function pad0x(s: string): string {
return s.length % 2 === 0 ? s : `0${s}`;
}
function trim0x(s: string): string {
return s.replace(/^0x/i, '');
}
function restore0x(s: string): string {
return s.startsWith('0x') ? s : `0x${s}`;
}
function numberToHex(n: number | bigint): string {
return restore0x(pad0x(n.toString(16)));
}
class Account {
public address: string;
public privateKey: string;
public static checksum(address: string): string {
const trimmed = trim0x(address);
if (trimmed.length !== 40) {
throw new Error('Invalid address');
}
const hash = trim0x(utils.bytesToHex(sha3.keccak_256(trimmed)));
let checksum = '';
for (let i = 0; i < trimmed.length; i++) {
const curr = parseInt(hash[i], 16);
const character = trimmed[i];
if (curr > 7) {
checksum += character.toUpperCase();
} else {
checksum += character;
}
}
return restore0x(checksum);
}
public static fromPrivateKey(privateKey: string): Account {
const buf = utils.hexToBytes(trim0x(privateKey));
const pub = secp256k1.getPublicKey(buf);
const address = utils
.bytesToHex(sha3.keccak_256(pub.slice(1, 65)))
.slice(24);
const account = new Account();
account.privateKey = privateKey;
account.address = restore0x(address);
return account;
}
}
enum TransactionType {
ValueTransfer = 8,
}
interface RawValueTransfer {
nonce: string;
gasPrice: string;
gasLimit: string;
chainId: string;
to: string;
value: string;
from: string;
}
class ValueTransfer {
nonce: bigint;
gasPrice: bigint;
gasLimit: bigint;
chainId: bigint;
to: string;
value: bigint;
from: string;
public static fromObject(raw: RawValueTransfer) {
const tx = new ValueTransfer();
tx.nonce = BigInt(raw.nonce);
tx.gasPrice = BigInt(raw.gasPrice);
tx.gasLimit = BigInt(raw.gasLimit);
tx.chainId = BigInt(raw.chainId);
tx.to = raw.to.toLowerCase();
tx.value = BigInt(raw.value);
tx.from = raw.from.toLowerCase();
return tx;
}
public serializeForSignature() {
return sha3.keccak_256(
RLP.encode([
RLP.encode([
numberToHex(TransactionType.ValueTransfer),
numberToHex(this.nonce),
numberToHex(this.gasPrice),
numberToHex(this.gasLimit),
this.to.toLowerCase(),
numberToHex(this.value),
this.from.toLowerCase(),
]),
numberToHex(this.chainId),
'0x',
'0x',
]),
);
}
public async sign(account: Account) {
const [buf, recov] = await secp256k1.sign(
this.serializeForSignature(),
trim0x(account.privateKey),
{
recovered: true,
},
);
const signature = secp256k1.Signature.fromHex(buf);
const chainId = Number(this.chainId.toString());
const v = recov + (chainId * 2 + 35);
return restore0x(
(
numberToHex(TransactionType.ValueTransfer) +
utils.bytesToHex(
RLP.encode([
numberToHex(this.nonce),
numberToHex(this.gasPrice),
numberToHex(this.gasLimit),
this.to.toLowerCase(),
numberToHex(this.value),
this.from.toLowerCase(),
[[v, signature.r, signature.s].map((x) => numberToHex(x))],
]),
)
).slice(2),
);
}
}
async function bootstrap() {
const sender = Account.fromPrivateKey(process.env.PRIVATE_KEY);
const tx = ValueTransfer.fromObject({
nonce: '16',
gasPrice: '250000000000',
gasLimit: '300000',
chainId: '1001',
to: '0x0000000000000000000000000000000000000000',
value: '1000000000000000000',
from: sender.address,
});
const message = await tx.sign(sender);
const payload = await fetch('https://api.baobab.klaytn.net:8651/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: 1,
jsonrpc: '2.0',
method: 'klay_sendRawTransaction',
params: [message],
}),
});
console.log(await payload.json());
}
bootstrap().then(() => {
process.exit(0);
});
ValueTransfer 타입밖에 없는데
Abstract Transaction 클래스를 만드시고 상속받아서 여러가지 트랜잭션을 구현해볼 수 있을것 같습니다.
Dependencies
"dependencies": {
"@noble/hashes": "^1.0.0",
"@noble/secp256k1": "^1.5.5",
"cross-fetch": "^3.1.5",
"rlp": "^3.0.0"
}