Skip to content
Draft
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
199 changes: 174 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ yarn add @1inch/fusion-sdk@2

## Modules docs

- [auction-details](src/fusion-order/auction-details/README.md)
- [fusion-order](src/fusion-order/README.md)
- [sdk](src/sdk/README.md)
- [ws-api](src/ws-api/README.md)
- [auction-details](src/fusion-order/auction-details/README.md)
- [fusion-order](src/fusion-order/README.md)
- [sdk](src/sdk/README.md)
- [ws-api](src/ws-api/README.md)

## How to swap with Fusion Mode

```typescript
import {FusionSDK, NetworkEnum, OrderStatus, PrivateKeyProviderConnector, Web3Like,} from "@1inch/fusion-sdk";
import {computeAddress, formatUnits, JsonRpcProvider} from "ethers";
import {
FusionSDK,
NetworkEnum,
OrderStatus,
PrivateKeyProviderConnector,
Web3Like
} from '@1inch/fusion-sdk'
import {computeAddress, formatUnits, JsonRpcProvider} from 'ethers'

const PRIVATE_KEY = 'YOUR_PRIVATE_KEY'
const NODE_URL = 'YOUR_WEB3_NODE_URL'
Expand All @@ -48,7 +54,7 @@ const connector = new PrivateKeyProviderConnector(
)

const sdk = new FusionSDK({
url: 'https://api.1inch.dev/fusion',
url: 'https://api.1inch.com/fusion',
network: NetworkEnum.BINANCE,
blockchainProvider: connector,
authKey: DEV_PORTAL_API_TOKEN
Expand All @@ -57,7 +63,7 @@ const sdk = new FusionSDK({
async function main() {
const params = {
fromTokenAddress: '0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d', // USDC
toTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // BNB
toTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // BNB
amount: '10000000000000000000', // 10 USDC
walletAddress: computeAddress(PRIVATE_KEY),
source: 'sdk-test'
Expand All @@ -66,12 +72,25 @@ async function main() {
const quote = await sdk.getQuote(params)

const dstTokenDecimals = 18
console.log('Auction start amount', formatUnits(quote.presets[quote.recommendedPreset].auctionStartAmount, dstTokenDecimals))
console.log('Auction end amount', formatUnits(quote.presets[quote.recommendedPreset].auctionEndAmount), dstTokenDecimals)
console.log(
'Auction start amount',
formatUnits(
quote.presets[quote.recommendedPreset].auctionStartAmount,
dstTokenDecimals
)
)
console.log(
'Auction end amount',
formatUnits(quote.presets[quote.recommendedPreset].auctionEndAmount),
dstTokenDecimals
)

const preparedOrder = await sdk.createOrder(params)

const info = await sdk.submitOrder(preparedOrder.order, preparedOrder.quoteId)
const info = await sdk.submitOrder(
preparedOrder.order,
preparedOrder.quoteId
)

console.log('OrderHash', info.orderHash)

Expand All @@ -90,15 +109,14 @@ async function main() {
console.log('Order Expired')
break
}

if (data.status === OrderStatus.Cancelled) {
console.log('Order Cancelled')
break
}
} catch (e) {
console.log(e)
}

}

console.log('Order executed for', (Date.now() - start) / 1000, 'sec')
Expand All @@ -108,9 +126,18 @@ main()
```

## How to swap with Fusion mode from Native asset

```typescript
import {FusionSDK, NetworkEnum, OrderStatus, PrivateKeyProviderConnector, Web3Like, Address, NativeOrdersFactory} from "@1inch/fusion-sdk";
import {computeAddress, formatUnits, JsonRpcProvider, Wallet} from "ethers";
import {
FusionSDK,
NetworkEnum,
OrderStatus,
PrivateKeyProviderConnector,
Web3Like,
Address,
NativeOrdersFactory
} from '@1inch/fusion-sdk'
import {computeAddress, formatUnits, JsonRpcProvider, Wallet} from 'ethers'

const PRIVATE_KEY = 'YOUR_PRIVATE_KEY'
const NODE_URL = 'YOUR_WEB3_NODE_URL'
Expand All @@ -133,7 +160,7 @@ const connector = new PrivateKeyProviderConnector(
)

const sdk = new FusionSDK({
url: 'https://api.1inch.dev/fusion',
url: 'https://api.1inch.com/fusion',
network: NetworkEnum.BINANCE,
blockchainProvider: connector,
authKey: DEV_PORTAL_API_TOKEN
Expand All @@ -144,26 +171,43 @@ const wallet = new Wallet(PRIVATE_KEY, ethersRpcProvider)
async function main() {
const params = {
fromTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // ETH
toTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
toTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
amount: '2000000000000000', // 0.002 ETH
walletAddress: computeAddress(PRIVATE_KEY),
source: 'sdk-test'
}

const quote = await sdk.getQuote(params)

const dstTokenDecimals = 6
console.log('Auction start amount', formatUnits(quote.presets[quote.recommendedPreset].auctionStartAmount, dstTokenDecimals))
console.log('Auction end amount', formatUnits(quote.presets[quote.recommendedPreset].auctionEndAmount), dstTokenDecimals)
console.log(
'Auction start amount',
formatUnits(
quote.presets[quote.recommendedPreset].auctionStartAmount,
dstTokenDecimals
)
)
console.log(
'Auction end amount',
formatUnits(quote.presets[quote.recommendedPreset].auctionEndAmount),
dstTokenDecimals
)

const preparedOrder = await sdk.createOrder(params)

const info = await sdk.submitNativeOrder(preparedOrder.order, new Address(params.walletAddress), preparedOrder.quoteId)
const info = await sdk.submitNativeOrder(
preparedOrder.order,
new Address(params.walletAddress),
preparedOrder.quoteId
)

console.log('OrderHash', info.orderHash)

const factory = NativeOrdersFactory.default(NetworkEnum.BINANCE)
const call = factory.create(new Address(wallet.address), preparedOrder.order.build())
const call = factory.create(
new Address(wallet.address),
preparedOrder.order.build()
)

const txRes = await wallet.sendTransaction({
to: call.to.toString(),
Expand All @@ -175,7 +219,6 @@ async function main() {

await wallet.provider.waitForTransaction(txRes.hash)


const start = Date.now()

while (true) {
Expand All @@ -191,15 +234,14 @@ async function main() {
console.log('Order Expired')
break
}

if (data.status === OrderStatus.Cancelled) {
console.log('Order Cancelled')
break
}
} catch (e) {
console.log(e)
}

}

console.log('Order executed for', (Date.now() - start) / 1000, 'sec')
Expand All @@ -208,6 +250,113 @@ async function main() {
main()
```

## How to swap with Fusion Mode using TransferPermit

Instead of granting a token approval to the 1inch Limit Order Protocol, you can use a `TransferPermit` for signature-based transfers via a Permit2Proxy contract.

The maker only needs to approve tokens to the Permit2 contract once. Each order then carries a single-use `PermitTransferFrom` signature instead of an on-chain allowance to the protocol.

```typescript
import {
FusionSDK,
NetworkEnum,
OrderStatus,
PrivateKeyProviderConnector,
Web3Like,
getPermit2Address
} from '@1inch/fusion-sdk'
import {computeAddress, JsonRpcProvider, Wallet} from 'ethers'

const PRIVATE_KEY = 'YOUR_PRIVATE_KEY'
const NODE_URL = 'YOUR_WEB3_NODE_URL'
const DEV_PORTAL_API_TOKEN = 'YOUR_DEV_PORTAL_API_TOKEN'

const ethersRpcProvider = new JsonRpcProvider(NODE_URL)

const ethersProviderConnector: Web3Like = {
eth: {
call(transactionConfig): Promise<string> {
return ethersRpcProvider.call(transactionConfig)
}
},
extend(): void {}
}

const connector = new PrivateKeyProviderConnector(
PRIVATE_KEY,
ethersProviderConnector
)

const sdk = new FusionSDK({
url: 'https://api.1inch.com/fusion',
network: NetworkEnum.ETHEREUM,
blockchainProvider: connector,
authKey: DEV_PORTAL_API_TOKEN
})

const wallet = new Wallet(PRIVATE_KEY, ethersRpcProvider)

async function main() {
// Step 1: Approve token to the Permit2 contract (one-time, can be unlimited)
// This replaces the usual approval to the 1inch Limit Order Protocol
const permit2Address = getPermit2Address(NetworkEnum.ETHEREUM)
// await approveToken(fromTokenAddress, permit2Address, MAX_UINT256)

// Step 2: Get quote and create order
const params = {
fromTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH
toTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
amount: '50000000000000000', // 0.05 WETH
walletAddress: computeAddress(PRIVATE_KEY)
}

const {order, quoteId} = await sdk.createOrder(params)

// Step 3: Create a transfer permit for the order
const permit = order.createTransferPermit(NetworkEnum.ETHEREUM)

// Step 4: Sign the transfer permit
const permitTypedData = permit.getTypedData(NetworkEnum.ETHEREUM)
const permitSignature = await connector.signTypedData(
params.walletAddress,
permitTypedData
)

// Step 5: Attach the signed permit to the order
const orderWithPermit = order.withTransferPermit(permit, permitSignature)

// Step 6: Submit the order (the SDK signs the order and sends it to the relayer)
const info = await sdk.submitOrder(orderWithPermit, quoteId)

console.log('OrderHash', info.orderHash)

while (true) {
const data = await sdk.getOrderStatus(info.orderHash)

if (data.status === OrderStatus.Filled) {
console.log('fills', data.fills)
break
}

if (
data.status === OrderStatus.Expired ||
data.status === OrderStatus.Cancelled
) {
console.log('Order', data.status)
break
}
}
}

main()
```

**Key differences from a standard swap:**

- Token approval goes to `Permit2` instead of the 1inch protocol
- Create and sign a `PermitTransferFrom` using the Permit2Proxy address as spender
- Call `withTransferPermit` before submitting — this modifies the order to route through the Permit2Proxy

## Resolvers

`settleOrders` function usage and Resolver contract examples you can find [here](https://github.com/1inch/fusion-resolver-example)
19 changes: 19 additions & 0 deletions contracts/src/ImmutableOwner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

/// @title A helper contract with helper modifiers to allow access to original contract creator only
contract ImmutableOwner {
error IOAccessDenied();

address public immutable IMMUTABLE_OWNER;

modifier onlyImmutableOwner() {
if (msg.sender != IMMUTABLE_OWNER) revert IOAccessDenied();
_;
}

constructor(address _immutableOwner) {
IMMUTABLE_OWNER = _immutableOwner;
}
}
66 changes: 66 additions & 0 deletions contracts/src/Permit2Proxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

import './interfaces/IPermit2TransferFrom.sol';
import './ImmutableOwner.sol';

/* solhint-disable func-name-mixedcase */

/// @title Permit2Proxy
/// @notice A proxy contract that enables using Uniswap's Permit2 `permitTransferFrom` within the limit order protocol.
/// @dev Permit2 nonces are single-use

contract Permit2Proxy is ImmutableOwner {
/// @notice The Permit2 contract address.
/// @dev Use `0x000000000022D473030F116dDEE9F6B43aC78BA3` for EVM chains
/// or `0x0000000000225e31d15943971f47ad3022f714fa` for zkSync Era.
/// See https://docs.uniswap.org/contracts/v3/reference/deployments
IPermit2TransferFrom private immutable _PERMIT2;

/// @notice Thrown when `func_nZHTch` selector does not match `IERC20.transferFrom` selector.
error Permit2ProxyBadSelector();

/// @notice Initializes the proxy with the immutable owner and the Permit2 contract address.
/// @param _immutableOwner The address of the limit order protocol contract.
/// @param _permit2 The Permit2 contract address for the target chain.
constructor(
address _immutableOwner,
address _permit2
) ImmutableOwner(_immutableOwner) {
if (Permit2Proxy.func_nZHTch.selector != IERC20.transferFrom.selector)
revert Permit2ProxyBadSelector();
_PERMIT2 = IPermit2TransferFrom(_permit2);
}

/// @notice Proxy transfer method for `Permit2.permitTransferFrom`. Selector must match `IERC20.transferFrom`.
/// @dev The function name `func_nZHTch` is chosen so that its selector equals `0x23b872dd`
/// (same as `IERC20.transferFrom`), allowing it to be used as a maker asset in limit orders.
/// keccak256("func_nZHTch(address,address,uint256,((address,uint256),uint256,uint256),bytes)") == 0x23b872dd
/// @param from The token owner whose tokens are being transferred.
/// @param to The recipient of the tokens.
/// @param amount The amount of tokens to transfer.
/// @param permit The Permit2 permit data containing token permissions, nonce, and deadline.
/// @param sig The signature authorizing the transfer, signed by `from`.
function func_nZHTch(
address from,
address to,
uint256 amount,
IPermit2TransferFrom.PermitTransferFrom calldata permit,
bytes calldata sig
) external onlyImmutableOwner {
_PERMIT2.permitTransferFrom(
permit,
IPermit2TransferFrom.SignatureTransferDetails({
to: to,
requestedAmount: amount
}),
from,
sig
);
}
}

/* solhint-enable func-name-mixedcase */
Loading
Loading