Skip to content

Commit 894504a

Browse files
authored
Merge pull request #190 from aave/cesare/aave-2957-support-demo-chains
feat: API driven chains configuration
2 parents 28e711b + c12ca89 commit 894504a

File tree

16 files changed

+262
-158
lines changed

16 files changed

+262
-158
lines changed

.changeset/fast-rats-bet.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@aave/graphql": minor
3+
"@aave/client": minor
4+
"@aave/react": minor
5+
---
6+
7+
**feat:** viem integration to leverage API as source of truth about chain details.

packages/client/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@
5151
"require": "./dist/viem.cjs"
5252
},
5353
"./test-utils": {
54-
"import": "./dist/test-utils.js",
55-
"require": "./dist/test-utils.cjs"
54+
"default": "./dist/test-utils.js"
5655
}
5756
},
5857
"typesVersions": {

packages/client/src/test-utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
chainId,
99
type EvmAddress,
1010
evmAddress,
11+
never,
12+
nonNullable,
1113
ResultAsync,
1214
} from '@aave/types';
1315
import {
@@ -23,8 +25,9 @@ import {
2325
} from 'viem';
2426
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
2527
import { AaveClient } from './AaveClient';
28+
import { chain } from './actions';
2629
import { local, production, staging } from './environments';
27-
import { devnetChain } from './viem';
30+
import { toViemChain } from './viem';
2831

2932
export const environment =
3033
import.meta.env.ENVIRONMENT === 'local'
@@ -105,6 +108,14 @@ export const client = AaveClient.create({
105108
environment,
106109
});
107110

111+
const devnetChain = await chain(client, { chainId: ETHEREUM_FORK_ID })
112+
.map(nonNullable)
113+
.map(toViemChain)
114+
.match(
115+
(c) => c,
116+
() => never('No devnet chain found'),
117+
);
118+
108119
export async function createNewWallet(
109120
privateKey?: `0x${string}`,
110121
): Promise<WalletClient<Transport, Chain, Account>> {

packages/client/src/viem.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { sendWith } from './viem';
1818

1919
const walletClient = await createNewWallet();
2020

21-
describe(`Given a viem's WalletClient instance`, () => {
21+
describe.skip(`Given a viem's WalletClient instance`, () => {
2222
describe(`And the '${sendWith.name}' handler is used to send a TransactionRequest`, () => {
2323
const request: TransactionRequest = {
2424
__typename: 'TransactionRequest',

packages/client/src/viem.ts

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '@aave/core';
88
import type {
99
CancelSwapTypedData,
10+
Chain,
1011
ExecutionPlan,
1112
PermitTypedDataResponse,
1213
SwapByIntentTypedData,
@@ -26,7 +27,6 @@ import {
2627
} from '@aave/types';
2728
import {
2829
type Account,
29-
type Chain,
3030
defineChain,
3131
type ProviderRpcError,
3232
type RpcError,
@@ -36,6 +36,7 @@ import {
3636
type TypedData,
3737
type TypedDataDomain,
3838
UserRejectedRequestError,
39+
type Chain as ViemChain,
3940
type WalletClient,
4041
} from 'viem';
4142
import {
@@ -44,6 +45,9 @@ import {
4445
signTypedData,
4546
waitForTransactionReceipt,
4647
} from 'viem/actions';
48+
import { mainnet, sepolia } from 'viem/chains';
49+
import type { AaveClient } from './AaveClient';
50+
import { chain as fetchChain } from './actions';
4751
import type {
4852
ERC20PermitHandler,
4953
ExecutionPlanHandler,
@@ -66,10 +70,7 @@ function isProviderRpcError(
6670
: true;
6771
}
6872

69-
/**
70-
* @internal
71-
*/
72-
export const devnetChain: Chain = defineChain({
73+
const devnetChain: ViemChain = defineChain({
7374
id: Number.parseInt(import.meta.env.ETHEREUM_TENDERLY_FORK_ID, 10),
7475
name: 'Devnet',
7576
network: 'ethereum-fork',
@@ -87,57 +88,101 @@ export const devnetChain: Chain = defineChain({
8788

8889
/**
8990
* @internal
91+
* @deprecated
9092
*/
91-
export const supportedChains: Record<
92-
ChainId,
93-
ReturnType<typeof defineChain>
94-
> = {
95-
// TODO add them back when deployed on these chains
96-
// [chainId(mainnet.id)]: mainnet,
97-
// [chainId(sepolia.id)]: sepolia,
93+
export const supportedChains: Record<ChainId, ViemChain> = {
9894
[chainId(devnetChain.id)]: devnetChain,
9995
};
10096

101-
function ensureChain(
97+
/**
98+
* @internal
99+
*/
100+
export function toViemChain(chain: Chain): ViemChain {
101+
// known chains
102+
switch (chain.chainId) {
103+
case chainId(mainnet.id):
104+
return mainnet;
105+
106+
case chainId(sepolia.id):
107+
return sepolia;
108+
}
109+
110+
// most likely a tenderly fork
111+
return defineChain({
112+
id: chain.chainId,
113+
name: chain.name,
114+
nativeCurrency: {
115+
name: chain.nativeInfo.name,
116+
symbol: chain.nativeInfo.symbol,
117+
decimals: chain.nativeInfo.decimals,
118+
},
119+
rpcUrls: { default: { http: [chain.rpcUrl] } },
120+
blockExplorers: {
121+
default: {
122+
name: `${chain.name} Explorer`,
123+
url: chain.explorerUrl,
124+
},
125+
},
126+
});
127+
}
128+
129+
/**
130+
* @internal
131+
*/
132+
export function viemChainsFrom(chains: Chain[]): ViemChain[] {
133+
return chains.map(toViemChain);
134+
}
135+
136+
/**
137+
* @internal
138+
*/
139+
export function ensureChain(
140+
aaveClient: AaveClient,
102141
walletClient: WalletClient,
103142
request: TransactionRequest,
104-
): ResultAsync<void, CancelError | SigningError> {
143+
): ResultAsync<void, CancelError | SigningError | UnexpectedError> {
105144
return ResultAsync.fromPromise(walletClient.getChainId(), (err) =>
106145
SigningError.from(err),
107146
).andThen((chainId) => {
108147
if (chainId === request.chainId) {
109148
return okAsync();
110149
}
111150

112-
return ResultAsync.fromPromise(
113-
walletClient.switchChain({ id: request.chainId }),
114-
(err) => SigningError.from(err),
115-
).orElse((err) => {
116-
const code = isRpcError(err.cause)
117-
? err.cause.code
118-
: // Unwrapping for MetaMask Mobile
119-
// https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
120-
isProviderRpcError(err.cause)
121-
? err.cause.data?.originalError?.code
122-
: undefined;
123-
124-
if (
125-
code === SwitchChainError.code &&
126-
request.chainId in supportedChains
127-
) {
151+
return fetchChain(aaveClient, { chainId: request.chainId }).andThen(
152+
(chain) => {
153+
invariant(chain, `Chain ${request.chainId} is not supported`);
154+
128155
return ResultAsync.fromPromise(
129-
walletClient.addChain({ chain: supportedChains[request.chainId] }),
130-
(err) => {
131-
if (isRpcError(err) && err.code === UserRejectedRequestError.code) {
132-
return CancelError.from(err);
133-
}
134-
return SigningError.from(err);
135-
},
136-
);
137-
}
156+
walletClient.switchChain({ id: request.chainId }),
157+
(err) => SigningError.from(err),
158+
).orElse((err) => {
159+
const code = isRpcError(err.cause)
160+
? err.cause.code
161+
: // Unwrapping for MetaMask Mobile
162+
// https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
163+
isProviderRpcError(err.cause)
164+
? err.cause.data?.originalError?.code
165+
: undefined;
138166

139-
return err.asResultAsync();
140-
});
167+
if (code === SwitchChainError.code) {
168+
return ResultAsync.fromPromise(
169+
walletClient.addChain({ chain: toViemChain(chain) }),
170+
(err) => {
171+
if (
172+
isRpcError(err) &&
173+
err.code === UserRejectedRequestError.code
174+
) {
175+
return CancelError.from(err);
176+
}
177+
return SigningError.from(err);
178+
},
179+
);
180+
}
181+
182+
return err.asResultAsync();
183+
});
184+
},
185+
);
141186
});
142187
}
143188

@@ -157,7 +202,7 @@ function estimateGas(
157202
}
158203

159204
function sendEip1559Transaction(
160-
walletClient: WalletClient<Transport, Chain, Account>,
205+
walletClient: WalletClient<Transport, ViemChain, Account>,
161206
request: TransactionRequest,
162207
): ResultAsync<TxHash, CancelError | SigningError> {
163208
return estimateGas(walletClient, request)
@@ -190,7 +235,7 @@ function sendEip1559Transaction(
190235

191236
function isWalletClientWithAccount(
192237
walletClient: WalletClient,
193-
): walletClient is WalletClient<Transport, Chain, Account> {
238+
): walletClient is WalletClient<Transport, ViemChain, Account> {
194239
return walletClient.account !== undefined;
195240
}
196241

@@ -206,16 +251,14 @@ export function sendTransaction(
206251
'Wallet client with account is required',
207252
);
208253

209-
return ensureChain(walletClient, request).andThen((_) =>
210-
sendEip1559Transaction(walletClient, request),
211-
);
254+
return sendEip1559Transaction(walletClient, request);
212255
}
213256

214257
/**
215258
* @internal
216259
*/
217260
export function transactionError(
218-
chain: Chain | undefined,
261+
chain: ViemChain | undefined,
219262
txHash: TxHash,
220263
request: TransactionRequest,
221264
): TransactionError {

packages/client/tsconfig.build.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"compilerOptions": {
55
"outDir": "dist"
66
},
7-
"include": ["./src/**/*.ts"],
7+
"include": ["./src/**/*.ts", "../../vite-env.d.ts"],
88
"exclude": ["node_modules", "dist"]
99
}

packages/client/tsup.config.ts

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,62 @@ import { defineConfig } from 'tsup';
66

77
dotenv.config({ path: `${pnpmWorkspaceRootSync()}/.env` });
88

9-
export default defineConfig(() => ({
10-
entry: [
11-
'src/index.ts',
12-
'src/actions/index.ts',
13-
'src/utils/index.ts',
14-
'src/ethers.ts',
15-
'src/privy.ts',
16-
'src/thirdweb.ts',
17-
'src/viem.ts',
18-
'src/test-utils.ts',
19-
],
20-
outDir: 'dist',
21-
sourcemap: true,
22-
treeshake: true,
23-
clean: true,
24-
tsconfig: 'tsconfig.build.json',
25-
bundle: true,
26-
minify: true,
27-
dts: true,
28-
platform: 'neutral',
29-
format: ['esm', 'cjs'],
30-
define: {
31-
'import.meta.env.ETHEREUM_TENDERLY_FORK_ID': JSON.stringify(
32-
process.env.ETHEREUM_TENDERLY_FORK_ID,
33-
),
34-
'import.meta.env.ETHEREUM_TENDERLY_PUBLIC_RPC': JSON.stringify(
35-
process.env.ETHEREUM_TENDERLY_PUBLIC_RPC,
36-
),
37-
'import.meta.env.ETHEREUM_TENDERLY_BLOCKEXPLORER': JSON.stringify(
38-
process.env.ETHEREUM_TENDERLY_BLOCKEXPLORER,
39-
),
9+
export default defineConfig(() => [
10+
{
11+
entry: [
12+
'src/index.ts',
13+
'src/actions/index.ts',
14+
'src/utils/index.ts',
15+
'src/ethers.ts',
16+
'src/privy.ts',
17+
'src/thirdweb.ts',
18+
'src/viem.ts',
19+
],
20+
outDir: 'dist',
21+
sourcemap: true,
22+
treeshake: true,
23+
clean: true,
24+
tsconfig: 'tsconfig.build.json',
25+
bundle: true,
26+
minify: true,
27+
dts: true,
28+
platform: 'neutral',
29+
format: ['esm', 'cjs'],
30+
define: {
31+
'import.meta.env.ETHEREUM_TENDERLY_FORK_ID': JSON.stringify(
32+
process.env.ETHEREUM_TENDERLY_FORK_ID,
33+
),
34+
'import.meta.env.ETHEREUM_TENDERLY_PUBLIC_RPC': JSON.stringify(
35+
process.env.ETHEREUM_TENDERLY_PUBLIC_RPC,
36+
),
37+
'import.meta.env.ETHEREUM_TENDERLY_BLOCKEXPLORER': JSON.stringify(
38+
process.env.ETHEREUM_TENDERLY_BLOCKEXPLORER,
39+
),
40+
},
4041
},
41-
}));
42+
// ESM only
43+
{
44+
entry: ['src/test-utils.ts'],
45+
outDir: 'dist',
46+
sourcemap: true,
47+
treeshake: true,
48+
clean: false, // Don't clean on second config to avoid deleting first build
49+
tsconfig: 'tsconfig.build.json',
50+
bundle: true,
51+
minify: true,
52+
dts: true,
53+
platform: 'neutral',
54+
format: ['esm'],
55+
define: {
56+
'import.meta.env.ETHEREUM_TENDERLY_FORK_ID': JSON.stringify(
57+
process.env.ETHEREUM_TENDERLY_FORK_ID,
58+
),
59+
'import.meta.env.ETHEREUM_TENDERLY_PUBLIC_RPC': JSON.stringify(
60+
process.env.ETHEREUM_TENDERLY_PUBLIC_RPC,
61+
),
62+
'import.meta.env.ETHEREUM_TENDERLY_BLOCKEXPLORER': JSON.stringify(
63+
process.env.ETHEREUM_TENDERLY_BLOCKEXPLORER,
64+
),
65+
},
66+
},
67+
]);

packages/graphql/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ type Chain {
195195
nativeInfo: TokenInfo!
196196
nativeGateway: EvmAddress!
197197
signatureGateway: EvmAddress!
198+
rpcUrl: String!
198199
}
199200

200201
"""A supported blockchain chain ID"""

0 commit comments

Comments
 (0)