diff --git a/src/constants/notifications.ts b/src/constants/notifications.ts
index 5262c83..a455fd1 100644
--- a/src/constants/notifications.ts
+++ b/src/constants/notifications.ts
@@ -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;
diff --git a/src/hooks/useNotificationTypes.tsx b/src/hooks/useNotificationTypes.tsx
index 619a072..e76ff65 100644
--- a/src/hooks/useNotificationTypes.tsx
+++ b/src/hooks/useNotificationTypes.tsx
@@ -20,6 +20,7 @@ import {
type NotificationTypeConfig,
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
+ TransferNotificationTypes,
} from '@/constants/notifications';
import { useSelectedNetwork, useStringGetter } from '@/hooks';
@@ -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 = ;
- 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;
@@ -190,7 +191,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
slotIcon={icon}
slotTitle={title}
transfer={transfer}
- type={type}
+ type={transferType}
triggeredAt={transfer.triggeredAt}
notification={notification}
/>
diff --git a/src/hooks/useSubaccount.tsx b/src/hooks/useSubaccount.tsx
index cd5fa57..43c048e 100644
--- a/src/hooks/useSubaccount.tsx
+++ b/src/hooks/useSubaccount.tsx
@@ -293,12 +293,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((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 txHash = await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
+ return `0x${Buffer.from(txHash?.hash).toString('hex')}`;
},
[subaccountClient, sendSquidWithdrawFromSubaccount]
);
diff --git a/src/lib/abacus/dydxChainTransactions.ts b/src/lib/abacus/dydxChainTransactions.ts
index 425c16d..f93dbb3 100644
--- a/src/lib/abacus/dydxChainTransactions.ts
+++ b/src/lib/abacus/dydxChainTransactions.ts
@@ -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,
@@ -380,6 +381,90 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
}
+ async withdrawToNobleIBC(
+ params: {
+ subaccountNumber: number,
+ amount: string,
+ ibcPayload: string,
+ }
+ ): Promise {
+ 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: `0x${Buffer.from(tx?.hash).toString('hex')}`
+ });
+ } catch (error) {
+ log('DydxChainTransactions/withdrawToNobleIBC', error);
+
+ return JSON.stringify({
+ error,
+ });
+ }
+ }
+
+ async cctpWithdraw(params: {
+ typeUrl: string,
+ value: any,
+ }): Promise {
+ 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,
@@ -414,6 +499,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;
}
diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts
index b8ffaae..51d9343 100644
--- a/src/lib/abacus/index.ts
+++ b/src/lib/abacus/index.ts
@@ -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(
'',
@@ -262,6 +262,14 @@ class AbacusStateManager {
) => void
) => this.stateManager.cancelOrder(orderId, callback);
+ cctpWithdraw = (
+ callback: (
+ success: boolean,
+ parsingError: Nullable,
+ data: string,
+ ) => void
+ ): void => this.stateManager.commitCCTPWithdraw(callback);
+
// ------ Utils ------ //
getHistoricalPnlPeriod = (): Nullable =>
this.stateManager.historicalPnlPeriod;
diff --git a/src/views/forms/AccountManagementForms/WithdrawForm.tsx b/src/views/forms/AccountManagementForms/WithdrawForm.tsx
index f575366..2c308e3 100644
--- a/src/views/forms/AccountManagementForms/WithdrawForm.tsx
+++ b/src/views/forms/AccountManagementForms/WithdrawForm.tsx
@@ -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 {
@@ -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';
@@ -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('');
diff --git a/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx b/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx
index 758c60b..3cb050b 100644
--- a/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx
+++ b/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx
@@ -12,10 +12,11 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
+import { TransferNotificationTypes } from '@/constants/notifications';
type ElementProps = {
status?: StatusResponse;
- type: 'withdrawal' | 'deposit';
+ type: TransferNotificationTypes;
};
type StyleProps = {
@@ -46,11 +47,13 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
{
label: stringGetter({
key:
- type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
+ type === TransferNotificationTypes.Deposit
+ ? STRING_KEYS.INITIATED_DEPOSIT
+ : STRING_KEYS.INITIATED_WITHDRAWAL,
}),
step: TransferStatusStep.FromChain,
- link:
- type === 'deposit'
+ link:
+ type === TransferNotificationTypes.Deposit
? status?.fromChain?.transactionUrl
: routeStatus?.[0]?.chainId === dydxChainId && routeStatus[0].txHash
? `${mintscanTxUrl?.replace('{tx_hash}', routeStatus[0].txHash.replace('0x', ''))}`
@@ -63,14 +66,20 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
},
{
label: stringGetter({
- key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
+ key:
+ type === TransferNotificationTypes.Deposit
+ ? STRING_KEYS.DEPOSIT_TO_CHAIN
+ : STRING_KEYS.WITHDRAW_TO_CHAIN,
params: {
- CHAIN: type === 'deposit' ? 'dYdX' : status?.toChain?.chainData?.chainName,
+ CHAIN:
+ type === TransferNotificationTypes.Deposit
+ ? 'dYdX'
+ : status?.toChain?.chainData?.chainName,
},
}),
step: TransferStatusStep.ToChain,
link:
- type === 'withdrawal'
+ type === TransferNotificationTypes.Withdrawal
? status?.toChain?.transactionUrl
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash.replace('0x', ''))}`
diff --git a/src/views/notifications/TransferStatusNotification/index.tsx b/src/views/notifications/TransferStatusNotification/index.tsx
index fdbbcbf..36a0bbe 100644
--- a/src/views/notifications/TransferStatusNotification/index.tsx
+++ b/src/views/notifications/TransferStatusNotification/index.tsx
@@ -5,7 +5,7 @@ import { useInterval, useStringGetter } from '@/hooks';
import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';
-import { TransferNotifcation } from '@/constants/notifications';
+import { TransferNotifcation, TransferNotificationTypes } from '@/constants/notifications';
import { formatSeconds } from '@/lib/timeUtils';
@@ -22,7 +22,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { TransferStatusSteps } from './TransferStatusSteps';
type ElementProps = {
- type: 'withdrawal' | 'deposit';
+ type: TransferNotificationTypes;
transfer: TransferNotifcation;
triggeredAt?: number;
};
@@ -55,7 +55,7 @@ export const TransferStatusNotification = ({
useInterval({ callback: updateSecondsLeft });
const inProgressStatusString =
- type === 'deposit'
+ type === TransferNotificationTypes.Deposit
? secondsLeft > 0
? STRING_KEYS.DEPOSIT_STATUS
: STRING_KEYS.DEPOSIT_STATUS_SHORTLY
@@ -64,7 +64,7 @@ export const TransferStatusNotification = ({
: STRING_KEYS.WITHDRAW_STATUS_SHORTLY;
const statusString =
- type === 'deposit'
+ type === TransferNotificationTypes.Deposit
? status?.squidTransactionStatus === 'success'
? STRING_KEYS.DEPOSIT_COMPLETE
: inProgressStatusString