@arcana/ca-sdk
Version:
Arcana Network's chain abstraction SDK for unified balance in Web3 apps
129 lines (128 loc) • 4.92 kB
JavaScript
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 };