koilib 5.5.5 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
2
+ import { sha256 } from "@noble/hashes/sha256";
1
3
  import { Contract } from "./Contract";
2
4
  import { Provider } from "./Provider";
3
5
  import { Signer } from "./Signer";
@@ -8,8 +10,75 @@ import {
8
10
  TransactionJson,
9
11
  TransactionOptions,
10
12
  TransactionReceipt,
13
+ TypeField,
11
14
  WaitFunction,
12
15
  } from "./interface";
16
+ import {
17
+ btypeDecode,
18
+ calculateMerkleRoot,
19
+ encodeBase64url,
20
+ toHexString,
21
+ } from "./utils";
22
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
23
+ // @ts-ignore
24
+ import { koinos } from "./protoModules/protocol-proto.js";
25
+
26
+ const btypeTransactionHeader: TypeField["subtypes"] = {
27
+ chain_id: { type: "bytes" },
28
+ rc_limit: { type: "uint64" },
29
+ nonce: { type: "bytes" },
30
+ operation_merkle_root: { type: "bytes" },
31
+ payer: { type: "bytes", btype: "ADDRESS" },
32
+ payee: { type: "bytes", btype: "ADDRESS" },
33
+ };
34
+
35
+ const btypesOperation: TypeField["subtypes"] = {
36
+ upload_contract: {
37
+ type: "object",
38
+ subtypes: {
39
+ contract_id: { type: "bytes", btype: "CONTRACT_ID" },
40
+ bytecode: { type: "bytes" },
41
+ abi: { type: "string" },
42
+ authorizes_call_contract: { type: "bool" },
43
+ authorizes_transaction_application: { type: "bool" },
44
+ authorizes_upload_contract: { type: "bool" },
45
+ },
46
+ },
47
+ call_contract: {
48
+ type: "object",
49
+ subtypes: {
50
+ contract_id: { type: "bytes", btype: "CONTRACT_ID" },
51
+ entry_point: { type: "uint32" },
52
+ args: { type: "bytes" },
53
+ },
54
+ },
55
+ set_system_call: {
56
+ type: "object",
57
+ subtypes: {
58
+ call_id: { type: "uint32" },
59
+ target: {
60
+ type: "object",
61
+ subtypes: {
62
+ thunk_id: { type: "uint32" },
63
+ system_call_bundle: {
64
+ type: "object",
65
+ subtypes: {
66
+ contract_id: { type: "bytes", btype: "CONTRACT_ID" },
67
+ entry_point: { type: "uint32" },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ set_system_contract: {
75
+ type: "object",
76
+ subtypes: {
77
+ contract_id: { type: "bytes", btype: "CONTRACT_ID" },
78
+ system_contract: { type: "bool" },
79
+ },
80
+ },
81
+ };
13
82
 
14
83
  export class Transaction {
15
84
  /**
@@ -43,7 +112,7 @@ export class Transaction {
43
112
  options?: TransactionOptions;
44
113
  }) {
45
114
  this.signer = c?.signer;
46
- this.provider = c?.provider;
115
+ this.provider = c?.provider || c?.signer?.provider;
47
116
  this.options = {
48
117
  broadcast: true,
49
118
  sendAbis: true,
@@ -147,6 +216,110 @@ export class Transaction {
147
216
  this.transaction.operations.push(operation);
148
217
  }
149
218
 
219
+ /**
220
+ * Function to prepare a transaction
221
+ * @param tx - Do not set the nonce to get it from the blockchain
222
+ * using the provider. The rc_limit is 1e8 by default.
223
+ * @param provider - Provider
224
+ * @param payer - payer to be used in case it is not defined in the transaction
225
+ * @returns A prepared transaction.
226
+ */
227
+ static async prepareTransaction(
228
+ tx: TransactionJson,
229
+ provider?: Provider,
230
+ payer?: string
231
+ ): Promise<TransactionJson> {
232
+ if (!tx.header) {
233
+ tx.header = {};
234
+ }
235
+
236
+ const { payer: payerHeader, payee } = tx.header;
237
+ if (!payerHeader) tx.header.payer = payer;
238
+ payer = tx.header.payer;
239
+ if (!payer) throw new Error("payer is undefined");
240
+
241
+ let nonce: string;
242
+ if (tx.header.nonce === undefined) {
243
+ if (!provider)
244
+ throw new Error(
245
+ "Cannot get the nonce because provider is undefined. To skip this call set a nonce in the transaction header"
246
+ );
247
+ nonce = await provider.getNextNonce(payee || payer);
248
+ } else {
249
+ nonce = tx.header.nonce;
250
+ }
251
+
252
+ let rcLimit: string;
253
+ if (tx.header.rc_limit === undefined) {
254
+ if (!provider)
255
+ throw new Error(
256
+ "Cannot get the rc_limit because provider is undefined. To skip this call set a rc_limit in the transaction header"
257
+ );
258
+ rcLimit = await provider.getAccountRc(payer);
259
+ } else {
260
+ rcLimit = tx.header.rc_limit;
261
+ }
262
+
263
+ if (!tx.header.chain_id) {
264
+ if (!provider)
265
+ throw new Error(
266
+ "Cannot get the chain_id because provider is undefined. To skip this call set a chain_id"
267
+ );
268
+ tx.header.chain_id = await provider.getChainId();
269
+ }
270
+
271
+ const operationsHashes: Uint8Array[] = [];
272
+
273
+ if (tx.operations) {
274
+ for (let index = 0; index < tx.operations?.length; index += 1) {
275
+ const operationDecoded = btypeDecode(
276
+ tx.operations[index],
277
+ btypesOperation!,
278
+ false
279
+ );
280
+ const message = koinos.protocol.operation.create(operationDecoded);
281
+ const operationEncoded = koinos.protocol.operation
282
+ .encode(message)
283
+ .finish() as Uint8Array;
284
+ operationsHashes.push(sha256(operationEncoded));
285
+ }
286
+ }
287
+ const operationMerkleRoot = encodeBase64url(
288
+ new Uint8Array([
289
+ // multihash sha256: 18, 32
290
+ 18,
291
+ 32,
292
+ ...calculateMerkleRoot(operationsHashes),
293
+ ])
294
+ );
295
+
296
+ tx.header = {
297
+ chain_id: tx.header.chain_id,
298
+ rc_limit: rcLimit,
299
+ nonce,
300
+ operation_merkle_root: operationMerkleRoot,
301
+ payer,
302
+ ...(payee && { payee }),
303
+ // TODO: Option to resolve names (payer, payee)
304
+ };
305
+
306
+ const headerDecoded = btypeDecode(
307
+ tx.header,
308
+ btypeTransactionHeader!,
309
+ false
310
+ );
311
+ const message = koinos.protocol.transaction_header.create(headerDecoded);
312
+ const headerBytes = koinos.protocol.transaction_header
313
+ .encode(message)
314
+ .finish() as Uint8Array;
315
+
316
+ const hash = sha256(headerBytes);
317
+
318
+ // multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
319
+ tx.id = `0x1220${toHexString(hash)}`;
320
+ return tx;
321
+ }
322
+
150
323
  /**
151
324
  * Functon to prepare the transaction (set headers, merkle
152
325
  * root, etc)
@@ -165,17 +338,11 @@ export class Transaction {
165
338
  ...header,
166
339
  };
167
340
  }
168
-
169
- if (this.signer) {
170
- this.transaction = await this.signer.prepareTransaction(this.transaction);
171
- } else {
172
- if (!this.transaction.header || !this.transaction.header.payer) {
173
- throw new Error("no payer defined");
174
- }
175
- const signer = Signer.fromSeed("0");
176
- signer.provider = this.provider;
177
- this.transaction = await signer.prepareTransaction(this.transaction);
178
- }
341
+ this.transaction = await Transaction.prepareTransaction(
342
+ this.transaction,
343
+ this.provider,
344
+ this.signer?.getAddress()
345
+ );
179
346
  return this.transaction;
180
347
  }
181
348
 
package/src/utils.ts CHANGED
@@ -308,6 +308,14 @@ export function parseUnits(value: string, decimals: number): string {
308
308
  .split(".");
309
309
  if (!decimalPart) decimalPart = "";
310
310
  decimalPart = decimalPart.padEnd(decimals, "0");
311
+ if (decimalPart.length > decimals) {
312
+ // approximate
313
+ const miniDecimals = decimalPart.substring(decimals);
314
+ decimalPart = decimalPart.substring(0, decimals);
315
+ if (miniDecimals.startsWith("5")) {
316
+ decimalPart = (BigInt(decimalPart) + BigInt(1)).toString();
317
+ }
318
+ }
311
319
  return `${sign}${`${integerPart}${decimalPart}`.replace(/^0+(?=\d)/, "")}`;
312
320
  }
313
321