Skip to content
This repository was archived by the owner on Aug 12, 2023. It is now read-only.

Commit 43c3fa4

Browse files
authored
Merge pull request #6 from abigger87/ab/zc-options
[WIP] Graceful Enhancements
2 parents fccea41 + e3d8ccc commit 43c3fa4

File tree

10 files changed

+434
-21
lines changed

10 files changed

+434
-21
lines changed

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const config: Config.InitialOptions = {
88
coverageProvider: 'v8',
99
preset: 'ts-jest',
1010
testEnvironment: 'node',
11-
testMatch: ['**/tests/*.test.ts'],
11+
testMatch: ['**/*.test.ts'],
1212
};
1313

1414
export default config;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "Lightweight, Modern Multicall3 Typescript Library",
44
"author": "https://github.com/abigger87",
55
"license": "AGPL-3.0-only",
6-
"version": "2.0.0",
6+
"version": "2.1.0",
77
"repository": "git://github.com/abigger87/pilum.git",
88
"publishConfig": {
99
"registry": "https://registry.npmjs.org/"

src/AbiMapper.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Interface } from '@ethersproject/abi';
2+
import { multicall1, multicall2, multicall3 } from './abis';
3+
import { Options } from './models';
4+
import { networks } from './networks';
5+
import { Address, Mapping, Network } from './types';
6+
7+
// Checks if the provided address is a valid multicall address in our network definitions
8+
const constructWithAddress = (address: Address): Mapping => {
9+
let mapping: Mapping = {
10+
found: false,
11+
address: networks['1']['multicall3'],
12+
network: 1,
13+
interface: new Interface(multicall3),
14+
abi: multicall3,
15+
};
16+
17+
// Iterate over the networks
18+
for (const network in networks) {
19+
// If we have a match, return the network
20+
const networkObject: Mapping = networkToMapping(
21+
parseInt(network),
22+
networks[network],
23+
address
24+
);
25+
if (networkObject.found) {
26+
mapping = networkObject;
27+
break;
28+
}
29+
}
30+
31+
return mapping;
32+
};
33+
34+
// Attempts to construct a mapping from a network object with the provided multicall address
35+
const networkToMapping = (
36+
network: Network,
37+
networkObject: object,
38+
address: Address
39+
): Mapping => {
40+
let mapping: Mapping = {
41+
found: false,
42+
address: networks['1']['multicall3'],
43+
network: 1,
44+
interface: new Interface(multicall3),
45+
abi: multicall3,
46+
};
47+
48+
if (networkObject['multicall3'].toLowerCase() == address.toLowerCase()) {
49+
mapping = {
50+
found: true,
51+
address: networkObject['multicall3'],
52+
network,
53+
interface: new Interface(multicall3),
54+
abi: multicall3,
55+
};
56+
} else if (
57+
networkObject['multicall2'].toLowerCase() == address.toLowerCase()
58+
) {
59+
mapping = {
60+
found: true,
61+
address: networkObject['multicall2'],
62+
network,
63+
interface: new Interface(multicall2),
64+
abi: multicall2,
65+
};
66+
} else if (
67+
networkObject['multicall'].toLowerCase() == address.toLowerCase()
68+
) {
69+
mapping = {
70+
found: true,
71+
address: networks[network.toString()]['multicall'],
72+
network,
73+
interface: new Interface(multicall1),
74+
abi: multicall1,
75+
};
76+
}
77+
78+
return mapping;
79+
};
80+
81+
// Maps Multicall address string to an Interface generated from our stored ABIs
82+
// Network defaults to 1 (mainnet) if multicall address isn't found
83+
// If an inconsistency is found between the network and multicall address string,
84+
// the multicall address is first checked, then we will default to the multicall
85+
// instance of the provided network
86+
const abiMap = (options?: Options): Mapping => {
87+
// Craft the default mapping
88+
let mapping: Mapping = {
89+
found: false,
90+
address: networks['1']['multicall3'],
91+
network: 1,
92+
interface: new Interface(multicall3),
93+
abi: multicall3,
94+
};
95+
96+
// If we have options
97+
if (options) {
98+
// Deconstruct our option parameters
99+
const { address, network } = options;
100+
101+
if (network) {
102+
const networkObject: object = networks[network.toString()];
103+
104+
// If we have a network, let's check if we have a multicall address
105+
if (networkObject) {
106+
// Try to get multicall with address
107+
const foundMulticall = networkToMapping(
108+
network,
109+
networkObject,
110+
address || '0x0' // if no address, 0x0 shouldn't work
111+
);
112+
if (foundMulticall.found) {
113+
mapping = foundMulticall;
114+
} else {
115+
// The network didn't contain the expected multicall address
116+
// Check if networks contains the address
117+
const validatedNetwork: Mapping = constructWithAddress(
118+
address || '0x0'
119+
);
120+
121+
// If the address isn't found, we can use the network
122+
if (!validatedNetwork.found) {
123+
// Grab the most up to date multicall address
124+
if (networkObject['multicall3']) {
125+
mapping = {
126+
found: true,
127+
address: networkObject['multicall3'],
128+
network,
129+
interface: new Interface(multicall3),
130+
abi: multicall3,
131+
};
132+
} else if (networkObject['multicall2']) {
133+
mapping = {
134+
found: true,
135+
address: networkObject['multicall2'],
136+
network,
137+
interface: new Interface(multicall2),
138+
abi: multicall2,
139+
};
140+
} else if (networkObject['multicall1']) {
141+
mapping = {
142+
found: true,
143+
address: networkObject['multicall1'],
144+
network,
145+
interface: new Interface(multicall1),
146+
abi: multicall1,
147+
};
148+
} else {
149+
// We have to use the default since none of the network multicalls are defined
150+
// This should really never happen
151+
}
152+
} else {
153+
// Otherwise, use the address
154+
mapping = validatedNetwork;
155+
}
156+
}
157+
} else {
158+
// No network, try to find multicall address and use it
159+
mapping = constructWithAddress(address || '0x0');
160+
}
161+
} else {
162+
// No network, try to find multicall address and use it
163+
mapping = constructWithAddress(address || '0x0');
164+
}
165+
}
166+
167+
// Return the constructed mapping
168+
return mapping;
169+
};
170+
171+
export default abiMap;
172+
export { Mapping, abiMap, constructWithAddress, networkToMapping };

src/Multicall.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
AggregatedCall,
77
AggregateFullResponse,
88
ContractCall,
9+
Options,
910
} from './models';
10-
11-
type Address = string;
11+
import { abiMap, Mapping } from './AbiMapper';
12+
import { Address } from './types';
13+
import { Interface } from '@ethersproject/abi';
1214

1315
// Multicall - A library for calling multiple contracts in aggregate
1416
export class Multicall {
@@ -17,12 +19,9 @@ export class Multicall {
1719
public network: object;
1820
public multicall: string;
1921
public abi: object;
22+
public interface: Interface;
2023

21-
constructor(options?: {
22-
address?: Address;
23-
provider?: ethers.providers.Provider | ethers.Signer;
24-
network?: number;
25-
}) {
24+
constructor(options?: Options) {
2625
// Extract the network or default to 1
2726
this.chainId = options && options.network ? options.network : 1;
2827
// If we have a network but not a provider, let's get the default provider for the given network
@@ -47,6 +46,7 @@ export class Multicall {
4746
this.multicall = this.network['multicall2'];
4847
}
4948
}
49+
this.interface = new ethers.utils.Interface(JSON.stringify(this.abi));
5050
}
5151

5252
public static encode(calls: ContractCall[] | ContractCall): AggregatedCall[] {
@@ -251,33 +251,49 @@ export class Multicall {
251251
}
252252

253253
public static async call(
254-
calls: ContractCall[] | ContractCall
254+
calls: ContractCall[] | ContractCall,
255+
options?: Options
255256
): Promise<AggregateFullResponse> {
256257
// Encode the calls
257258
const encoded: AggregatedCall[] = Multicall.encode(calls);
258259

259260
// Craft default configuration
260-
const abi: object = multicall3;
261-
const multicall3Address: string = networks['1']['multicall3'];
261+
const map: Mapping = abiMap(options);
262+
const abi: object = map.abi;
263+
const address: Address = map.address;
262264
const provider: ethers.providers.Provider | ethers.Signer =
263-
ethers.getDefaultProvider();
265+
options && options.provider
266+
? options.provider
267+
: ethers.getDefaultProvider();
264268

265269
// Execute and return the calls
266-
return await Multicall.execute(abi, multicall3Address, provider, encoded);
270+
return await Multicall.execute(abi, address, provider, encoded);
267271
}
268272

269273
public async call(
270-
calls: ContractCall[] | ContractCall
274+
calls: ContractCall[] | ContractCall,
275+
options?: Options
271276
): Promise<AggregateFullResponse> {
272277
// Encode the calls
273278
const encoded: AggregatedCall[] = Multicall.encode(calls);
274279

280+
// Craft custom configuration
281+
const map: Mapping =
282+
options && (options.address || options.network)
283+
? abiMap(options)
284+
: {
285+
found: false,
286+
abi: this.abi,
287+
address: this.multicall,
288+
network: this.chainId,
289+
interface: this.interface,
290+
};
291+
const abi: object = map.abi;
292+
const address: Address = map.address;
293+
const provider: ethers.providers.Provider | ethers.Signer =
294+
options && options.provider ? options.provider : this.provider;
295+
275296
// Execute and return the calls
276-
return await Multicall.execute(
277-
this.abi,
278-
this.multicall,
279-
this.provider,
280-
encoded
281-
);
297+
return await Multicall.execute(abi, address, provider, encoded);
282298
}
283299
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export { Multicall } from './Multicall';
2+
export * from './types';
3+
export * from './AbiMapper';

src/models/Options.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ethers } from 'ethers';
2+
3+
export interface Options {
4+
abi?: any[];
5+
address?: string;
6+
provider?: ethers.providers.Provider | ethers.Signer;
7+
network?: number;
8+
}

src/models/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AggregateFullResponse } from './AggregateFullResponse';
44
import { AggregatedCall } from './AggregatedCall';
55
import { ContractCall } from './ContractCall';
66
import { SingleCall } from './SingleCall';
7+
import { Options } from './Options';
78

89
// Re-exports
910
export {
@@ -12,5 +13,6 @@ export {
1213
AggregatedCall,
1314
ContractCall,
1415
SingleCall,
16+
Options,
1517
};
1618
export default {};

src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Interface } from 'ethers/lib/utils';
2+
3+
export type Network = number;
4+
export type Address = string;
5+
6+
export interface Mapping {
7+
found: boolean;
8+
address: Address;
9+
network: Network;
10+
interface: Interface;
11+
abi: object;
12+
}

0 commit comments

Comments
 (0)