Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OK-33669: Add order data to staking #6159

Merged
merged 12 commits into from
Nov 12, 2024
3 changes: 3 additions & 0 deletions packages/kit-bg/src/dbs/simple/base/SimpleDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SimpleDbEntityCustomTokens } from '../entity/SimpleDbEntityCustomTokens
import { SimpleDbEntityDappConnection } from '../entity/SimpleDbEntityDappConnection';
import { SimpleDbEntityDefaultWalletSettings } from '../entity/SimpleDbEntityDefaultWalletSettings';
import { SimpleDbEntityEarn } from '../entity/SimpleDbEntityEarn';
import { SimpleDbEntityEarnOrders } from '../entity/SimpleDbEntityEarnOrders';
import { SimpleDbEntityFeeInfo } from '../entity/SimpleDbEntityFeeInfo';
import { SimpleDbEntityLegacyWalletNames } from '../entity/SimpleDbEntityLegacyWalletNames';
import { SimpleDbEntityLightning } from '../entity/SimpleDbEntityLightning';
Expand Down Expand Up @@ -73,6 +74,8 @@ export class SimpleDb {

earn = new SimpleDbEntityEarn();

earnOrders = new SimpleDbEntityEarnOrders();

universalSearch = new SimpleDbEntityUniversalSearch();

customTokens = new SimpleDbEntityCustomTokens();
Expand Down
5 changes: 5 additions & 0 deletions packages/kit-bg/src/dbs/simple/base/SimpleDbProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { SimpleDbEntityCustomTokens } from '../entity/SimpleDbEntityCustomT
import type { SimpleDbEntityDappConnection } from '../entity/SimpleDbEntityDappConnection';
import type { SimpleDbEntityDefaultWalletSettings } from '../entity/SimpleDbEntityDefaultWalletSettings';
import type { SimpleDbEntityEarn } from '../entity/SimpleDbEntityEarn';
import type { SimpleDbEntityEarnOrders } from '../entity/SimpleDbEntityEarnOrders';
import type { SimpleDbEntityFeeInfo } from '../entity/SimpleDbEntityFeeInfo';
import type { SimpleDbEntityLegacyWalletNames } from '../entity/SimpleDbEntityLegacyWalletNames';
import type { SimpleDbEntityLightning } from '../entity/SimpleDbEntityLightning';
Expand Down Expand Up @@ -160,6 +161,10 @@ export class SimpleDbProxy

earn = this._createProxyService('earn') as SimpleDbEntityEarn;

earnOrders = this._createProxyService(
'earnOrders',
) as SimpleDbEntityEarnOrders;

localNFTs = this._createProxyService('localNFTs') as SimpleDbEntityLocalNFTs;

babylonSync = this._createProxyService(
Expand Down
121 changes: 121 additions & 0 deletions packages/kit-bg/src/dbs/simple/entity/SimpleDbEntityEarnOrders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { backgroundMethod } from '@onekeyhq/shared/src/background/backgroundDecorators';
import type { EDecodedTxStatus } from '@onekeyhq/shared/types/tx';

import { SimpleDbEntityBase } from '../base/SimpleDbEntityBase';

export interface IEarnOrderItem {
orderId: string;
networkId: string;
txId: string;
previousTxIds: string[];
status: EDecodedTxStatus;
updatedAt: number;
createdAt: number;
}

export interface IEarnOrderDBStructure {
data: Record<string, IEarnOrderItem>;
txIdToOrderIdMap: Record<string, string>;
}

export type IAddEarnOrderParams = Omit<
IEarnOrderItem,
'updatedAt' | 'createdAt' | 'previousTxIds'
>;

export class SimpleDbEntityEarnOrders extends SimpleDbEntityBase<IEarnOrderDBStructure> {
entityName = 'earnOrders';

override enableCache = false;

@backgroundMethod()
async addOrder(order: IAddEarnOrderParams) {
await this.setRawData(({ rawData }) => {
const data: IEarnOrderDBStructure = {
data: { ...(rawData?.data || {}) },
txIdToOrderIdMap: { ...(rawData?.txIdToOrderIdMap || {}) },
};
const now = Date.now();
data.data[order.orderId] = {
...order,
previousTxIds: [],
updatedAt: now,
createdAt: now,
};
data.txIdToOrderIdMap[order.txId] = order.orderId;
return data;
});
originalix marked this conversation as resolved.
Show resolved Hide resolved
}

@backgroundMethod()
async updateOrderStatusByTxId(params: {
currentTxId: string;
newTxId?: string;
status: EDecodedTxStatus;
}): Promise<{
success: boolean;
order?: IEarnOrderItem;
}> {
const { currentTxId, newTxId, status } = params;

await this.setRawData(({ rawData }) => {
const data: IEarnOrderDBStructure = {
data: { ...(rawData?.data || {}) },
txIdToOrderIdMap: { ...(rawData?.txIdToOrderIdMap || {}) },
};

const orderId = data.txIdToOrderIdMap[currentTxId];
if (!orderId) {
return data;
}
originalix marked this conversation as resolved.
Show resolved Hide resolved

const order = data.data[orderId];
if (!order) {
return data;
}
originalix marked this conversation as resolved.
Show resolved Hide resolved

// Update txId related info if new txId exists
if (newTxId && newTxId !== currentTxId) {
// Store old txId in history
const previousTxIds = [...(order.previousTxIds || [])];
previousTxIds.push(currentTxId);
originalix marked this conversation as resolved.
Show resolved Hide resolved

// Update txId mapping
delete data.txIdToOrderIdMap[currentTxId];
data.txIdToOrderIdMap[newTxId] = orderId;

// Update order details
data.data[orderId] = {
...order,
txId: newTxId,
previousTxIds,
status,
updatedAt: Date.now(),
};
} else {
// Update status only
data.data[orderId] = {
...order,
status,
updatedAt: Date.now(),
};
}

return data;
});

// Get updated order info
const updatedOrder = await this.getOrderByTxId(newTxId || currentTxId);
return {
success: updatedOrder?.status === status,
order: updatedOrder,
originalix marked this conversation as resolved.
Show resolved Hide resolved
};
}

@backgroundMethod()
async getOrderByTxId(txId: string): Promise<IEarnOrderItem | undefined> {
const rawData = await this.getRawData();
const orderId = rawData?.txIdToOrderIdMap?.[txId];
return orderId ? rawData?.data?.[orderId] : undefined;
}
originalix marked this conversation as resolved.
Show resolved Hide resolved
}
29 changes: 28 additions & 1 deletion packages/kit-bg/src/services/ServiceHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { EOnChainHistoryTxStatus } from '@onekeyhq/shared/types/history';
import type {
IAccountHistoryTx,
IAllNetworkHistoryExtraItem,
IChangedPendingTxInfo,
IFetchAccountHistoryParams,
IFetchAccountHistoryResp,
IFetchHistoryTxDetailsParams,
IFetchHistoryTxDetailsResp,
IFetchTxDetailsParams,
IOnChainHistoryTx,
IOnChainHistoryTxNFT,
Expand Down Expand Up @@ -287,15 +287,32 @@ class ServiceHistory extends ServiceBase {
}

const accountsWithChangedPendingTxs = new Set<string>(); // accountId_networkId
const changedPendingTxInfos: IChangedPendingTxInfo[] = [];
localHistoryPendingTxs.forEach((tx) => {
const txInResult = finalPendingTxs.find((item) => item.id === tx.id);
if (!txInResult) {
accountsWithChangedPendingTxs.add(
`${tx.decodedTx.accountId}_${tx.decodedTx.networkId}`,
);
const confirmedTx = result.find((item) => item.id === tx.id);
if (confirmedTx) {
changedPendingTxInfos.push({
accountId: confirmedTx.decodedTx.accountId,
networkId: confirmedTx.decodedTx.networkId,
txId: confirmedTx.decodedTx.txid,
status: confirmedTx.decodedTx.status,
});
}
}
});

if (changedPendingTxInfos.length > 0) {
// Check if staking transaction status has changed, if so request backend to update order status
void this.backgroundApi.serviceStaking.updateEarnOrder({
txs: changedPendingTxInfos,
});
}
originalix marked this conversation as resolved.
Show resolved Hide resolved

return {
txs: result,
accountsWithChangedPendingTxs: Array.from(
Expand Down Expand Up @@ -995,6 +1012,16 @@ class ServiceHistory extends ServiceBase {
? ESwapTxHistoryStatus.CANCELING
: ESwapTxHistoryStatus.PENDING,
});

// Listen for staking transaction speed-up changes
void this.backgroundApi.serviceStaking.updateOrderStatusByTxId({
currentTxId: prevTx.decodedTx.txid,
newTxId: newHistoryTx.decodedTx.txid,
status:
replaceTxInfo.replaceType === EReplaceTxType.Cancel
? EDecodedTxStatus.Removed
: EDecodedTxStatus.Pending,
});
originalix marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
91 changes: 86 additions & 5 deletions packages/kit-bg/src/services/ServiceStaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
backgroundClass,
backgroundMethod,
} from '@onekeyhq/shared/src/background/backgroundDecorators';
import { defaultLogger } from '@onekeyhq/shared/src/logger/logger';
import accountUtils from '@onekeyhq/shared/src/utils/accountUtils';
import { memoizee } from '@onekeyhq/shared/src/utils/cacheUtils';
import earnUtils from '@onekeyhq/shared/src/utils/earnUtils';
Expand All @@ -14,7 +15,10 @@ import type {
ISupportedSymbol,
} from '@onekeyhq/shared/types/earn';
import { EServiceEndpointEnum } from '@onekeyhq/shared/types/endpoint';
import type { IAccountHistoryTx } from '@onekeyhq/shared/types/history';
import type {
IAccountHistoryTx,
IChangedPendingTxInfo,
} from '@onekeyhq/shared/types/history';
import type {
IAllowanceOverview,
IAvailableAsset,
Expand All @@ -36,16 +40,23 @@ import type {
IStakeProtocolDetails,
IStakeProtocolListItem,
IStakeTag,
IStakeTx,
IStakeTxResponse,
IUnstakePushParams,
IWithdrawBaseParams,
} from '@onekeyhq/shared/types/staking';
import { EDecodedTxStatus } from '@onekeyhq/shared/types/tx';

import simpleDb from '../dbs/simple/simpleDb';
import { vaultFactory } from '../vaults/factory';

import ServiceBase from './ServiceBase';

import type {
IAddEarnOrderParams,
IEarnOrderItem,
} from '../dbs/simple/entity/SimpleDbEntityEarnOrders';

@backgroundClass()
class ServiceStaking extends ServiceBase {
constructor({ backgroundApi }: { backgroundApi: any }) {
Expand Down Expand Up @@ -160,7 +171,7 @@ class ServiceStaking extends ServiceBase {
}
const resp = await client.post<{
data: IStakeTxResponse;
}>(`/earn/v1/stake`, {
}>(`/earn/v2/stake`, {
originalix marked this conversation as resolved.
Show resolved Hide resolved
accountAddress: account.address,
publicKey: stakingConfig.usePublicKey ? account.pub : undefined,
term: params.term,
Expand Down Expand Up @@ -189,7 +200,7 @@ class ServiceStaking extends ServiceBase {
}
const resp = await client.post<{
data: IStakeTxResponse;
}>(`/earn/v1/unstake`, {
}>(`/earn/v2/unstake`, {
accountAddress: account.address,
networkId,
publicKey: stakingConfig.usePublicKey ? account.pub : undefined,
Expand Down Expand Up @@ -248,7 +259,7 @@ class ServiceStaking extends ServiceBase {

const resp = await client.post<{
data: IStakeTxResponse;
}>(`/earn/v1/claim`, {
}>(`/earn/v2/claim`, {
accountAddress: account.address,
networkId,
publicKey: stakingConfig.usePublicKey ? account.pub : undefined,
Expand Down Expand Up @@ -815,7 +826,7 @@ class ServiceStaking extends ServiceBase {
}: {
accountId: string;
networkId: string;
tx: IStakeTxResponse;
tx: IStakeTx;
}) {
const vault = await vaultFactory.getVault({ networkId, accountId });
const encodedTx = await vault.buildStakeEncodedTx(tx as any);
Expand Down Expand Up @@ -900,6 +911,76 @@ class ServiceStaking extends ServiceBase {
return item;
});
}

@backgroundMethod()
async addEarnOrder(order: IAddEarnOrderParams) {
defaultLogger.staking.order.addOrder(order);
return simpleDb.earnOrders.addOrder(order);
}

@backgroundMethod()
async updateEarnOrder({ txs }: { txs: IChangedPendingTxInfo[] }) {
for (const tx of txs) {
try {
const order =
await this.backgroundApi.simpleDb.earnOrders.getOrderByTxId(tx.txId);
if (order && tx.status !== EDecodedTxStatus.Pending) {
order.status = tx.status;
await this.updateEarnOrderStatusToServer({ order });
await this.backgroundApi.simpleDb.earnOrders.updateOrderStatusByTxId({
currentTxId: tx.txId,
status: tx.status,
});
defaultLogger.staking.order.updateOrderStatus({
txId: tx.txId,
status: tx.status,
});
}
} catch (e) {
// ignore error, continue loop
defaultLogger.staking.order.updateOrderStatusError({
txId: tx.txId,
status: tx.status,
});
}
}
}

@backgroundMethod()
async updateEarnOrderStatusToServer({ order }: { order: IEarnOrderItem }) {
const maxRetries = 3;
let lastError;

for (let i = 0; i < maxRetries; i += 1) {
try {
const client = await this.getClient(EServiceEndpointEnum.Earn);
await client.post('/earn/v1/orders', {
orderId: order.orderId,
networkId: order.networkId,
txId: order.txId,
});
return; // Return early on success
} catch (error) {
lastError = error;
if (i === maxRetries - 1) break; // Exit loop on final retry
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); // 1s, 2s, 3s
originalix marked this conversation as resolved.
Show resolved Hide resolved
}
}

throw lastError; // Throw last error after all retries fail
}

@backgroundMethod()
async updateOrderStatusByTxId(params: {
currentTxId: string;
newTxId?: string;
status: EDecodedTxStatus;
}) {
defaultLogger.staking.order.updateOrderStatusByTxId(params);
await this.backgroundApi.simpleDb.earnOrders.updateOrderStatusByTxId(
params,
);
}
}

export default ServiceStaking;
7 changes: 2 additions & 5 deletions packages/kit-bg/src/vaults/base/VaultBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,7 @@ import type {
IFetchServerTokenListParams,
IFetchServerTokenListResponse,
} from '@onekeyhq/shared/types/serverToken';
import type {
IStakeTxResponse,
IStakingInfo,
} from '@onekeyhq/shared/types/staking';
import type { IStakeTx, IStakingInfo } from '@onekeyhq/shared/types/staking';
import type { ISwapTxInfo } from '@onekeyhq/shared/types/swap/types';
import type {
IAccountToken,
Expand Down Expand Up @@ -1132,7 +1129,7 @@ export abstract class VaultBase extends VaultBaseChainOnly {
}

// Staking
buildStakeEncodedTx(params: IStakeTxResponse): Promise<IEncodedTx> {
buildStakeEncodedTx(params: IStakeTx): Promise<IEncodedTx> {
return Promise.resolve(params as IEncodedTx);
}
Comment on lines +1132 to 1134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

建议改进 buildStakeEncodedTx 方法的实现

当前实现存在以下问题:

  • 直接将输入参数转换为 IEncodedTx 类型,缺少必要的验证
  • 没有错误处理机制

建议按如下方式重构:

-  buildStakeEncodedTx(params: IStakeTx): Promise<IEncodedTx> {
-    return Promise.resolve(params as IEncodedTx);
-  }
+  buildStakeEncodedTx(params: IStakeTx): Promise<IEncodedTx> {
+    if (!params.amount || !params.contractAddress) {
+      throw new Error('无效的质押交易参数');
+    }
+    const encodedTx: IEncodedTx = {
+      ...params,
+      type: 'stake',
+    };
+    return Promise.resolve(encodedTx);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
buildStakeEncodedTx(params: IStakeTx): Promise<IEncodedTx> {
return Promise.resolve(params as IEncodedTx);
}
buildStakeEncodedTx(params: IStakeTx): Promise<IEncodedTx> {
if (!params.amount || !params.contractAddress) {
throw new Error('无效的质押交易参数');
}
const encodedTx: IEncodedTx = {
...params,
type: 'stake',
};
return Promise.resolve(encodedTx);
}


Expand Down
Loading
Loading