Skip to content

Commit

Permalink
Upgrade from wagmi 0.12.x -> 2.x
Browse files Browse the repository at this point in the history
Drop ethers, add viem.

Bump many other dependencies.

Migrate from BigNumber to bigint.

Avoid JSON.serialize with types containing bigints. Use wagmi serialize
instead.

Wagmi v2 introduced tanstack v5 as a peer dependency. Add a global
QueryClient.

Revamp useCaip222StyleSignature to verify collected signatures and work
correctly for EOA wallets (eg. metamask or coinbase wallet), EIP-1271
smart contract wallet (eg. gnosis safe), or ERC-6492 counterfactually
deployed smart contract wallet (eg. coinbase smart wallet).
  • Loading branch information
ryanberckmans committed Jul 8, 2024
1 parent 88dc53d commit 30af801
Show file tree
Hide file tree
Showing 54 changed files with 3,351 additions and 2,730 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ module.exports = {
"message": "Please use <Trans> instead of t."
},
{
"name": "@wagmi/core/chains",
"name": "wagmi/chains",
"message": "Import from 3cities './chains' instead."
},
{
"name": "viem/chains",
"message": "Import from 3cities './chains' instead."
},
],
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ yarn-error.log*
3cities-test-private-key

# vscode - don't track settings because the Disable Copilot completions extension works by continuously flipping a flag in this file and it causes confusing and spammy git diffs.
.vscode/settings.json
/.vscode/settings.json
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
"react"
],
"private": true,
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"eslint": "^8.57.0",
"eslint-plugin-rulesdir": "^0.2.2",
"typescript": "^5.5.2"
},
"scripts": {
"lint": "yarn workspace @3cities/react-app lint && yarn workspace @3cities/eth-transfer-proxy lint",
"build": "yarn workspace @3cities/eth-transfer-proxy build && yarn workspace @3cities/react-app build",
Expand Down
3 changes: 1 addition & 2 deletions packages/eth-transfer-proxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
"forge-std": "github:foundry-rs/forge-std#v1.8.1"
},
"devDependencies": {
"solhint": "^5.0.1",
"typescript": "^5.4.5"
"solhint": "^5.0.1"
},
"scripts": {
"clean:forge": "rm -rf cache out",
Expand Down
9 changes: 4 additions & 5 deletions packages/react-app/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# https://docs.buf.build/configuration/v1/buf-gen-yaml
version: v1
version: v2
plugins:
- plugin: es
opt: target=ts
out: src/gen
- local: protoc-gen-es
out: src/gen
opt: target=ts
4 changes: 0 additions & 4 deletions packages/react-app/buf.work.yaml

This file was deleted.

17 changes: 15 additions & 2 deletions packages/react-app/buf.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# https://buf.build/docs/configuration/v1/buf-yaml
version: v1
version: v2
modules:
# - path: .
# lint:
# except:
# - FIELD_NOT_REQUIRED
# - PACKAGE_NO_IMPORT_CYCLE
# disallow_comment_ignores: true
# breaking:
# except:
# - EXTENSION_NO_DELETE
# - FIELD_SAME_DEFAULT
- path: proto
lint:
disallow_comment_ignores: true
56 changes: 26 additions & 30 deletions packages/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,45 @@
},
"dependencies": {
"@3cities/eth-transfer-proxy": "^1.0.0",
"@bufbuild/protobuf": "^1.3.1",
"@ethersproject/providers": "5.7.2",
"connectkit": "1.3.0",
"ethers": "^5.7.2",
"immer": "^10.0.3",
"@bufbuild/protobuf": "^1.10.0",
"@tanstack/react-query": "^5.47",
"@walletconnect/ethereum-provider": "^2.13.3",
"connectkit": "^1.8.2",
"immer": "^10.1.1",
"localforage": "^1.10.0",
"qr-code-styling": "^1.6.0-rc.1",
"react": "^18.2.0",
"react-currency-input-field": "^3.6.11",
"react-dom": "^18.2.0",
"react-icons": "^4.8.0",
"react-router-dom": "^6.11.2",
"react": "^18.3.1",
"react-currency-input-field": "^3.8.0",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
"react-router-dom": "^6.24.0",
"react-use-clipboard": "^1.0.9",
"sonner": "^0.3.5",
"use-immer": "^0.9.0",
"wagmi": "0.12.19"
"sonner": "^1.5.0",
"use-immer": "^0.10.0",
"viem": "2.17.2",
"wagmi": "2.10.9"
},
"devDependencies": {
"@bufbuild/buf": "^1.26.1",
"@bufbuild/protoc-gen-es": "^1.3.1",
"@bufbuild/buf": "^1.34.0",
"@bufbuild/protoc-gen-es": "^1.10.0",
"@testing-library/dom": "9.3.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"autoprefixer": "^10.4.14",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"depcheck": "^1.4.7",
"env-cmd": "^10.1.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-rulesdir": "^0.2.2",
"postcss": "^8.4.31",
"postcss-import": "^15.1.0",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.2",
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"react-scripts": "https://gitpkg.now.sh/ryanberckmans/create-react-app/packages/react-scripts?6544f27623d9b156cee63774f7bf1a78ba8f1aab",
"source-map-explorer": "^2.5.3",
"tailwindcss": "^3.3.2",
"ts-macros": "^2.2.1",
"ts-prune": "^0.10.3",
"typescript": "^5.4.5"
"tailwindcss": "^3.4.4",
"ts-macros": "^2.6.0",
"ts-prune": "^0.10.3"
},
"eslintConfig": {
"extends": [
Expand Down
4 changes: 2 additions & 2 deletions packages/react-app/proto/threecities/v1/v1.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ message CheckoutSettings {
}

PayWhatYouWantFlags flags = 1; // required iff any flag is true, otherwise must be omitted iff all flags are false. See note on PayWhatYouWantFlags. Deserialized into multiple booleans inside CheckoutSettings.proposedPayment.paymentMode.payWhatYouWant
repeated bytes suggested_logical_asset_amounts = 2; // optional. Big-endian binary encoding of suggested payment logical asset amounts. Since this is "pay what you want" mode, these amounts are only suggested to the sender and not required. Deserialized as CheckoutSettings.proposedPayment.paymentMode.payWhatYouWant.suggestedLogicalAssetAmountsAsBigNumberHexStrings
repeated bytes suggested_logical_asset_amounts = 2; // optional. Big-endian binary encoding of suggested payment logical asset amounts. Since this is "pay what you want" mode, these amounts are only suggested to the sender and not required. Deserialized as CheckoutSettings.proposedPayment.paymentMode.payWhatYouWant.suggestedLogicalAssetAmounts
}

// SenderNoteSettingsMode determines whether the sender/buyer may not
Expand All @@ -76,7 +76,7 @@ message CheckoutSettings {

repeated LogicalAssetTicker proposed_payment_logical_asset_tickers = 4; // required. The first element is the required ticker for the primary logical asset in which the payment is denominated. Other elements are the optional prioritized secondary logical assets that are also accepted for payment. Eg. the 2nd element is a higher priority secondary logical asset than the 3rd element. Deserialized as CheckoutSettings.proposedPayment.logicalAssetTickers
oneof proposed_payment_payment_mode { // required.
bytes proposed_payment_payment_mode_logical_asset_amount = 5; // Big-endian binary encoding of payment logical asset amount. Deserialized as CheckoutSettings.proposedPayment.paymentMode.logicalAssetAmountAsBigNumberHexString
bytes proposed_payment_payment_mode_logical_asset_amount = 5; // Big-endian binary encoding of payment logical asset amount. Deserialized as CheckoutSettings.proposedPayment.paymentMode.logicalAssetAmount
PayWhatYouWant proposed_payment_payment_mode_pay_what_you_want = 6; // If oneof is this option, then this checkout is using "pay what you want" mode, which allows the sender to pay what they want within the constraints of these PayWhatYouWant details. Deserialized as CheckoutSettings.proposedPayment.paymentMode.payWhatYouWant
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BigNumber } from '@ethersproject/bignumber';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useImmer } from 'use-immer';
import { useAccount } from 'wagmi';
Expand Down Expand Up @@ -34,7 +33,7 @@ type NativeCurrencyBalanceUpdaterProps = {
nonce: number; // a nonce whose increment will cause the current native currency balance to be flushed into the updateNativeCurrencyBalance callback. Used to avoid a useEffect race condition where updated balances are pushed into the updateNativeCurrencyBalance callback and then discarded because the client AddressContext is reset to the empty value after the updated balances are applied
address: `0x${string}`; // address whose native currency balance we'll keep updated
nativeCurrency: NativeCurrency; // nativeCurrency whose balance we'll keep updated
updateNativeCurrencyBalance: (nc: NativeCurrency, b: BigNumber | undefined) => void; // callback we must call when native currency balance updates
updateNativeCurrencyBalance: (nc: NativeCurrency, b: bigint | undefined) => void; // callback we must call when native currency balance updates
}
const NativeCurrencyBalanceUpdater: React.FC<NativeCurrencyBalanceUpdaterProps> = ({ nonce, address, nativeCurrency, updateNativeCurrencyBalance }) => {
const b = useLiveNativeCurrencyBalance(address, nativeCurrency.chainId);
Expand All @@ -48,7 +47,7 @@ type TokenBalanceUpdaterProps = {
nonce: number; // a nonce whose increment will cause the current token balance to be flushed into the updateTokenBalance callback. Used to avoid a useEffect race condition where updated balances are pushed into the updateTokenBalance callback and then discarded because the AddressContext is client reset to the empty value after the updated balances are applied
address: `0x${string}`; // address whose token balance we'll update
token: Token; // token whose balance we'll update
updateTokenBalance: (t: Token, b: BigNumber | undefined) => void; // callback we must call when token balance updates
updateTokenBalance: (t: Token, b: bigint | undefined) => void; // callback we must call when token balance updates
}
const TokenBalanceUpdater: React.FC<TokenBalanceUpdaterProps> = ({ nonce, address, token, updateTokenBalance }) => {
const b = useLiveTokenBalance(token.contractAddress, address, token.chainId);
Expand Down Expand Up @@ -85,7 +84,7 @@ const ConnectedAccountContextUpdaterInner: React.FC<ConnectedAccountContextUpdat
ovu.setValueAndNotifyObservers(ac);
}, [ovu, ac]);

const updateNativeCurrencyOrTokenBalance = useCallback((nativeCurrencyOrToken: NativeCurrency | Token, newBalance: BigNumber | undefined) => { // updateNativeCurrencyOrTokenBalance is a single callback to be shared among all NativeCurrencyUpdaters/TokenBalanceUpdaters because each updater passes its own NativeCurrency/Token to this callback to be mapped into a tokenKey to then update AddressContext. Alternatively, we could have baked/curried the NativeCurrency/Token into the callback and created N callbacks, one per token
const updateNativeCurrencyOrTokenBalance = useCallback((nativeCurrencyOrToken: NativeCurrency | Token, newBalance: bigint | undefined) => { // updateNativeCurrencyOrTokenBalance is a single callback to be shared among all NativeCurrencyUpdaters/TokenBalanceUpdaters because each updater passes its own NativeCurrency/Token to this callback to be mapped into a tokenKey to then update AddressContext. Alternatively, we could have baked/curried the NativeCurrency/Token into the callback and created N callbacks, one per token
// console.log("top of updateNativeCurrencyOrTokenBalance callback");
setAC(draft => {
// console.log("top of updateNativeCurrencyOrTokenBalance setAC body");
Expand All @@ -98,7 +97,7 @@ const ConnectedAccountContextUpdaterInner: React.FC<ConnectedAccountContextUpdat
const tb: TokenBalance = {
address: connectedAccount,
tokenKey: tk,
balanceAsBigNumberHexString: newBalance.toHexString(),
balance: newBalance,
balanceAsOf: Date.now(),
};
if (isDust(tb)) {
Expand Down
5 changes: 2 additions & 3 deletions packages/react-app/src/CurrencyAmountInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BigNumber } from "@ethersproject/bignumber";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import CurrencyInput from "react-currency-input-field";
import { FaTimesCircle } from "react-icons/fa";
Expand Down Expand Up @@ -106,7 +105,7 @@ function useCurrencyAmountInput(logicalAssetTicker: LogicalAssetTicker, inputId:
return amount && er && logicalAssetTicker !== 'USD' ? convert({
fromTicker: logicalAssetTicker,
toTicker: 'USD',
fromAmount: parseLogicalAssetAmount(amount.toString()).toBigInt(),
fromAmount: parseLogicalAssetAmount(amount.toString()),
er,
}) : undefined;
}, [logicalAssetTicker, amount, er]);
Expand All @@ -123,7 +122,7 @@ function useCurrencyAmountInput(logicalAssetTicker: LogicalAssetTicker, inputId:
const amountInputElement = useMemo<JSX.Element>(() => {
const la: LogicalAsset = logicalAssetsByTicker[logicalAssetTicker];
return <div className="relative flex items-center justify-center">
{amountUsdEquivalent ? <span className="absolute bottom-[-1.5em] w-fit text-lg text-gray-500"><RenderLogicalAssetAmount logicalAssetTicker={"USD"} amountAsBigNumberHexString={BigNumber.from(amountUsdEquivalent).toHexString()} /></span> : undefined}
{amountUsdEquivalent ? <span className="absolute bottom-[-1.5em] w-fit text-lg text-gray-500"><RenderLogicalAssetAmount logicalAssetTicker={"USD"} amount={amountUsdEquivalent} /></span> : undefined}
<label className={`flex-none text-6xl font-medium text-black ${la.symbol.prefix ? '' : 'invisible pl-6'}`} htmlFor={inputId}>{la.symbol.prefix}{la.symbol.suffix}</label>
<div className="text-6xl font-bold" style={amountInputContainerStyle}>
<CurrencyInput
Expand Down
47 changes: 12 additions & 35 deletions packages/react-app/src/DemoAccountProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Signer } from '@ethersproject/abstract-signer';
import { isAddress } from "@ethersproject/address";
import { MockConnector } from '@wagmi/core/connectors/mock';
import React, { FC, useCallback, useEffect, useState } from "react";
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { useSearchParams } from 'react-router-dom';
import { useAccount, useConnect, useDisconnect } from 'wagmi';
import { isAddress } from "viem";
import { useAccount, useAccountEffect, useConnect, useDisconnect } from 'wagmi';
import { mock } from 'wagmi/connectors';
import { ActiveDemoAccountContext } from './ActiveDemoAccountContext';
import { ObservableValueUpdater, Observer, makeObservableValue } from './observer';
import { useEnsAddress } from './useEnsAddress';
Expand Down Expand Up @@ -54,29 +53,6 @@ const DemoAccountProviderInner: FC<DemoAccountProviderInnerProps> = ({ children,
// mock adddress -> change to invalid mock address -> change to valid mock address
// mock address -> change mock address

class ReadonlySigner extends Signer {
signMessage(): Promise<string> {
throw new Error('Method not implemented.');
}
signTransaction(): Promise<string> {
throw new Error('Method not implemented.');
}
connect(): Signer {
return new ReadonlySigner(this.address);
}

readonly address;

constructor(address: `0x${string}`) {
super();
this.address = address;
}

getAddress() {
return Promise.resolve(this.address);
}
}

// useMockConnectedAddress sets the wagmi connected wallet address to
// the passed mockAddressOrENS. This allows the connected address to be
// overridden for demo purposes on a read-only basis (the mocked address
Expand Down Expand Up @@ -128,12 +104,11 @@ function useMockConnectedAddress(mockAddressOrENS?: string | undefined): undefin
setMockAddressConnectionStatus('connecting');
setDoConnectThunk(() => () => { // NB here we need to wrap the thunk value in an outer function to actually cause the value to be set to the thunk, otherwise the thunk is interpreted as a setter function and the connectAsync is incorrectly executed as part of the state update instead of correctly in the doConnectThunk effect.
connectAsync({
connector: new MockConnector({
options: {
signer: new ReadonlySigner(addressToConnect),
},
})
}).then(({ account: connectedToAccount }) => {
connector: mock(({
accounts: [addressToConnect],
})),
}).then(({ accounts }) => {
const connectedToAccount = accounts[0];
if (connectedToAccount === addressToConnect) setMockAddressConnectionStatus('connected');
else throw new Error(`useMockConnectedAddress: connectAsync: connected to an unexpected account ${connectedToAccount} != addressToConnect ${addressToConnect}`);
}).catch((e: unknown) => {
Expand Down Expand Up @@ -180,7 +155,9 @@ function useCalcActiveDemoAccount() {
// no-op: a disconnection occurred unrelated to any demo account.
}
}, [demoAccountToConnect, setShouldConnectToDemoAccount, demoAccountSuccessfullyConnected, demoAccountThatWasSuccessfullyConnected]);
useAccount({ onDisconnect });

const useAccountEffectArgs = useMemo(() => { return { onDisconnect }; }, [onDisconnect]);
useAccountEffect(useAccountEffectArgs);

const activeDemoAccount: string | undefined = demoAccountSuccessfullyConnected && rawDemoAccountSearchParam ? rawDemoAccountSearchParam : undefined;
return activeDemoAccount;
Expand Down
7 changes: 4 additions & 3 deletions packages/react-app/src/ExchangeRates.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DeepWritable } from "./Writable";
import { hasOwnProperty } from "./hasOwnProperty";
import { LogicalAssetTicker, parseLogicalAssetAmount } from "./logicalAssets";
import { toUppercase } from "./toUppercase";

Expand All @@ -24,7 +25,7 @@ export function areExchangeRatesEqual(a: ExchangeRates | undefined, b: ExchangeR
let isEqual = true;
for (const keyARaw of keysA) {
const keyA = toUppercase(keyARaw);
if (!Object.prototype.hasOwnProperty.call(b, keyA)) {
if (!hasOwnProperty(b, keyA)) {
isEqual = false;
break;
} else {
Expand All @@ -38,7 +39,7 @@ export function areExchangeRatesEqual(a: ExchangeRates | undefined, b: ExchangeR
} else for (const innerKeyRaw of innerKeysA) {
const innerKey = toUppercase(innerKeyRaw);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- here we know it exists
if (!Object.prototype.hasOwnProperty.call(b[keyA], innerKey) || a[keyA]![innerKey] !== b[keyA]![innerKey]) {
if (!hasOwnProperty(b[keyA], innerKey) || a[keyA]![innerKey] !== b[keyA]![innerKey]) {
isEqual = false;
break;
}
Expand Down Expand Up @@ -125,7 +126,7 @@ function pow(base: bigint, exponent: bigint): bigint { // TODO deprecate this wh
}
}

const oneUnitOfLogicalAsset: bigint = parseLogicalAssetAmount('1').toBigInt();
const oneUnitOfLogicalAsset: bigint = parseLogicalAssetAmount('1');

// unitRate computes the exchange rate for the passed pair. This can be
// useful if the pair is not included directly in ExchangeRates. TODO
Expand Down
Loading

0 comments on commit 30af801

Please sign in to comment.