diff --git a/ethers-ext/src/core/index.ts b/ethers-ext/src/core/index.ts index 5ac9077b7..f86446493 100644 --- a/ethers-ext/src/core/index.ts +++ b/ethers-ext/src/core/index.ts @@ -1,4 +1,4 @@ -export { KlaytnTx, KlaytnTxFactory } from "./klaytn_tx"; +export { KlaytnTx, KlaytnTxFactory, objectFromRLP } from "./klaytn_tx"; export { AccountKey, AccountKeyFactory } from "./accountKey"; /* eslint-disable import/order */ diff --git a/ethers-ext/src/index.ts b/ethers-ext/src/index.ts index 92cd05d22..66f79a6ac 100644 --- a/ethers-ext/src/index.ts +++ b/ethers-ext/src/index.ts @@ -1,4 +1,4 @@ -export { KlaytnTxFactory, AccountKeyFactory } from "./core"; +export { KlaytnTxFactory, AccountKeyFactory, objectFromRLP } from "./core"; export { TxType, AccountKeyType, formatKlaytnUnits, formatKlay, parseKlaytnUnits, parseKlay } from "./core/util"; export { Wallet, JsonRpcProvider, Accounts, AccountStore } from "./ethers"; export { verifyMessageAsKlaytnAccountKey } from "./ethers/signer"; diff --git a/web3js-ext/example/transactions/Basic_08_TxTypeValueTransfer.js b/web3js-ext/example/transactions/Basic_08_TxTypeValueTransfer.js new file mode 100644 index 000000000..568fab42f --- /dev/null +++ b/web3js-ext/example/transactions/Basic_08_TxTypeValueTransfer.js @@ -0,0 +1,40 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, parseKlay } = require("@klaytn/ethers-ext"); + +// +// TxTypeValueTransfer +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypevaluetransfer +// +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + + let tx = { + type: TxType.ValueTransfer, + to: recieverAddr, + value: 1e9, + // value: convertToPeb('1', 'KLAY'), + from: senderAddr, + gas: 21000, + gasPrice: 25e9, + }; + + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + console.log({ signResult }); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); \ No newline at end of file diff --git a/web3js-ext/example/transactions/Basic_10_TxTypeValueTransferMemo.js b/web3js-ext/example/transactions/Basic_10_TxTypeValueTransferMemo.js new file mode 100644 index 000000000..58b330235 --- /dev/null +++ b/web3js-ext/example/transactions/Basic_10_TxTypeValueTransferMemo.js @@ -0,0 +1,40 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, parseKlay } = require("@klaytn/ethers-ext"); + +// +// TxTypeValueTransferMemo +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypevaluetransfermemo +// + +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + + let tx = { + type: TxType.ValueTransferMemo, + to: recieverAddr, + value: 1e9, + // value: parseKlay("1"), // TODO: add type conversion function + from: senderAddr, + input: "0x1234567890", + }; + + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + console.log({ signResult }); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/Basic_20_TxTypeAccountUpdate.js b/web3js-ext/example/transactions/Basic_20_TxTypeAccountUpdate.js new file mode 100644 index 000000000..945e5758b --- /dev/null +++ b/web3js-ext/example/transactions/Basic_20_TxTypeAccountUpdate.js @@ -0,0 +1,50 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, AccountKeyType } = require("@klaytn/ethers-ext"); + +const { secp256k1 } = require("ethereum-cryptography/secp256k1.js") + +// +// TxTypeAccountUpdate +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypeaccountupdate +// +// from: address of sender to be updated +// key: Refer Klaytn account key +// https://docs.klaytn.foundation/content/klaytn/design/accounts#account-key +// + +// create new account for testing +// https://baobab.wallet.klaytn.foundation/ +const senderAddr = "0x7532967dda17c5e367c7a5c5dcb56ef6ed299e20"; +const senderPriv = "0x4ef44daeb7941877bebc7b97c023e1e51b5593ee1fbc4a232387774603173b86"; +const senderNewPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const publicKey = "0x" + Buffer.from(secp256k1.getPublicKey( BigInt(senderNewPriv), true)).toString('hex') + console.log(publicKey); + + let tx = { + type: TxType.AccountUpdate, + from: senderAddr, + key: { + type: AccountKeyType.Public, + key: publicKey, + } + }; + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + console.log({ signResult }); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/Basic_28_TxTypeSmartContractDeploy.js b/web3js-ext/example/transactions/Basic_28_TxTypeSmartContractDeploy.js new file mode 100644 index 000000000..3bc647642 --- /dev/null +++ b/web3js-ext/example/transactions/Basic_28_TxTypeSmartContractDeploy.js @@ -0,0 +1,49 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType } = require("@klaytn/ethers-ext"); + + +// +// TxTypeSmartContractDeploy +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypesmartcontractdeploy +// +// to: Must be "0x0000000000000000000000000000000000000000", +// value: Must be 0, if not payable +// input: SmartContract binary, +// humanReadable: Must be false, +// codeFormat: Must be 0x00 +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + + let tx = { + type: TxType.SmartContractDeploy, + to: "0x0000000000000000000000000000000000000000", + value: 0, + from: senderAddr, + input: "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220e0f4e7861cb6d7acf0f61d34896310975b57b5bc109681dbbfb2e548ef7546b364736f6c63430008120033", + humanReadable: false, + codeFormat: 0x00, + gas: 210000, + gasPrice: 25e9, + }; + + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + console.log({ signResult }); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/Basic_30_TxTypeSmartContractExecution.js b/web3js-ext/example/transactions/Basic_30_TxTypeSmartContractExecution.js new file mode 100644 index 000000000..7a83a15dd --- /dev/null +++ b/web3js-ext/example/transactions/Basic_30_TxTypeSmartContractExecution.js @@ -0,0 +1,68 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType } = require("@klaytn/ethers-ext"); + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const contractAddr = "0xD7fA6634bDDe0B2A9d491388e2fdeD0fa25D2067"; + +// +// TxTypeSmartContractExecution +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypesmartcontractexecution +// +// to : deployed contract address +// value: Must be 0, if not payable +// input: Refer https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#methods-mymethod-encodeabi +// +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + + const CONTRACT_ADDRESS = contractAddr; + const CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256" + } + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]; + const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS); + const param = contract.methods.setNumber(0x123).encodeABI(); + + let tx = { + type: TxType.SmartContractExecution, + to: contractAddr, + value: 0, + from: senderAddr, + input: param, + }; + + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + console.log({ signResult }); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/Basic_38_TxTypeCancel.js b/web3js-ext/example/transactions/Basic_38_TxTypeCancel.js new file mode 100644 index 000000000..30619087f --- /dev/null +++ b/web3js-ext/example/transactions/Basic_38_TxTypeCancel.js @@ -0,0 +1,64 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType } = require("@klaytn/ethers-ext"); + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + +// +// TxTypeCancel +// https://docs.klaytn.foundation/content/klaytn/design/transactions/basic#txtypecancel +// +// 1) send ValueTransfer tx with the next nonce + 1 +// 2) send Cancel tx with the next nonce + 1 +// 3) send ValueTransfer tx with the next nonce +// then you can see Cancel tx with the next nonce + 1 +// +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const sender = web3.eth.accounts.privateKeyToAccount(senderPriv); + + // 1) send ValueTransfer tx with the next nonce+1 + let nextNonce = await web3.eth.getTransactionCount(senderAddr); + let tx = { + type: TxType.ValueTransfer, + nonce: nextNonce + 1n, + to: recieverAddr, + value: 1e12, + from: senderAddr, + }; + + let signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + web3.eth.sendSignedTransaction(signResult.rawTransaction); + // TODO: .on function not working + //.on("receipt", (receipt) => console.log("tx next + 1", receipt)); + + // 2) send Cancel tx with the next nonce+1 + let txCancel = { + type: TxType.Cancel, + nonce: nextNonce + 1n, + from: senderAddr, + }; + + signResult = await web3.eth.accounts.signTransaction(txCancel, sender.privateKey); + web3.eth.sendSignedTransaction(signResult.rawTransaction) + // TODO: .on function not working + //.on("receipt", (receipt) => console.log("tx next + 1 cancel", receipt)); + + // 3) send ValueTransfer tx with the next nonce + tx.nonce = nextNonce; + + signResult = await web3.eth.accounts.signTransaction(tx, sender.privateKey); + sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + + console.log("sendResult", sendResult); + + let receipt = await web3.eth.getTransactionReceipt(sendResult.transactionHash); + console.log( receipt ); +} + +main(); diff --git a/web3js-ext/example/transactions/FeeDel_09_TxTypeFeeDelegatedValueTransfer.js b/web3js-ext/example/transactions/FeeDel_09_TxTypeFeeDelegatedValueTransfer.js new file mode 100644 index 000000000..e7172264c --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_09_TxTypeFeeDelegatedValueTransfer.js @@ -0,0 +1,55 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + +// +// TxTypeFeeDelegatedValueTransfer +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfer +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedValueTransfer, + to: recieverAddr, + value: 1e9, + // value: convertToPeb('1', 'KLAY'), + from: senderAddr, + gas: 300000, + gasPrice: 100e9, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/FeeDel_11_TxTypeFeeDelegatedValueTransferMemo.js b/web3js-ext/example/transactions/FeeDel_11_TxTypeFeeDelegatedValueTransferMemo.js new file mode 100644 index 000000000..3e02648af --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_11_TxTypeFeeDelegatedValueTransferMemo.js @@ -0,0 +1,57 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + +// +// TxTypeFeeDelegatedValueTransferMemo +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfermemo +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedValueTransferMemo, + to: recieverAddr, + value: 1e9, + // value: convertToPeb('1', 'KLAY'), + from: senderAddr, + input: "0x1234567890", + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/FeeDel_21_TxTypeFeeDelegatedAccountUpdate.js b/web3js-ext/example/transactions/FeeDel_21_TxTypeFeeDelegatedAccountUpdate.js new file mode 100644 index 000000000..9308d4e52 --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_21_TxTypeFeeDelegatedAccountUpdate.js @@ -0,0 +1,61 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, AccountKeyType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + +const { secp256k1 } = require("ethereum-cryptography/secp256k1.js") + +// +// TxTypeFeeDelegatedAccountUpdate +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedaccountupdate +// + +// create new account for testing +// https://baobab.wallet.klaytn.foundation/ +const senderAddr = "0x9b0d00d5ffcc2024f1816feb99522d8c0e519170"; +const senderPriv = "0xc7fd5cceea90867c80193d9cfbd2b8b5dc0f4b794c5dd2413d569e332b0ae4c7"; +const senderNewPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const publicKey = "0x" + Buffer.from(secp256k1.getPublicKey( BigInt(senderNewPriv), true)).toString('hex') + console.log(publicKey); + + let tx = { + type: TxType.FeeDelegatedAccountUpdate, + from: senderAddr, + key: { + type: AccountKeyType.Public, + key: publicKey + } + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/FeeDel_29_TxTypeFeeDelegatedSmartContractDeploy.js b/web3js-ext/example/transactions/FeeDel_29_TxTypeFeeDelegatedSmartContractDeploy.js new file mode 100644 index 000000000..7d12c1a6a --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_29_TxTypeFeeDelegatedSmartContractDeploy.js @@ -0,0 +1,61 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + +// TxTypeFeeDelegatedSmartContractDeploy +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractdeploy +// +// to: Must be "0x0000000000000000000000000000000000000000", +// value: Must be 0, if not payable +// input: SmartContract binary, +// humanReadable: Must be false, +// codeFormat: Must be 0x00 +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedSmartContractDeploy, + to: "0x0000000000000000000000000000000000000000", + value: 0, + from: senderAddr, + input: "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220e0f4e7861cb6d7acf0f61d34896310975b57b5bc109681dbbfb2e548ef7546b364736f6c63430008120033", + humanReadable: false, + codeFormat: 0x00, + gas: 400000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); \ No newline at end of file diff --git a/web3js-ext/example/transactions/FeeDel_31_TxTypeFeeDelegatedSmartContractExecution.js b/web3js-ext/example/transactions/FeeDel_31_TxTypeFeeDelegatedSmartContractExecution.js new file mode 100644 index 000000000..5000434c0 --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_31_TxTypeFeeDelegatedSmartContractExecution.js @@ -0,0 +1,84 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + +// TxTypeFeeDelegatedSmartContractExecution +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractexecution +// +// to : deployed contract address +// value: Must be 0, if not payable +// input: Refer https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#methods-mymethod-encodeabi +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const contractAddr = "0xD7fA6634bDDe0B2A9d491388e2fdeD0fa25D2067"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const CONTRACT_ADDRESS = contractAddr; + const CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256" + } + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]; + const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS); + const param = contract.methods.setNumber(0x123).encodeABI(); + + let tx = { + type: TxType.FeeDelegatedSmartContractExecution, + to: CONTRACT_ADDRESS, + value: 0, + from: senderAddr, + input: param, + gas: 400000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/FeeDel_39_TxTypeFeeDelegatedCancel.js b/web3js-ext/example/transactions/FeeDel_39_TxTypeFeeDelegatedCancel.js new file mode 100644 index 000000000..a5d03ad0a --- /dev/null +++ b/web3js-ext/example/transactions/FeeDel_39_TxTypeFeeDelegatedCancel.js @@ -0,0 +1,84 @@ +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); + +const { TxType } = require("@klaytn/ethers-ext"); + +// +// TxTypeFeeDelegatedCancel +// https://docs.klaytn.foundation/content/klaytn/design/transactions/fee-delegation#txtypefeedelegatedcancel +// +// 1) send ValueTransfer tx with the next nonce + 1 +// 2) send Cancel tx with the next nonce + 1 +// 3) send ValueTransfer tx with the next nonce +// then you can see Cancel tx with the next nonce + 1 +// + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv, provider); + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + + // 1) send ValueTransfer tx with the next nonce + 1 + let nextNonce = await web3.eth.getTransactionCount(senderAddr); + console.log(nextNonce); + let tx = { + type: TxType.FeeDelegatedValueTransfer, + nonce: nextNonce + 1n, + to: recieverAddr, + value: 1e12, + from: senderAddr, + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender sign + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + web3.eth.sendSignedTransaction(signResult.rawTransaction); + + // 2) send Cancel tx with the next nonce+1 + let txCancel = { + type: TxType.FeeDelegatedCancel, + nonce: nextNonce + 1n, + from: senderAddr, + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender sign + senderTx = await web3.eth.accounts.signTransaction(txCancel, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + web3.eth.sendSignedTransaction(signResult.rawTransaction); + + // 3) send ValueTransfer tx with the next nonce + tx.nonce = nextNonce; + + // sender sign + senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + console.log("sendResult", sendResult); + + let receipt = await web3.eth.getTransactionReceipt(sendResult.transactionHash); + console.log( receipt ); +} + +main(); diff --git a/web3js-ext/example/transactions/PartialFeeDel_0a_TxTypeFeeDelegatedValueTransferWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_0a_TxTypeFeeDelegatedValueTransferWithRatio.js new file mode 100644 index 000000000..019d6a4e5 --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_0a_TxTypeFeeDelegatedValueTransferWithRatio.js @@ -0,0 +1,56 @@ +// +// TxTypeFeeDelegatedValueTransferWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransferwithratio +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedValueTransferWithRatio, + to: recieverAddr, + value: 1e9, + // value: convertToPeb('1', 'KLAY'), + from: senderAddr, + gas: 300000, + gasPrice: 100e9, + feeRatio: 40, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/PartialFeeDel_12_TxTypeFeeDelegatedValueTransferMemoWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_12_TxTypeFeeDelegatedValueTransferMemoWithRatio.js new file mode 100644 index 000000000..6e3761c97 --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_12_TxTypeFeeDelegatedValueTransferMemoWithRatio.js @@ -0,0 +1,59 @@ +// +// TxTypeFeeDelegatedValueTransferMemoWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransfermemowithratio +// +// nonce: In signTransactionAsFeePayer, must not be omitted, because feePayer's nonce is filled when populating +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedValueTransferMemoWithRatio, + to: recieverAddr, + value: 1e9, + // value: convertToPeb('1', 'KLAY'), + from: senderAddr, + input: "0x1234567890", + gas: 300000, + gasPrice: 100e9, + feeRatio: 30, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/PartialFeeDel_22_TxTypeFeeDelegatedAccountUpdateWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_22_TxTypeFeeDelegatedAccountUpdateWithRatio.js new file mode 100644 index 000000000..59d0ffbab --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_22_TxTypeFeeDelegatedAccountUpdateWithRatio.js @@ -0,0 +1,74 @@ +// +// TxTypeFeeDelegatedAccountUpdateWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedaccountupdatewithratio +// +// nonce: In signTransactionAsFeePayer, must not be omitted, because feePayer's nonce is filled when populating +// gasLimit: Must be large enough +// If SDK users (wallet or dapp devs) want to add some margin, they can always +// 1) As in this example, fill in enough values by referring to the results of your previous transactions. +// 2) Manually call eth_estimateGas or klay_estimateGas and multiply by a factor. +// Edit the populatedTx (e.g. tx.gas = tx.gas * 1.8) +// +// Learn how Klaytn Tx intrinsic gas are calculated - which is unlikely because there's no documentation for it. +// You should see the source code for the info (e.g. VTwithMemo intrinsic gas is 21000 + len(memo)*100 ) +// https://github.com/klaytn/klaytn/blob/dev/blockchain/types/tx_internal_data_value_transfer_memo.go#L239 +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType, AccountKeyType, objectFromRLP } = require("../../../ethers-ext/dist/src"); +const { secp256k1 } = require("ethereum-cryptography/secp256k1.js") + + +// create new account for testing +// https://baobab.wallet.klaytn.foundation/ +const senderAddr = "0xd34c89278e763b8ea7663db2df199984d6b3ae55"; +const senderPriv = "0xdde24aa1236ff2304171c46376f6b6b4d82c9aa97a4406c4be9c95014a02b9ee"; +const senderNewPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const publicKey = "0x" + Buffer.from(secp256k1.getPublicKey( BigInt(senderNewPriv), true)).toString('hex') + console.log(publicKey); + + let tx = { + type: TxType.FeeDelegatedAccountUpdateWithRatio, + from: senderAddr, + key: { + type: AccountKeyType.Public, + key: publicKey + }, + feeRatio: 40, + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/PartialFeeDel_2a_TxTypeFeeDelegatedSmartContractDeployWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_2a_TxTypeFeeDelegatedSmartContractDeployWithRatio.js new file mode 100644 index 000000000..1ec751de1 --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_2a_TxTypeFeeDelegatedSmartContractDeployWithRatio.js @@ -0,0 +1,62 @@ +// TxTypeFeeDelegatedSmartContractDeployWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedsmartcontractdeploywithratio +// +// to: Must be "0x0000000000000000000000000000000000000000", +// value: Must be 0, if not payable +// input: SmartContract binary, +// humanReadable: Must be false, +// codeFormat: Must be 0x00 +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + let tx = { + type: TxType.FeeDelegatedSmartContractDeployWithRatio, + to: "0x0000000000000000000000000000000000000000", + value: 0, + from: senderAddr, + input: "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220e0f4e7861cb6d7acf0f61d34896310975b57b5bc109681dbbfb2e548ef7546b364736f6c63430008120033", + humanReadable: false, + codeFormat: 0x00, + gas: 400000, // intrinsic gas too low + gasPrice: 100e9, + feeRatio: 30, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); \ No newline at end of file diff --git a/web3js-ext/example/transactions/PartialFeeDel_32_TxTypeFeeDelegatedSmartContractExecutionWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_32_TxTypeFeeDelegatedSmartContractExecutionWithRatio.js new file mode 100644 index 000000000..8269d1ff8 --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_32_TxTypeFeeDelegatedSmartContractExecutionWithRatio.js @@ -0,0 +1,84 @@ +// TxTypeFeeDelegatedSmartContractExecutionWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedsmartcontractexecutionwithratio +// +// to : deployed contract address +// value: Must be 0, if not payable +// input: Refer https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#methods-mymethod-encodeabi +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType, objectFromRLP } = require("../../../ethers-ext/dist/src"); + + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const contractAddr = '0xdd43ebd381060a31ca2827311835649e9819439b'; + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const CONTRACT_ADDRESS = contractAddr; + const CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256" + } + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]; + const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS); + const param = contract.methods.setNumber(0x123).encodeABI(); + + let tx = { + type: TxType.FeeDelegatedSmartContractExecutionWithRatio, + to: CONTRACT_ADDRESS, + value: 0, + from: senderAddr, + input: param, + gas: 400000, // intrinsic gas too low + gasPrice: 100e9, + feeRatio: 30, + }; + + // sender + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv); + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log(senderTx); + + // tx = objectFromRLP(senderTx.rawTransaction); + // console.log(tx); + + // fee payer + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log(signResult); + + // tx = objectFromRLP(signResult.rawTransaction); + // console.log(tx); + + let sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + let txhash = sendResult.transactionHash; + + let receipt = await web3.eth.getTransactionReceipt(txhash); + console.log({ receipt }); +} + +main(); diff --git a/web3js-ext/example/transactions/PartialFeeDel_3a_TxTypeFeeDelegatedCancelWithRatio.js b/web3js-ext/example/transactions/PartialFeeDel_3a_TxTypeFeeDelegatedCancelWithRatio.js new file mode 100644 index 000000000..dd8a402ca --- /dev/null +++ b/web3js-ext/example/transactions/PartialFeeDel_3a_TxTypeFeeDelegatedCancelWithRatio.js @@ -0,0 +1,87 @@ +// +// TxTypeFeeDelegatedCancelWithRatio +// https://docs.klaytn.foundation/content/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedcancelwithratio +// +// 1) send ValueTransfer tx with the next nonce + 1 +// 2) send Cancel tx with the next nonce + 1 +// 3) send ValueTransfer tx with the next nonce +// then you can see Cancel tx with the next nonce + 1 +// + +const { Web3 } = require("web3"); +const { KlaytnWeb3 } = require( "../../dist/src"); +const { TxType } = require("@klaytn/ethers-ext"); + + +const senderAddr = "0xa2a8854b1802d8cd5de631e690817c253d6a9153"; +const senderPriv = "0x0e4ca6d38096ad99324de0dde108587e5d7c600165ae4cd6c2462c597458c2b8"; +const feePayerAddr = "0xcb0eb737dfda52756495a5e08a9b37aab3b271da"; +const feePayerPriv = "0x9435261ed483b6efa3886d6ad9f64c12078a0e28d8d80715c773e16fc000cff4"; +const recieverAddr = "0xc40b6909eb7085590e1c26cb3becc25368e249e9"; + + +async function main() { + const provider = new Web3.providers.HttpProvider("https://public-en-baobab.klaytn.net"); + const web3 = new KlaytnWeb3(provider); + + const senderWallet = web3.eth.accounts.privateKeyToAccount(senderPriv, provider); + const feePayerWallet = web3.eth.accounts.privateKeyToAccount(feePayerPriv, provider); + + // 1) send ValueTransfer tx with the next nonce + 1 + let nextNonce = await web3.eth.getTransactionCount(senderAddr); + console.log(nextNonce); + let tx = { + type: TxType.FeeDelegatedValueTransferWithRatio, + nonce: nextNonce + 1n, + to: recieverAddr, + value: 1e12, + from: senderAddr, + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + feeRatio: 30, + }; + + + // sender sign + let senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + let signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + web3.eth.sendSignedTransaction(signResult.rawTransaction); + + // 2) send Cancel tx with the next nonce+1 + let txCancel = { + type: TxType.FeeDelegatedCancelWithRatio, + nonce: nextNonce + 1n, + from: senderAddr, + gas: 300000, // intrinsic gas too low + gasPrice: 100e9, + feeRatio: 30, + }; + + // sender sign + senderTx = await web3.eth.accounts.signTransaction(txCancel, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + web3.eth.sendSignedTransaction(signResult.rawTransaction); + + // 3) send ValueTransfer tx with the next nonce + tx.nonce = nextNonce; + + // sender sign + senderTx = await web3.eth.accounts.signTransaction(tx, senderWallet.privateKey); + console.log({senderTx}); + // feePayer sign + signResult = await web3.eth.accounts.signTransactionAsFeePayer(senderTx.rawTransaction, feePayerWallet.privateKey); + console.log({signResult}); + sendResult = await web3.eth.sendSignedTransaction(signResult.rawTransaction); + console.log("sendResult", sendResult); + + let receipt = await web3.eth.getTransactionReceipt(sendResult.transactionHash); + console.log( receipt ); +} + +main(); diff --git a/web3js-ext/src/web3/account.ts b/web3js-ext/src/web3/account.ts new file mode 100644 index 000000000..aa3f036cb --- /dev/null +++ b/web3js-ext/src/web3/account.ts @@ -0,0 +1,50 @@ +import { + TransactionSigningError, +} from 'web3-errors'; +import { + HexString, +} from 'web3-types'; +import { + bytesToHex, + hexToBytes, + sha3Raw, +} from 'web3-utils'; + +import { TypedTransaction, SignTransactionResult } from "web3-eth-accounts"; +import { isNullish } from 'web3-validator'; + + +export const signTransactionAsFeePayer = async ( + transaction: TypedTransaction, + privateKey: HexString, + // To make it compatible with rest of the API, have to keep it async + // eslint-disable-next-line @typescript-eslint/require-await +): Promise => { + // @ts-ignore + const signedTx = transaction.signAsFeePayer(hexToBytes(privateKey)); + if (isNullish(signedTx.feePayer_v) || isNullish(signedTx.feePayer_r) || isNullish(signedTx.feePayer_s)) + throw new TransactionSigningError('Signer Error'); + + const validationErrors = signedTx.validate(true); + + if (validationErrors.length > 0) { + let errorString = 'Signer Error '; + for (const validationError of validationErrors) { + errorString += `${errorString} ${validationError}.`; + } + throw new TransactionSigningError(errorString); + } + + // @ts-ignore + const rawTx = bytesToHex(signedTx.serializeAsFeePayer()); + const txHash = sha3Raw(rawTx); // using keccak in web3-utils.sha3Raw instead of SHA3 (NIST Standard) as both are different + + return { + messageHash: bytesToHex(signedTx.getMessageToSignAsFeePayer(true)), + v: `0x${signedTx.feePayer_v.toString(16)}`, + r: `0x${signedTx.feePayer_r.toString(16).padStart(64, '0')}`, + s: `0x${signedTx.feePayer_s.toString(16).padStart(64, '0')}`, + rawTransaction: rawTx, + transactionHash: bytesToHex(txHash), + }; +}; \ No newline at end of file diff --git a/web3js-ext/src/web3/index.ts b/web3js-ext/src/web3/index.ts index c6800c9fe..65cd9dd96 100644 --- a/web3js-ext/src/web3/index.ts +++ b/web3js-ext/src/web3/index.ts @@ -1,2 +1,2 @@ export { KlaytnWeb3 } from "./web3"; -export { KlaytnTx } from "./klaytn_tx"; +export { KlaytnTx } from "./klaytn_tx"; \ No newline at end of file diff --git a/web3js-ext/src/web3/klaytn_tx.ts b/web3js-ext/src/web3/klaytn_tx.ts index 4abb36505..e8455d7ca 100644 --- a/web3js-ext/src/web3/klaytn_tx.ts +++ b/web3js-ext/src/web3/klaytn_tx.ts @@ -1,15 +1,28 @@ -import { Transaction as LegacyTransaction, TypedTransaction, TxData, TxOptions } from "web3-eth-accounts"; +import { Transaction as LegacyTransaction, TypedTransaction, TxData, TxOptions, ECDSASignature } from "web3-eth-accounts"; import { bytesToHex, hexToBytes, toHex, toNumber, numberToHex, toBigInt } from "web3-utils"; import { keccak256 } from 'ethereum-cryptography/keccak.js'; import { RLP } from '@ethereumjs/rlp'; -import { Bytes, Numbers, Transaction as TransactionFields, Web3Context } from "web3"; +import { Bytes, Numbers, Transaction as TransactionFields, Uint, Web3Context } from "web3"; import _ from "lodash"; import { prepareTransactionForSigning } from "web3-eth"; -import { KlaytnTxFactory } from "@klaytn/ethers-ext"; +// eslint-disable-next-line import/extensions +import * as ethereumCryptography from 'ethereum-cryptography/secp256k1.js'; + +export const secp256k1 = ethereumCryptography.secp256k1 ?? ethereumCryptography; + +import { KlaytnTxFactory, TxType } from "@klaytn/ethers-ext"; export interface KlaytnTxData extends TxData { from?: string, chainId?: bigint, + key? : any, + feePayer? : string, + feePayer_v? : bigint, + feePayer_r? : Uint8Array, + feePayer_s? : Uint8Array, + txSignatures? : any, + feePayerSignatures? : any, + feeRatio? : Uint, } // See web3-types/src/eth_types.ts:TransactionBase and its child interfaces @@ -53,6 +66,12 @@ export async function prepareTransaction( transaction = _.clone(transaction); let savedFields = saveCustomFields(transaction); + // prepareTransactionForSigning expects ANY value (not undefined) + // because otherwise eth_estimateGas will fail with an RPC error '"0x"..*hexutil.Big'. + // however, some Klaytn tx types stipulates to NOT have value (e.g. TxTypeCancel, TxTypeAccountUpdate) + // Therefore we fill with zero value if not defined. + transaction.value ??= 0; + let tx = await prepareTransactionForSigning( transaction, context, privateKey, true, true); @@ -88,6 +107,14 @@ export class KlaytnTx extends LegacyTransaction { private readonly _klaytnType: number; public readonly from?: string; public readonly chainId?: bigint; + public readonly key?: any; + public readonly feePayer?: string; + public readonly feePayer_v?: bigint; + public readonly feePayer_r?: Uint8Array; + public readonly feePayer_s?: Uint8Array; + public readonly txSignatures?: any; + public readonly feePayerSignatures?: any; + public readonly feeRatio?: Uint; // Parsed KlaytnTx object private readonly klaytnTxData: any; // TODO: import KlaytnTx as CoreKlaytnTx from ethers-ext @@ -116,9 +143,16 @@ export class KlaytnTx extends LegacyTransaction { this._klaytnType = toNumber(txData.type) as number; this.from = txData.from; this.chainId = txData.chainId; + this.key = txData.key; + this.feePayer = txData.feePayer; + this.feePayer_v = txData.feePayer_v; + this.feePayer_r = txData.feePayer_r; + this.feePayer_s = txData.feePayer_s; + this.txSignatures = txData.txSignatures; + this.feePayerSignatures = txData.feePayerSignatures; + this.feeRatio = txData.feeRatio; - // A readonly CoreKlaytnTx object - this.klaytnTxData = KlaytnTxFactory.fromObject({ + let klaytnTxObject = { // Convert to type understood by CoreKlaytnTx. // TODO: add more fields for other TxTypes type: toHex(this.type || 0), @@ -131,7 +165,24 @@ export class KlaytnTx extends LegacyTransaction { data: bytesToHex(this.data), input: bytesToHex(this.data), chainId: this.chainId ? toHex(this.chainId) : undefined, - }); + humanReadable: false, + codeFormat: 0x00, + key: this.key, + feePayer: this.feePayer, + txSignatures: this.txSignatures, + feePayerSignatures: this.feePayerSignatures, + feeRatio: this.feeRatio, + }; + + if ( txData.type == TxType.SmartContractDeploy || + txData.type == TxType.FeeDelegatedSmartContractDeploy || + txData.type == TxType.FeeDelegatedSmartContractDeployWithRatio) { + klaytnTxObject.to = "0x0000000000000000000000000000000000000000"; + } + + // A readonly CoreKlaytnTx object + this.klaytnTxData = KlaytnTxFactory.fromObject(klaytnTxObject); + if (this.v && this.r && this. s) { this.klaytnTxData.addSenderSig([ Number(this.v), @@ -140,6 +191,18 @@ export class KlaytnTx extends LegacyTransaction { ]); } + // @ts-ignore + if (this.feePayer_v && this.feePayer_r && this.feePayer_s) { + this.klaytnTxData.addFeePayerSig([ + // @ts-ignore + Number(this.feePayer_v), + // @ts-ignore + numberToHex(this.feePayer_r), + // @ts-ignore + numberToHex(this.feePayer_s), + ]); + } + // Recreate the behavior at the end of LegacyTransaction.constructor(). this.txOptions.freeze = savedFreeze; if (this.txOptions.freeze ?? true) { @@ -158,6 +221,36 @@ export class KlaytnTx extends LegacyTransaction { } } + public getMessageToSignAsFeePayer(hashMessage: false): Uint8Array[]; + public getMessageToSignAsFeePayer(hashMessage?: true): Uint8Array; + public getMessageToSignAsFeePayer(hashMessage = true) { + const rlp = hexToBytes(this.klaytnTxData.sigFeePayerRLP()); + if (hashMessage) { + return keccak256(rlp); // Hashed Uint8Array + } else { + return RLP.decode(rlp); // RLP-decoded Uint8Array[] + } + } + + public signAsFeePayer(privateKey: Uint8Array): KlaytnTx { + if (privateKey.length !== 32) { + const msg = this._errorMsg('Private key must be 32 bytes in length.'); + throw new Error(msg); + } + + const msgHash = this.getMessageToSignAsFeePayer(true); + const signature = secp256k1.sign(msgHash, privateKey); + const signatureBytes = signature.toCompactRawBytes(); + + const r = signatureBytes.subarray(0, 32); + const s = signatureBytes.subarray(32, 64); + const v = BigInt(signature.recovery! + 27); + + const tx = this._processSignatureAsFeePayer(v, r, s); + + return tx; + } + // Returns a new KlaytnTx by adding the signature protected _processSignature(v: bigint, r: Uint8Array, s: Uint8Array): KlaytnTx { // Klaytn TxTypes must comply to EIP-155. @@ -172,8 +265,36 @@ export class KlaytnTx extends LegacyTransaction { }, this.txOptions); } + // Returns a new KlaytnTx by adding the signature + protected _processSignatureAsFeePayer(v: bigint, r: Uint8Array, s: Uint8Array): KlaytnTx { + // Klaytn TxTypes must comply to EIP-155. + v += this.common.chainId() * BigInt(2) + BigInt(8); + + return new KlaytnTx({ + ...this, + type: this.type, // The '...this' expression does not include this.type because it's a getter. + feePayer_v: v, + feePayer_r: toBigInt(bytesToHex(r)), + feePayer_s: toBigInt(bytesToHex(s)), + }, this.txOptions); + } + // Returns the raw transaction public serialize(): Uint8Array { - return hexToBytes(this.klaytnTxData.txHashRLP()); + + let tx = this.klaytnTxData; + + if (tx.hasFeePayer()) { + return hexToBytes(tx.senderTxHashRLP()); + } + return hexToBytes(tx.txHashRLP()); + } + + // Returns the raw transaction + public serializeAsFeePayer(): Uint8Array { + + let tx = this.klaytnTxData; + + return hexToBytes(tx.txHashRLP()); } } diff --git a/web3js-ext/src/web3/send_transaction.ts b/web3js-ext/src/web3/send_transaction.ts index e152e627d..a2143419e 100644 --- a/web3js-ext/src/web3/send_transaction.ts +++ b/web3js-ext/src/web3/send_transaction.ts @@ -60,6 +60,9 @@ export function klay_sendSignedTransaction< saveCustomFields(unSerializedTransactionForCall); if (unSerializedTransactionForCall.value == "0x") { unSerializedTransactionForCall.value = "0x0"; + } + if (unSerializedTransactionForCall.nonce == "0x") { + unSerializedTransactionForCall.nonce = "0x0"; } // Because modifying the rpc name to "klay_sendRawTransaction" is not trivial, diff --git a/web3js-ext/src/web3/web3.ts b/web3js-ext/src/web3/web3.ts index 937f319cc..61fb7337d 100644 --- a/web3js-ext/src/web3/web3.ts +++ b/web3js-ext/src/web3/web3.ts @@ -1,5 +1,5 @@ import Web3, {Bytes, Transaction, Web3Context} from "web3"; -import { signTransaction, SignTransactionResult } from "web3-eth-accounts"; +import { signTransaction, SignTransactionResult, privateKeyToAddress } from "web3-eth-accounts"; import { bytesToHex } from "web3-utils"; import { DataFormat, DEFAULT_RETURN_FORMAT } from "web3-types"; import { SendTransactionOptions } from "web3-eth"; @@ -7,6 +7,10 @@ import _ from "lodash"; import { prepareTransaction } from "./klaytn_tx"; import { klay_sendSignedTransaction } from "./send_transaction"; +import { signTransactionAsFeePayer } from "./account"; + +// TODO: Change the path after web3-core deployed +const { objectFromRLP } = require("../../../../ethers-ext/dist/src"); export class KlaytnWeb3 extends Web3 { constructor(provider: any) { @@ -21,13 +25,19 @@ export class KlaytnWeb3 extends Web3 { // TODO: override more web3.eth.accounts methods this.eth.accounts.signTransaction = this.accounts_signTransaction(this); + // New added function for Klaytn + // @ts-ignore + this.eth.accounts.signTransactionAsFeePayer = this.accounts_signTransactionAsFeePayer(this); + // Override web3.eth RPC method wrappers. See web3-eth/src/web3_eth.ts:Web3Eth // Note that web3.eth methods should simply call eth_ RPCs to Klaytn node, // except a few methods below which call klay_ RPCs despite its name 'web3.eth'. this.eth.getProtocolVersion = this.eth_getProtocolVersion(this); this.eth.sendSignedTransaction = this.eth_sendSignedTransaction(this); + // TODO: Connect web3.klay, web3.net, etc from @klaytn/web3rpc + } // Below methods return a function bound to the context 'web3'. @@ -41,6 +51,31 @@ export class KlaytnWeb3 extends Web3 { }; } + accounts_signTransactionAsFeePayer(context: Web3Context): typeof this.eth.accounts.signTransaction { + // signTransactionWithContext. see web3/src/accounts.ts:initAccountsForContext + return async (transaction: any, privateKey: Bytes): Promise => { + let tx; + + if (typeof transaction === "string") { + if (Web3.utils.isHex(transaction)) { + tx = objectFromRLP(transaction); + } else { + throw new Error("String type input has to be RLP encoded Hex string."); + } + } else { + tx = transaction; + } + + if (!tx.feePayer) { + tx.feePayer = privateKeyToAddress(privateKey); + } + + let ftx = await prepareTransaction(tx, context, privateKey); + let priv = bytesToHex(privateKey); + return signTransactionAsFeePayer(ftx, priv); + }; + } + eth_getProtocolVersion(context: Web3Context): typeof this.eth.getProtocolVersion { // See web3-eth/src/web3_eth.ts:Web3Eth // See web3-rpc-methods/src/eth_rpc_methods.ts