/ src / frontend / utils.ts
utils.ts
  1  import { Principal } from "@dfinity/principal";
  2  import { getCrc32 } from "@dfinity/principal/lib/cjs/utils/getCrc";
  3  import { sha224 } from "@dfinity/principal/lib/cjs/utils/sha224.js";
  4  import { Buffer } from "buffer";
  5  import bigInt from "big-integer";
  6  import store from "@redux/Store";
  7  import {
  8    Transaction,
  9    Operation,
 10    RosettaTransaction,
 11    Asset,
 12  } from "./redux/models/AccountModels";
 13  import {
 14    IcrcTokenMetadataResponse,
 15    IcrcAccount,
 16    encodeIcrcAccount,
 17  } from "@dfinity/ledger";
 18  import {
 19    OperationStatusEnum,
 20    OperationTypeEnum,
 21    TransactionTypeEnum,
 22    TransactionType,
 23  } from "./const";
 24  import { Transaction as T } from "@dfinity/ledger/dist/candid/icrc1_index";
 25  import {
 26    isNullish,
 27    uint8ArrayToHexString,
 28    bigEndianCrc32,
 29    encodeBase32,
 30  } from "@dfinity/utils";
 31  import { AccountIdentifier, SubAccount as SubAccountNNS } from "@dfinity/nns";
 32  
 33  export const MILI_PER_SECOND = 1000000;
 34  
 35  export const getEncodeCrc = ({ owner, subaccount }: IcrcAccount): string => {
 36    const crc = bigEndianCrc32(
 37      Uint8Array.from([
 38        ...owner.toUint8Array(),
 39        ...(subaccount || toUint8Array(0)),
 40      ]),
 41    );
 42    return encodeBase32(crc);
 43  };
 44  
 45  export const uint8ArrayToNum = (arr: Uint8Array | undefined, len?: number) => {
 46    if (arr) {
 47      const size = len ? len : 32;
 48      let num = 0;
 49      for (let i = 0; i < size; i++) {
 50        num += Math.pow(256, size - 1 - i) * arr[i];
 51      }
 52      return num;
 53    } else return 0;
 54  };
 55  
 56  export const hexToNumber = (hexFormat: string) => {
 57    if (hexFormat.slice(0, 2) !== "0x") return undefined;
 58    const hex = hexFormat.substring(2);
 59    if (/^[a-fA-F0-9]+$/.test(hex)) {
 60      let numb = bigInt();
 61      for (let index = 0; index < hex.length; index++) {
 62        const digit = hex[hex.length - index - 1];
 63        numb = numb.add(
 64          bigInt(16)
 65            .pow(bigInt(index))
 66            .multiply(bigInt(`0x${digit}`)),
 67        );
 68      }
 69      return numb;
 70    } else {
 71      return undefined;
 72    }
 73  };
 74  
 75  export const checkHexString = (e: string) => {
 76    let newValue = e.trim();
 77    if (e.trim().slice(0, 2).toLowerCase() === "0x")
 78      newValue = newValue.substring(2);
 79    return (
 80      (newValue === "" || /^[a-fA-F0-9]+$/.test(newValue)) && newValue.length < 65
 81    );
 82  };
 83  
 84  export const getICRC1Acc = ({ owner, subaccount }: IcrcAccount): string => {
 85    const crc = encodeIcrcAccount({ owner, subaccount });
 86    return crc;
 87  };
 88  
 89  export const getFirstNFrom = (address: string, digits: number) => {
 90    return `${address.slice(0, digits).toUpperCase()}`;
 91  };
 92  
 93  export const getFirstNChars = (str: string, digits: number) => {
 94    if (str.length > digits) return `${str.slice(0, digits)}...`;
 95    else return str;
 96  };
 97  
 98  export const shortAddress = (
 99    address: string,
100    digitsL: number,
101    digitsR: number,
102    prefix?: string,
103    sufix?: string,
104  ) => {
105    return `${prefix ? prefix : ""}${address.slice(
106      0,
107      digitsL,
108    )} ... ${address.slice(-digitsR)}${sufix ? sufix : ""}`;
109  };
110  
111  export const principalToAccountIdentifier = (
112    p: string,
113    s?: number[] | number,
114  ) => {
115    const padding = Buffer.from("\x0Aaccount-id");
116    const array = new Uint8Array([
117      ...padding,
118      ...Principal.fromText(p).toUint8Array(),
119      ...getSubAccountArray(s),
120    ]);
121    const hash = sha224(array);
122    const checksum = to32bits(getCrc32(hash));
123    const array2 = new Uint8Array([...checksum, ...hash]);
124    return array2;
125  };
126  export const roundToDecimalN = (
127    numb: number | string,
128    decimal: number | string,
129  ) => {
130    return (
131      Math.round(Number(numb) * Math.pow(10, Number(decimal))) /
132      Math.pow(10, Number(decimal))
133    );
134  };
135  
136  export function toFullDecimal(numb: number | string, decimal: number | string) {
137    if (Number(numb) === 0) return "0";
138    let x =
139      Math.round(Number(numb) * Math.pow(10, Number(decimal))) /
140      Math.pow(10, Number(decimal));
141    if (Math.abs(x) < 0.000001) {
142      const e = parseInt(x.toString().split("e-")[1]);
143      if (e) {
144        (x *= Math.pow(10, e - 1)), decimal;
145        return (
146          "0." +
147          new Array(e).join("0") +
148          roundToDecimalN(x, decimal).toString().substring(2)
149        );
150      }
151    }
152    return x.toString();
153  }
154  export const getUSDfromToken = (
155    tokenAmount: string | number,
156    marketPrice: string | number,
157    decimal: string | number,
158  ) => {
159    return (
160      (Number(tokenAmount) * Number(marketPrice)) /
161      Math.pow(10, Number(decimal))
162    ).toFixed(2);
163  };
164  
165  export const removeLeadingZeros = (text: string): string =>
166    text.replace(/^0+/, "");
167  
168  export const getSubAccountNumber = (
169    subaccount?: Uint8Array,
170    prefix?: string,
171    sufix?: string,
172  ) => {
173    if (isNullish(subaccount))
174      return `${prefix ? prefix : ""}0${sufix ? sufix : ""}`;
175  
176    const subaccountText = removeLeadingZeros(uint8ArrayToHexString(subaccount));
177  
178    if (subaccountText.length === 0) {
179      return `${prefix ? prefix : ""}0${sufix ? sufix : ""}`;
180    }
181    return `${prefix ? prefix : ""}${subaccountText}${sufix ? sufix : ""}`;
182  };
183  
184  export const getSubAccountUint8Array = (subaccount: string | number) => {
185    return new Uint8Array(getSubAccountArray(Number(subaccount)));
186  };
187  
188  export const getSubAccountArray = (s?: number[] | number) => {
189    if (Array.isArray(s)) {
190      return s.concat(Array(32 - s.length).fill(0));
191    } else {
192      return Array(28)
193        .fill(0)
194        .concat(to32bits(s ? s : 0));
195    }
196  };
197  
198  export const hexToUint8Array = (hex: string) => {
199    const zero = bigInt(0);
200    const n256 = bigInt(256);
201    let bigNumber = hexToNumber(hex);
202    if (bigNumber) {
203      const result = new Uint8Array(32);
204      let i = 0;
205      while (bigNumber.greater(zero)) {
206        result[32 - i - 1] = bigNumber.mod(n256).toJSNumber();
207        bigNumber = bigNumber.divide(n256);
208        i += 1;
209      }
210      return result;
211    } else return new Uint8Array(32);
212  };
213  
214  export const subUint8ArrayToHex = (sub: Uint8Array | undefined) => {
215    if (sub) {
216      const hex = removeLeadingZeros(Buffer.from(sub).toString("hex"));
217      if (hex === "") return "0";
218      else return hex;
219    } else {
220      return "0";
221    }
222  };
223  
224  export const to32bits = (num: number) => {
225    const b = new ArrayBuffer(4);
226    new DataView(b).setUint32(0, num);
227    return Array.from(new Uint8Array(b));
228  };
229  
230  export const toUint8Array = (num: number) => {
231    return new Uint8Array(num);
232  };
233  
234  export const toNumberFromUint8Array = (Uint8Arr: Uint8Array) => {
235    let size = Uint8Arr.length;
236    let buffer = Buffer.from(Uint8Arr);
237    let result = buffer.readUIntBE(0, size);
238    return result;
239  };
240  
241  export const getAddress = (
242    type: TransactionType,
243    fromAddress: string,
244    fromSub: string,
245    accountAddres: string,
246    accountSub: string,
247  ) => {
248    if (type !== TransactionTypeEnum.Enum.NONE) {
249      if (type === TransactionTypeEnum.Enum.RECEIVE) {
250        return false;
251      } else {
252        return true;
253      }
254    } else {
255      if (accountAddres !== fromAddress) {
256        return false;
257      } else {
258        if (fromSub === accountSub) {
259          return true;
260        } else {
261          return false;
262        }
263      }
264    }
265  };
266  
267  export const getICPSubaccountsArray = async () => {
268    let sub: string[] = [];
269    const myAgent = store.getState().auth.userAgent;
270    const myPrincipal = await myAgent.getPrincipal();
271  
272    for (let index = 0; index <= 10; index++) {
273      sub[index] = AccountIdentifier.fromPrincipal({
274        principal: myPrincipal,
275        subAccount: SubAccountNNS.fromID(Number(index)),
276      }).toHex();
277    }
278  
279    return sub;
280  };
281  
282  export const getAccountIdentifier = (pricipal: string, sub: number) => {
283    return AccountIdentifier.fromPrincipal({
284      principal: Principal.fromText(pricipal),
285      subAccount: SubAccountNNS.fromID(Number(sub)),
286    }).toHex();
287  };
288  
289  export const formatIcpTransaccion = (
290    accountId: string,
291    rosettaTransaction: RosettaTransaction,
292  ): Transaction => {
293    const {
294      operations,
295      metadata: { timestamp, block_height },
296      transaction_identifier: { hash },
297    } = rosettaTransaction;
298    const transaction = {
299      status: OperationStatusEnum.Enum.COMPLETED,
300    } as Transaction;
301    operations?.forEach((operation: Operation, i: number) => {
302      const value = BigInt(operation.amount.value);
303      const amount = value.toString();
304      if (operation.type === OperationTypeEnum.Enum.FEE) {
305        transaction.fee = amount;
306        return;
307      }
308  
309      if (value > 0) {
310        transaction.to = operation.account.address;
311      } else if (value < 0) {
312        transaction.from = operation.account.address;
313      } else {
314        if (i === 0) {
315          transaction.from = operation.account.address;
316        }
317        if (i === 1) {
318          transaction.to = operation.account.address;
319        }
320      }
321  
322      if (
323        transaction.status === OperationStatusEnum.Enum.COMPLETED &&
324        operation.status !== OperationStatusEnum.Enum.COMPLETED
325      )
326        transaction.status = operation.status;
327  
328      transaction.type =
329        transaction.to === accountId
330          ? TransactionTypeEnum.Enum.RECEIVE
331          : TransactionTypeEnum.Enum.SEND;
332      transaction.amount = amount;
333      transaction.canisterId = import.meta.env.VITE_ICP_LEDGER_CANISTER_ID;
334      transaction.idx = block_height.toString();
335      transaction.symbol = operation.amount.currency.symbol;
336    });
337  
338    return {
339      ...transaction,
340      hash,
341      timestamp: Math.floor(timestamp / MILI_PER_SECOND),
342    } as Transaction;
343  };
344  
345  export const formatckBTCTransaccion = (
346    ckBTCTransaction: T,
347    id: bigint,
348    principal: string,
349    symbol: string,
350    canister: string,
351    subNumber?: string,
352  ): Transaction => {
353    const { timestamp, transfer } = ckBTCTransaction;
354    const trans = { status: OperationStatusEnum.Enum.COMPLETED } as Transaction;
355    transfer?.forEach((operation: any) => {
356      const value = operation.amount;
357      const amount = value.toString();
358      trans.to = (operation.to.owner as Principal).toString();
359      trans.from = (operation.from.owner as Principal).toString();
360  
361      if (operation.to.subaccount.length > 0)
362        trans.toSub = `0x${subUint8ArrayToHex(
363          (operation.to.subaccount as [Uint8Array])[0],
364        )}`;
365      else trans.toSub = "0x0";
366  
367      if (operation.from.subaccount.length > 0)
368        trans.fromSub = `0x${subUint8ArrayToHex(
369          (operation.from.subaccount as [Uint8Array])[0],
370        )}`;
371      else trans.fromSub = "0x0";
372  
373      const subCheck = subNumber;
374      if (trans.from === principal && trans.fromSub === subCheck) {
375        trans.type = TransactionTypeEnum.Enum.SEND;
376      } else {
377        trans.type = TransactionTypeEnum.Enum.RECEIVE;
378      }
379  
380      trans.canisterId = canister;
381      trans.symbol = symbol;
382      trans.amount = amount;
383      trans.idx = id.toString();
384  
385      let subaccTo: SubAccountNNS | undefined = undefined;
386      try {
387        subaccTo = SubAccountNNS.fromBytes(
388          (operation.to.subaccount as [Uint8Array])[0],
389        ) as SubAccountNNS;
390      } catch {
391        subaccTo = undefined;
392      }
393      trans.identityTo = AccountIdentifier.fromPrincipal({
394        principal: operation.to.owner as Principal,
395        subAccount: subaccTo,
396      }).toHex();
397  
398      let subaccFrom: SubAccountNNS | undefined = undefined;
399      try {
400        subaccFrom = SubAccountNNS.fromBytes(
401          (operation.to.subaccount as [Uint8Array])[0],
402        ) as SubAccountNNS;
403      } catch {
404        subaccFrom = undefined;
405      }
406      trans.identityFrom = AccountIdentifier.fromPrincipal({
407        principal: operation.from.owner as Principal,
408        subAccount: subaccFrom,
409      }).toHex();
410    });
411    return {
412      ...trans,
413      timestamp: Math.floor(Number(timestamp) / MILI_PER_SECOND),
414    } as Transaction;
415  };
416  
417  export const getMetadataInfo = (myMetadata: IcrcTokenMetadataResponse) => {
418    let symbol = "symbol";
419    let name = "symbol";
420    let decimals = 0;
421    let logo = "";
422  
423    myMetadata.map((dt) => {
424      if (dt[0] === "icrc1:symbol") {
425        const auxSymbol = dt[1] as { Text: string };
426        symbol = auxSymbol.Text;
427      }
428      if (dt[0] === "icrc1:name") {
429        const auxName = dt[1] as { Text: string };
430        name = auxName.Text;
431      }
432      if (dt[0] === "icrc1:decimals") {
433        const auxDec = dt[1] as any;
434        decimals = Number(auxDec.Nat);
435      }
436      if (dt[0] === "icrc1:logo") {
437        const auxName = dt[1] as { Text: string };
438        logo = auxName.Text;
439      }
440    });
441  
442    return { symbol, name, decimals, logo };
443  };
444  
445  export const getInitialFromName = (name: string, length: number) => {
446    if (name.length === 0) {
447      return "";
448    } else {
449      const names = name.split(" ");
450      let initials = "";
451      names.map((nm) => {
452        if (nm.trim().length > 0) initials = initials + nm.trim()[0];
453      });
454      return initials.toUpperCase().slice(0, length);
455    }
456  };
457  
458  export const getAssetSymbol = (symbol: string, assets: Array<Asset>) => {
459    return assets.find((a: Asset) => {
460      return a.tokenSymbol === symbol;
461    })?.symbol;
462  };