From 97cd55881a509aacef5c96100262a56a6082c541 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 30 Aug 2024 10:12:13 +0200 Subject: [PATCH 01/21] up node version --- .github/workflows/node.js.yml | 2 +- .tool-versions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From c8fd1dbed817bf6f583c7169f2fcc763db02c208 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 30 Aug 2024 10:12:41 +0200 Subject: [PATCH 02/21] Update contract function detection --- src/contract.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/contract.ts b/src/contract.ts index 984663d..3698328 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,11 +2,40 @@ import { ContractAction } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; +import { hexToUint8Array, isHex } from "./utils.js"; + +export async function extractActionsFromContract(code: string): Promise { + let actions = []; + + if (isHex(code)) { + const wasmModule = await WebAssembly.instantiate(hexToUint8Array(code), { + "archethic/env": { + log: (offset: bigint, length: bigint) => {}, + store_u8: (offset: bigint, data: bigint) => {}, + input_load_u8: (offset: bigint): number => 0, + input_size: (): bigint => 0n, + alloc: (length: bigint): bigint => 0n, + set_output: (offset: bigint, length: bigint) => {}, + set_error: (offset: bigint, length: bigint) => {} + } + }); + + const reservedFunctions = ["spec", "init", "onUpgrade"]; + for (let key in wasmModule.instance.exports) { + if (wasmModule.instance.exports[key] instanceof Function) { + if (!reservedFunctions.includes(key)) { + actions.push({ name: key, parameters: ["WASM JSON Input"] }); + } + } + } + + actions.push({ name: "upgrade", parameters: ['WASM JSON Input ( {"code": "wasm code as hex"})'] }); + + return actions; + } -export function extractActionsFromContract(code: string): ContractAction[] { 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]; From e408541a7303882f2ef159187decf6b4ea733fd2 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 30 Aug 2024 10:13:26 +0200 Subject: [PATCH 03/21] Support compressed contract code in tx builder --- src/api/node_rpc.ts | 6 +++--- src/transaction_builder.ts | 29 ++++++++++++++++++++++++++--- tests/network.test.ts | 6 +++--- tests/transaction_builder.test.ts | 11 +++++++++-- 4 files changed, 41 insertions(+), 11 deletions(-) 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/transaction_builder.ts b/src/transaction_builder.ts index 846f9f3..9acc8d2 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -103,7 +103,7 @@ export default class TransactionBuilder { * @param {string} code Smart contract code */ setCode(code: string) { - this.data.code = new TextEncoder().encode(code); + this.data.code = maybeStringToUint8Array(code); return this; } @@ -432,14 +432,37 @@ export default class TransactionBuilder { /** * JSON RPC API SEND_TRANSACTION */ - toNodeRPC(): TransactionRPC { + async toNodeRPC(): Promise { + async function asyncConcatUint8Arrays(uint8arrays: Uint8Array[]) { + const blob = new Blob(uint8arrays); + const buffer = await blob.arrayBuffer(); + return new Uint8Array(buffer); + } + + async function compress(str: Uint8Array): Promise { + // Convert the string to a byte stream. + const stream = new Blob([str]).stream(); + + // Create a compressed stream. + const compressedStream = stream.pipeThrough(new CompressionStream("deflate-raw")); + + // Read all the bytes from this stream. + const chunks: Uint8Array[] = []; + //@ts-ignore + for await (const chunk of compressedStream) { + chunks.push(chunk); + } + + return await asyncConcatUint8Arrays(chunks); + } + 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), + code: uint8ArrayToHex(await compress(this.data.code)), ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), diff --git a/tests/network.test.ts b/tests/network.test.ts index d7b16e6..bfec707 100644 --- a/tests/network.test.ts +++ b/tests/network.test.ts @@ -116,7 +116,7 @@ describe("Network", () => { id: 1, method: "estimate_transaction_fee", params: { - transaction: tx.toNodeRPC() + transaction: await tx.toNodeRPC() } }) // @ts-ignore @@ -155,7 +155,7 @@ describe("Network", () => { id: 1, method: "send_transaction", params: { - transaction: tx.toNodeRPC() + transaction: await tx.toNodeRPC() } }) // @ts-ignore @@ -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..ef224cb 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -445,10 +445,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); }); @@ -603,4 +603,11 @@ describe("Transaction builder", () => { expect(txRPC.data.content).toStrictEqual("Hello world"); }); }); + + describe("toNodeRPC", () => { + it("should compress using zlib contract's code", async () => { + const tx = new TransactionBuilder("code").setCode("0061736d01000000015e1160017f017f60067f7f7f7f7f7f0060037f7f7f"); + console.log(await tx.toNodeRPC()); + }); + }); }); From 6ca7eb048f51abe9c2c1419cadd80a7391211179 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 30 Aug 2024 10:13:36 +0200 Subject: [PATCH 04/21] update example transaction builder --- example/transactionBuilder/app.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index 154f608..d04fbad 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -256,7 +256,8 @@ window.onChangeRecipient = async () => { document.querySelector("#namedActionsContainer").style.display = "block"; - Contract.extractActionsFromContract(contractCode).forEach((action) => { + const actions = await Contract.extractActionsFromContract(contractCode); + actions.forEach((action) => { const option = document.createElement("option"); option.text = action.name; option.value = action.name; @@ -280,7 +281,16 @@ 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") { + namedParams[index] = Contract.parseTypedArgument(json); + } else { + namedParams[index] = Contract.parseTypedArgument(value); + } + } catch (e) { + namedParams[index] = Contract.parseTypedArgument(value); + } }); paramsContainer.appendChild(paramLabel); @@ -309,7 +319,7 @@ window.onClickAddRecipient = () => { if (recipientList.textContent != "") { recipientList.textContent = recipientList.textContent + "\n"; } - recipientList.textContent += `${recipientAddress} - ${namedAction} - ${namedParams}`; + recipientList.textContent += `${recipientAddress} - ${namedAction} - ${JSON.stringify(namedParams)}`; document.querySelector("#namedActionsContainer").style.display = "none"; document.querySelector("#namedActions").innerHTML = ""; From 70c94e0e7dc3b13dc755dd66e17c28848ec43726 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 6 Sep 2024 10:13:25 +0200 Subject: [PATCH 05/21] update --- src/contract.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/contract.ts b/src/contract.ts index 3698328..29c6111 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -12,11 +12,15 @@ export async function extractActionsFromContract(code: string): Promise {}, store_u8: (offset: bigint, data: bigint) => {}, - input_load_u8: (offset: bigint): number => 0, + load_u8: (offset: bigint): number => 0, input_size: (): bigint => 0n, alloc: (length: bigint): bigint => 0n, set_output: (offset: bigint, length: bigint) => {}, set_error: (offset: bigint, length: bigint) => {} + }, + // FIXME with JSON RPC like request + "archethic/IO": { + get_balance: (offset: bigint, length: bigint) => 0 } }); From 053367d3e82050d8c84a017bb08e94c06603a2c8 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Wed, 11 Sep 2024 22:24:26 +0200 Subject: [PATCH 06/21] use manifest to contract --- example/transactionBuilder/app.js | 7 +-- src/api.ts | 4 +- src/contract.ts | 90 ++++++++++++++++--------------- src/transaction_builder.ts | 25 +-------- tests/transaction_builder.test.ts | 7 --- 5 files changed, 53 insertions(+), 80 deletions(-) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index d04fbad..694007e 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -249,14 +249,11 @@ let namedParams = []; 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"; - const actions = await Contract.extractActionsFromContract(contractCode); + const actions = await Contract.extractActionsFromContract(contractContext); actions.forEach((action) => { const option = document.createElement("option"); option.text = action.name; diff --git a/src/api.ts b/src/api.ts index 6fa1648..3e8c0b7 100644 --- a/src/api.ts +++ b/src/api.ts @@ -355,9 +355,9 @@ export async function getContractCode(address: string | Uint8Array, endpoint: st .then(handleResponse) .then((res): string => { if (res.errors || res.data == null) { - return ""; + throw new Error("No contract at this address") } else { - return res.data.lastTransaction.data.code; + return res.data.lastTransaction.data.code } }); } diff --git a/src/contract.ts b/src/contract.ts index 29c6111..bb89190 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,59 +2,65 @@ import { ContractAction } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; -import { hexToUint8Array, isHex } from "./utils.js"; +import { isHex } from "./utils.js"; -export async function extractActionsFromContract(code: string): Promise { - let actions = []; +type CodeWithManifest = { + bytecode: string; + manifest: WasmManifest +} - if (isHex(code)) { - const wasmModule = await WebAssembly.instantiate(hexToUint8Array(code), { - "archethic/env": { - log: (offset: bigint, length: bigint) => {}, - store_u8: (offset: bigint, data: bigint) => {}, - load_u8: (offset: bigint): number => 0, - input_size: (): bigint => 0n, - alloc: (length: bigint): bigint => 0n, - set_output: (offset: bigint, length: bigint) => {}, - set_error: (offset: bigint, length: bigint) => {} - }, - // FIXME with JSON RPC like request - "archethic/IO": { - get_balance: (offset: bigint, length: bigint) => 0 - } - }); +type WasmManifest = { + abi: WasmABI +} - const reservedFunctions = ["spec", "init", "onUpgrade"]; - for (let key in wasmModule.instance.exports) { - if (wasmModule.instance.exports[key] instanceof Function) { - if (!reservedFunctions.includes(key)) { - actions.push({ name: key, parameters: ["WASM JSON Input"] }); - } - } - } +type WasmABI = { + functions: Record +} - actions.push({ name: "upgrade", parameters: ['WASM JSON Input ( {"code": "wasm code as hex"})'] }); +type WASMFunctionABI = { + type: string; + triggerType?: string; + name: string; + input: Record +} - return actions; +export async function extractActionsFromContract(code: string): Promise { + try { + const codeWithManifest: CodeWithManifest = JSON.parse(code) + const manifest = codeWithManifest.manifest + let actions: ContractAction[] = [] + for (let name of Object.keys(manifest.abi.functions)) { + const functionABI = manifest.abi.functions[name] + if (functionABI.type == "action" && functionABI.triggerType == "transaction") { + actions.push({ + name: name, + parameters: functionABI.input ? Object.keys(functionABI.input) : [] + }) + } + } + return actions } + catch(e) { + let actions = []; - const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; + const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; - for (const match of code.matchAll(regex)) { - const fullAction = match[1]; + for (const match of code.matchAll(regex)) { + const fullAction = match[1]; - const regexActionName = /(\w+)\((.*?)\)/g; - for (const actionMatch of fullAction.matchAll(regexActionName)) { - const name = actionMatch[1]; - const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; - actions.push({ - name: name, - parameters: parameters - }); + const regexActionName = /(\w+)\((.*?)\)/g; + for (const actionMatch of fullAction.matchAll(regexActionName)) { + const name = actionMatch[1]; + const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; + actions.push({ + name: name, + parameters: parameters + }); + } } - } - return actions; + return actions; + } } export function parseTypedArgument(input: any): any { diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 9acc8d2..06fc718 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -433,36 +433,13 @@ export default class TransactionBuilder { * JSON RPC API SEND_TRANSACTION */ async toNodeRPC(): Promise { - async function asyncConcatUint8Arrays(uint8arrays: Uint8Array[]) { - const blob = new Blob(uint8arrays); - const buffer = await blob.arrayBuffer(); - return new Uint8Array(buffer); - } - - async function compress(str: Uint8Array): Promise { - // Convert the string to a byte stream. - const stream = new Blob([str]).stream(); - - // Create a compressed stream. - const compressedStream = stream.pipeThrough(new CompressionStream("deflate-raw")); - - // Read all the bytes from this stream. - const chunks: Uint8Array[] = []; - //@ts-ignore - for await (const chunk of compressedStream) { - chunks.push(chunk); - } - - return await asyncConcatUint8Arrays(chunks); - } - return { version: this.version, address: uint8ArrayToHex(this.address), type: this.type, data: { content: new TextDecoder().decode(this.data.content), - code: uint8ArrayToHex(await compress(this.data.code)), + code: new TextDecoder().decode(this.data.code), ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index ef224cb..4099fd2 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -603,11 +603,4 @@ describe("Transaction builder", () => { expect(txRPC.data.content).toStrictEqual("Hello world"); }); }); - - describe("toNodeRPC", () => { - it("should compress using zlib contract's code", async () => { - const tx = new TransactionBuilder("code").setCode("0061736d01000000015e1160017f017f60067f7f7f7f7f7f0060037f7f7f"); - console.log(await tx.toNodeRPC()); - }); - }); }); From 54aed4ffd26fab5fae617835b4e42255592231fc Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Tue, 1 Oct 2024 17:21:16 +0200 Subject: [PATCH 07/21] fix null contract arguments --- example/transactionBuilder/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index 694007e..314e7f6 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -267,6 +267,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"); From 7977e77bceefbc448880cdb0fa53b191af048d9f Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 4 Oct 2024 11:39:08 +0200 Subject: [PATCH 08/21] Support object for recipient arg --- src/transaction_builder.ts | 12 +- src/types.ts | 4 +- tests/transaction_builder.test.ts | 255 ++++-------------------------- 3 files changed, 36 insertions(+), 235 deletions(-) diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 06fc718..9e37f30 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -19,7 +19,7 @@ import { import TE from "./typed_encoding.js"; import { deriveAddress, deriveKeyPair, sign } from "./crypto.js"; -const VERSION = 3; +export const VERSION = 4; function getTransactionTypeId(type: UserTypeTransaction): number { switch (type) { @@ -202,17 +202,17 @@ 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 {any[] | object} args The arguments for the named action */ - addRecipient(to: string | Uint8Array, action?: string, args?: any[]) { + addRecipient(to: string | Uint8Array, action?: string, args?: any[] | object) { const address = maybeHexToUint8Array(to); if (action && 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 or an array"); } if (action && !args) { @@ -383,7 +383,7 @@ export default class TransactionBuilder { address ); } else { - const serializedArgs = args.map((arg) => TE.serialize(arg)); + const serializedArgs = args instanceof Array ? args.map((arg) => TE.serialize(arg)) : [TE.serialize(args)]; return concatUint8Arrays( // 1 = named action diff --git a/src/types.ts b/src/types.ts index 525aa35..a58c3b9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -111,7 +111,7 @@ type UcoTransfer = { export type Recipient = { address: Uint8Array; action?: string; - args?: any[]; + args?: any[] | object; }; export type Ownership = { @@ -213,7 +213,7 @@ type OwnershipRPC = { type RecipientRPC = { address: string; action?: string; - args?: any[]; + args?: any[] | object; }; export type TransactionRPC = { diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index 4099fd2..8c9051f 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -1,10 +1,8 @@ -import TransactionBuilder from "../src/transaction_builder"; +import TransactionBuilder, { VERSION } from "../src/transaction_builder"; import { deriveAddress, deriveKeyPair, sign, verify } from "../src/crypto"; import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, parseBigInt } from "../src/utils"; import TE from "../src/typed_encoding"; -const VERSION = 3; - // all assert should be transformed to jest expect describe("Transaction builder", () => { describe("setType", () => { @@ -118,16 +116,23 @@ 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", + ["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 listArgs = tx.data.recipients[0].args as string[] + expect(listArgs.length).toBe(1); + expect(listArgs[0]).toBe("Miles"); + + const objectArg = tx.data.recipients[1].args as { name: string } + expect(objectArg.name).toBe("John Doe"); }); it("should throw if types are incorrect", () => { @@ -173,7 +178,8 @@ describe("Transaction builder", () => { ) .setCode(code) .setContent(content) - .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"); + .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") + .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe"}); const keypair = deriveKeyPair("seed", 0); @@ -231,123 +237,22 @@ describe("Transaction builder", () => { // Nb of byte to encode nb of recipients Uint8Array.from([1]), // Nb of recipients - Uint8Array.from([1]), + Uint8Array.from([2]), // 0 = unnamed recipient Uint8Array.from([0]), - hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") + hexToUint8Array("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"), + // 1 = named recipient + Uint8Array.from([1]), + hexToUint8Array("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd"), + Uint8Array.from([7]), + new TextEncoder().encode("addHero"), + // Nb args + Uint8Array.from([1]), + 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", @@ -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); From 581892cc17e13f71a818e125a635c22fd19ac6ba Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 4 Oct 2024 15:10:55 +0200 Subject: [PATCH 09/21] change tx structure to add contract object --- src/contract.ts | 21 +++++++++---- src/transaction_builder.ts | 49 ++++++++++++++++++------------- src/types.ts | 34 +++++++++++++++++++-- tests/account.test.ts | 2 +- tests/transaction_builder.test.ts | 43 +++++++++++++++------------ 5 files changed, 100 insertions(+), 49 deletions(-) diff --git a/src/contract.ts b/src/contract.ts index bb89190..906762a 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -1,4 +1,4 @@ -import { ContractAction } from "./types.js"; +import { Contract, ContractAction, TransactionData } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; @@ -82,17 +82,26 @@ 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) + .setContract(contract) .addOwnership(encryptedSecret, authorizedKeys) - .build(seed, index); + + 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); } diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 9e37f30..d2f7079 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -5,7 +5,8 @@ import { HashAlgorithm, TransactionData, UserTypeTransaction, - TransactionRPC + TransactionRPC, + Contract } from "./types.js"; import { concatUint8Arrays, @@ -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: [] @@ -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 = maybeStringToUint8Array(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; } @@ -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 = intToUint32Array(0) + if (this.data.contract != undefined) { + const contract = this.data.contract + const manifestJSON = JSON.stringify(contract.manifest) + bufContract = concatUint8Arrays( + intToUint32Array(contract.bytecode.byteLength), + contract.bytecode, + intToUint32Array(manifestJSON.length), + new TextEncoder().encode(manifestJSON) + ) + } let contentSize = this.data.content.length; @@ -410,10 +417,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, @@ -438,8 +444,11 @@ export default class TransactionBuilder { 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 +505,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 a58c3b9..85fd783 100644 --- a/src/types.ts +++ b/src/types.ts @@ -76,13 +76,36 @@ type CrossValidationStamp = { }; export type TransactionData = { - code: Uint8Array; - content: Uint8Array; + contract?: Contract; + content: string; ledger: Ledger; ownerships: Ownership[]; recipients: Recipient[]; }; +interface actionABI extends functionABI { + triggerType: string, + triggerArgument?: string +} + +type functionABI = { + type: string; + input?: Record | string | (Record | string)[], + output?: any +} + +export type ContractManifest = { + abi: { + functions: Record, + state: Record + } +} + +export type Contract = { + bytecode: Uint8Array; + manifest: ContractManifest; +} + type Ledger = { token: TokenLedger; uco: UcoLedger; @@ -216,13 +239,18 @@ type RecipientRPC = { args?: any[] | object; }; +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: { 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/transaction_builder.test.ts b/tests/transaction_builder.test.ts index 8c9051f..efb56cb 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -38,17 +38,20 @@ 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 tx = new TransactionBuilder("transfer").setContract({ + bytecode: new Uint8Array(), + manifest: { abi: { state: {}, functions: {}}} + }); + expect(tx.data.contract?.bytecode).toStrictEqual(new Uint8Array()); }); }); 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"); }); }); @@ -147,16 +150,16 @@ 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 = { + bytecode: new Uint8Array(5), + manifest: { + 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."; @@ -176,7 +179,7 @@ describe("Transaction builder", () => { parseBigInt("100"), "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88" ) - .setCode(code) + .setContract(contract) .setContent(content) .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe"}); @@ -193,9 +196,11 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - //Code size - intToUint32Array(code.length), - new TextEncoder().encode(code), + //Contract bytecode size + intToUint32Array(contract.bytecode.length), + contract.bytecode, + intToUint32Array(JSON.stringify(contract.manifest).length), + new TextEncoder().encode(JSON.stringify(contract.manifest)), //Content size intToUint32Array(content.length), new TextEncoder().encode(content), @@ -272,7 +277,7 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - //Code size + //Contract size intToUint32Array(0), //Content size intToUint32Array(0), From dfa99a72720c95561c3917101511aecf120f38b2 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Tue, 8 Oct 2024 22:53:39 +0200 Subject: [PATCH 10/21] use typedencoding to serialize contract manfiest --- src/transaction_builder.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index d2f7079..eef9f2a 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -338,12 +338,10 @@ export default class TransactionBuilder { let bufContract: Uint8Array = intToUint32Array(0) if (this.data.contract != undefined) { const contract = this.data.contract - const manifestJSON = JSON.stringify(contract.manifest) bufContract = concatUint8Arrays( intToUint32Array(contract.bytecode.byteLength), contract.bytecode, - intToUint32Array(manifestJSON.length), - new TextEncoder().encode(manifestJSON) + TE.serialize(this.data.contract.manifest) ) } From 2f66a4dee0247dfbabc83dc133f5f968ce6d37ae Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Tue, 8 Oct 2024 22:55:36 +0200 Subject: [PATCH 11/21] use pako to compress contract --- package-lock.json | 19 +++++++++++++++++++ package.json | 2 ++ src/contract.ts | 7 +++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c1da38..1ba5602 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", @@ -32,6 +33,7 @@ "@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", @@ -3738,6 +3740,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", @@ -5542,6 +5550,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 +7248,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..f2ad9f4 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@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 +57,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/contract.ts b/src/contract.ts index 906762a..fdc2a3c 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,7 +2,7 @@ import { Contract, ContractAction, TransactionData } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; -import { isHex } from "./utils.js"; +import { deflateRaw } from "pako" type CodeWithManifest = { bytecode: string; @@ -90,6 +90,9 @@ export async function newContractTransaction( const index = await archethic.transaction.getTransactionIndex(deriveAddress(seed, 0)); const { encryptedSecret, authorizedKeys } = encryptSecret(seed, storageNoncePublicKey); + const compressBytes = deflateRaw(contract.bytecode) + contract.bytecode = compressBytes + const tx = archethic.transaction .new() .setType("contract") @@ -104,4 +107,4 @@ export async function newContractTransaction( } return tx.build(seed, index); -} +} \ No newline at end of file From d20a9c3d94a7bfbd0c9175fc3018e31a516c9ffc Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Tue, 8 Oct 2024 22:55:53 +0200 Subject: [PATCH 12/21] improve types with CryptoJS --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 28 insertions(+) diff --git a/package-lock.json b/package-lock.json index 1ba5602..3f32e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "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", @@ -1381,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", @@ -1472,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", @@ -5459,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", diff --git a/package.json b/package.json index f2ad9f4..fa2839b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "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", From 8cfb9dc92a605e693c84ecece7e11dac0833ea58 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 11 Oct 2024 10:17:27 +0200 Subject: [PATCH 13/21] update tx builder to support wasm --- example/transactionBuilder/app.js | 64 +++++++++++--- example/transactionBuilder/index.html | 17 ++-- src/api.ts | 36 ++++++-- src/contract.ts | 122 +++++++++++++++----------- src/network.ts | 2 +- src/transaction_builder.ts | 2 +- src/types.ts | 63 ++++--------- tests/transaction_builder.test.ts | 27 +++--- 8 files changed, 195 insertions(+), 138 deletions(-) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index 314e7f6..87bb117 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,7 +250,8 @@ window.onClickAddTokenTransfer = async () => { document.querySelector("#token_id").value = "0"; }; -let namedParams = []; +let namedParams = [] +let objectParams = {}; window.onChangeRecipient = async () => { const address = document.querySelector("#recipient").value; @@ -253,7 +259,7 @@ window.onChangeRecipient = async () => { document.querySelector("#namedActionsContainer").style.display = "block"; - const actions = await Contract.extractActionsFromContract(contractContext); + const actions = contractContext.contract ? contractContext.contract.getActions() : Contract.extractActionsFromContract(contractContext.code); actions.forEach((action) => { const option = document.createElement("option"); option.text = action.name; @@ -283,12 +289,26 @@ window.onChangeRecipient = async () => { try { const json = JSON.parse(value); if (typeof json === "object") { - namedParams[index] = Contract.parseTypedArgument(json); + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(json); + } else { + namedParams[index] = Contract.parseTypedArgument(json); + } } else { - namedParams[index] = Contract.parseTypedArgument(value); + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(value); + } + else { + namedParams[index] = Contract.parseTypedArgument(value); + } } } catch (e) { - namedParams[index] = Contract.parseTypedArgument(value); + if (contractContext.contract) { + objectParams[parameter] = Contract.parseTypedArgument(value); + } + else { + namedParams[index] = Contract.parseTypedArgument(value); + } } }); @@ -314,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} - ${JSON.stringify(namedParams)}`; + recipientList.textContent += `${recipientAddress} - ${namedAction} - ${JSON.stringify(Object.keys(objectParams).length > 0 ? objectParams : namedParams)}`; document.querySelector("#namedActionsContainer").style.display = "none"; document.querySelector("#namedActions").innerHTML = ""; @@ -399,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/src/api.ts b/src/api.ts index 3e8c0b7..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) { - throw new Error("No contract at this address") + 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/contract.ts b/src/contract.ts index fdc2a3c..768e45a 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -1,66 +1,51 @@ -import { Contract, ContractAction, TransactionData } 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" +import { deflateRaw } from "pako"; +import TransactionBuilder from "./transaction_builder.js"; -type CodeWithManifest = { +export type CodeWithManifest = { bytecode: string; - manifest: WasmManifest -} + manifest: ContractManifest; +}; -type WasmManifest = { - abi: WasmABI -} +export type ContractManifest = { + abi: WasmABI; +}; -type WasmABI = { - functions: Record -} +export type WasmABI = { + state: Record; + functions: Record; +}; -type WASMFunctionABI = { +export type WASMFunctionABI = { type: string; triggerType?: string; name: string; - input: Record -} + input: Record; +}; -export async function extractActionsFromContract(code: string): Promise { - try { - const codeWithManifest: CodeWithManifest = JSON.parse(code) - const manifest = codeWithManifest.manifest - let actions: ContractAction[] = [] - for (let name of Object.keys(manifest.abi.functions)) { - const functionABI = manifest.abi.functions[name] - if (functionABI.type == "action" && functionABI.triggerType == "transaction") { - actions.push({ - name: name, - parameters: functionABI.input ? Object.keys(functionABI.input) : [] - }) - } - } - return actions - } - catch(e) { - let actions = []; +export function extractActionsFromContract(code: string): ContractAction[] { + let actions = []; - const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; + const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; - for (const match of code.matchAll(regex)) { - const fullAction = match[1]; + for (const match of code.matchAll(regex)) { + const fullAction = match[1]; - const regexActionName = /(\w+)\((.*?)\)/g; - for (const actionMatch of fullAction.matchAll(regexActionName)) { - const name = actionMatch[1]; - const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; - actions.push({ - name: name, - parameters: parameters - }); - } + const regexActionName = /(\w+)\((.*?)\)/g; + for (const actionMatch of fullAction.matchAll(regexActionName)) { + const name = actionMatch[1]; + const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; + actions.push({ + name: name, + parameters: parameters + }); } - - return actions; } + + return actions; } export function parseTypedArgument(input: any): any { @@ -90,21 +75,52 @@ export async function newContractTransaction( const index = await archethic.transaction.getTransactionIndex(deriveAddress(seed, 0)); const { encryptedSecret, authorizedKeys } = encryptSecret(seed, storageNoncePublicKey); - const compressBytes = deflateRaw(contract.bytecode) - contract.bytecode = compressBytes const tx = archethic.transaction .new() .setType("contract") .setContract(contract) - .addOwnership(encryptedSecret, authorizedKeys) + .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) + 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 async function updateContractTransaction( + archethic: Archethic, + contractAddress: string, + contract: Contract +): Promise { + 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; + } } \ No newline at end of file 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 eef9f2a..19be7e3 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -6,7 +6,6 @@ import { TransactionData, UserTypeTransaction, TransactionRPC, - Contract } from "./types.js"; import { concatUint8Arrays, @@ -19,6 +18,7 @@ import { } from "./utils.js"; import TE from "./typed_encoding.js"; import { deriveAddress, deriveKeyPair, sign } from "./crypto.js"; +import { Contract } from "./contract.js"; export const VERSION = 4; diff --git a/src/types.ts b/src/types.ts index 85fd783..79594f0 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", @@ -83,53 +85,28 @@ export type TransactionData = { recipients: Recipient[]; }; -interface actionABI extends functionABI { - triggerType: string, - triggerArgument?: string -} - -type functionABI = { - type: string; - input?: Record | string | (Record | string)[], - output?: any -} - -export type ContractManifest = { - abi: { - functions: Record, - state: Record - } -} - -export type Contract = { - bytecode: Uint8Array; - manifest: ContractManifest; -} - 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; @@ -213,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; @@ -233,13 +208,13 @@ type OwnershipRPC = { }[]; }; -type RecipientRPC = { +export type RecipientRPC = { address: string; action?: string; args?: any[] | object; }; -type ContractRPC = { +export type ContractRPC = { bytecode: string; manifest: ContractManifest; } @@ -254,7 +229,7 @@ export type TransactionRPC = { ownerships: OwnershipRPC[]; ledger: { uco: { - transfers: Transfer[]; + transfers: TransferRPC[]; }; token: { transfers: TokenTransferRPC[]; diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index efb56cb..b8af2fc 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -2,6 +2,8 @@ import TransactionBuilder, { VERSION } from "../src/transaction_builder"; import { deriveAddress, deriveKeyPair, sign, verify } from "../src/crypto"; import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, parseBigInt } from "../src/utils"; import TE from "../src/typed_encoding"; +import { Contract } from "../src/contract"; +import typed_encoding from "../src/typed_encoding"; // all assert should be transformed to jest expect describe("Transaction builder", () => { @@ -40,11 +42,9 @@ describe("Transaction builder", () => { describe("setContract", () => { it("should insert the code into the transaction data", () => { - const tx = new TransactionBuilder("transfer").setContract({ - bytecode: new Uint8Array(), - manifest: { abi: { state: {}, functions: {}}} - }); - expect(tx.data.contract?.bytecode).toStrictEqual(new Uint8Array()); + const contract = new Contract(new Uint8Array(), { abi: {state: {}, functions: {}}}) + const tx = new TransactionBuilder("transfer").setContract(contract) + expect(tx.data.contract?.bytecode).toStrictEqual(contract); }); }); @@ -150,16 +150,12 @@ describe("Transaction builder", () => { describe("previousSignaturePayload", () => { it("should generate binary encoding of the transaction before signing", () => { - const contract = { - bytecode: new Uint8Array(5), - manifest: { - abi: { - state: { "value": "u32"}, - functions: { - } - } + 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."; @@ -199,8 +195,7 @@ describe("Transaction builder", () => { //Contract bytecode size intToUint32Array(contract.bytecode.length), contract.bytecode, - intToUint32Array(JSON.stringify(contract.manifest).length), - new TextEncoder().encode(JSON.stringify(contract.manifest)), + typed_encoding.serialize(contract.manifest), //Content size intToUint32Array(content.length), new TextEncoder().encode(content), From d01fc62514b69a63f6d510da380656ef1ae1cbce Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 11 Oct 2024 13:43:38 +0200 Subject: [PATCH 14/21] lint --- src/contract.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/contract.ts b/src/contract.ts index 768e45a..a94c4a1 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -92,12 +92,15 @@ export async function newContractTransaction( return tx.build(seed, index); } -export async function updateContractTransaction( +export function updateContractTransaction( archethic: Archethic, contractAddress: string, contract: Contract -): Promise { - return archethic.transaction.new().setType("transfer").addRecipient(contractAddress, "upgrade", contract); +): ExtendedTransactionBuilder { + return archethic.transaction + .new() + .setType("transfer") + .addRecipient(contractAddress, "upgrade", contract); } export class Contract { From 4a26cef435cff6878b866dc639c1760aa59a1034 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Mon, 9 Dec 2024 16:16:54 +0100 Subject: [PATCH 15/21] improve error logging in tx builder example --- example/transactionBuilder/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index 87bb117..96b1196 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -384,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)}

`; } } From d4420d7163eaeccfe466f29708eaa108436a4e4e Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Fri, 20 Dec 2024 13:55:19 +0100 Subject: [PATCH 16/21] fix code serialization issue --- src/transaction_builder.ts | 1 + tests/transaction_builder.test.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 19be7e3..f835f5d 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -415,6 +415,7 @@ export default class TransactionBuilder { intToUint32Array(VERSION), this.address, Uint8Array.from([getTransactionTypeId(this.type)]), + intToUint32Array(0), // Default code size bufContract, bufContentSize, new TextEncoder().encode(this.data.content), diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index b8af2fc..2922490 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -44,7 +44,7 @@ describe("Transaction builder", () => { it("should insert the code into the transaction data", () => { const contract = new Contract(new Uint8Array(), { abi: {state: {}, functions: {}}}) const tx = new TransactionBuilder("transfer").setContract(contract) - expect(tx.data.contract?.bytecode).toStrictEqual(contract); + expect(tx.data.contract?.bytecode).toStrictEqual(contract.bytecode); }); }); @@ -192,6 +192,7 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), + intToUint32Array(0), // Default code size //Contract bytecode size intToUint32Array(contract.bytecode.length), contract.bytecode, @@ -272,6 +273,7 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), + intToUint32Array(0), // Default code size //Contract size intToUint32Array(0), //Content size From 5b871a6561be0d6c9020e65f7a0544c85df519ff Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 27 Dec 2024 11:21:01 +0100 Subject: [PATCH 17/21] Update serialization, set recipient args only object --- src/transaction_builder.ts | 86 ++++++++++++++----------------- src/types.ts | 4 +- tests/network.test.ts | 8 +-- tests/transaction_builder.test.ts | 48 +++++++++-------- 4 files changed, 67 insertions(+), 79 deletions(-) diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index f835f5d..636a2a9 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -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; @@ -199,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[] | object} args The arguments for the named action + * @param {object} args The arguments for the named action */ - addRecipient(to: string | Uint8Array, action?: string, args?: any[] | object) { + 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 && typeof(args) !== "object") { - throw new Error("`args` must be an object or 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 }); @@ -274,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(", ") ); } @@ -335,14 +335,16 @@ export default class TransactionBuilder { * Generate the payload for the previous signature by encoding address, type and data */ previousSignaturePayload() { - let bufContract: Uint8Array = intToUint32Array(0) + let bufContract: Uint8Array; if (this.data.contract != undefined) { - const contract = this.data.contract bufContract = concatUint8Arrays( - intToUint32Array(contract.bytecode.byteLength), - contract.bytecode, + 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; @@ -364,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, @@ -380,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 instanceof Array ? args.map((arg) => TE.serialize(arg)) : [TE.serialize(args)]; - - 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); @@ -415,7 +406,6 @@ export default class TransactionBuilder { intToUint32Array(VERSION), this.address, Uint8Array.from([getTransactionTypeId(this.type)]), - intToUint32Array(0), // Default code size bufContract, bufContentSize, new TextEncoder().encode(this.data.content), @@ -447,7 +437,7 @@ export default class TransactionBuilder { contract: this.data.contract ? { bytecode: uint8ArrayToHex(this.data.contract?.bytecode), manifest: this.data.contract?.manifest - } : undefined, + } : undefined, ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), diff --git a/src/types.ts b/src/types.ts index 79594f0..dc423f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -110,8 +110,8 @@ type TokenTransfer = { export type Recipient = { address: Uint8Array; - action?: string; - args?: any[] | object; + action: string; + args: object; }; export type Ownership = { diff --git a/tests/network.test.ts b/tests/network.test.ts index bfec707..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: { @@ -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: { @@ -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: { diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index 2922490..bddbcf8 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -1,6 +1,6 @@ import TransactionBuilder, { VERSION } from "../src/transaction_builder"; -import { deriveAddress, deriveKeyPair, sign, verify } from "../src/crypto"; -import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, parseBigInt } from "../src/utils"; +import { deriveAddress, deriveKeyPair, verify } from "../src/crypto"; +import { concatUint8Arrays, hexToUint8Array, intToUint32Array, intToUint64Array, intToUint8Array, parseBigInt } from "../src/utils"; import TE from "../src/typed_encoding"; import { Contract } from "../src/contract"; import typed_encoding from "../src/typed_encoding"; @@ -42,7 +42,7 @@ describe("Transaction builder", () => { describe("setContract", () => { it("should insert the code into the transaction data", () => { - const contract = new Contract(new Uint8Array(), { abi: {state: {}, functions: {}}}) + const contract = new Contract(new Uint8Array(), { abi: { state: {}, functions: {} } }) const tx = new TransactionBuilder("transfer").setContract(contract) expect(tx.data.contract?.bytecode).toStrictEqual(contract.bytecode); }); @@ -123,19 +123,18 @@ describe("Transaction builder", () => { .addRecipient( "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646", "vote", - ["Miles"] + { name: "Miles" } ) - .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe"}) + .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe" }) expect(tx.data.recipients.length).toBe(2); expect(tx.data.recipients[0].action).toBe("vote"); - const listArgs = tx.data.recipients[0].args as string[] - expect(listArgs.length).toBe(1); - expect(listArgs[0]).toBe("Miles"); + const objectArg1 = tx.data.recipients[0].args as { name: string } + expect(objectArg1.name).toBe("Miles"); - const objectArg = tx.data.recipients[1].args as { name: string } - expect(objectArg.name).toBe("John Doe"); + const objectArg2 = tx.data.recipients[1].args as { name: string } + expect(objectArg2.name).toBe("John Doe"); }); it("should throw if types are incorrect", () => { @@ -152,7 +151,7 @@ describe("Transaction builder", () => { it("should generate binary encoding of the transaction before signing", () => { const contract = new Contract(new Uint8Array(5), { abi: { - state: { "value": "u32"}, + state: { "value": "u32" }, functions: {} } }) @@ -177,8 +176,8 @@ describe("Transaction builder", () => { ) .setContract(contract) .setContent(content) - .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88") - .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe"}); + .addRecipient("0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88", "action") + .addRecipient("0000ae12908c889ba2728614abc6ae472ed9f1df0b3afefd1faa290473a47ceb42bd", "addHero", { name: "John Doe" }); const keypair = deriveKeyPair("seed", 0); @@ -192,7 +191,8 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - intToUint32Array(0), // Default code size + //Contract is filled + intToUint8Array(1), //Contract bytecode size intToUint32Array(contract.bytecode.length), contract.bytecode, @@ -239,17 +239,18 @@ describe("Transaction builder", () => { Uint8Array.from([1]), // Nb of recipients Uint8Array.from([2]), - // 0 = unnamed recipient - Uint8Array.from([0]), + // 0 = named recipient + Uint8Array.from([1]), 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"), - // Nb args - Uint8Array.from([1]), - TE.serialize({ name: "John Doe"}) + TE.serialize({ name: "John Doe" }) ); expect(payload).toEqual(expected_binary); }); @@ -258,7 +259,7 @@ describe("Transaction builder", () => { const tx = new TransactionBuilder("transfer").addRecipient( "0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88", "set_geopos", - [{ lng: 2, lat: 1 }] + { lng: 2, lat: 1 } ); const keypair = deriveKeyPair("seed", 0); @@ -273,9 +274,8 @@ describe("Transaction builder", () => { intToUint32Array(VERSION), tx.address, Uint8Array.from([253]), - intToUint32Array(0), // Default code size - //Contract size - intToUint32Array(0), + //No contract + intToUint8Array(0), //Content size intToUint32Array(0), // Nb of byte to encode nb of ownerships @@ -302,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 }) ); From 9c84de9144764bae6b8826d1da4a3ddeb8ea4a6b Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 27 Dec 2024 15:50:21 +0100 Subject: [PATCH 18/21] Update readme --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 010a8f7..fc28d46 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: + - `bytescode` 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(); + ``` From 330c7e674e89f459f5028bf8a26107d4499b1886 Mon Sep 17 00:00:00 2001 From: Neylix Date: Fri, 27 Dec 2024 15:50:55 +0100 Subject: [PATCH 19/21] 2.0.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa2839b..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", From 0403c5709e7052fd6bc9071fc3357e4ec2862423 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 9 Jan 2025 11:04:47 +0100 Subject: [PATCH 20/21] Remove unused class --- src/contract.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/contract.ts b/src/contract.ts index a94c4a1..ec84e2c 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -3,12 +3,6 @@ import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; import { deflateRaw } from "pako"; -import TransactionBuilder from "./transaction_builder.js"; - -export type CodeWithManifest = { - bytecode: string; - manifest: ContractManifest; -}; export type ContractManifest = { abi: WasmABI; @@ -111,7 +105,7 @@ export class Contract { this.bytecode = compress ? deflateRaw(bytecode) : bytecode; this.manifest = manifest; } - + getActions(): ContractAction[] { let actions: ContractAction[] = []; for (let name of Object.keys(this.manifest.abi.functions)) { @@ -126,4 +120,4 @@ export class Contract { } return actions; } -} \ No newline at end of file +} From 012f5057899517d72bca6e7403915ed659d8fd64 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 9 Jan 2025 11:07:52 +0100 Subject: [PATCH 21/21] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc28d46..d071aa1 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,7 @@ Define the transaction type Add the contract in the `data.contract` section of the transaction - `contract` is an object with following keys: - - `bytescode` Uint8Array of the compiled wasm code compressed using zip + - `bytecode` Uint8Array of the compiled wasm code compressed using zip - `manifest` the manifest of the contrat containing actions and functions spec ```js