Skip to content

Commit

Permalink
Feat/ether v6 support (#182)
Browse files Browse the repository at this point in the history
* install ethersV6 as alias

* providers/ethersV6

* distinct ethersV5 exports

* legacy/support ethersV6

* simple SDK/support ethersV6

* simpleSDK.tests/add ethersV6

* partialSDK.tests/add ethersV6

* LOrders.tests/add ethersV6

* NFT_Orders.tests/add ethersV6

* update snapshots

* examples/ add ethersV6

* perrDeps/update ethers versions

* update snapshots

* cleanup

* move ethers types to provider/ethers

* move web3 types to provider/web3

* untie FetchError type from AxiosError

* ethers -> ethersV5, ethersV6 -> ethers to fix types when used as lib

* cleanup

* README/update version

* less dependency on ethers types

* update README

* rremove temp tests

* tests/ethersV6/fix derivation path arg position
  • Loading branch information
Velenir authored Nov 12, 2024
1 parent 3978301 commit 2613e3b
Show file tree
Hide file tree
Showing 31 changed files with 975 additions and 296 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Refer to the documentation of the ParaSwap API: https://developers.paraswap.netw

**Canonical**: bring only the functions you actually need

**Lightweight**: 400B Gzipped for the minimal variant
**Lightweight**: 10KB Gzipped for the minimal variant

## Installing ParaSwap SDK

Expand All @@ -29,7 +29,7 @@ You can see some examples in [/src/examples](src/examples) directory.

### Simple SDK

Can be created by providing `chainId` and either `axios` or `window.fetch` (or alternative `fetch` implementation), and an optional `version` (`'5'` or `'6.1'`) parameter that corresponds to the API version SDK will be making requests to. The resulting SDK will be able to use all methods that query the API.
Can be created by providing `chainId` and either `axios` or `window.fetch` (or alternative `fetch` implementation), and an optional `version` (`'5'` or `'6.2'`) parameter that corresponds to the API version SDK will be making requests to. The resulting SDK will be able to use all methods that query the API.

```ts
import { constructSimpleSDK } from '@paraswap/sdk';
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"bignumber.js": "^9.1.2",
"dotenv": "^16.4.5",
"dts-cli": "^2.0.5",
"ethers": "^5.7.2",
"ethers": "^6.13.4",
"ethersV5": "npm:ethers@5",
"hardhat": "^2.22.15",
"hardhat-switch-network": "^1.1.1",
"husky": "^9.1.6",
Expand All @@ -72,9 +73,9 @@
},
"peerDependencies": {
"axios": ">=0.25.0 <2.0.0",
"ethers": "^5.5.0",
"web3": "^4.14.0",
"viem": "^2.21.0"
"ethers": "^5.5.0 || ^6.0.0",
"viem": "^2.21.0",
"web3": "^4.14.0"
},
"peerDependenciesMeta": {
"axios": {
Expand Down
38 changes: 38 additions & 0 deletions src/examples/ethersV6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import { ethers } from 'ethers';
import {
constructPartialSDK,
constructFullSDK,
constructGetAdapters,
constructEthersV6ContractCaller,
constructAxiosFetcher,
} from '..';

const fetcher = constructAxiosFetcher(axios);

const provider = ethers.getDefaultProvider(1);
const contractCaller = constructEthersV6ContractCaller({
ethersV6ProviderOrSigner: provider,
EthersV6Contract: ethers.Contract,
});

const paraswap = constructFullSDK({
chainId: 1,
fetcher,
contractCaller,
});

const res = paraswap.swap.getAdapters();

// type Promise<ContractTransaction>
const txResponse = paraswap.swap.approveToken('1', '0x...');
// type Promise<ContractTransaction[]>
const txResponses = paraswap.swap.approveTokenBulk('1', ['0x...']);

const partial = constructPartialSDK(
{ apiURL: '', chainId: 1, fetcher },
constructGetAdapters
);

const res1 = partial.getAdapters();
2 changes: 1 addition & 1 deletion src/examples/limitOrders_all.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { ethers } from 'ethersV5';
import { assert } from 'ts-essentials';
import {
// swap methods
Expand Down
2 changes: 1 addition & 1 deletion src/examples/limitOrders_partial.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { ethers } from 'ethersV5';
import { assert } from 'ts-essentials';
import {
// swap methods
Expand Down
2 changes: 1 addition & 1 deletion src/examples/limitOrders_postOrder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import { ethers } from 'ethers';
import { ethers } from 'ethersV5';

import {
// swap methods
Expand Down
2 changes: 1 addition & 1 deletion src/examples/partial.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import { ethers } from 'ethers';
import { ethers } from 'ethersV5';
import {
constructPartialSDK,
constructGetAdapters,
Expand Down
6 changes: 3 additions & 3 deletions src/examples/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import { ethers } from 'ethers';
import { ethers } from 'ethersV5';
import {
constructPartialSDK,
constructFullSDK,
constructGetAdapters,
constructEthersContractCaller,
constructEthersV5ContractCaller,
constructAxiosFetcher,
} from '..';

const fetcher = constructAxiosFetcher(axios);

const provider = ethers.getDefaultProvider(1);
const contractCaller = constructEthersContractCaller({
const contractCaller = constructEthersV5ContractCaller({
ethersProviderOrSigner: provider,
EthersContract: ethers.Contract,
});
Expand Down
2 changes: 1 addition & 1 deletion src/examples/simple.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import axios from 'axios';
import { ethers, Wallet } from 'ethers';
import { ethers, Wallet } from 'ethersV5';
import {
constructSimpleSDK,
ContractMethod,
Expand Down
13 changes: 11 additions & 2 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { EthersV5ProviderDeps } from './providers/ethers';
import { EthersV6ProviderDeps } from './providers/ethersV6';
export { constructFetcher as constructAxiosFetcher } from './fetchers/axios';
export { constructFetcher as constructFetchFetcher } from './fetchers/fetch';
export {
constructContractCaller as constructEthersContractCaller,
EthersProviderDeps,
constructEthersV5ContractCaller as constructEthersContractCaller,
constructEthersV5ContractCaller,
EthersV5ProviderDeps,
} from './providers/ethers';
export {
constructContractCaller as constructEthersV6ContractCaller,
EthersV6ProviderDeps,
} from './providers/ethersV6';
export type EthersProviderDeps = EthersV5ProviderDeps | EthersV6ProviderDeps;

export {
constructContractCaller as constructWeb3ContractCaller,
Web3UnpromiEvent,
Expand Down
96 changes: 18 additions & 78 deletions src/helpers/misc.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,4 @@
import type {
Contract as EthersContract,
ContractFunction as EthersContractFunction,
PopulatedTransaction as EthersPopulatedTransaction,
BigNumber as EthersBigNumber,
} from 'ethers';
import { assert, Primitive } from 'ts-essentials';

import type { AxiosError, AxiosResponse } from 'axios';
import type { ContractAbi, Contract as Web3Contract } from 'web3';

export type Web3ContractSendMethod =
Web3Contract<ContractAbi>['methods'][string];

export type EthersContractWithMethod<T extends string> = EthersContract & {
readonly [method in T]: EthersContractFunction;
} & {
readonly functions: { [method in T]: EthersContractFunction };

readonly callStatic: { [method in T]: EthersContractFunction };
readonly estimateGas: {
[method in T]: EthersContractFunction<EthersBigNumber>;
};
readonly populateTransaction: {
[method in T]: EthersContractFunction<EthersPopulatedTransaction>;
};
};

export function ethersContractHasMethods<T extends string>(
contract: EthersContract,
...methods: T[]
): contract is EthersContractWithMethod<T> {
return methods.every((method) => typeof contract[method] === 'function');
}

export function assertEthersContractHasMethods<T extends string>(
contract: EthersContract,
...methods: T[]
): asserts contract is EthersContractWithMethod<T> {
assert(
ethersContractHasMethods(contract, ...methods),
`Contract must have methods: ${methods.join(', ')}`
);
}

export type Web3ContractWithMethod<T extends string> =
Web3Contract<ContractAbi> & {
methods: { [method in T]: Web3ContractSendMethod };
};

export function web3ContractHasMethods<T extends string>(
contract: Web3Contract<ContractAbi>,
...methods: T[]
): contract is Web3ContractWithMethod<T> {
return methods.every(
(method) => typeof contract.methods[method] === 'function'
);
}

export function assertWeb3ContractHasMethods<T extends string>(
contract: Web3Contract<ContractAbi>,
...methods: T[]
): asserts contract is Web3ContractWithMethod<T> {
assert(
web3ContractHasMethods(contract, ...methods),
`Contract must have methods: ${methods.join(', ')}`
);
}
import type { Primitive } from 'ts-essentials';

export const objectToFilledEntries = <T extends Record<string, unknown>>(
object: T
Expand All @@ -92,16 +25,23 @@ export const constructSearchString = <
return queryString && `?${queryString}`;
};

type FetcherErrorConstructorInput = Pick<
AxiosError,
'code' | 'request' | 'isAxiosError' | 'message'
> & {
response?: Pick<
AxiosResponse,
'data' | 'status' | 'statusText' | 'headers'
> & {
config: { url?: string; method?: string };
};
type MinAxiosError = {
code?: string;
request?: any;
isAxiosError: boolean;
message: string;
};

type MinAxiosResponse = {
data: any;
status: number;
statusText: string;
headers: Record<string, any>;
config: { url?: string; method?: string };
};

type FetcherErrorConstructorInput = MinAxiosError & {
response?: MinAxiosResponse;
};

export interface FetcherErrorInterface extends FetcherErrorConstructorInput {
Expand Down
52 changes: 44 additions & 8 deletions src/helpers/providers/ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,36 @@ import type {
} from '@ethersproject/providers';
import type { Signer } from '@ethersproject/abstract-signer';
import type {
Contract as EthersContract,
Contract as EthersV5Contract,
ContractFunction as EthersContractFunctionV5,
PopulatedTransaction as EthersPopulatedTransactionV5,
PayableOverrides,
CallOverrides,
ContractTransaction,
} from '@ethersproject/contracts';
import { assertEthersContractHasMethods } from '../misc';

import type { BigNumber as EthersBigNumberV5 } from '@ethersproject/bignumber';

import { assert } from 'ts-essentials';

export interface EthersProviderDeps {
export interface EthersV5ProviderDeps {
ethersProviderOrSigner: BaseProvider | Signer;
EthersContract: typeof EthersContract; // passing Contract in allows not to include ethers as dependency even when using legacy ParaSwap class
EthersContract: typeof EthersV5Contract; // passing Contract in allows not to include ethers as dependency even when using legacy ParaSwap class
}

export const constructContractCaller = (
export const constructEthersV5ContractCaller = (
{
ethersProviderOrSigner: providerOrSigner,
EthersContract: Contract,
}: EthersProviderDeps,
}: EthersV5ProviderDeps,
account?: Address
): ContractCallerFunctions<ContractTransaction> => {
const staticCall: StaticContractCallerFn = async (params) => {
const { address, abi, contractMethod, args, overrides } = params;

const contract = new Contract(address, abi, providerOrSigner);

assertEthersContractHasMethods(contract, contractMethod);
assertEthersContractHasMethodsV5(contract, contractMethod);
// drop keys not in CallOverrides
const { block, gas, ...restOverrides } = overrides;
// reassign values to keys in CallOverrides
Expand Down Expand Up @@ -80,7 +84,7 @@ export const constructContractCaller = (

const contract = new Contract(address, abi, signer);

assertEthersContractHasMethods(contract, contractMethod);
assertEthersContractHasMethodsV5(contract, contractMethod);
// drop keys not in PayableOverrides
const { gas, from, ...restOverrides } = overrides;
// reassign values to keys in PayableOverrides
Expand Down Expand Up @@ -154,3 +158,35 @@ function isTypedDataCapableSigner(
): signer is Signer & Pick<JsonRpcSigner, '_signTypedData'> {
return '_signTypedData' in signer;
}

/// ethers v5
type EthersContractWithMethodV5<T extends string> = EthersV5Contract & {
readonly [method in T]: EthersContractFunctionV5;
} & {
readonly functions: { [method in T]: EthersContractFunctionV5 };

readonly callStatic: { [method in T]: EthersContractFunctionV5 };
readonly estimateGas: {
[method in T]: EthersContractFunctionV5<EthersBigNumberV5>;
};
readonly populateTransaction: {
[method in T]: EthersContractFunctionV5<EthersPopulatedTransactionV5>;
};
};

function ethersContractHasMethodsV5<T extends string>(
contract: EthersV5Contract,
...methods: T[]
): contract is EthersContractWithMethodV5<T> {
return methods.every((method) => typeof contract[method] === 'function');
}

function assertEthersContractHasMethodsV5<T extends string>(
contract: EthersV5Contract,
...methods: T[]
): asserts contract is EthersContractWithMethodV5<T> {
assert(
ethersContractHasMethodsV5(contract, ...methods),
`Contract must have methods: ${methods.join(', ')}`
);
}
Loading

0 comments on commit 2613e3b

Please sign in to comment.