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

feat: migrate to Unified Bridge, introduce the ICTT bridge #82

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@avalabs/avalanche-module": "0.11.12",
"@avalabs/avalanchejs": "4.1.0-alpha.25",
"@avalabs/bitcoin-module": "0.11.12",
"@avalabs/bridge-unified": "0.0.0-feat-ictt-configs-20241009072139",
"@avalabs/bridge-unified": "3.1.0",
"@avalabs/core-bridge-sdk": "3.1.0-alpha.19",
"@avalabs/core-chains-sdk": "3.1.0-alpha.19",
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.19",
Expand Down
12 changes: 12 additions & 0 deletions src/background/services/featureFlags/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export enum FeatureGates {
SEEEDLESS_MFA_SETTINGS = 'seedless-mfa-settings',
SEEDLESS_OPTIONAL_MFA = 'seedless-optional-mfa',
UNIFIED_BRIDGE_CCTP = 'unified-bridge-cctp',
UNIFIED_BRIDGE_ICTT = 'unified-bridge-ictt',
UNIFIED_BRIDGE_AB_EVM = 'unified-bridge-ab-evm',
UNIFIED_BRIDGE_AB_AVA_TO_BTC = 'unified-bridge-ab-ava-to-btc',
UNIFIED_BRIDGE_AB_BTC_TO_AVA = 'unified-bridge-ab-btc-to-ava',
DEBANK_TRANSACTION_PARSING = 'debank-transaction-parsing',
DEBANK_TRANSACTION_PRE_EXECUTION = 'debank-transaction-pre-execution',
PRIMARY_ACCOUNT_REMOVAL = 'primary-account-removal',
Expand Down Expand Up @@ -76,6 +80,10 @@ export const DISABLED_FLAG_VALUES: FeatureFlags = {
[FeatureGates.SEEEDLESS_MFA_SETTINGS]: false,
[FeatureGates.SEEDLESS_OPTIONAL_MFA]: false,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: false,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: false,
[FeatureGates.DEBANK_TRANSACTION_PARSING]: false,
[FeatureGates.DEBANK_TRANSACTION_PRE_EXECUTION]: false,
[FeatureGates.PRIMARY_ACCOUNT_REMOVAL]: false,
Expand Down Expand Up @@ -121,6 +129,10 @@ export const DEFAULT_FLAGS: FeatureFlags = {
[FeatureGates.SEEEDLESS_MFA_SETTINGS]: true,
[FeatureGates.SEEDLESS_OPTIONAL_MFA]: true,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.DEBANK_TRANSACTION_PARSING]: false,
[FeatureGates.DEBANK_TRANSACTION_PRE_EXECUTION]: false,
[FeatureGates.PRIMARY_ACCOUNT_REMOVAL]: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@ describe('background/services/storage/schemaMigrations/migrations/unified_bridge
error: undefined,
value: stateWithPendingTransfers,
});

expect(
unified_bridge_v2.previousSchema.validate({
addresses: [],
pendingTransfers: {},
version: 1,
})
).toEqual({
error: undefined,
value: {
addresses: [],
pendingTransfers: {},
version: 1,
},
});

expect(
unified_bridge_v2.previousSchema.validate({ pendingTransfers: {} })
).toEqual({
error: undefined,
value: { pendingTransfers: {} },
});

expect(unified_bridge_v2.previousSchema.validate({})).toEqual({
error: undefined,
value: {},
});
});

it('rejects incorrect inputs', () => {
Expand Down Expand Up @@ -151,7 +178,7 @@ describe('background/services/storage/schemaMigrations/migrations/unified_bridge
},
targetConfirmationCount: 2,
targetRequiredConfirmationCount: 4,
targetStartBlockNumber: 1234567,
targetStartBlockNumber: 1234567n,
},
},
version: 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ const VERSION = 2;
// Schemas & types below only list the relevant properties
// that actually changed. The rest is untouched & untyped.
type LegacyBridgeTransfer = {
amountDecimals: number;
amountDecimals?: number; // the very first version of the SDK did not have it
symbol: string;
sourceChain: {
chainId: string;
};
requiredSourceConfirmationCount: number;
requiredTargetConfirmationCount: number;
startBlockNumber?: number;
startBlockNumber?: number | bigint; // Even though types say it's always a bigint, I've seen it as a number in my storage.
};

type NewBridgeTransfer = {
Expand All @@ -25,12 +25,13 @@ type NewBridgeTransfer = {
};
sourceRequiredConfirmationCount: number;
targetRequiredConfirmationCount: number;
targetStartBlockNumber?: number;
targetStartBlockNumber?: bigint;
};

type PreviousSchema = {
pendingTransfers?: Record<string, LegacyBridgeTransfer>;
addresses?: string[];
version?: number;
};

type NewSchema = {
Expand Down Expand Up @@ -63,7 +64,7 @@ const previousSchema = Joi.object<PreviousSchema>({
sourceChain: Joi.object({ chainId: Joi.string() }).unknown(true),
}).unknown(true)
),
});
}).unknown(true);

const getUsdcAddressByChainId = (caipId: string) => {
switch (caipId) {
Expand Down Expand Up @@ -102,16 +103,19 @@ const up = async (
newTransfers[id] = {
...rest,
asset: {
// Prior to this schema upgrad, only USDC was possible to bridge (via CCTP)
decimals: amountDecimals,
// Prior to this schema upgrade, only USDC was possible to bridge (via CCTP)
decimals: amountDecimals ?? 6,
symbol,
type: 'erc20',
name: 'USD Coin',
address: getUsdcAddressByChainId(rest.sourceChain.chainId),
},
sourceRequiredConfirmationCount: requiredSourceConfirmationCount,
targetRequiredConfirmationCount: requiredTargetConfirmationCount,
targetStartBlockNumber: startBlockNumber,
targetStartBlockNumber:
typeof startBlockNumber !== 'undefined'
? BigInt(startBlockNumber)
: undefined,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export const migrateToLatest = async <T>(
return result;
}

if (currentMigration.migration.previousSchema.validate(result).error) {
const { error: validationError } =
currentMigration.migration.previousSchema.validate(result);

if (validationError) {
throw new Error(
`Error while upgrading ${key} to version ${currentMigration.version}`
);
Expand Down
35 changes: 16 additions & 19 deletions src/background/services/unifiedBridge/UnifiedBridgeService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
isMainnet: jest.fn(),
getNetwork: jest.fn(),
getProviderForNetwork: jest.fn(),
getBitcoinProvider: jest.fn(),
sendTransaction: jest.fn(),
} as any;

Expand All @@ -41,6 +42,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
featureFlags: {
[FeatureGates.IMPORT_FIREBLOCKS]: true,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
},
addListener: jest.fn(),
} as any;
Expand All @@ -64,6 +69,7 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
networkService.getNetwork.mockImplementation(async (chainId) => ({
chainId,
}));
networkService.getBitcoinProvider.mockResolvedValue({} as any);
});

it('creates core instance with proper environment', async () => {
Expand Down Expand Up @@ -118,6 +124,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
// Toggle an irrelevant flag off
mockFeatureFlagChanges({
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.IMPORT_FIREBLOCKS]: false,
});

Expand All @@ -127,6 +137,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
// Toggle a relevant flag off
mockFeatureFlagChanges({
[FeatureGates.UNIFIED_BRIDGE_CCTP]: false,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.IMPORT_FIREBLOCKS]: false,
});

Expand Down Expand Up @@ -180,30 +194,13 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
});

new UnifiedBridgeService(networkService, storageService, flagsService);
await jest.runAllTimersAsync();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(1);
expect(getEnabledBridgeServices).toHaveBeenCalledTimes(4);
expect(wait).toHaveBeenNthCalledWith(1, 2000);

jest.advanceTimersByTime(2000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(2);
expect(wait).toHaveBeenNthCalledWith(2, 4000);

jest.advanceTimersByTime(4000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(3);
expect(wait).toHaveBeenNthCalledWith(3, 8000);

jest.advanceTimersByTime(8000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(4);
expect(createUnifiedBridgeService).toHaveBeenCalled();
});
});
Expand Down
56 changes: 47 additions & 9 deletions src/background/services/unifiedBridge/UnifiedBridgeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { singleton } from 'tsyringe';
import {
AnalyzeTxParams,
AnalyzeTxResult,
BridgeInitializer,
BridgeTransfer,
BridgeType,
createUnifiedBridgeService,
Environment,
getEnabledBridgeServices,
} from '@avalabs/bridge-unified';
import { BitcoinProvider } from '@avalabs/core-wallets-sdk';
import { wait } from '@avalabs/core-utils-sdk';
import EventEmitter from 'events';

Expand All @@ -32,6 +34,7 @@ import {
import sentryCaptureException, {
SentryExceptionTypes,
} from '@src/monitoring/sentryCaptureException';
import { getEnabledBridgeTypes } from '@src/utils/getEnabledBridgeTypes';

@singleton()
export class UnifiedBridgeService implements OnStorageReady {
Expand Down Expand Up @@ -110,16 +113,49 @@ export class UnifiedBridgeService implements OnStorageReady {
});
}

#getDisabledBridges(): BridgeType[] {
const bridges: BridgeType[] = [
BridgeType.ICTT_ERC20_ERC20,
BridgeType.AVALANCHE_EVM,
];
#getBridgeInitializers(
bitcoinProvider: BitcoinProvider
): BridgeInitializer[] {
return getEnabledBridgeTypes(this.#flagStates).map((type) =>
this.#getInitializerForBridgeType(type, bitcoinProvider)
);
}

if (!this.#flagStates[FeatureGates.UNIFIED_BRIDGE_CCTP]) {
bridges.push(BridgeType.CCTP);
#getInitializerForBridgeType(
type: BridgeType,
bitcoinProvider: BitcoinProvider
): BridgeInitializer {
// This backend service is only used for transaction tracking purposes,
// therefore we don't need to provide true signing capabilities.
const dummySigner = {
async sign() {
return '0x' as const;
},
};

switch (type) {
case BridgeType.CCTP:
case BridgeType.ICTT_ERC20_ERC20:
case BridgeType.AVALANCHE_EVM:
return {
type,
signer: dummySigner,
};

case BridgeType.AVALANCHE_AVA_BTC:
return {
type,
signer: dummySigner,
bitcoinFunctions: bitcoinProvider,
};

case BridgeType.AVALANCHE_BTC_AVA:
return {
type,
signer: dummySigner,
bitcoinFunctions: bitcoinProvider,
};
}
return bridges;
}

async #recreateService() {
Expand All @@ -128,11 +164,13 @@ export class UnifiedBridgeService implements OnStorageReady {
: Environment.TEST;

try {
const bitcoinProvider = await this.networkService.getBitcoinProvider();

this.#core = createUnifiedBridgeService({
environment,
enabledBridgeServices: await getEnabledBridgeServices(
environment,
this.#getDisabledBridges()
this.#getBridgeInitializers(bitcoinProvider)
),
});
this.#failedInitAttempts = 0;
Expand Down
9 changes: 8 additions & 1 deletion src/background/services/unifiedBridge/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ export enum UnifiedBridgeError {
InvalidFee = 'invalid-fee',
UnsupportedNetwork = 'unsupported-network',
InvalidTxPayload = 'invalid-tx-payload',
NonBitcoinAccount = 'non-bitcoin-account',
}

export type UnifiedBridgeState = {
pendingTransfers: Record<string, BridgeTransfer>;
};

export const UNIFIED_BRIDGE_TRACKED_FLAGS = [FeatureGates.UNIFIED_BRIDGE_CCTP];
export const UNIFIED_BRIDGE_TRACKED_FLAGS = [
FeatureGates.UNIFIED_BRIDGE_CCTP,
FeatureGates.UNIFIED_BRIDGE_ICTT,
FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC,
FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA,
FeatureGates.UNIFIED_BRIDGE_AB_EVM,
];

export const UNIFIED_BRIDGE_DEFAULT_STATE: UnifiedBridgeState = {
pendingTransfers: {},
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ContainedDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const ContainedDropdown = ({
width: width ?? '100%',
borderRadius: borderRadius ?? spacing(0, 0, 1, 1),
margin: margin ?? '0',
height: isOpen ? `${height || calculatedHeight}px` : 0,
height: isOpen ? `${height || calculatedHeight - top}px` : 0,
top,
opacity: isOpen ? 1 : 0,
}}
Expand Down
Loading
Loading