Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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