From 4de1c9e6026c875ada1a2420edfcf2314a09de38 Mon Sep 17 00:00:00 2001 From: Sharon Sheah <37326128+sharonsheah@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:23:05 +1100 Subject: [PATCH 1/5] fix: analytics tracking for connect widget (#1609) --- .../widgets/connect/components/WalletList.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx index ded4cc8308..43026275c3 100644 --- a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx @@ -127,18 +127,6 @@ export function WalletList(props: WalletListProps) { const isMetaMask = providerDetail.info.rdns === WalletProviderRdns.METAMASK; const web3Provider = new Web3Provider(providerDetail.provider as any); - track({ - userJourney: UserJourney.CONNECT, - screen: 'ConnectWallet', - control: 'Wallet', - controlType: 'MenuItem', - extras: { - wallet: getProviderSlugFromRdns(providerDetail.info.rdns), - walletRdns: providerDetail.info.rdns, - walletUuid: providerDetail.info.uuid, - }, - }); - try { // TODO: Find a nice way to detect if the wallet supports switching accounts via requestPermissions const changeAccount = isMetaMask; @@ -203,6 +191,12 @@ export function WalletList(props: WalletListProps) { }; const handleWalletConnectConnection = async () => { + track({ + userJourney: UserJourney.CONNECT, + screen: 'ConnectWallet', + control: 'WalletConnect', + controlType: 'MenuItem', + }); await openWalletConnectModal({ connectCallback, restoreSession: true, @@ -254,6 +248,12 @@ export function WalletList(props: WalletListProps) { }, [chosenProviderDetail]); const onBrowserWalletsClick = useCallback(() => { + track({ + userJourney: UserJourney.CONNECT, + screen: 'ConnectWallet', + control: 'BrowserWallets', + controlType: 'MenuItem', + }); setShowWalletDrawer(true); }, [track]); From b85da76a10af7916bc170ffdbdc1c8b8244bc6a8 Mon Sep 17 00:00:00 2001 From: Carl Menezes Date: Thu, 21 Mar 2024 13:51:38 +1300 Subject: [PATCH 2/5] chore: [DX-2725] fix update docs link script (#1610) --- .github/scripts/update-docs-link.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/scripts/update-docs-link.sh b/.github/scripts/update-docs-link.sh index d91c9eb8d0..6ff5e339b3 100755 --- a/.github/scripts/update-docs-link.sh +++ b/.github/scripts/update-docs-link.sh @@ -27,18 +27,18 @@ fi # Update versions in the browserBundle docs (https://docs.immutable.com/docs/x/sdks/typescript/#browser-bundle) FILE=docs/main/sdks/_typescript.mdx - major=$(awk '{ + major=$(echo $VERSION | awk '{ split($0, a, "."); print a[1]; - }' <<< $VERSION) - minor=$(awk '{ + }') + minor=$(echo $VERSION | awk '{ split($0, a, "."); print a[2]; - }' <<< $VERSION) - patch=$(awk '{ + }') + patch=$(echo $VERSION | awk '{ split($0, a, "."); print a[3]; - }' <<< $VERSION) + }') echo "major: $major minor: $minor patch: $patch" # On Mac OS, sed requires an empty string as an argument to -i to avoid creating a backup file From fa15494b8d6fb58216c379e34695622b7fa53524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:08:09 +1300 Subject: [PATCH 3/5] chore(deps): bump follow-redirects from 1.15.3 to 1.15.6 in /tests/func-tests/imx (#1596) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/func-tests/imx/yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/func-tests/imx/yarn.lock b/tests/func-tests/imx/yarn.lock index 2082ed5353..60b36c5cd2 100644 --- a/tests/func-tests/imx/yarn.lock +++ b/tests/func-tests/imx/yarn.lock @@ -1151,7 +1151,7 @@ __metadata: "@imtbl/sdk@file:../../../sdk::locator=func-tests-imx%40workspace%3A.": version: 0.0.0 - resolution: "@imtbl/sdk@file:../../../sdk#../../../sdk::hash=d58b95&locator=func-tests-imx%40workspace%3A." + resolution: "@imtbl/sdk@file:../../../sdk#../../../sdk::hash=502a0e&locator=func-tests-imx%40workspace%3A." dependencies: "@0xsequence/abi": ^1.4.3 "@0xsequence/core": ^1.4.3 @@ -1200,7 +1200,7 @@ __metadata: peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 - checksum: 1e06b047e5d89c569f91e6bc51cff823653de502bfe05f2376becb3cfe8c6a7af219895ce5d6157b188e5b22e41b0c0e191c775094adae1b8506acc031b92ab7 + checksum: 1bae1fe584e0cf7a92a3b4be98a13b73b6fc4bd81d8c098c829b7620af6036d94e989d689a266e3525babb31ecfa343f62e4f1aee91d57391f40dc2419662074 languageName: node linkType: hard @@ -6686,12 +6686,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.15.4": - version: 1.15.5 - resolution: "follow-redirects@npm:1.15.5" + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" peerDependenciesMeta: debug: optional: true - checksum: 5ca49b5ce6f44338cbfc3546823357e7a70813cecc9b7b768158a1d32c1e62e7407c944402a918ea8c38ae2e78266312d617dc68783fac502cbb55e1047b34ec + checksum: a62c378dfc8c00f60b9c80cab158ba54e99ba0239a5dd7c81245e5a5b39d10f0c35e249c3379eae719ff0285fff88c365dd446fab19dee771f1d76252df1bbf5 languageName: node linkType: hard From 0fc0ed31b5c7d37f9381a79f3d577ba7a0d41c70 Mon Sep 17 00:00:00 2001 From: Natalie Date: Thu, 21 Mar 2024 14:49:00 +1300 Subject: [PATCH 4/5] feat: [DX-2756] add get passport id to game bridge (#1611) --- packages/game-bridge/src/index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/game-bridge/src/index.ts b/packages/game-bridge/src/index.ts index a419546931..468a503f4c 100644 --- a/packages/game-bridge/src/index.ts +++ b/packages/game-bridge/src/index.ts @@ -36,6 +36,7 @@ const PASSPORT_FUNCTIONS = { getIdToken: 'getIdToken', logout: 'logout', getEmail: 'getEmail', + getPassportId: 'getPassportId', imx: { getAddress: 'getAddress', isRegisteredOffchain: 'isRegisteredOffchain', @@ -411,6 +412,19 @@ window.callFunction = async (jsonData: string) => { }); break; } + case PASSPORT_FUNCTIONS.getPassportId: { + const userProfile = await passportClient?.getUserInfo(); + track(moduleName, 'performedGetPassportId', { + timeMs: Date.now() - markStart, + }); + callbackToGame({ + responseFor: fxName, + requestId, + success: true, + result: userProfile?.sub, + }); + break; + } case PASSPORT_FUNCTIONS.imx.getAddress: { const address = await getProvider().getAddress(); track(moduleName, 'performedImxGetAddress', { From 4aa9a47ba72838da88cfe029f4f042cd54e7df3b Mon Sep 17 00:00:00 2001 From: Charlie McKenzie Date: Thu, 21 Mar 2024 16:47:32 +1100 Subject: [PATCH 5/5] fix: Smart checkout balance token information (#1612) Co-authored-by: Mimi Immutable --- .../balanceCheck/balanceCheck.ts | 13 +- .../balanceCheck/balanceRequirement.test.ts | 138 +++++++++++------- .../balanceCheck/balanceRequirement.ts | 123 ++++++++++++---- 3 files changed, 186 insertions(+), 88 deletions(-) diff --git a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceCheck.ts b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceCheck.ts index 19636fc559..d249066e09 100644 --- a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceCheck.ts +++ b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceCheck.ts @@ -158,14 +158,17 @@ export const balanceCheck = async ( // Wait for all balances and calculate the requirements const promisesResponses = await Promise.all(balancePromises); - const balanceRequirements: BalanceRequirement[] = []; + const erc721BalanceRequirements: BalanceRequirement[] = []; + const tokenBalanceRequirementPromises: Promise[] = []; // Get all ERC20 and NATIVE balances if (requiredToken.length > 0 && promisesResponses.length > 0) { const result = promisesResponses.shift(); if (result) { requiredToken.forEach((item) => { - balanceRequirements.push(getTokenBalanceRequirement(item as (NativeItem | ERC20Item), result)); + tokenBalanceRequirementPromises.push( + getTokenBalanceRequirement(item as (NativeItem | ERC20Item), result, provider), + ); }); } } @@ -175,10 +178,14 @@ export const balanceCheck = async ( const result = promisesResponses.shift(); if (result) { requiredERC721.forEach((item) => { - balanceRequirements.push(getERC721BalanceRequirement(item as (ERC721Item), result)); + erc721BalanceRequirements.push(getERC721BalanceRequirement(item as (ERC721Item), result)); }); } } + const balanceRequirements = [ + ...erc721BalanceRequirements, + ...(await Promise.all(tokenBalanceRequirementPromises)), + ]; // Find if there are any requirements that aren't sufficient. // If there is not item with sufficient === false then the requirements diff --git a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.test.ts b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.test.ts index 840a105607..c01acc451d 100644 --- a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.test.ts @@ -1,6 +1,13 @@ import { BigNumber } from 'ethers'; +import { Web3Provider } from '@ethersproject/providers'; import { - ERC20Item, ERC721Item, ItemBalance, ItemRequirement, ItemType, NativeItem, + ChainId, + ERC20Item, + ERC721Item, + ItemBalance, + ItemRequirement, + ItemType, + NativeItem, } from '../../types'; import { getERC721BalanceRequirement, @@ -63,30 +70,28 @@ describe('balanceRequirement', () => { }, ]; const result = getERC721BalanceRequirement(itemRequirement, balances); - expect(result).toEqual( - { - sufficient: true, + expect(result).toEqual({ + sufficient: true, + type: ItemType.ERC721, + delta: { + balance: BigNumber.from(0), + formattedBalance: '0', + }, + required: { type: ItemType.ERC721, - delta: { - balance: BigNumber.from(0), - formattedBalance: '0', - }, - required: { - type: ItemType.ERC721, - balance: BigNumber.from(1), - formattedBalance: '1', - contractAddress: '0xERC721', - id: '0', - }, - current: { - type: ItemType.ERC721, - balance: BigNumber.from(1), - formattedBalance: '1', - contractAddress: '0xERC721', - id: '0', - }, + balance: BigNumber.from(1), + formattedBalance: '1', + contractAddress: '0xERC721', + id: '0', }, - ); + current: { + type: ItemType.ERC721, + balance: BigNumber.from(1), + formattedBalance: '1', + contractAddress: '0xERC721', + id: '0', + }, + }); }); it('should return sufficient false if does not meet requirement for ERC721', () => { @@ -110,35 +115,46 @@ describe('balanceRequirement', () => { }, ]; const result = getERC721BalanceRequirement(itemRequirement, balances); - expect(result).toEqual( - { - sufficient: false, + expect(result).toEqual({ + sufficient: false, + type: ItemType.ERC721, + delta: { + balance: BigNumber.from(1), + formattedBalance: '1', + }, + required: { type: ItemType.ERC721, - delta: { - balance: BigNumber.from(1), - formattedBalance: '1', - }, - required: { - type: ItemType.ERC721, - balance: BigNumber.from(1), - formattedBalance: '1', - contractAddress: '0xERC721', - id: '0', - }, - current: { - type: ItemType.ERC721, - balance: BigNumber.from(0), - formattedBalance: '0', - contractAddress: '0xERC721', - id: '0', - }, + balance: BigNumber.from(1), + formattedBalance: '1', + contractAddress: '0xERC721', + id: '0', }, - ); + current: { + type: ItemType.ERC721, + balance: BigNumber.from(0), + formattedBalance: '0', + contractAddress: '0xERC721', + id: '0', + }, + }); }); }); describe('getTokenBalanceRequirement', () => { - it('should return sufficient true if meets requirements for NATIVE', () => { + let mockProvider: Web3Provider; + + beforeEach(() => { + mockProvider = { + getSigner: jest.fn().mockReturnValue({ + getAddress: jest.fn().mockResolvedValue('0xADDRESS'), + }), + network: { + chainId: ChainId.ETHEREUM, + }, + } as unknown as Web3Provider; + }); + + it('should return sufficient true if meets requirements for NATIVE', async () => { const itemRequirement: NativeItem = { type: ItemType.NATIVE, amount: BigNumber.from('1000000000000000000'), @@ -156,7 +172,11 @@ describe('balanceRequirement', () => { }, ]; - const result = getTokenBalanceRequirement(itemRequirement, balances); + const result = await getTokenBalanceRequirement( + itemRequirement, + balances, + mockProvider, + ); expect(result).toEqual({ sufficient: true, type: ItemType.NATIVE, @@ -187,7 +207,7 @@ describe('balanceRequirement', () => { }); }); - it('should return sufficient true if meets requirements for ERC20', () => { + it('should return sufficient true if meets requirements for ERC20', async () => { const itemRequirement: ERC20Item = { type: ItemType.ERC20, tokenAddress: '0xERC20', @@ -208,7 +228,11 @@ describe('balanceRequirement', () => { }, ]; - const result = getTokenBalanceRequirement(itemRequirement, balances); + const result = await getTokenBalanceRequirement( + itemRequirement, + balances, + mockProvider, + ); expect(result).toEqual({ sufficient: true, type: ItemType.ERC20, @@ -241,7 +265,7 @@ describe('balanceRequirement', () => { }); }); - it('should return sufficient false if requirements not met for NATIVE', () => { + it('should return sufficient false if requirements not met for NATIVE', async () => { const itemRequirement: NativeItem = { type: ItemType.NATIVE, amount: BigNumber.from('1000000000000000000'), @@ -270,7 +294,11 @@ describe('balanceRequirement', () => { }, ]; - const result = getTokenBalanceRequirement(itemRequirement, balances); + const result = await getTokenBalanceRequirement( + itemRequirement, + balances, + mockProvider, + ); expect(result).toEqual({ sufficient: false, type: ItemType.NATIVE, @@ -301,7 +329,7 @@ describe('balanceRequirement', () => { }); }); - it('should return sufficient false if requirements not met for ERC20', () => { + it('should return sufficient false if requirements not met for ERC20', async () => { const itemRequirement: ERC20Item = { type: ItemType.ERC20, tokenAddress: '0xERC20', @@ -332,7 +360,11 @@ describe('balanceRequirement', () => { }, ]; - const result = getTokenBalanceRequirement(itemRequirement, balances); + const result = await getTokenBalanceRequirement( + itemRequirement, + balances, + mockProvider, + ); expect(result).toEqual({ sufficient: false, type: ItemType.ERC20, diff --git a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.ts b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.ts index 2e5e027652..ba4089effd 100644 --- a/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.ts +++ b/packages/checkout/sdk/src/smartCheckout/balanceCheck/balanceRequirement.ts @@ -1,5 +1,6 @@ /* eslint-disable arrow-body-style */ -import { BigNumber, utils } from 'ethers'; +import { BigNumber, Contract, utils } from 'ethers'; +import { Web3Provider } from '@ethersproject/providers'; import { ERC20Item, ERC721Balance, @@ -16,28 +17,34 @@ import { BalanceERC721Requirement, BalanceNativeRequirement, } from './types'; -import { DEFAULT_TOKEN_DECIMALS, NATIVE, ZKEVM_NATIVE_TOKEN } from '../../env'; +import { + DEFAULT_TOKEN_DECIMALS, + ERC20ABI, + NATIVE, + ZKEVM_NATIVE_TOKEN, +} from '../../env'; import { isNativeToken } from '../../tokens'; import { isMatchingAddress } from '../../utils/utils'; -export const getTokensFromRequirements = (itemRequirements: ItemRequirement[]): TokenInfo[] => itemRequirements - .map((itemRequirement) => { - switch (itemRequirement.type) { - case ItemType.ERC20: - return { - address: itemRequirement.tokenAddress, - } as TokenInfo; - case ItemType.NATIVE: - return { - address: NATIVE, - } as TokenInfo; - case ItemType.ERC721: - default: - return { - address: itemRequirement.contractAddress, - } as TokenInfo; - } - }); +export const getTokensFromRequirements = ( + itemRequirements: ItemRequirement[], +): TokenInfo[] => itemRequirements.map((itemRequirement) => { + switch (itemRequirement.type) { + case ItemType.ERC20: + return { + address: itemRequirement.tokenAddress, + } as TokenInfo; + case ItemType.NATIVE: + return { + address: NATIVE, + } as TokenInfo; + case ItemType.ERC721: + default: + return { + address: itemRequirement.contractAddress, + } as TokenInfo; + } +}); /** * Gets the balance requirement with delta for an ERC721 requirement. @@ -45,20 +52,27 @@ export const getTokensFromRequirements = (itemRequirements: ItemRequirement[]): export const getERC721BalanceRequirement = ( itemRequirement: ERC721Item, balances: ItemBalance[], -) : BalanceERC721Requirement => { +): BalanceERC721Requirement => { const requiredBalance = BigNumber.from(1); // Find the requirements related balance const itemBalanceResult = balances.find((balance) => { const balanceERC721Result = balance as ERC721Balance; - return isMatchingAddress(balanceERC721Result.contractAddress, itemRequirement.contractAddress) - && balanceERC721Result.id === itemRequirement.id; + return ( + isMatchingAddress( + balanceERC721Result.contractAddress, + itemRequirement.contractAddress, + ) && balanceERC721Result.id === itemRequirement.id + ); }); // Calculate the balance delta - const sufficient = (requiredBalance.isNegative() || requiredBalance.isZero()) + const sufficient = requiredBalance.isNegative() + || requiredBalance.isZero() || (itemBalanceResult?.balance.gte(requiredBalance) ?? false); - const delta = requiredBalance.sub(itemBalanceResult?.balance ?? BigNumber.from(0)); + const delta = requiredBalance.sub( + itemBalanceResult?.balance ?? BigNumber.from(0), + ); let erc721BalanceResult = itemBalanceResult as ERC721Balance; if (!erc721BalanceResult) { erc721BalanceResult = { @@ -88,16 +102,20 @@ export const getERC721BalanceRequirement = ( /** * Gets the balance requirement for a NATIVE or ERC20 requirement. */ -export const getTokenBalanceRequirement = ( +export const getTokenBalanceRequirement = async ( itemRequirement: ERC20Item | NativeItem, balances: ItemBalance[], -) : BalanceNativeRequirement | BalanceERC20Requirement => { + provider: Web3Provider, +): Promise => { let itemBalanceResult: ItemBalance | undefined; // Get the requirements related balance if (itemRequirement.type === ItemType.ERC20) { itemBalanceResult = balances.find((balance) => { - return isMatchingAddress((balance as TokenBalance).token?.address, itemRequirement.tokenAddress); + return isMatchingAddress( + (balance as TokenBalance).token?.address, + itemRequirement.tokenAddress, + ); }); } else if (itemRequirement.type === ItemType.NATIVE) { itemBalanceResult = balances.find((balance) => { @@ -107,17 +125,44 @@ export const getTokenBalanceRequirement = ( // Calculate the balance delta const requiredBalance: BigNumber = itemRequirement.amount; - const sufficient = (requiredBalance.isNegative() || requiredBalance.isZero()) + const sufficient = requiredBalance.isNegative() + || requiredBalance.isZero() || (itemBalanceResult?.balance.gte(requiredBalance) ?? false); - const delta = requiredBalance.sub(itemBalanceResult?.balance ?? BigNumber.from(0)); + const delta = requiredBalance.sub( + itemBalanceResult?.balance ?? BigNumber.from(0), + ); let name = ''; let symbol = ''; let decimals = DEFAULT_TOKEN_DECIMALS; if (itemBalanceResult) { - decimals = (itemBalanceResult as TokenBalance).token?.decimals ?? DEFAULT_TOKEN_DECIMALS; + decimals = (itemBalanceResult as TokenBalance).token?.decimals + ?? DEFAULT_TOKEN_DECIMALS; name = (itemBalanceResult as TokenBalance).token.name; symbol = (itemBalanceResult as TokenBalance).token.symbol; + } else if (itemRequirement.type === ItemType.ERC20) { + // Missing item balance so we need to query contract + try { + const contract = new Contract( + itemRequirement.tokenAddress, + JSON.stringify(ERC20ABI), + provider, + ); + const [contractName, contractSymbol, contractDecimals] = await Promise.all([ + contract.name(), + contract.symbol(), + contract.decimals(), + ]); + decimals = contractDecimals; + name = contractName; + symbol = contractSymbol; + } catch (error) { + // eslint-disable-next-line no-console + console.error( + 'Failed to query contract information', + itemRequirement.tokenAddress, + ); + } } let tokenBalanceResult = itemBalanceResult as TokenBalance; @@ -174,9 +219,23 @@ export const getTokenBalanceRequirement = ( balance: delta, formattedBalance: utils.formatUnits(delta, decimals), }, - current: tokenBalanceResult, + current: { + ...tokenBalanceResult, + token: { + address: itemRequirement.tokenAddress, + name, + symbol, + decimals, + }, + }, required: { ...tokenBalanceResult, + token: { + address: itemRequirement.tokenAddress, + name, + symbol, + decimals, + }, balance: BigNumber.from(itemRequirement.amount), formattedBalance: utils.formatUnits(itemRequirement.amount, decimals), },