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 };