diff --git a/package.json b/package.json
index bd4c75c..7e31863 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d3a46c..f1f9f60 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,11 +27,11 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
- specifier: ^1.1.16
- version: 1.1.16
+ specifier: ^1.1.22
+ version: 1.1.22
'@dydxprotocol/v4-client-js':
- specifier: ^1.0.6
- version: 1.0.6
+ specifier: ^1.0.11
+ version: 1.0.11
'@dydxprotocol/v4-localization':
specifier: ^1.0.18
version: 1.0.18
@@ -988,12 +988,12 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
- /@dydxprotocol/v4-abacus@1.1.16:
- resolution: {integrity: sha512-R9ROWx70f0a4lHxAPP/iJd9qnLhzL1SG8vIowfNg2VvWLk6f+f1HwsgMhDd07H328RqY016GNrRmnigeyVcz/g==}
+ /@dydxprotocol/v4-abacus@1.1.22:
+ resolution: {integrity: sha512-txinqnKGblCEcn1FjMj5nW9Tv2446fbkRaquodwQSk0GTP92/78xl4VSOwMngc0f4heiVGbirlCKyHmtZUADmA==}
dev: false
- /@dydxprotocol/v4-client-js@1.0.6:
- resolution: {integrity: sha512-xiWH+kbix+zhI6EsAnd+NDvkjBgxWtGwQmvpd0PjljWNYSFgUtNe5M+piDdRbl2nhy6YWbxAGTwwS3K/ih5qSw==}
+ /@dydxprotocol/v4-client-js@1.0.11:
+ resolution: {integrity: sha512-rJNFGTO+HU1GQppXeRLKXNk4HWx/h+DjU2sUCieEv5mnRIMQpLw8dH/L+Uyhxx2oovccY1MaKB4Fr9IngdrF0A==}
dependencies:
'@cosmjs/amino': 0.30.1
'@cosmjs/encoding': 0.31.1
@@ -1013,7 +1013,7 @@ packages:
ethers: 6.6.1
long: 4.0.0
protobufjs: 6.11.4
- ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
+ ws: 8.15.0
transitivePeerDependencies:
- bufferutil
- debug
@@ -14037,6 +14037,19 @@ packages:
utf-8-validate: 5.0.10
dev: false
+ /ws@8.15.0:
+ resolution: {integrity: sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: false
+
/ws@8.5.0:
resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==}
engines: {node: '>=10.0.0'}
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..b44b2ac 100644
--- a/src/hooks/useSubaccount.tsx
+++ b/src/hooks/useSubaccount.tsx
@@ -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;
const SubaccountContext = createContext({} as SubaccountContextType);
@@ -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((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]
);
diff --git a/src/lib/abacus/dydxChainTransactions.ts b/src/lib/abacus/dydxChainTransactions.ts
index 425c16d..dd0620d 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,
@@ -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;
@@ -380,6 +382,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: hashFromTx(tx?.hash)
+ });
+ } 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 +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;
}
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/lib/hashfromTx.ts b/src/lib/hashfromTx.ts
new file mode 100644
index 0000000..98696e6
--- /dev/null
+++ b/src/lib/hashfromTx.ts
@@ -0,0 +1,3 @@
+export const hashFromTx = (
+ txHash: string | Uint8Array
+): string => `0x${Buffer.from(txHash).toString('hex')}`;
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