Skip to content

Commit

Permalink
OK-33669: Add order data to staking (#6159)
Browse files Browse the repository at this point in the history
* feat: Earn orders db

* feat: Update earn orders

* feat: Withdraw and Claim add to order

* feat: Staking tx speed up

* feat: Add log
  • Loading branch information
originalix authored Nov 12, 2024
1 parent 4073f60 commit ac67099
Show file tree
Hide file tree
Showing 13 changed files with 397 additions and 20 deletions.
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;
});
}

@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;
}

const order = data.data[orderId];
if (!order) {
return data;
}

// Update txId related info if new txId exists
if (newTxId && newTxId !== currentTxId) {
// Store old txId in history
const previousTxIds = [...(order.previousTxIds || [])];
previousTxIds.push(currentTxId);

// 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,
};
}

@backgroundMethod()
async getOrderByTxId(txId: string): Promise<IEarnOrderItem | undefined> {
const rawData = await this.getRawData();
const orderId = rawData?.txIdToOrderIdMap?.[txId];
return orderId ? rawData?.data?.[orderId] : undefined;
}
}
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,
});
}

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,
});
}
}

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`, {
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
}
}

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);
}

Expand Down
Loading

0 comments on commit ac67099

Please sign in to comment.