Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add 1inch visualization supoprt #37

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
logs
*.log
npm-debug.log*
yarn.lock
package-lock.json

# Runtime data
pids
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
},
"devDependencies": {
"@types/jest": "^29.5.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"jest": "^29.5.0",
"prettier": "^2.8.4",
"ts-jest": "^29.0.5",
"eslint": "^8.36.0",
"typescript": "^5.0.2"
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * as blurTypes from "./blur";
export * as looksrareTypes from "./looksrare";
export * as seaportTypes from "./seaport";
export * as visualizer from "./visualizer";
export * as oneinch from "./oneinch";

export enum ASSET_TYPE {
NATIVE = "NATIVE",
Expand Down
36 changes: 36 additions & 0 deletions src/types/oneinch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { BytesLike } from "ethers";

/**
* @dev type taken from 1inch contract
* @see https://portal.1inch.dev/documentation/fusion/fusion-sdk/for-resolvers/auction-calculator
*/

export type OneInchFusionOrder = {
// Contains of auction start time, auction duration, initial rate bump, fee and some unique value
salt: string;
// Address of the asset user want to sell
makerAsset: string;
// Address of the asset user want to buy
takerAsset: string;
// An address of the maker (wallet or contract address)
maker: string;
/**
* If it contains a zero address, which means that taker asset will be sent to the address of the creator of the
* limit order. If user set any other value, then taker asset will be sent to the specified address
*/
receiver: string;
/**
* If it contains a zero address, which means that a limit order is available for everyone to fill.
* If user set any other value, then the limit order will be available for execution only
* for the specified address (private limit order)
*/
allowedSender: string;
// Order maker's token amount
makingAmount: string;
// Order taker's token amount
takingAmount: string;
// Merged offsets of each field in interactions
offsets: string;
// An interaction call data. ABI encoded set of makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preInteraction, postInteraction
interactions: BytesLike;
};
22 changes: 22 additions & 0 deletions src/types/rarible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BytesLike } from "ethers";

export type AssetType = {
assetClass: string;
data: BytesLike;
};
export type Asset = {
assetType: AssetType;
value: string;
};

export type RaribleOrder = {
maker: string;
makeAsset: Asset;
taker: string;
takeAsset: Asset;
start: string;
end: string;
salt: string;
dataType: string;
data: BytesLike;
};
12 changes: 10 additions & 2 deletions src/visualizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,35 @@ import { PermitMessage } from "../types";
import { SeaPortPayload } from "../types/seaport";
import { BlurIoOrder } from "../types/blur";
import { LooksrareMakerOrderWithEncodedParams } from "../types/looksrare";
import { LooksRareV2MakerOrder } from "../types/looksrare-v2";
import { OneInchFusionOrder } from "../types/oneinch";
import { RaribleOrder } from "../types/rarible";

import blurIo from "./blur-io";
import erc20Permit from "./erc20-permit";
import looksrare from "./looksrare";
import looksrareV2 from "./looksrare-v2";
import oneinch from "./oneinch";
import seaport from "./seaport";
import { Domain, VisualizationResult } from "../types/visualizer";
import { WizardError } from "../utils";
import { LooksRareV2MakerOrder } from "../types/looksrare-v2";

export enum PROTOCOL_ID {
OPENSEA_SEAPORT = "OPENSEA_SEAPORT",
LOOKSRARE_EXCHANGE = "LOOKSRARE_EXCHANGE",
LOOKSRARE_EXCHANGE_V2 = "LOOKSRARE_EXCHANGE_V2",
BLUR_IO_MARKETPLACE = "BLUR_IO_MARKETPLACE",
ERC20_PERMIT = "ERC20_PERMIT",
RARIBLE = "EXCHANGE",
ONEINCH_FUSION = "ONEINCH_FUSION",
}

export const getProtocolId = (domain: Domain): PROTOCOL_ID | undefined => {
if (seaport.isCorrectDomain(domain)) return PROTOCOL_ID.OPENSEA_SEAPORT;
if (blurIo.isCorrectDomain(domain)) return PROTOCOL_ID.BLUR_IO_MARKETPLACE;
if (looksrareV2.isCorrectDomain(domain)) return PROTOCOL_ID.LOOKSRARE_EXCHANGE_V2;
if (looksrare.isCorrectDomain(domain)) return PROTOCOL_ID.LOOKSRARE_EXCHANGE;

if (oneinch.isCorrectDomain(domain)) return PROTOCOL_ID.ONEINCH_FUSION;
return;
};

Expand Down Expand Up @@ -55,6 +60,9 @@ export default async function visualize<T extends object>(
case PROTOCOL_ID.BLUR_IO_MARKETPLACE:
return blurIo.visualize(message as BlurIoOrder, domain);

case PROTOCOL_ID.ONEINCH_FUSION:
return oneinch.visualize(message as OneInchFusionOrder, domain);

default:
if (erc20Permit.isERC20Permit(message)) {
return erc20Permit.visualize(message as PermitMessage, domain);
Expand Down
77 changes: 77 additions & 0 deletions src/visualizer/oneinch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { PROTOCOL_ID } from "..";
import { ASSET_TYPE, AssetInOut } from "../../types";
import { OneInchFusionOrder } from "../../types/oneinch";
import { Domain, EIP712Protocol, VisualizationResult } from "../../types/visualizer";
import { WizardError, ZERO_ADDRESS, getPaymentAssetType } from "../../utils";
import { getAuctionEndTime, getAuctionStartTime } from "./utils";

export const isCorrectDomain = (domain: Domain) => {
return (
supportedChains.includes(Number(domain.chainId)) &&
addressesBook.includes(domain.verifyingContract.toLocaleLowerCase())
);
};

/**
*
* @param message The decoded message of the oneinch limit order to be visualized
* @param domain Domain of the oneinch limit order
* @returns Returns the visualization result in the ERC6865 format
*/
export const visualize = (
message: OneInchFusionOrder,
domain: Domain
): VisualizationResult => {
/** Verifies the domain of the oneinch limit order */
if (!isCorrectDomain(domain)) throw new Error("wrong oneinch domain");

/** Returns the ERC6865 format of the order */
return {
protocol: PROTOCOL_ID.ONEINCH_FUSION,
assetsIn: [
{
address: message.takerAsset,
type: message.takerAsset == ZERO_ADDRESS ? ASSET_TYPE.NATIVE : ASSET_TYPE.ERC20,
amounts: [message.takingAmount],
},
],
assetsOut: [
{
address: message.makerAsset,
type: message.makerAsset == ZERO_ADDRESS ? ASSET_TYPE.NATIVE : ASSET_TYPE.ERC20,
amounts: [message.makingAmount],
},
],
liveness: {
from: Number(getAuctionStartTime(message.salt)),
to: Number(getAuctionEndTime(message.salt)),
},
approvals: [],
};
};

/**
* @see https://github.com/1inch/fusion-sdk/blob/main/src/constants.ts
*/
const supportedChains = [
1, // Ethereum Mainnet
137, // Polygon
56, // Binance Smart Chain
42161, // Arbitrum
43114, // Avalanche
10, // Optimism
250, // Fantom
100, // Gnosis
];
/**
* @see https://github.com/1inch/fusion-sdk/blob/main/src/constants.ts
*/
const addressesBook = [
"0x1111111254eeb25477b68fb85ed929f73a960582", // Consistent address for all chains
].map((e) => e.toLocaleLowerCase());

const oneinch: EIP712Protocol<OneInchFusionOrder> = {
isCorrectDomain,
visualize,
};
export default oneinch;
14 changes: 14 additions & 0 deletions src/visualizer/oneinch/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const _TIME_START_MASK = BigInt(
"0xFFFFFFFF00000000000000000000000000000000000000000000000000000000"
);
const _DURATION_MASK = BigInt(
"0x00000000FFFFFF00000000000000000000000000000000000000000000000000"
);
const _TIME_START_SHIFT = BigInt(224);
const _DURATION_SHIFT = BigInt(200);

export const getAuctionStartTime = (salt: string) =>
(BigInt(salt) & _TIME_START_MASK) >> _TIME_START_SHIFT;

export const getAuctionEndTime = (salt: string) =>
getAuctionStartTime(salt) + ((BigInt(salt) & _DURATION_MASK) >> _DURATION_SHIFT);
18 changes: 18 additions & 0 deletions test/visualizer/oneinch/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { OneInchFusionOrder } from "../../../src/types/oneinch";

const oneinchNormalLimitOrder: OneInchFusionOrder = {
salt: "45118768841948961586167738353692277076075522015101619148498725069326976558864",
makerAsset: "0x5f04D47D698F79d76F85E835930170Ff4c4EBdB7",
takerAsset: "0x000075B45Dff84C00Cf597d5C3E766108CeA0000",
maker: "0xa88800cd213da5ae406ce248380802bd53b47647",
receiver: "0x11799622F4D98A24514011E8527B969f7488eF47",
allowedSender: "0xd9145CCE52D386f254917e481eB44e9943F39138",
takingAmount: "990000000000000000",
makingAmount: "25000000000000",
offsets: "0",
interactions: "0x000000000000000000000000000000090000000000000000000000000000008e",
};

Object.freeze(oneinchNormalLimitOrder);

export { oneinchNormalLimitOrder };
59 changes: 59 additions & 0 deletions test/visualizer/oneinch/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { OneInchFusionOrder } from "../../../src/types/oneinch";
import { Domain } from "../../../src/types/visualizer";
import visualize from "../../../src/visualizer";
import oneinch from "../../../src/visualizer/oneinch";
import { oneinchNormalLimitOrder } from "./data";

describe("oneinch", () => {
const oneinchDomain: Domain = {
verifyingContract: "0x1111111254eeb25477b68fb85ed929f73a960582",
name: "1inch Aggregation Router",
version: "5",
chainId: "1",
};

it("should revert if domain is not recognized by SDK entry", async () => {
await expect(
visualize(oneinchNormalLimitOrder, { ...oneinchDomain, chainId: "32412" })
).rejects.toThrowError("Unrecognized/Unsupported EIP712Protocol Domain");
});

it("should revert at oneinch module level if accessed directly with wrong domain", () => {
expect(() => {
oneinch.visualize(oneinchNormalLimitOrder, {
...oneinchDomain,
verifyingContract: "0x0",
});
}).toThrow("wrong oneinch domain");
});

it("should successfully visualize oneinch limit order", async () => {
const result = await visualize<OneInchFusionOrder>(
oneinchNormalLimitOrder,
oneinchDomain
);

expect(result).toEqual({
protocol: "ONEINCH_FUSION",
assetsIn: [
{
address: "0x000075B45Dff84C00Cf597d5C3E766108CeA0000",
amounts: ["990000000000000000"],
type: "ERC20",
},
],
assetsOut: [
{
address: "0x5f04D47D698F79d76F85E835930170Ff4c4EBdB7",
amounts: ["25000000000000"],
type: "ERC20",
},
],
liveness: {
from: 1673548149,
to: 1673548329,
},
approvals: [],
});
});
});
Loading