UNPKG

myria-core-sdk

Version:

Latest version SDK

448 lines 41 kB
import { MyriaClient } from "../"; import { ContractFactory } from "../core/ContractFactory"; import { ConfirmationType, TokenType } from "../types/CommonTypes"; import { DepositAPI, CommonAPI } from "../core/apis"; import { convertEthToWei } from "../utils/Converter"; import BN from "bn.js"; import DepositABI from '../contracts/Deposits.json'; import { validationChainNetwork } from "../utils/ValidationUtils"; import { DELAY_IN_RETRY, retryPromise, RETRY_DEFAULT } from "../utils/RetryUtils"; import { UserManager } from "./UserManager"; import { WithdrawalModule } from "./WithdrawalModule"; import Web3 from "web3"; import { CommonModule } from "./CommonModule"; const StarkwareLib = require("@starkware-industries/starkware-crypto-utils"); const assetUtils = StarkwareLib.asset; const DEFAULT_QUANTUM = "10000000000"; export class DepositModule { constructor(client) { this.client = new MyriaClient(client); if (!validationChainNetwork(this.client)) { throw Error('Network has been wrong and mismatch with the environment'); } this.commonModule = new CommonModule(client); this.depositContract = this.getDepositContract(this.client); this.erc20Contract = this.getERC20Contract(this.client); this.depositAPI = new DepositAPI(this.client.env); this.commonAPI = new CommonAPI(this.client.env); this.userManager = new UserManager(client); this.withdrawModule = new WithdrawalModule(client); } getDepositContract(client) { const contractFactory = new ContractFactory(client); return contractFactory.getDepositContract(); } getCustomDepositContract(customERC20Network) { const contractFactory = new ContractFactory(this.client); return contractFactory.getCustomDepositContract(customERC20Network); } getERC20Contract(client) { const contractFactory = new ContractFactory(client); return contractFactory.getERC20Contract(); } getCustomERC20Contract(customERC20Network) { const contractFactory = new ContractFactory(this.client); return contractFactory.getCustomERC20Contract(customERC20Network); } async depositEthereum(depositParams, options) { var _a; if (depositParams.tokenType !== TokenType.ETH) { throw new Error("Token type is invalid, only allow to execute the operation with Ethereum"); } if (!depositParams.starkKey) { throw new Error("Stark key is required to do this operation"); } if (!depositParams.amount || depositParams.amount === "0") { throw new Error("Amount must be greater than 0 to do this operation"); } if (!(options === null || options === void 0 ? void 0 : options.from)) { throw new Error('Required wallet address to operate'); } const quantumEth = "10000000000"; console.time('Get_asset_type'); const assetType = assetUtils.getAssetType({ type: depositParams.tokenType, data: { quantum: quantumEth, }, }); console.timeEnd('Get_asset_type'); let vaultIdData; console.time('retrieveVault'); if (!depositParams.vaultId) { try { const vaultResponse = await this.commonAPI.retrieveVault({ starkKey: depositParams.starkKey, tokenType: depositParams.tokenType, quantum: quantumEth, tokenAddress: (depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenAddress) || undefined, }); if ((vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.status) === "success") { vaultIdData = (_a = vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.data) === null || _a === void 0 ? void 0 : _a.vaultId; } else { throw new Error("Fetching vaultID failure - check BE server or validation request for calling"); } } catch (ex) { throw new Error("Fetching vaultID failure"); } } else { vaultIdData = depositParams.vaultId; } console.timeEnd('retrieveVault'); // Make on-chain deposit request console.time('Deposit_onchain'); let onchainDepositResult; try { onchainDepositResult = await this.depositContract.deposit(depositParams.starkKey, assetType, vaultIdData, options); console.log(`Onchain deposit result ==> ${JSON.stringify(onchainDepositResult)}`); } catch (ex) { throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`); } console.timeEnd('Deposit_onchain'); if (!(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) || (onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) === undefined) { throw new Error("Onchain deposit has been failure - transaction hash must be returned from onchain.."); } const convertAmount = convertEthToWei(depositParams.amount); console.time('getSignatureDepositEthOffchain'); const depositSignParams = { ethAddress: options === null || options === void 0 ? void 0 : options.from, vaultId: String(vaultIdData), txHash: onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash, amount: convertAmount }; const depositSignature = await this.commonModule.generateStarkSignatureForDeposit(depositSignParams); if (!depositSignature) { throw new Error("Failed to generate deposit signature"); } console.timeEnd('getSignatureDepositEthOffchain'); const depositEthRequestPayload = { amount: convertAmount, starkKey: depositParams.starkKey, transactionHash: onchainDepositResult.transactionHash, vaultId: vaultIdData, isNetworkTimeout: onchainDepositResult.isNetworkTimeout || false, signature: depositSignature }; console.time('Deposit_offchain'); try { const depositResponse = await retryPromise(this.depositAPI.offchainDepositEth(depositEthRequestPayload), RETRY_DEFAULT, DELAY_IN_RETRY); console.timeEnd('Deposit_offchain'); if ((depositResponse === null || depositResponse === void 0 ? void 0 : depositResponse.status) !== "success") { console.log('[Core-SDK] Deposit offchain failure'); throw new Error("Off-chain deposit failed!"); } } catch (err) { console.log('[Core-SDK] Deposit offchain failure with exception', err); throw new Error("Off-chain deposit failed!"); } return onchainDepositResult; } async depositEth(depositParams, options) { return this.depositEthereum(depositParams, options); } async depositCancel(depositParams, options) { if (!depositParams.starkKey) { throw new Error('Stark Key Is Required'); } if (!depositParams.assetId) { throw new Error("Asset ID is required"); } if (!depositParams.vaultId) { throw new Error("Vault ID is required"); } if (!depositParams.transactionId) { throw new Error("Transaction id is required"); } // Register on-chain let userHasRegistered = false; const responseUserOnchain = await this.withdrawModule.checkUserRegisterOnchain(depositParams.starkKey); if (!responseUserOnchain || String(responseUserOnchain).toLowerCase() !== depositParams.ethAddress.toLowerCase() || Web3.utils.toBN(String(responseUserOnchain)).isZero()) { const registerOnchainResult = await this.userManager.registerUserOnChain(depositParams.ethAddress, options); if (registerOnchainResult && registerOnchainResult.transactionHash) { await this.userManager.patchRegisterUserOnchain(depositParams.ethAddress, registerOnchainResult.transactionHash); userHasRegistered = true; console.log('Register Onchain Result -> ', registerOnchainResult); } } else { userHasRegistered = true; } if (!userHasRegistered) { throw new Error('User has not registered on-chain successfully yet, required register on-chain before doing this operation'); } // Call deposit cancel in the onchain let depositCancelResponse = undefined; try { depositCancelResponse = await this.depositContract.depositCancel(depositParams.starkKey, depositParams.assetId, depositParams.vaultId, { ...options, gas: 100000 }); } catch (ex) { console.log('Deposit cancellation error -> ', JSON.stringify(ex)); throw new Error('Deposit cancellation error -> ' + JSON.stringify(ex)); } if (!depositCancelResponse.transactionHash) { throw new Error('Transaction hash for depositCancel is required'); } // Submit deposit cancel to system let depositCancelOffchain = undefined; const depositParamsAPI = { ...depositParams, transactionHash: depositCancelResponse.transactionHash || '' }; try { depositCancelOffchain = await this.depositAPI.depositCancel(depositParamsAPI); if (depositCancelOffchain.status != 'success') { throw new Error('Deposit cancel offchain has been failed with internal server error'); } } catch (ex) { console.log('Deposit cancellation error -> ', JSON.stringify(ex)); throw new Error('Deposit cancellation error -> ' + JSON.stringify(ex)); } return depositCancelResponse; } async depositReclaim(depositParams, options) { if (!depositParams.starkKey) { throw new Error('Stark Key Is Required'); } if (!depositParams.assetId) { throw new Error("Asset ID is required"); } if (!depositParams.vaultId) { throw new Error("Vault ID is required"); } if (!depositParams.transactionId) { throw new Error("Transaction id is required"); } // Call deposit cancel in the onchain const depositReclaimResponse = await this.depositContract.depositReclaim(depositParams.starkKey, depositParams.assetId, depositParams.vaultId, options); if (!depositReclaimResponse || !depositReclaimResponse.transactionHash) { throw new Error('Deposit reclaimed have been failed'); } // Call deposit cancel complete endpoints const depositReclaimParams = { starkKey: depositParams.starkKey, vaultId: depositParams.vaultId, assetId: depositParams.assetId, reclaimTransactionHash: depositReclaimResponse.transactionHash, transactionId: depositParams.transactionId }; try { await this.depositAPI.depositReclaim(depositReclaimParams); } catch (ex) { console.log('Patch reclaim to system error -> ', ex); throw new Error('Patch reclaim to system error -> ' + ex); } return depositReclaimResponse; } async depositERC20(params, options) { if (!params.starkKey) { throw new Error('Stark key is required!'); } if (!params.ethAddress) { throw new Error('Connected wallet address is required!'); } if (!params.amount) { throw new Error('Amount is required!'); } if (!params.contractAddress) { throw new Error('Token address is required!'); } if (!params.quantum) { params.quantum = DEFAULT_QUANTUM; } if (!options) { options = { confirmationType: ConfirmationType.Confirmed }; } const networkId = String(this.client.networkId); const spenderAddress = params.spenderTokenAddress ? params.spenderTokenAddress : DepositABI.networks[networkId].address; const approvalOfSpender = await this.erc20Contract.allowance(params.ethAddress, spenderAddress, options); console.log('Approval of spender -> ', approvalOfSpender); if (Number(approvalOfSpender) < Number(params.amount)) { console.time('Approve_erc20'); try { await this.erc20Contract.approve(spenderAddress, params.amount, { from: params.ethAddress, confirmationType: ConfirmationType.Confirmed, nonce: options.nonce }); } catch (err) { throw new Error(`Approve Error: ${err}`); } console.timeEnd('Approve_erc20'); } console.timeEnd('Approve_erc20'); let vaultId = params.vaultId; if (!options.confirmationType) { options = { ...options, confirmationType: ConfirmationType.Confirmed }; } console.time('Get_vaults'); if (!params.vaultId) { try { const vaultRequest = { quantum: params.quantum, starkKey: params.starkKey, tokenAddress: params.contractAddress }; const retrieveResult = await this.commonAPI.retrieveERC20Vault(vaultRequest); if (retrieveResult.status === "success") { vaultId = retrieveResult.data.vaultId; } else { throw new Error('Retrieve vault failed!'); } } catch (err) { throw new Error(`Retriving vault error!: ${err}`); } } console.timeEnd('Get_vaults'); const assetType = assetUtils.getAssetType({ type: TokenType.ERC20, data: { quantum: params.quantum, tokenAddress: params.contractAddress, }, }); const quantizedAmount = new BN(params.amount, 10).div(new BN(params.quantum, 10)).toString(); let onchainDepositResult; console.time('depositOnchain'); try { onchainDepositResult = await this.depositContract.depositERC20(params.starkKey, assetType, String(vaultId), quantizedAmount, options); } catch (ex) { throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`); } console.timeEnd('depositOnchain'); if (!(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) || (onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) === undefined) { throw new Error("Onchain deposit has been failure - transaction hash must be returned from onchain.."); } console.time('getSignatureDepositOffchain'); const depositSignParams = { ethAddress: params.ethAddress, vaultId: String(vaultId), txHash: onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash, amount: params.amount }; const depositSignature = await this.commonModule.generateStarkSignatureForDeposit(depositSignParams); if (!depositSignature) { throw new Error("Failed to generate deposit signature"); } console.timeEnd('getSignatureDepositOffchain'); console.time('depositOffchain'); const depositERC20Request = { ...params, vaultId, transactionHash: onchainDepositResult.transactionHash, isNetworkTimeout: onchainDepositResult.isNetworkTimeout || false, signature: depositSignature }; try { const depositResult = await retryPromise(this.depositAPI.offchainDepositERC20(depositERC20Request), RETRY_DEFAULT, DELAY_IN_RETRY); console.timeEnd('depositOffchain'); return depositResult; } catch (err) { throw new Error(`Deposit ERC20 for off-chain failed: ${err}`); } } /** * Function will be DEPRECATED due to the off-chain events * will be in place at core-service * @param vaultIdData * @param starkKey * @param quantizedAmount * @param assetType * @param transactionHash * */ async depositOffchain(vaultIdData, starkKey, quantizedAmount, assetType, transactionHash) { // Make off-chain deposit request let offchainResult; try { offchainResult = await this.depositAPI.makeDepositTransaction(vaultIdData, starkKey, quantizedAmount, assetType, transactionHash); if ((offchainResult === null || offchainResult === void 0 ? void 0 : offchainResult.status) === "failed") { throw new Error("Onchain deposit transaction has been failed"); } console.log(`Offchain deposit result ==> ${JSON.stringify(offchainResult)}`); } catch (ex) { throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`); } return offchainResult; } async depositNFT(depositParams, options) { var _a; if ((depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.ERC20 || (depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.MINTABLE_ERC20 || (depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.ETH) { throw new Error("Token type is invalided and only support for ERC721/MINTABLE_ERC721 token"); } if (!depositParams.starkKey) { throw new Error("Stark key is required to execute this operation"); } if (!depositParams.tokenId) { throw new Error("Token Id is required to execute this operation"); } if (!depositParams.tokenAddress) { throw new Error("ERC721 token address is required to execute this operation"); } const quantumNft = "1"; let vaultData = ""; const assetType = assetUtils.getAssetType({ type: depositParams.tokenType, data: { quantum: quantumNft, tokenAddress: depositParams.tokenAddress, }, }); if (!(depositParams === null || depositParams === void 0 ? void 0 : depositParams.vaultId)) { try { const vaultResponse = await this.commonAPI.retrieveVault({ starkKey: depositParams.starkKey, tokenType: depositParams.tokenType, quantum: "1", tokenAddress: depositParams.tokenAddress, }); if ((vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.status) === "success") { vaultData = (_a = vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.data) === null || _a === void 0 ? void 0 : _a.vaultId; } else { throw new Error("Fetching vaultID failure - check BE server or validation request for calling"); } } catch (ex) { throw new Error("Fetching vaultID failure"); } } else { vaultData = depositParams === null || depositParams === void 0 ? void 0 : depositParams.vaultId; } // Make on-chain deposit request let onchainDepositResult; try { onchainDepositResult = await this.depositContract.depositNft(depositParams === null || depositParams === void 0 ? void 0 : depositParams.starkKey, assetType, depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenId, vaultData, options); console.log(`Onchain deposit nft result ==> ${JSON.stringify(onchainDepositResult)}`); } catch (ex) { throw new Error(`Onchain deposit nft has been failure... ${JSON.stringify(ex)}`); } return onchainDepositResult; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGVwb3NpdE1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL0RlcG9zaXRNb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFnQixXQUFXLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFaEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzFELE9BQU8sRUFBRSxnQkFBZ0IsRUFBa0MsU0FBUyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbkcsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFlckQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRXJELE9BQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQztBQUV2QixPQUFPLFVBQVUsTUFBTSw0QkFBNEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNsRSxPQUFPLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNsRixPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzVDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3RELE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUN4QixPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFOUMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7QUFDN0UsTUFBTSxVQUFVLEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQztBQUV0QyxNQUFNLGVBQWUsR0FBRyxhQUFhLENBQUM7QUFFdEMsTUFBTSxPQUFPLGFBQWE7SUFVeEIsWUFBWSxNQUFvQjtRQUM5QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksV0FBVyxDQUFDLE1B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