diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 01b4eb9..b901dbd 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [20.x] steps: - uses: actions/checkout@v2 diff --git a/.tool-versions b/.tool-versions index 5686ee0..ffb152e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 18.12.1 +nodejs 20.17.0 diff --git a/README.md b/README.md index 010a8f7..d071aa1 100644 --- a/README.md +++ b/README.md @@ -375,11 +375,27 @@ Define the transaction type - `type` is the string defining the type of transaction to generate ("keychain", "keychain_access", "transfer", "hosting", "code_proposal", "code_approval", "token") -#### setCode(code) +#### setContract(contract) -Add the code in the `data.code` section of the transaction +Add the contract in the `data.contract` section of the transaction -- `code` is a string defining the smart contract +- `contract` is an object with following keys: + - `bytecode` Uint8Array of the compiled wasm code compressed using zip + - `manifest` the manifest of the contrat containing actions and functions spec + +```js +const bytecode = fs.readFileSync("./dist/contract.wasm") +const manifestFile = fs.readFileSync('./dist/manifest.json', 'utf-8') +const manifest = JSON.parse(manifestFile) + +const contract = new Contract(bytecode, manifest) + +const txBuilder = archethic.transaction.new().setType("contract").setContract(contract) +// or use Contract function +import { Contract } from "@archethicjs/sdk" +const archethic = new Archethic("https://testnet.archethic.net") +const tx = Contract.newContractTransaction(archethic, contract, seed) +``` #### setGenerateEncryptedSeedSC(flag) @@ -423,8 +439,8 @@ Add a token transfer to the `data.ledger.token.transfers` section of the transac Adds a recipient to call the smart contract's "transaction" action. - `to` is the contract's address in hexadecimal or Uint8Array -- `action` is the name of the action. This parameter is not mandatory -- `args` is the list of arguments for the action (must contain only JSON valid data). This parameter is not mandatory +- `action` is the name of the action +- `args` is an object containing the parameter for the contract action. This parameter is not mandatory ```js import Archethic from "@archethicjs/sdk"; @@ -433,8 +449,8 @@ const archethic = new Archethic("https://testnet.archethic.net"); const tx = archethic.transaction .new() .setType("transfer") - .addRecipient("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646") - .addRecipient("0000bc96b1a9751d3750edb9381a55b5b4e4fb104c10b0b6c9a00433ec464637bfab", "vote", ["Dr. Who"]); + .addRecipient("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", "increment") + .addRecipient("0000bc96b1a9751d3750edb9381a55b5b4e4fb104c10b0b6c9a00433ec464637bfab", "vote", {name: "Dr. Who"}); ``` #### build(seed, index, curve, hashAlgo) @@ -829,6 +845,34 @@ const storageNoncePublicKey = await archethic.network.getStorageNoncePublicKey() // 00b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646 ``` +### getContractCode(address) + +Return the contract code + +- `address`: string or Uint8Array of the contract address + +```js +import Archethic from "@archethicjs/sdk" + +const archethic = new Archethic("https://testnet.archethic.net") +await archethic.connect() + +const res = archethic.network.getContractCode("0001234...") +console.log(res) +{ + code: "...", + contract: { + bytecode: "00231654", + manifest: { + abi: { + state: ..., + functions: [] + } + } + } +} +``` + ### getOracleData(timestamp) Fetch the OracleChain data @@ -1375,15 +1419,31 @@ tx.originSign(originPrivateKey)
- ### newContractTransaction + ### newContractTransaction(archethic, contract, contractSeed, txData?) - Create a new contract transaction injecting the code and the authorized public key encryption to allow node to emit transaction on your behalf + Create a new contract transaction injecting the bytecode, manifest and the authorized public key encryption to allow node to emit transaction on your behalf. Returned transaction is already signed. ```js import Archethic, { Utils, Contract } from "@archethicjs/sdk" const archethic = new Archethic("https://testnet.archethic.net") - const tx = await Contract.newContractTransaction(archethic, contractCode, contractSeed) + const bytecode = fs.readFileSync("./dist/contract.wasm") + const manifestFile = fs.readFileSync('./dist/manifest.json', 'utf-8') + const manifest = JSON.parse(manifestFile) + + const contract = new Contract(bytecode, manifest) + + // txData is optional + const txData = { + content: "content", + ledger: { + uco: { + transfers: [{to: "1234", amount: Utils.parseBigInt("10")}] + } + } + } + + const tx = await Contract.newContractTransaction(archethic, contract, contractSeed, txData) tx .originSign(Utils.originPrivateKey) @@ -1392,7 +1452,29 @@ tx.originSign(originPrivateKey) .send(); ``` + ### updateContractTransaction(archethic, contractAddress, contract) + + Create a new transaction containing the recipient filled with appropriated function to update a contract code + + ```js + import Archethic, { Utils, Contract } from "@archethicjs/sdk" + const archethic = new Archethic("https://testnet.archethic.net") + + const bytecode = fs.readFileSync("./dist/contract.wasm") + const manifestFile = fs.readFileSync('./dist/manifest.json', 'utf-8') + const manifest = JSON.parse(manifestFile) + + const contract = new Contract(bytecode, manifest) + + const tx = await Contract.updateContractTransaction(archethic, contractAddress, contract) + tx + .build(seed, index) + .originSign(Utils.originPrivateKey) + .on("requiredConfirmation", () => console.log("ok updated")) + .on("error", (context, reason) => console.error(reason)) + .send(); + ``` diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index 154f608..96b1196 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -1,9 +1,12 @@ import Archethic, { Utils, Crypto, Contract } from "@archethicjs/sdk"; import { ExtendedTransactionBuilder } from "../../dist/transaction"; +import { Contract as ContractCode } from "../../dist/contract"; const { parseBigInt, formatBigInt } = Utils; -let file_content = ""; +let file_content = "" +let bytecode = new Uint8Array(); +let manifest = {}; let ucoTransfers = []; let tokenTransfers = []; @@ -80,8 +83,7 @@ window.generate_transaction = async () => { const seed = document.querySelector("#seed").value; - const code = document.querySelector("#code").value; - if (code != "") { + if (bytecode.byteLength > 0) { const ownershipIndex = ownerships.findIndex(function (ownership) { return ownership.secret == seed; }); @@ -113,8 +115,11 @@ window.generate_transaction = async () => { txBuilder = archethic.transaction .new() .setType(document.querySelector("#type").value) - .setCode(document.querySelector("#code").value) - .setContent(content); + .setContent(content) + + if (bytecode.byteLength > 0) { + txBuilder.setContract(new ContractCode(bytecode, manifest)) + } ownerships.forEach(function (ownership) { const secretKey = Crypto.randomSecretKey(); @@ -245,18 +250,17 @@ window.onClickAddTokenTransfer = async () => { document.querySelector("#token_id").value = "0"; }; -let namedParams = []; +let namedParams = [] +let objectParams = {}; window.onChangeRecipient = async () => { const address = document.querySelector("#recipient").value; - const contractCode = await archethic.network.getContractCode(address); - if (contractCode == "") { - return; - } + const contractContext = await archethic.network.getContractCode(address); document.querySelector("#namedActionsContainer").style.display = "block"; - Contract.extractActionsFromContract(contractCode).forEach((action) => { + const actions = contractContext.contract ? contractContext.contract.getActions() : Contract.extractActionsFromContract(contractContext.code); + actions.forEach((action) => { const option = document.createElement("option"); option.text = action.name; option.value = action.name; @@ -269,6 +273,8 @@ window.onChangeRecipient = async () => { paramsContainer.setAttribute("style", "display: none"); paramsContainer.setAttribute("class", "namedActionParams"); + namedParams = new Array(action.parameters.length).fill(null) + action.parameters.forEach((parameter, index) => { const inputId = paramsContainerId + "_param_" + parameter; const paramLabel = document.createElement("label"); @@ -280,7 +286,30 @@ window.onChangeRecipient = async () => { paramInput.setAttribute("class", "input"); paramInput.addEventListener("change", function (e) { const value = e.target.value; - namedParams[index] = Contract.parseTypedArgument(value); + try { + const json = JSON.parse(value); + if (typeof json === "object") { + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(json); + } else { + namedParams[index] = Contract.parseTypedArgument(json); + } + } else { + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(value); + } + else { + namedParams[index] = Contract.parseTypedArgument(value); + } + } + } catch (e) { + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(value); + } + else { + namedParams[index] = Contract.parseTypedArgument(value); + } + } }); paramsContainer.appendChild(paramLabel); @@ -305,11 +334,11 @@ window.onClickAddRecipient = () => { const recipientList = document.querySelector("#recipients"); if (namedAction != "") { - recipients.push({ address: recipientAddress, action: namedAction, args: namedParams }); + recipients.push({ address: recipientAddress, action: namedAction, args: Object.keys(objectParams).length > 0 ? objectParams : namedParams }); if (recipientList.textContent != "") { recipientList.textContent = recipientList.textContent + "\n"; } - recipientList.textContent += `${recipientAddress} - ${namedAction} - ${namedParams}`; + recipientList.textContent += `${recipientAddress} - ${namedAction} - ${JSON.stringify(Object.keys(objectParams).length > 0 ? objectParams : namedParams)}`; document.querySelector("#namedActionsContainer").style.display = "none"; document.querySelector("#namedActions").innerHTML = ""; @@ -355,7 +384,7 @@ window.sendTransaction = async () => { if (error.data.data) { errMsg += `

(${error.data.data.code}) ${error.data.data.message}

`; } else { - errMsg += `

${error.data.message}

`; + errMsg += `

${JSON.stringify(error.data.message)}

`; } } @@ -390,6 +419,26 @@ document.querySelector("#content_upload").addEventListener("change", (event) => fr.readAsArrayBuffer(fileList[0]); }); +document.querySelector("#bytecode_upload").addEventListener("change", (event) => { + const fileList = event.target.files; + + const fr = new FileReader(); + fr.onload = function (e) { + bytecode = new Uint8Array(e.target.result); + }; + fr.readAsArrayBuffer(fileList[0]); +}); + +document.querySelector("#manifest_upload").addEventListener("change", (event) => { + const fileList = event.target.files; + + const fr = new FileReader(); + fr.onload = function (e) { + manifest = JSON.parse(new TextDecoder().decode(e.target.result)) + }; + fr.readAsArrayBuffer(fileList[0]); +}); + window.addOwnership = () => { const ownershipIndex = ownerships.length; diff --git a/example/transactionBuilder/index.html b/example/transactionBuilder/index.html index a685522..0dafcd1 100644 --- a/example/transactionBuilder/index.html +++ b/example/transactionBuilder/index.html @@ -60,14 +60,21 @@

Transaction Builder

- - +
- -
- +
+
+ + +
+
+ + +
+
diff --git a/package-lock.json b/package-lock.json index 8c1da38..3f32e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "isomorphic-ws": "^5.0.0", "js-sha3": "^0.9.0", "json-rpc-2.0": "^1.6.0", + "pako": "^2.1.0", "phoenix": "^1.7.2", "sjcl": "^1.0.8", "tweetnacl": "^1.0.3", @@ -29,9 +30,11 @@ "devDependencies": { "@types/absinthe__socket": "^0.2.3", "@types/chrome": "0.0.266", + "@types/crypto-js": "^4.2.2", "@types/ed2curve": "^0.2.4", "@types/elliptic": "^6.4.14", "@types/jest": "^29.5.0", + "@types/pako": "^2.0.3", "@types/sjcl": "^1.0.34", "esbuild": "^0.19.0", "jest": "^29.5.0", @@ -1379,6 +1382,16 @@ "@types/har-format": "*" } }, +<<<<<<< HEAD +======= + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, +>>>>>>> fbd6a7b (improve types with CryptoJS) "node_modules/@types/ed2curve": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.4.tgz", @@ -1470,6 +1483,13 @@ "undici-types": "~6.19.8" } }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/phoenix": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", @@ -3738,6 +3758,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -5451,6 +5477,15 @@ "@types/har-format": "*" } }, +<<<<<<< HEAD +======= + "@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, +>>>>>>> fbd6a7b (improve types with CryptoJS) "@types/ed2curve": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.4.tgz", @@ -5542,6 +5577,12 @@ "undici-types": "~6.19.8" } }, + "@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true + }, "@types/phoenix": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", @@ -7234,6 +7275,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", diff --git a/package.json b/package.json index 2a59d24..cc2c699 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@archethicjs/sdk", - "version": "1.21.3", + "version": "2.0.0-rc.0", "description": "Archethic Javascript SDK", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -32,9 +32,11 @@ "devDependencies": { "@types/absinthe__socket": "^0.2.3", "@types/chrome": "0.0.266", + "@types/crypto-js": "^4.2.2", "@types/ed2curve": "^0.2.4", "@types/elliptic": "^6.4.14", "@types/jest": "^29.5.0", + "@types/pako": "^2.0.3", "@types/sjcl": "^1.0.34", "esbuild": "^0.19.0", "jest": "^29.5.0", @@ -56,6 +58,7 @@ "isomorphic-ws": "^5.0.0", "js-sha3": "^0.9.0", "json-rpc-2.0": "^1.6.0", + "pako": "^2.1.0", "phoenix": "^1.7.2", "sjcl": "^1.0.8", "tweetnacl": "^1.0.3", diff --git a/src/api.ts b/src/api.ts index 6fa1648..0153fcd 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,8 +1,9 @@ import fetch from "cross-fetch"; import absinthe from "./api/absinthe.js"; -import { maybeUint8ArrayToHex } from "./utils.js"; +import { hexToUint8Array, maybeUint8ArrayToHex } from "./utils.js"; import { Balance, NearestEndpoint, OracleData, Ownership, Token } from "./types.js"; import Transaction from "./transaction.js"; +import { Contract } from "./contract.js"; /** * Send a custom query to the Archethic API @@ -332,7 +333,12 @@ export async function getBalance(address: string | Uint8Array, endpoint: string }); } -export async function getContractCode(address: string | Uint8Array, endpoint: string | URL): Promise { +export type ContractCode = { + code: string; + contract?: Contract; +}; + +export async function getContractCode(address: string | Uint8Array, endpoint: string | URL): Promise { address = maybeUint8ArrayToHex(address); const url = new URL("/api", endpoint); @@ -346,18 +352,36 @@ export async function getContractCode(address: string | Uint8Array, endpoint: st query: `query { lastTransaction(address: "${address}") { data { - code + code, + contract { + bytecode, + manifest { + abi { + functions, + state + } + } + } } } }` }) }) .then(handleResponse) - .then((res): string => { + .then((res): ContractCode => { if (res.errors || res.data == null) { - return ""; + throw new Error("No contract at this address"); } else { - return res.data.lastTransaction.data.code; + return { + code: res.data.lastTransaction.data.code, + contract: res.data.lastTransaction.data.contract + ? new Contract( + hexToUint8Array(res.data.lastTransaction.data.contract.bytecode), + res.data.lastTransaction.data.contract.manifest, + false + ) + : undefined + }; } }); } diff --git a/src/api/node_rpc.ts b/src/api/node_rpc.ts index 450d83f..c7db9ca 100644 --- a/src/api/node_rpc.ts +++ b/src/api/node_rpc.ts @@ -39,7 +39,7 @@ export class NodeRPCClient { * @returns {Promise} The transaction fee */ async getTransactionFee(tx: TransactionBuilder): Promise { - return this.client.request("estimate_transaction_fee", { transaction: tx.toNodeRPC() }); + return this.client.request("estimate_transaction_fee", { transaction: await tx.toNodeRPC() }); } /** @@ -48,7 +48,7 @@ export class NodeRPCClient { * @returns {Promise} The transaction response */ async sendTransaction(tx: TransactionBuilder): Promise { - return this.client.request("send_transaction", { transaction: tx.toNodeRPC() }); + return this.client.request("send_transaction", { transaction: await tx.toNodeRPC() }); } /** @@ -66,7 +66,7 @@ export class NodeRPCClient { * @returns {Promise} The simulation response per recipient */ async simulateContractExecution(tx: TransactionBuilder): Promise { - return this.client.request("simulate_contract_execution", { transaction: tx.toNodeRPC() }); + return this.client.request("simulate_contract_execution", { transaction: await tx.toNodeRPC() }); } async handleRequest(jsonRPCRequest: any): Promise { diff --git a/src/contract.ts b/src/contract.ts index 984663d..ec84e2c 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -1,12 +1,30 @@ -import { ContractAction } from "./types.js"; +import { ContractAction, TransactionData } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; +import { deflateRaw } from "pako"; + +export type ContractManifest = { + abi: WasmABI; +}; + +export type WasmABI = { + state: Record; + functions: Record; +}; + +export type WASMFunctionABI = { + type: string; + triggerType?: string; + name: string; + input: Record; +}; export function extractActionsFromContract(code: string): ContractAction[] { + let actions = []; + const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; - let actions = []; for (const match of code.matchAll(regex)) { const fullAction = match[1]; @@ -43,17 +61,63 @@ This function abstract the wrapping of encrypted keys towards the node's shared */ export async function newContractTransaction( archethic: Archethic, - code: string, - seed: string | Uint8Array + contract: Contract, + seed: string | Uint8Array, + txData?: TransactionData ): Promise { const storageNoncePublicKey = await archethic.network.getStorageNoncePublicKey(); const index = await archethic.transaction.getTransactionIndex(deriveAddress(seed, 0)); const { encryptedSecret, authorizedKeys } = encryptSecret(seed, storageNoncePublicKey); - return archethic.transaction + + const tx = archethic.transaction .new() .setType("contract") - .setCode(code) - .addOwnership(encryptedSecret, authorizedKeys) - .build(seed, index); + .setContract(contract) + .addOwnership(encryptedSecret, authorizedKeys); + + if (txData) { + txData.ledger.uco.transfers.forEach((t) => tx.addUCOTransfer(t.to, t.amount)); + txData.ledger.token.transfers.forEach((t) => tx.addTokenTransfer(t.to, t.amount, t.tokenAddress, t.tokenId)); + txData.recipients.forEach((r) => tx.addRecipient(r.address, r.action, r.args)); + tx.setContent(txData.content); + } + + return tx.build(seed, index); +} + +export function updateContractTransaction( + archethic: Archethic, + contractAddress: string, + contract: Contract +): ExtendedTransactionBuilder { + return archethic.transaction + .new() + .setType("transfer") + .addRecipient(contractAddress, "upgrade", contract); +} + +export class Contract { + bytecode: Uint8Array; + manifest: ContractManifest; + + constructor(bytecode: Uint8Array, manifest: ContractManifest, compress: boolean = true) { + this.bytecode = compress ? deflateRaw(bytecode) : bytecode; + this.manifest = manifest; + } + + getActions(): ContractAction[] { + let actions: ContractAction[] = []; + for (let name of Object.keys(this.manifest.abi.functions)) { + const functionABI = this.manifest.abi.functions[name]; + console.log(functionABI) + if (functionABI.type == "action" && functionABI.triggerType == "transaction") { + actions.push({ + name: name, + parameters: functionABI.input ? Object.keys(functionABI.input) : [] + }); + } + } + return actions; + } } diff --git a/src/network.ts b/src/network.ts index fb0878d..8adb6de 100644 --- a/src/network.ts +++ b/src/network.ts @@ -44,7 +44,7 @@ export default class Network { return this.core.requestNode((endpoint) => API.rawGraphQLQuery(query, endpoint)); } - async getContractCode(address: string): Promise { + async getContractCode(address: string): Promise { return this.core.requestNode((endpoint) => API.getContractCode(address, endpoint)); } } diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 846f9f3..636a2a9 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -5,7 +5,7 @@ import { HashAlgorithm, TransactionData, UserTypeTransaction, - TransactionRPC + TransactionRPC, } from "./types.js"; import { concatUint8Arrays, @@ -18,8 +18,9 @@ import { } from "./utils.js"; import TE from "./typed_encoding.js"; import { deriveAddress, deriveKeyPair, sign } from "./crypto.js"; +import { Contract } from "./contract.js"; -const VERSION = 3; +export const VERSION = 4; function getTransactionTypeId(type: UserTypeTransaction): number { switch (type) { @@ -62,9 +63,8 @@ export default class TransactionBuilder { this.type = type as UserTypeTransaction; this.address = new Uint8Array(); this.data = { - content: new Uint8Array(), - code: new Uint8Array(), ownerships: [], + content: "", ledger: { uco: { transfers: [] @@ -89,9 +89,9 @@ export default class TransactionBuilder { if (!Object.keys(UserTypeTransaction).includes(type)) { throw new Error( "Transaction type must be one of " + - Object.keys(UserTypeTransaction) - .map((t) => `'${t}'`) - .join(", ") + Object.keys(UserTypeTransaction) + .map((t) => `'${t}'`) + .join(", ") ); } this.type = type as UserTypeTransaction; @@ -99,22 +99,19 @@ export default class TransactionBuilder { } /** - * Add smart contract code to the transcation - * @param {string} code Smart contract code + * Add smart contract's definition to the transcation + * @param {Contract} code Smart contract code */ - setCode(code: string) { - this.data.code = new TextEncoder().encode(code); + setContract(contract: Contract) { + this.data.contract = contract; return this; } /** * Add a content to the transaction - * @param {String | Uint8Array} content Hosted content + * @param {String} content Hosted content */ - setContent(content: string | Uint8Array) { - if (typeof content == "string") { - content = new TextEncoder().encode(content); - } + setContent(content: string) { this.data.content = content; return this; } @@ -202,21 +199,21 @@ export default class TransactionBuilder { * Add recipient to the transaction * @param {string | Uint8Array} to Recipient address (hexadecimal or binary buffer) * @param {string} action The named action - * @param {any[]} args The arguments list for the named action (can only contain JSON valid data) + * @param {object} args The arguments for the named action */ - addRecipient(to: string | Uint8Array, action?: string, args?: any[]) { + addRecipient(to: string | Uint8Array, action: string, args?: object) { const address = maybeHexToUint8Array(to); - if (action && typeof action != "string") { + if (typeof action != "string") { throw new Error("`action` must be a string"); } - if (args && !Array.isArray(args)) { - throw new Error("`args` must be an array"); + if (args && typeof args !== "object") { + throw new Error("`args` must be an object"); } - if (action && !args) { - args = []; + if (!args) { + args = {}; } this.data.recipients.push({ address, action, args }); @@ -277,17 +274,17 @@ export default class TransactionBuilder { if (!Object.keys(Curve).includes(curve)) { throw new Error( "Curve must be one of " + - Object.keys(Curve) - .map((t) => `'${t}'`) - .join(", ") + Object.keys(Curve) + .map((t) => `'${t}'`) + .join(", ") ); } if (!Object.keys(HashAlgorithm).includes(hashAlgo)) { throw new Error( "Hash algorithm must be one of " + - Object.keys(HashAlgorithm) - .map((t) => `'${t}'`) - .join(", ") + Object.keys(HashAlgorithm) + .map((t) => `'${t}'`) + .join(", ") ); } @@ -338,7 +335,17 @@ export default class TransactionBuilder { * Generate the payload for the previous signature by encoding address, type and data */ previousSignaturePayload() { - const bufCodeSize = intToUint32Array(this.data.code.length); + let bufContract: Uint8Array; + if (this.data.contract != undefined) { + bufContract = concatUint8Arrays( + intToUint8Array(1), + intToUint32Array(this.data.contract.bytecode.byteLength), + this.data.contract.bytecode, + TE.serialize(this.data.contract.manifest) + ) + } else { + bufContract = intToUint8Array(0) + } let contentSize = this.data.content.length; @@ -359,11 +366,11 @@ export default class TransactionBuilder { return concatUint8Arrays(intToUint32Array(secret.byteLength), secret, concatUint8Arrays(...authorizedKeysBuffer)); }); - const ucoTransfersBuffers = this.data.ledger.uco.transfers.map(function (transfer) { + const ucoTransfersBuffers = this.data.ledger.uco.transfers.map(function(transfer) { return concatUint8Arrays(transfer.to, intToUint64Array(transfer.amount)); }); - const tokenTransfersBuffers = this.data.ledger.token.transfers.map(function (transfer) { + const tokenTransfersBuffers = this.data.ledger.token.transfers.map(function(transfer) { const bufTokenId = intToUint8Array(transfer.tokenId); return concatUint8Arrays( transfer.tokenAddress, @@ -375,30 +382,19 @@ export default class TransactionBuilder { }); const recipientsBuffer = this.data.recipients.map(({ address, action, args }) => { - if (action == undefined || args == undefined) { - return concatUint8Arrays( - // 0 = unnamed action - Uint8Array.from([0]), - // address - address - ); - } else { - const serializedArgs = args.map((arg) => TE.serialize(arg)); - - return concatUint8Arrays( - // 1 = named action - Uint8Array.from([1]), - // address - address, - // action - Uint8Array.from([action.length]), - new TextEncoder().encode(action), - // args count - Uint8Array.from([serializedArgs.length]), - // args - ...serializedArgs - ); - } + const serializedArgs = TE.serialize(args); + + return concatUint8Arrays( + // 1 = named action + intToUint8Array(1), + // address + address, + // action + Uint8Array.from([action.length]), + new TextEncoder().encode(action), + // args + serializedArgs + ); }); const bufOwnershipLength = intToUint8Array(this.data.ownerships.length); @@ -410,10 +406,9 @@ export default class TransactionBuilder { intToUint32Array(VERSION), this.address, Uint8Array.from([getTransactionTypeId(this.type)]), - bufCodeSize, - this.data.code, + bufContract, bufContentSize, - this.data.content, + new TextEncoder().encode(this.data.content), Uint8Array.from([bufOwnershipLength.length]), bufOwnershipLength, ...ownershipsBuffer, @@ -432,14 +427,17 @@ export default class TransactionBuilder { /** * JSON RPC API SEND_TRANSACTION */ - toNodeRPC(): TransactionRPC { + async toNodeRPC(): Promise { return { version: this.version, address: uint8ArrayToHex(this.address), type: this.type, data: { - content: new TextDecoder().decode(this.data.content), - code: new TextDecoder().decode(this.data.code), + content: this.data.content, + contract: this.data.contract ? { + bytecode: uint8ArrayToHex(this.data.contract?.bytecode), + manifest: this.data.contract?.manifest + } : undefined, ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), @@ -496,8 +494,8 @@ export default class TransactionBuilder { version: this.version, type: this.type, data: { - content: new TextDecoder().decode(this.data.content), - code: new TextDecoder().decode(this.data.code), + content: this.data.content, + contract: this.data.contract, ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), diff --git a/src/types.ts b/src/types.ts index 525aa35..dc423f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { Contract, ContractManifest } from "./contract"; + export enum Curve { ed25519 = "ed25519", P256 = "P256", @@ -76,42 +78,40 @@ type CrossValidationStamp = { }; export type TransactionData = { - code: Uint8Array; - content: Uint8Array; + contract?: Contract; + content: string; ledger: Ledger; ownerships: Ownership[]; recipients: Recipient[]; }; type Ledger = { - token: TokenLedger; uco: UcoLedger; + token: TokenLedger; }; -type TokenLedger = { - transfers: TokenTransfer[]; +type UcoLedger = { + transfers: Transfer[]; }; -type TokenTransfer = { +type Transfer = { amount: bigint; to: Uint8Array; - tokenAddress: Uint8Array; - tokenId: number; }; -type UcoLedger = { - transfers: UcoTransfer[]; +type TokenLedger = { + transfers: TokenTransfer[]; }; -type UcoTransfer = { - amount: bigint; - to: Uint8Array; -}; +type TokenTransfer = { + tokenAddress: Uint8Array; + tokenId: number; +} & Transfer; export type Recipient = { address: Uint8Array; - action?: string; - args?: any[]; + action: string; + args: object; }; export type Ownership = { @@ -190,19 +190,17 @@ export type Keypair = { privateKey: Uint8Array; }; -type Transfer = { +export type TransferRPC = { to: string; amount: bigint; }; -type TokenTransferRPC = { - to: string; - amount: bigint; +export type TokenTransferRPC = { tokenAddress: string; tokenId: number; -}; +} & TransferRPC; -type OwnershipRPC = { +export type OwnershipRPC = { secret: string; authorizedKeys: { publicKey: string; @@ -210,23 +208,28 @@ type OwnershipRPC = { }[]; }; -type RecipientRPC = { +export type RecipientRPC = { address: string; action?: string; - args?: any[]; + args?: any[] | object; }; +export type ContractRPC = { + bytecode: string; + manifest: ContractManifest; +} + export type TransactionRPC = { version: number; address: string; type: UserTypeTransaction; data: { content: string; - code: string; + contract?: ContractRPC; ownerships: OwnershipRPC[]; ledger: { uco: { - transfers: Transfer[]; + transfers: TransferRPC[]; }; token: { transfers: TokenTransferRPC[]; diff --git a/tests/account.test.ts b/tests/account.test.ts index 87f4073..0a8b597 100644 --- a/tests/account.test.ts +++ b/tests/account.test.ts @@ -20,7 +20,7 @@ describe("Account", () => { const tx = account.newKeychainTransaction(expectedKeychain, 0); expect(tx.type).toBe("keychain"); - expect(new TextDecoder().decode(tx.data.content)).toBe(JSON.stringify(expectedKeychain.toDID())); + expect(tx.data.content).toBe(JSON.stringify(expectedKeychain.toDID())); expect(tx.data.ownerships.length).toBe(1); diff --git a/tests/network.test.ts b/tests/network.test.ts index d7b16e6..686e372 100644 --- a/tests/network.test.ts +++ b/tests/network.test.ts @@ -103,7 +103,7 @@ describe("Network", () => { const tx = archethic.transaction.new(); tx.setType("data"); tx.setContent("content"); - tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065"); + tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065", "action"); nock("http://127.0.0.1:4000", { reqheaders: { @@ -116,7 +116,7 @@ describe("Network", () => { id: 1, method: "estimate_transaction_fee", params: { - transaction: tx.toNodeRPC() + transaction: await tx.toNodeRPC() } }) // @ts-ignore @@ -142,7 +142,7 @@ describe("Network", () => { const tx = archethic.transaction.new(); tx.setType("data"); tx.setContent("content"); - tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065"); + tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065", "action"); nock("http://127.0.0.1:4000", { reqheaders: { @@ -155,7 +155,7 @@ describe("Network", () => { id: 1, method: "send_transaction", params: { - transaction: tx.toNodeRPC() + transaction: await tx.toNodeRPC() } }) // @ts-ignore @@ -177,8 +177,8 @@ describe("Network", () => { const tx = archethic.transaction.new(); tx.setType("data"); tx.setContent("content"); - tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065"); - tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5064"); + tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5065", "action"); + tx.addRecipient("0000EE9DDC5229EBFFE197277058F11A41E22252D86A904C8CBCF38C1EFC42AB5064", "action"); nock("http://127.0.0.1:4000", { reqheaders: { @@ -191,7 +191,7 @@ describe("Network", () => { id: 1, method: "simulate_contract_execution", params: { - transaction: tx.toNodeRPC() + transaction: await tx.toNodeRPC() } }) // @ts-ignore diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index 68d7484..bddbcf8 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -1,9 +1,9 @@ -import TransactionBuilder from "../src/transaction_builder"; -import { deriveAddress, deriveKeyPair, sign, verify } from "../src/crypto"; -import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, parseBigInt } from "../src/utils"; +import TransactionBuilder, { VERSION } from "../src/transaction_builder"; +import { deriveAddress, deriveKeyPair, verify } from "../src/crypto"; +import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, intToUint8Array, parseBigInt } from "../src/utils"; import TE from "../src/typed_encoding"; - -const VERSION = 3; +import { Contract } from "../src/contract"; +import typed_encoding from "../src/typed_encoding"; // all assert should be transformed to jest expect describe("Transaction builder", () => { @@ -40,17 +40,18 @@ describe("Transaction builder", () => { }); }); - describe("setCode", () => { + describe("setContract", () => { it("should insert the code into the transaction data", () => { - const tx = new TransactionBuilder("transfer").setCode("my smart contract code"); - expect(new TextDecoder().decode(tx.data.code)).toBe("my smart contract code"); + const contract = new Contract(new Uint8Array(), { abi: { state: {}, functions: {} } }) + const tx = new TransactionBuilder("transfer").setContract(contract) + expect(tx.data.contract?.bytecode).toStrictEqual(contract.bytecode); }); }); describe("setContent", () => { it("should insert the content into the transaction data", () => { const tx = new TransactionBuilder("transfer").setContent("my super content"); - expect(tx.data.content).toStrictEqual(new TextEncoder().encode("my super content")); + expect(tx.data.content).toBe("my super content"); }); }); @@ -118,16 +119,22 @@ describe("Transaction builder", () => { describe("addRecipient", () => { it("should add a recipient for named action", () => { - const tx = new TransactionBuilder("transfer").addRecipient( - "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - "vote", - ["Miles"] - ); + const tx = new TransactionBuilder("transfer") + .addRecipient( + "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", + "vote", + { name: "Miles" } + ) + .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe" }) - expect(tx.data.recipients.length).toBe(1); + expect(tx.data.recipients.length).toBe(2); expect(tx.data.recipients[0].action).toBe("vote"); - expect(tx.data.recipients[0].args!.length).toBe(1); - expect(tx.data.recipients[0].args![0]).toBe("Miles"); + + const objectArg1 = tx.data.recipients[0].args as { name: string } + expect(objectArg1.name).toBe("Miles"); + + const objectArg2 = tx.data.recipients[1].args as { name: string } + expect(objectArg2.name).toBe("John Doe"); }); it("should throw if types are incorrect", () => { @@ -142,16 +149,12 @@ describe("Transaction builder", () => { describe("previousSignaturePayload", () => { it("should generate binary encoding of the transaction before signing", () => { - const code = ` - condition inherit: [ - uco_transferred: 0.020 - ] - - actions triggered by: transaction do - set_type transfer - add_uco_ledger to: "000056E763190B28B4CF9AAF3324CF379F27DE9EF7850209FB59AA002D71BA09788A", amount: 0.020 - end - `; + const contract = new Contract(new Uint8Array(5), { + abi: { + state: { "value": "u32" }, + functions: {} + } + }) const content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sit amet leo egestas, lobortis lectus a, dignissim orci."; @@ -171,9 +174,10 @@ describe("Transaction builder", () => { parseBigInt("100"), "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" ) - .setCode(code) + .setContract(contract) .setContent(content) - .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"); + .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88", "action") + .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe" }); const keypair = deriveKeyPair("seed", 0); @@ -187,9 +191,12 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - //Code size - intToUint32Array(code.length), - new TextEncoder().encode(code), + //Contract is filled + intToUint8Array(1), + //Contract bytecode size + intToUint32Array(contract.bytecode.length), + contract.bytecode, + typed_encoding.serialize(contract.manifest), //Content size intToUint32Array(content.length), new TextEncoder().encode(content), @@ -231,128 +238,28 @@ describe("Transaction builder", () => { // Nb of byte to encode nb of recipients Uint8Array.from([1]), // Nb of recipients + Uint8Array.from([2]), + // 0 = named recipient Uint8Array.from([1]), - // 0 = unnamed recipient - Uint8Array.from([0]), - hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") + hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), + Uint8Array.from([6]), + new TextEncoder().encode("action"), + TE.serialize({}), + // 1 = named recipient + Uint8Array.from([1]), + hexToUint8Array("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd"), + Uint8Array.from([7]), + new TextEncoder().encode("addHero"), + TE.serialize({ name: "John Doe" }) ); expect(payload).toEqual(expected_binary); }); - // it("should generate binary encoding of the transaction before signing with named action", () => { - // const code = ` - // condition inherit: [ - // uco_transferred: 0.020 - // ] - - // actions triggered by: transaction do - // set_type transfer - // add_uco_ledger to: "000056E763190B28B4CF9AAF3324CF379F27DE9EF7850209FB59AA002D71BA09788A", amount: 0.020 - // end - // `; - - // const content = - // "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sit amet leo egestas, lobortis lectus a, dignissim orci."; - - // const secret = "mysecret"; - - // const tx = new TransactionBuilder("transfer") - // .addOwnership(secret, [ - // { - // publicKey: "0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - // encryptedSecretKey: "00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" - // } - // ]) - // .addUCOTransfer("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", parseBigInt("0.202")) - // .addTokenTransfer( - // "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - // parseBigInt("100"), - // "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" - // ) - // .setCode(code) - // .setContent(content) - // .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88", "vote_for_mayor", [ - // "Ms. Smith" - // ]); - - // const keypair = deriveKeyPair("seed", 0); - - // tx.address = deriveAddress("seed", 1); - // tx.previousPublicKey = keypair.publicKey; - - // const payload = tx.previousSignaturePayload(); - - // const expected_binary = concatUint8Arrays( - // //Version - // intToUint32Array(VERSION), - // tx.address, - // Uint8Array.from([253]), - // //Code size - // intToUint32Array(code.length), - // new TextEncoder().encode(code), - // //Content size - // intToUint32Array(content.length), - // new TextEncoder().encode(content), - // // Nb of byte to encode nb of ownerships - // Uint8Array.from([1]), - // //Nb of ownerships - // Uint8Array.from([1]), - // //Secret size - // intToUint32Array(secret.length), - // new TextEncoder().encode(secret), - // // Nb of byte to encode nb of authorized keys - // Uint8Array.from([1]), - // // Nb of authorized keys - // Uint8Array.from([1]), - // // Authorized keys encoding - // concatUint8Arrays( - // hexToUint8Array("0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // hexToUint8Array("00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") - // ), - // // Nb of byte to encode nb of uco transfers - // Uint8Array.from([1]), - // // Nb of uco transfers - // Uint8Array.from([1]), - // concatUint8Arrays( - // hexToUint8Array("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // intToUint8Array(parseBigInt("0.202")) - // ), - // // Nb of byte to encode nb of Token transfers - // Uint8Array.from([1]), - // // Nb of Token transfers - // Uint8Array.from([1]), - // concatUint8Arrays( - // hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), - // hexToUint8Array("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // intToUint8Array(parseBigInt("100")), - // Uint8Array.from([1]), - // Uint8Array.from([0]) - // ), - // // Nb of byte to encode nb of recipients - // Uint8Array.from([1]), - // // Nb of recipients - // Uint8Array.from([1]), - // // 1 = named recipient - // Uint8Array.from([1]), - // hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), - // // action - // // action size on 1 byte - // Uint8Array.from([14]), - // // action value - // new TextEncoder().encode("vote_for_mayor"), - // // args size - // Uint8Array.from([1]), - // // args value - // TE.serialize("Ms. Smith") - // ); - // expect(payload).toEqual(expected_binary); - // }); - it("should order the keys or named action args in the generated binary", () => { const tx = new TransactionBuilder("transfer").addRecipient( "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88", "set_geopos", - [{ lng: 2, lat: 1 }] + { lng: 2, lat: 1 } ); const keypair = deriveKeyPair("seed", 0); @@ -367,8 +274,8 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - //Code size - intToUint32Array(0), + //No contract + intToUint8Array(0), //Content size intToUint32Array(0), // Nb of byte to encode nb of ownerships @@ -395,8 +302,6 @@ describe("Transaction builder", () => { Uint8Array.from([10]), // action value new TextEncoder().encode("set_geopos"), - // args size - Uint8Array.from([1]), // args value TE.serialize({ lng: 2, lat: 1 }) ); @@ -445,10 +350,10 @@ describe("Transaction builder", () => { expect(txRPC).toHaveProperty("generateEncryptedSeedSC", true); }); - it("should not affect the NodeTransactionRPC", () => { + it("should not affect the NodeTransactionRPC", async () => { const generateEncryptedSeedSC = true; const tx = new TransactionBuilder("transfer").setGenerateEncryptedSeedSC(generateEncryptedSeedSC); - const txRPC = tx.toNodeRPC(); + const txRPC = await tx.toNodeRPC(); expect(txRPC).not.toHaveProperty("generateEncryptedSeedSC", true); }); @@ -470,110 +375,6 @@ describe("Transaction builder", () => { }); }); - // describe("originSignaturePayload", () => { - // it("should generate binary encoding of the transaction before signing", () => { - // const secret = "mysecret"; - // const content = - // "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sit amet leo egestas, lobortis lectus a, dignissim orci."; - // const code = `condition inherit: [ - // uco_transferred: 0.020 - // ] - - // actions triggered by: transaction do - // set_type transfer - // add_uco_ledger to: "000056E763190B28B4CF9AAF3324CF379F27DE9EF7850209FB59AA002D71BA09788A", amount: 0.020 - // end - // `; - - // const tx = new TransactionBuilder("transfer") - // .addOwnership(secret, [ - // { - // publicKey: "0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - // encryptedSecretKey: "00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" - // }, - // { - // publicKey: "0001a1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - // encryptedSecretKey: "00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" - // } - // ]) - // .addUCOTransfer("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", parseBigInt("0.202")) - // .addTokenTransfer( - // "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", - // parseBigInt("100"), - // "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" - // ) - // .setCode(code) - // .setContent(content) - // .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") - // .build("seed", 0, "P256"); - - // const transactionKeyPair = deriveKeyPair("seed", 0, Curve.P256); - // const previousSig = sign(tx.previousSignaturePayload(), transactionKeyPair.privateKey); - - // const payload = tx.originSignaturePayload(); - // const expected_binary = concatUint8Arrays( - // //Version - // intToUint32Array(VERSION), - // tx.address, - // Uint8Array.from([253]), - // //Code size - // intToUint32Array(code.length), - // new TextEncoder().encode(code), - // //Content size - // intToUint32Array(content.length), - // new TextEncoder().encode(content), - // // Nb of byte to encode nb of ownerships - // Uint8Array.from([1]), - // //Nb ownerships - // Uint8Array.from([1]), - // //Secret size - // intToUint32Array(secret.length), - // new TextEncoder().encode(secret), - // // Nb of byte to encode nb of authorized key - // Uint8Array.from([1]), - // // Nb of authorized keys - // Uint8Array.from([2]), - // // Authorized keys encoding - // concatUint8Arrays( - // hexToUint8Array("0001a1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // hexToUint8Array("00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), - // hexToUint8Array("0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // hexToUint8Array("00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") - // ), - // // Nb of byte to encode nb of uco transfers - // Uint8Array.from([1]), - // // Nb of uco transfers - // Uint8Array.from([1]), - // concatUint8Arrays( - // hexToUint8Array("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // intToUint8Array(parseBigInt("0.202")) - // ), - // // Nb of byte to encode nb of Token transfers - // Uint8Array.from([1]), - // // Nb of Token transfers - // Uint8Array.from([1]), - // concatUint8Arrays( - // hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), - // hexToUint8Array("0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"), - // intToUint8Array(parseBigInt("100")), - // Uint8Array.from([1]), - // Uint8Array.from([0]) - // ), - // // Nb of byte to encode nb of recipients - // Uint8Array.from([1]), - // // Nb of recipients - // Uint8Array.from([1]), - // // 0 = unnamed recipient - // Uint8Array.from([0]), - // hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), - // transactionKeyPair.publicKey, - // Uint8Array.from([previousSig.length]), - // previousSig - // ); - // expect(payload).toStrictEqual(expected_binary); - // }); - // }); - describe("originSign", () => { it("should sign the transaction with a origin private key", () => { const originKeypair = deriveKeyPair("origin_seed", 0);