Skip to content

Commit

Permalink
Implement CCTP withdraw (#188)
Browse files Browse the repository at this point in the history
* Implement CCTP withdraw

* bump packages

* address comments
  • Loading branch information
rosepuppy authored Dec 12, 2023
1 parent 2709d79 commit 0f16396
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 39 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.1.16",
"@dydxprotocol/v4-client-js": "^1.0.6",
"@dydxprotocol/v4-abacus": "^1.1.22",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.0.18",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
Expand Down
31 changes: 22 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/constants/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,15 @@ export type NotificationDisplayData = {
toastDuration?: number;
};

export enum TransferNotificationTypes {
Withdrawal = 'withdrawal',
Deposit = 'deposit',
}

// Notification types
export type TransferNotifcation = {
txHash: string;
type?: TransferNotificationTypes;
toChainId?: string;
fromChainId?: string;
toAmount?: number;
Expand Down
13 changes: 7 additions & 6 deletions src/hooks/useNotificationTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
type NotificationTypeConfig,
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
TransferNotificationTypes,
} from '@/constants/notifications';

import { useSelectedNetwork, useStringGetter } from '@/hooks';
Expand Down Expand Up @@ -152,20 +153,20 @@ export const notificationTypes: NotificationTypeConfig[] = [

useEffect(() => {
for (const transfer of transferNotifications) {
const { fromChainId, status, txHash, toAmount } = transfer;
const { fromChainId, status, txHash, toAmount, type } = transfer;
const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;

const type =
const transferType = type ??
fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
? 'withdrawal'
: 'deposit';
? TransferNotificationTypes.Withdrawal
: TransferNotificationTypes.Deposit;

const title = stringGetter({
key: {
deposit: isFinished ? STRING_KEYS.DEPOSIT : STRING_KEYS.DEPOSIT_IN_PROGRESS,
withdrawal: isFinished ? STRING_KEYS.WITHDRAW : STRING_KEYS.WITHDRAW_IN_PROGRESS,
}[type],
}[transferType],
});

const toChainEta = status?.toChain?.chainData?.estimatedRouteDuration || 0;
Expand All @@ -190,7 +191,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
slotIcon={icon}
slotTitle={title}
transfer={transfer}
type={type}
type={transferType}
triggeredAt={transfer.triggeredAt}
notification={notification}
/>
Expand Down
24 changes: 21 additions & 3 deletions src/hooks/useSubaccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts';
import { useTokenConfigs } from './useTokenConfigs';
import { useDydxClient } from './useDydxClient';
import { hashFromTx } from '@/lib/hashfromTx';

type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
Expand Down Expand Up @@ -293,12 +294,29 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
);

const sendSquidWithdraw = useCallback(
async (amount: number, payload: string) => {
async (amount: number, payload: string, isCctp?: boolean) => {

const cctpWithdraw = () => {
return new Promise<string>((resolve, reject) =>
abacusStateManager.cctpWithdraw((success, error, data) => {
const parsedData = JSON.parse(data);
if (success && parsedData?.code == 0) {
resolve(parsedData?.transactionHash);
} else {
reject(error);
}
})
)
}
if (isCctp) {
return await cctpWithdraw();
}

if (!subaccountClient) {
return;
}

return await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
const tx = await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
return hashFromTx(tx?.hash);
},
[subaccountClient, sendSquidWithdrawFromSubaccount]
);
Expand Down
97 changes: 97 additions & 0 deletions src/lib/abacus/dydxChainTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Abacus, { type Nullable } from '@dydxprotocol/v4-abacus';
import Long from 'long';
import type { IndexedTx } from '@cosmjs/stargate';
import { GAS_MULTIPLIER, encodeJson } from '@dydxprotocol/v4-client-js';
import { EncodeObject } from '@cosmjs/proto-signing';

import {
CompositeClient,
Expand Down Expand Up @@ -41,6 +42,7 @@ import { openDialog } from '@/state/dialogs';
import { StatefulOrderError } from '../errors';
import { bytesToBigInt } from '../numbers';
import { log } from '../telemetry';
import { hashFromTx } from '../hashfromTx';

class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
private compositeClient: CompositeClient | undefined;
Expand Down Expand Up @@ -380,6 +382,90 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
}

async withdrawToNobleIBC(
params: {
subaccountNumber: number,
amount: string,
ibcPayload: string,
}
): Promise<string> {
if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}

const { subaccountNumber, amount, ibcPayload } = params ?? {};
const parsedIbcPayload: {
msgTypeUrl: string,
msg: any,
} = ibcPayload ? JSON.parse(ibcPayload) : undefined;

try {
const msg = this.compositeClient.withdrawFromSubaccountMessage(
new SubaccountClient(this.localWallet, subaccountNumber),
parseFloat(amount).toFixed(this.compositeClient.validatorClient.config.denoms.USDC_DECIMALS)
);
const ibcMsg: EncodeObject = {
typeUrl: parsedIbcPayload.msgTypeUrl,
value: parsedIbcPayload.msg,
};

const tx = await this.compositeClient.send(
this.localWallet,
() => Promise.resolve([msg, ibcMsg]),
false
);

return JSON.stringify({
txHash: hashFromTx(tx?.hash)
});
} catch (error) {
log('DydxChainTransactions/withdrawToNobleIBC', error);

return JSON.stringify({
error,
});
}
}

async cctpWithdraw(params: {
typeUrl: string,
value: any,
}): Promise<string> {
if (!this.nobleClient?.isConnected) {
throw new Error('Missing nobleClient or localWallet');
}

try {
const ibcMsg = {
typeUrl: params.typeUrl, // '/circle.cctp.v1.MsgDepositForBurn',
value: params.value,
};
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);

// take out fee from amount before sweeping
const amount = parseInt(ibcMsg.value.amount, 10) -
Math.floor(parseInt(fee.amount[0].amount, 10) * GAS_MULTIPLIER);

if (amount <= 0) {
throw new Error('noble balance does not cover fees');
}

ibcMsg.value.amount = amount.toString();

const tx = await this.nobleClient.send([ibcMsg]);

const parsedTx = this.parseToPrimitives(tx);

return JSON.stringify(parsedTx);
} catch (error) {
log('DydxChainTransactions/cctpWithdraw', error);

return JSON.stringify({
error,
});
}
}

async transaction(
type: TransactionTypes,
paramsInJson: Abacus.Nullable<string>,
Expand Down Expand Up @@ -414,6 +500,17 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
callback(result);
break;
}
case TransactionType.WithdrawToNobleIBC: {
const result = await this.withdrawToNobleIBC(params);
callback(result);
break;
}
case TransactionType.CctpWithdraw: {
const result = await this.cctpWithdraw(params);
callback(result);
break;
break;
}
default: {
break;
}
Expand Down
10 changes: 9 additions & 1 deletion src/lib/abacus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class AbacusStateManager {

const appConfigs = AbacusAppConfig.Companion.forWeb;
if (!isMainnet || testFlags.withCCTP)
appConfigs.squidVersion = AbacusAppConfig.SquidVersion.V2DepositOnly;
appConfigs.squidVersion = AbacusAppConfig.SquidVersion.V2;

this.stateManager = new AsyncAbacusStateManager(
'',
Expand Down Expand Up @@ -262,6 +262,14 @@ class AbacusStateManager {
) => void
) => this.stateManager.cancelOrder(orderId, callback);

cctpWithdraw = (
callback: (
success: boolean,
parsingError: Nullable<ParsingError>,
data: string,
) => void
): void => this.stateManager.commitCCTPWithdraw(callback);

// ------ Utils ------ //
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
this.stateManager.historicalPnlPeriod;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/hashfromTx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const hashFromTx = (
txHash: string | Uint8Array
): string => `0x${Buffer.from(txHash).toString('hex')}`;
15 changes: 8 additions & 7 deletions src/views/forms/AccountManagementForms/WithdrawForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NotificationStatus } from '@/constants/notifications';
import { NotificationStatus, TransferNotificationTypes } from '@/constants/notifications';
import { NumberSign } from '@/constants/numbers';

import {
Expand Down Expand Up @@ -45,6 +45,7 @@ import { getTransferInputs } from '@/state/inputsSelectors';

import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers';
import { getNobleChainId } from '@/lib/squid';

import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
Expand Down Expand Up @@ -167,16 +168,16 @@ export const WithdrawForm = () => {
})
);
} else {
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload.data);
if (txHash?.hash) {
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload.data, isCctp);
if (txHash) {
addTransferNotification({
txHash: hash,
fromChainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
txHash: txHash,
type: TransferNotificationTypes.Withdrawal,
fromChainId: !isCctp ? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId : getNobleChainId(),
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
notificationStatus: NotificationStatus.Triggered,
isCctp,
});
abacusStateManager.clearTransferInputValues();
setWithdrawAmount('');
Expand Down
Loading

2 comments on commit 0f16396

@vercel
Copy link

@vercel vercel bot commented on 0f16396 Dec 12, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on 0f16396 Dec 12, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.