diff --git a/web3js-ext/package-lock.json b/web3js-ext/package-lock.json index f8fa88f8c..7bb215aff 100644 --- a/web3js-ext/package-lock.json +++ b/web3js-ext/package-lock.json @@ -1,22 +1,22 @@ { - "name": "@klaytn/web3js-ext", - "version": "1.0.0", + "name": "@kaiachain/web3js-ext", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@klaytn/web3js-ext", - "version": "1.0.0", + "name": "@kaiachain/web3js-ext", + "version": "1.0.1", "license": "MIT", "dependencies": { - "@klaytn/js-ext-core": "^1.0.0", - "@klaytn/web3rpc": "^0.9.9", + "@kaiachain/js-ext-core": "^1.0.0", + "@kaiachain/web3rpc": "^1.0.0", "ethereum-cryptography": "^2.1.2", "lodash": "^4.17.21", "web3": "^4.1.0" }, "devDependencies": { - "@klaytn/web3js-ext": "file:./", + "@kaiachain/web3js-ext": "file:./", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", "@types/lodash": "^4.14.192", @@ -794,10 +794,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@klaytn/js-ext-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@klaytn/js-ext-core/-/js-ext-core-1.0.0.tgz", - "integrity": "sha512-OCFHcVHvWrs53hwCMvgcLVyz39Yry1+ppkgx2e/SkrPyUXC2lXNGzoDVxAa04/kfypxP25+0zZJX75ZwOUkYMQ==", + "node_modules/@kaiachain/js-ext-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@kaiachain/js-ext-core/-/js-ext-core-1.1.0.tgz", + "integrity": "sha512-4/f6NPnlddh8EqBPSVGdbenLmGNpCdGBhIvSUAeww55WX23CJ/dfGdesW8Vn0UH7nvxFKmlr8D9juMyQsDEKZw==", "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -809,14 +809,14 @@ "lodash": "^4.17.21" } }, - "node_modules/@klaytn/web3js-ext": { + "node_modules/@kaiachain/web3js-ext": { "resolved": "", "link": true }, - "node_modules/@klaytn/web3rpc": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/@klaytn/web3rpc/-/web3rpc-0.9.9.tgz", - "integrity": "sha512-kSh2GurDV3SP0i+3fOmZRm8xJHj43ofyKx32jQ8rxlP4OAo87egG7WLe2usAy77Y+kaecwBxxob+JaSuJ+GSiw==", + "node_modules/@kaiachain/web3rpc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@kaiachain/web3rpc/-/web3rpc-1.0.1.tgz", + "integrity": "sha512-S193DG/pxO5YFx3SLtxhNsnDSj4nvNjAFdbzY/D/pY/8sMRKI99GiMh9cInAzVBFkWoGtV9hlmH8COa/SixUuQ==", "dependencies": { "@babel/cli": "^7.0.0", "superagent": "^5.3.0" diff --git a/web3js-ext/test/role_based_key_feepayer.spec.ts b/web3js-ext/test/role_based_key_feepayer.spec.ts new file mode 100644 index 000000000..2c4b21d09 --- /dev/null +++ b/web3js-ext/test/role_based_key_feepayer.spec.ts @@ -0,0 +1,186 @@ +import { Web3, TxType, AccountKeyType, getPublicKeyFromPrivate, toPeb } from "@kaiachain/web3js-ext"; +import { assert } from "chai"; + +const provider = new Web3.providers.HttpProvider("https://public-en-kairos.node.kaia.io"); +const web3 = new Web3(provider); + +type Account = { + address: string; + privateKey: string; + signTransaction: (tx: any) => Promise; +} + +// Feedback1. Generate Temporary Key. +function generateTemporaryAccount(): Account { + return web3.eth.accounts.create(); +} + +describe("Role-based Key Tests", function () { + this.timeout(10000); + + let roleTransactionAccount: Account; + let roleAccountUpdate: Account; + let roleFeePayerAccount: Account; + + // Before all tests, set up Role-based Key + before(async function () { + roleTransactionAccount = generateTemporaryAccount(); + roleAccountUpdate = generateTemporaryAccount(); + roleFeePayerAccount = generateTemporaryAccount(); + + // Feedback 5. PubKey name change + const roleTransactionAccountPubkey = getPublicKeyFromPrivate(roleTransactionAccount.privateKey); + const roleAccountUpdatePubKey = getPublicKeyFromPrivate(roleAccountUpdate.privateKey); + const roleFeePayerPubkey = getPublicKeyFromPrivate(roleFeePayerAccount.privateKey); + + const updateTx = { + type: TxType.AccountUpdate, + from: roleAccountUpdate.address, + gasLimit: 100000, + key: { + type: AccountKeyType.RoleBased, + keys: [ + { type: AccountKeyType.Public, key: roleTransactionAccountPubkey }, + { type: AccountKeyType.Public, key: roleAccountUpdatePubKey }, + { type: AccountKeyType.Public, key: roleFeePayerPubkey } + ] + } + }; + + const signedUpdateTx = await roleAccountUpdate.signTransaction(updateTx); + assert.isNotNull(signedUpdateTx.transactionHash, "Account update transaction should succeed"); + }); + + // Test Case 1: Sending a normal transaction with RoleTransaction key + it("1. Sending a normal transaction with RoleTransaction key", async function () { + const valueTx = { + type: TxType.ValueTransfer, + from: generateTemporaryAccount().address, + to: generateTemporaryAccount().address, + value: toPeb("0.01"), + gasLimit: 100000 + }; + + const signedTx = await roleTransactionAccount.signTransaction(valueTx); + assert.isNotNull(signedTx.transactionHash, "RoleTransaction transaction should succeed"); + }); + + // Test Case 2: Attempting to sign a regular transaction with RoleFeePayer key (should fail) + it("2. Attempting to sign a regular transaction with RoleFeePayer key (failure test)", async function () { + const valueTx = { + type: TxType.ValueTransfer, + from: generateTemporaryAccount().address, + to: generateTemporaryAccount().address, + value: toPeb("0.01"), + gasLimit: 100000 + }; + + try { + const signedTx = await roleFeePayerAccount.signTransaction(valueTx); + assert.fail("RoleFeePayer key should not sign a ValueTransfer transaction."); + } catch (error: any) { + assert.isTrue(true, "Error occurred as expected due to role mismatch"); + } + }); + + it("3. Fee Delegated transaction signed by RoleTransaction and RoleFeePayer keys", async function () { + const userAccount = generateTemporaryAccount(); + + const feeDelegatedTx = { + type: TxType.FeeDelegatedValueTransfer, + from: userAccount.address, + to: generateTemporaryAccount().address, + value: toPeb("0.01"), + gasLimit: "100000", + gasPrice: "25000000000", + nonce: "0x0", + chainId: "0x1001" + }; + + // 1) A User signs a transaction + const signedTxByUser = await userAccount.signTransaction(feeDelegatedTx); + assert.isNotNull(signedTxByUser.rawTransaction, "Transaction should be signed by User"); + + console.log("Signed Transaction by User (rawTransaction):", signedTxByUser.rawTransaction); + + try { + // 2) FeePayer signs a transaction + const feePayerSignInput = { + type: feeDelegatedTx.type, + from: feeDelegatedTx.from, + to: feeDelegatedTx.to, + value: feeDelegatedTx.value, + gasPrice: feeDelegatedTx.gasPrice, + gasLimit: feeDelegatedTx.gasLimit, + nonce: feeDelegatedTx.nonce, + chainId: feeDelegatedTx.chainId, + feePayer: roleFeePayerAccount.address, + senderRawTransaction: signedTxByUser.rawTransaction, + }; + console.log("FeePayer SignTransaction Input:", feePayerSignInput); + + const signedTxByFeePayer = await roleFeePayerAccount.signTransaction(feePayerSignInput); + assert.isNotNull(signedTxByFeePayer.rawTransaction, "FeePayer should sign the transaction"); + + console.log("Signed Transaction by FeePayer (rawTransaction):", signedTxByFeePayer.rawTransaction); + + // 3) RLP format check + if (!signedTxByFeePayer.rawTransaction.startsWith("0x")) { + console.error("Invalid rawTransaction format"); + } else { + // 4) RLP Decoding + const { KlaytnTxFactory } = require("@kaiachain/web3js-ext"); + const decoded = KlaytnTxFactory.fromRLP(signedTxByFeePayer.rawTransaction); + console.log("Decoded Transaction:", decoded); + + // feepayer field check + if (decoded.fields.feePayer) { + console.log("Decoded feePayer Field:", decoded.fields.feePayer); + // Check if feepayer and roleFeePayerAccount.address are the same + assert.equal( + decoded.fields.feePayer.toLowerCase(), + roleFeePayerAccount.address.toLowerCase(), + "FeePayer address should match roleFeePayerAccount" + ); + } else { + // Unsigned if the feePayer field itself is not present + console.error("No feePayer field found in decoded transaction."); + assert.fail("FeePayer address not found in the final transaction."); + } + } + } catch (error: any) { + console.log("Error during FeePayer signing or decoding:", error.message); + assert.fail("FeePayer failed to sign the already signed transaction."); + } + }); + + // Test Case 4: Attempting to sign Fee Delegated transaction with RoleTransaction key (should fail) + it("4. Attempting to sign Fee Delegated transaction with RoleTransaction key (should fail)", async function () { + const userAccount = generateTemporaryAccount(); + + const feeDelegatedTx = { + type: TxType.FeeDelegatedValueTransfer, + from: userAccount.address, + to: generateTemporaryAccount().address, + value: toPeb("0.01"), + gasLimit: 100000, + gasPrice: "25000000000", + nonce: "0x0", + chainId: "0x1001" + }; + + const signedTxByUser = await userAccount.signTransaction(feeDelegatedTx); + assert.isNotNull(signedTxByUser.rawTransaction, "Transaction should be signed by User"); + + try { + const signedTxByRoleTransaction = await roleTransactionAccount.signTransaction({ + senderRawTransaction: signedTxByUser.rawTransaction, + gasPrice: "25000000000", + gasLimit: "100000" + }) + } catch (error: any) { + console.log("error is : ", error.message); + assert.isTrue(true, "Error occurred as expected"); + } + }); +});