Skip to content

Commit

Permalink
Merge branch 'feat/ledger-revoke' into tmp/20240816
Browse files Browse the repository at this point in the history
  • Loading branch information
heisenberg-2077 committed Aug 16, 2024
2 parents 787aeef + f6d3a21 commit b8d9d88
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 341 deletions.
329 changes: 329 additions & 0 deletions src/ui/utils/sendTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
import {
CHAINS_ENUM,
EVENTS,
INTERNAL_REQUEST_ORIGIN,
INTERNAL_REQUEST_SESSION,
} from '@/constant';
import { intToHex, WalletControllerType } from '@/ui/utils';
import { findChain } from '@/utils/chain';
import {
calcGasLimit,
calcMaxPriorityFee,
checkGasAndNonce,
explainGas,
getNativeTokenBalance,
getPendingTxs,
} from '@/utils/transaction';
import { Tx } from '@rabby-wallet/rabby-api/dist/types';
import BigNumber from 'bignumber.js';
import {
fetchActionRequiredData,
parseAction,
} from '@/ui/views/Approval/components/Actions/utils';
import Browser from 'webextension-polyfill';
import eventBus from '@/eventBus';

// fail code
export enum FailedCode {
GasNotEnough = 'GasNotEnough',
GasTooHigh = 'GasTooHigh',
SubmitTxFailed = 'SubmitTxFailed',
DefaultFailed = 'DefaultFailed',
}

type ProgressStatus = 'building' | 'builded' | 'signed' | 'submitted';

/**
* send transaction without rpcFlow
*/
export const sendTransaction = async ({
tx,
chainServerId,
wallet,
ignoreGasCheck,
onProgress,
}: {
tx: Tx;
chainServerId: string;
wallet: WalletControllerType;
ignoreGasCheck?: boolean;
onProgress?: (status: ProgressStatus) => void;
}) => {
onProgress?.('building');
const chain = findChain({
serverId: chainServerId,
})!;
const support1559 = chain.eip['1559'];
const { address } = (await wallet.getCurrentAccount())!;
const recommendNonce = await wallet.getRecommendNonce({
from: tx.from,
chainId: chain.id,
});

// get gas
const gasMarket = await wallet.openapi.gasMarket(chainServerId);
const normalGas = gasMarket.find((item) => item.level === 'normal')!;
const signingTxId = await wallet.addSigningTx(tx);

// pre exec tx
const preExecResult = await wallet.openapi.preExecTx({
tx: {
...tx,
nonce: recommendNonce,
data: tx.data,
value: tx.value || '0x0',
gasPrice: intToHex(Math.round(normalGas.price)),
},
origin: INTERNAL_REQUEST_ORIGIN,
address: address,
updateNonce: true,
pending_tx_list: await getPendingTxs({
recommendNonce,
wallet,
address,
}),
});

const balance = await getNativeTokenBalance({
wallet,
chainId: chain.id,
address,
});
let estimateGas = 0;
if (preExecResult.gas.success) {
estimateGas = preExecResult.gas.gas_limit || preExecResult.gas.gas_used;
}
const { gas: gasRaw, needRatio, gasUsed } = await wallet.getRecommendGas({
gasUsed: preExecResult.gas.gas_used,
gas: estimateGas,
tx,
chainId: chain.id,
});
const gas = new BigNumber(gasRaw);
const { gasLimit, recommendGasLimitRatio } = await calcGasLimit({
chain,
tx,
gas,
selectedGas: normalGas,
nativeTokenBalance: balance,
explainTx: preExecResult,
needRatio,
wallet,
});

// calc gasCost
const gasCost = await explainGas({
gasUsed,
gasPrice: normalGas.price,
chainId: chain.id,
nativeTokenPrice: preExecResult.native_token.price,
wallet,
tx,
gasLimit,
});

// check gas errors
const checkErrors = checkGasAndNonce({
recommendGasLimit: `0x${gas.toString(16)}`,
recommendNonce,
gasLimit: Number(gasLimit),
nonce: Number(recommendNonce || tx.nonce),
gasExplainResponse: gasCost,
isSpeedUp: false,
isCancel: false,
tx,
isGnosisAccount: false,
nativeTokenBalance: balance,
recommendGasLimitRatio,
});

const isGasNotEnough = checkErrors.some((e) => e.code === 3001);
const ETH_GAS_USD_LIMIT = process.env.DEBUG
? (await Browser.storage.local.get('DEBUG_ETH_GAS_USD_LIMIT'))
.DEBUG_ETH_GAS_USD_LIMIT || 20
: 20;
const OTHER_CHAIN_GAS_USD_LIMIT = process.env.DEBUG
? (await Browser.storage.local.get('DEBUG_OTHER_CHAIN_GAS_USD_LIMIT'))
.DEBUG_OTHER_CHAIN_GAS_USD_LIMIT || 5
: 5;
let failedCode;
if (isGasNotEnough) {
failedCode = FailedCode.GasNotEnough;
} else if (
!ignoreGasCheck &&
// eth gas > $20
((chain.enum === CHAINS_ENUM.ETH &&
gasCost.gasCostUsd.isGreaterThan(ETH_GAS_USD_LIMIT)) ||
// other chain gas > $5
(chain.enum !== CHAINS_ENUM.ETH &&
gasCost.gasCostUsd.isGreaterThan(OTHER_CHAIN_GAS_USD_LIMIT)))
) {
failedCode = FailedCode.GasTooHigh;
}

if (failedCode) {
throw {
name: failedCode,
gasCost,
};
}

// generate tx with gas
const transaction: Tx = {
from: tx.from,
to: tx.to,
data: tx.data,
nonce: recommendNonce,
value: tx.value,
chainId: tx.chainId,
gas: gasLimit,
};
const maxPriorityFee = calcMaxPriorityFee([], normalGas, chain.id, true);
const maxFeePerGas = intToHex(Math.round(normalGas.price));

if (support1559) {
transaction.maxFeePerGas = maxFeePerGas;
transaction.maxPriorityFeePerGas =
maxPriorityFee <= 0
? tx.maxFeePerGas
: intToHex(Math.round(maxPriorityFee));
} else {
(transaction as Tx).gasPrice = maxFeePerGas;
}

// fetch action data
const actionData = await wallet.openapi.parseTx({
chainId: chain.serverId,
tx: {
...tx,
gas: '0x0',
nonce: recommendNonce || '0x1',
value: tx.value || '0x0',
to: tx.to || '',
},
origin: origin || '',
addr: address,
});
const parsed = parseAction(
actionData.action,
preExecResult.balance_change,
{
...tx,
gas: '0x0',
nonce: recommendNonce || '0x1',
value: tx.value || '0x0',
},
preExecResult.pre_exec_version,
preExecResult.gas.gas_used
);
const requiredData = await fetchActionRequiredData({
actionData: parsed,
contractCall: actionData.contract_call,
chainId: chain.serverId,
address,
wallet,
tx: {
...tx,
gas: '0x0',
nonce: recommendNonce || '0x1',
value: tx.value || '0x0',
},
origin,
});

await wallet.updateSigningTx(signingTxId, {
rawTx: {
nonce: recommendNonce,
},
explain: {
...preExecResult,
},
action: {
actionData: parsed,
requiredData,
},
});
const logId = actionData.log_id;
const estimateGasCost = {
gasCostUsd: gasCost.gasCostUsd,
gasCostAmount: gasCost.gasCostAmount,
nativeTokenSymbol: preExecResult.native_token.symbol,
gasPrice: normalGas.price,
nativeTokenPrice: preExecResult.native_token.price,
};

onProgress?.('builded');

if (process.env.DEBUG) {
const { DEBUG_MOCK_SUBMIT } = await Browser.storage.local.get(
'DEBUG_MOCK_SUBMIT'
);

if (DEBUG_MOCK_SUBMIT) {
return {
txHash: 'mock_hash',
gasCost: estimateGasCost,
};
}
}

// submit tx
let hash = '';
try {
hash = await Promise.race([
wallet.ethSendTransaction({
data: {
$ctx: {},
params: [transaction],
},
session: INTERNAL_REQUEST_SESSION,
approvalRes: {
...transaction,
signingTxId,
logId: logId,
},
pushed: false,
result: undefined,
}),
new Promise((_, reject) => {
eventBus.once(EVENTS.LEDGER.REJECTED, async (data) => {
reject(new Error(data));
});
}),
]);
} catch (e) {
const err = new Error(e.message);
err.name = FailedCode.SubmitTxFailed;
throw err;
}

onProgress?.('signed');

// wait tx completed
const txCompleted = await new Promise<{ gasUsed: number }>((resolve) => {
const handler = (res) => {
if (res?.hash === hash) {
eventBus.removeEventListener(EVENTS.TX_COMPLETED, handler);
resolve(res || {});
}
};
eventBus.addEventListener(EVENTS.TX_COMPLETED, handler);
});

// calc gas cost
const gasCostAmount = new BigNumber(txCompleted.gasUsed)
.times(estimateGasCost.gasPrice)
.div(1e18);
const gasCostUsd = new BigNumber(gasCostAmount).times(
estimateGasCost.nativeTokenPrice
);

return {
txHash: hash,
gasCost: {
...estimateGasCost,
gasCostUsd,
gasCostAmount,
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,11 @@ export const RevokeTable: React.FC<RevokeTableProps> = ({
{
title: '#',
key: 'index',
className: 'index-cell',
render: (text, record, index) => (
<span className="text-r-neutral-foot text-14">{index + 1}</span>
<span className="text-r-neutral-foot text-12 font-normal">
{index + 1}
</span>
),
width: 40,
},
Expand Down
12 changes: 10 additions & 2 deletions src/ui/views/ApprovalManagePage/components/BatchRevoke/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
background-color: transparent;
padding: 0;
margin-bottom: 0;
overflow: visible;
}

.ant-modal-confirm-btns {
Expand Down Expand Up @@ -87,9 +88,16 @@

.is-last-cell {
justify-content: end;
.am-virtual-table-cell-inner {
padding-right: 0;
.am-virtual-table-cell-inner {
padding-right: 0;
}
}


.index-cell {
.am-virtual-table-cell-inner {
padding-right: 6px;
}
}

.status-cell {
Expand Down
Loading

0 comments on commit b8d9d88

Please sign in to comment.