UNPKG

@arcana/ca-sdk

Version:

Arcana Network's chain abstraction SDK for unified balance in Web3 apps

129 lines (128 loc) 4.92 kB
import { getLogoFromSymbol } from "../../constants"; import { ErrorBuildingIntent, ErrorInsufficientBalance, ErrorUserDeniedIntent, } from "../../errors"; import { getLogger } from "../../logger"; import { INTENT_ACCEPTED } from "../../steps"; import { convertIntent, fetchBalances, getFeeStore } from "../../utils"; import BaseRequest from "./base"; const logger = getLogger(); class NativeRequestBase extends BaseRequest { constructor(input) { super(input); this.input = input; this.isNative = true; this.buildIntent = async () => { const [simulation, balances, feeStore] = await Promise.all([ this.simulateTx(), fetchBalances(this.input.options.networkConfig.VSC_DOMAIN, this.input.evm.address, this.input.chainList, this.input.fuel?.address), getFeeStore(this.input.options.networkConfig.GRPC_URL), ]); logger.debug("Step1:", { balances, feeStore, simulation, }); if (!simulation) { return; } const token = { contractAddress: simulation.token.contractAddress, decimals: this.input.chain.nativeCurrency.decimals, logo: getLogoFromSymbol(simulation.token.symbol) ?? "", name: this.input.chain.nativeCurrency.name, symbol: this.input.chain.nativeCurrency.symbol, type: "native", }; const { amount, gas, isIntentRequired } = this.parseSimulation({ assets: balances.assets, simulation, token, }); logger.debug("Step2:", { amount: amount.toString(), gas: gas.toString(), isIntentRequired, token, }); if (!isIntentRequired) { return; } const intent = this.createIntent({ amount, assets: balances.assets, feeStore, gas, gasInToken: gas, token, }); return { intent, token }; }; this.process = async () => { const i = await this.buildIntent(); if (!i) { return; } let intent = i.intent; const token = i.token; // make it rpc error if (intent.isAvailableBalanceInsufficient) { throw ErrorInsufficientBalance; } this.createExpectedSteps(intent); let accepted = false; // wait for intent acceptance hook await new Promise((resolve, reject) => { const allow = () => { accepted = true; return resolve("User allowed intent"); }; const deny = () => { return reject(ErrorUserDeniedIntent); }; const refresh = async () => { if (accepted) { logger.warn("Intent refresh called after allow()"); return convertIntent(intent, token, this.input.chainList); } const i = await this.buildIntent(); if (!i) { throw ErrorBuildingIntent; } intent = i.intent; return convertIntent(intent, token, this.input.chainList); }; this.input.hooks.onIntent({ allow, deny, intent: convertIntent(intent, token, this.input.chainList), refresh, }); }); this.markStepDone(INTENT_ACCEPTED); await this.processIntent(intent); }; } parseSimulation({ assets, simulation, token, }) { const { asset, chainsWithBalance, destinationGasBalance } = assets.getAssetDetails(this.input.chain, token.contractAddress); const gasMultiple = simulation.gasFee.mul(2); let isIntentRequired = false; if (this.input.options.bridge) { isIntentRequired = true; } if (chainsWithBalance && asset?.abstracted) { if (simulation.amount.add(gasMultiple).greaterThan(destinationGasBalance)) { isIntentRequired = true; } } logger.debug("parseSimulation", { amount: simulation.amount.toFixed(), destinationGasBalance: destinationGasBalance, gas: gasMultiple.toFixed(), }); return { amount: simulation.amount, gas: gasMultiple, isIntentRequired, }; } } export { NativeRequestBase };