diff --git a/packages/connectors/contracts/interfaces/socket/ISocketConnector.sol b/packages/connectors/contracts/interfaces/socket/ISocketConnector.sol
new file mode 100644
index 00000000..ba04445c
--- /dev/null
+++ b/packages/connectors/contracts/interfaces/socket/ISocketConnector.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.0;
+
+/**
+ * @title Socket connector interface
+ */
+interface ISocketConnector {
+ /**
+ * @dev The post token balance is lower than the previous token balance minus the amount bridged
+ */
+ error SocketBridgeBadPostTokenBalance(uint256 postBalance, uint256 preBalance, uint256 amount);
+
+ /**
+ * @dev Tells the reference to the Socket gateway of the source chain
+ */
+ function socketGateway() external view returns (address);
+
+ /**
+ * @dev Executes a bridge of assets using Socket
+ * @param token Address of the token to be bridged
+ * @param amount Amount of tokens to be bridged
+ * @param data Data to be sent to the socket gateway
+ */
+ function execute(address token, uint256 amount, bytes memory data) external;
+}
diff --git a/packages/connectors/contracts/socket/SocketConnector.sol b/packages/connectors/contracts/socket/SocketConnector.sol
new file mode 100644
index 00000000..e9c512d2
--- /dev/null
+++ b/packages/connectors/contracts/socket/SocketConnector.sol
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.0;
+
+import '@openzeppelin/contracts/utils/Address.sol';
+
+import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol';
+
+import '../interfaces/socket/ISocketConnector.sol';
+
+/**
+ * @title SocketConnector
+ * @dev Interfaces with Socket to bridge tokens
+ */
+contract SocketConnector is ISocketConnector {
+ // Reference to the Socket gateway of the source chain
+ address public immutable override socketGateway;
+
+ /**
+ * @dev Creates a new Socket connector
+ * @param _socketGateway Address of the Socket gateway for the source chain
+ */
+ constructor(address _socketGateway) {
+ socketGateway = _socketGateway;
+ }
+
+ /**
+ * @dev Executes a bridge of assets using Socket
+ * @param token Address of the token to be bridged
+ * @param amount Amount of tokens to be bridged
+ * @param data Data to be sent to the Socket gateway
+ */
+ function execute(address token, uint256 amount, bytes memory data) external override {
+ uint256 preBalance = IERC20(token).balanceOf(address(this));
+ ERC20Helpers.approve(token, socketGateway, amount);
+ Address.functionCall(socketGateway, data, 'SOCKET_BRIDGE_FAILED');
+
+ uint256 postBalance = IERC20(token).balanceOf(address(this));
+ bool isPostBalanceUnexpected = postBalance < preBalance - amount;
+ if (isPostBalanceUnexpected) revert SocketBridgeBadPostTokenBalance(postBalance, preBalance, amount);
+ }
+}
diff --git a/packages/connectors/package.json b/packages/connectors/package.json
index 0cb57dff..9687dc66 100644
--- a/packages/connectors/package.json
+++ b/packages/connectors/package.json
@@ -21,7 +21,7 @@
"test:arbitrum": "yarn test --fork arbitrum --block-number 212259071 --chain-id 42161",
"test:gnosis": "yarn test --fork gnosis --block-number 28580764 --chain-id 100",
"test:avalanche": "yarn test --fork avalanche --block-number 31333905 --chain-id 43114",
- "test:bsc": "yarn test --fork bsc --block-number 41410900 --chain-id 56",
+ "test:bsc": "yarn test --fork bsc --block-number 42144988 --chain-id 56",
"test:fantom": "yarn test --fork fantom --block-number 61485606 --chain-id 250",
"test:zkevm": "yarn test --fork zkevm --block-number 9014946 --chain-id 1101",
"test:base": "yarn test --fork base --block-number 18341220 --chain-id 8453",
diff --git a/packages/connectors/src/socket.ts b/packages/connectors/src/socket.ts
new file mode 100644
index 00000000..1b0a45b7
--- /dev/null
+++ b/packages/connectors/src/socket.ts
@@ -0,0 +1,73 @@
+import axios, { AxiosError } from 'axios'
+import { BigNumber, Contract } from 'ethers'
+
+const SOCKET_URL = 'https://api.socket.tech/v2'
+const SOCKET_API_KEY = '72a5b4b0-e727-48be-8aa1-5da9d62fe635'
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+export type QuoteResponse = { data: { result: { routes: any[] } } }
+export type TransactionDataResponse = { data: { result: { txData: string } } }
+
+export async function getSocketBridgeData(
+ sender: Contract,
+ fromChainId: number,
+ fromToken: Contract,
+ fromAmount: BigNumber,
+ toChainId: number,
+ toToken: Contract,
+ slippage: number
+): Promise {
+ try {
+ const quote = await getQuote(sender, fromChainId, fromToken, fromAmount, toChainId, toToken, slippage)
+ const transaction = await getTransactionData(quote.data.result.routes[0])
+ return transaction.data.result.txData
+ } catch (error) {
+ if (error instanceof AxiosError) throw Error(error.toString() + ' - ' + error.response?.data?.description)
+ else throw error
+ }
+}
+
+async function getQuote(
+ sender: Contract,
+ fromChainId: number,
+ fromToken: Contract,
+ fromAmount: BigNumber,
+ toChainId: number,
+ toToken: Contract,
+ slippage: number
+): Promise {
+ return axios.get(`${SOCKET_URL}/quote`, {
+ headers: {
+ 'API-KEY': SOCKET_API_KEY,
+ Accept: 'application/json',
+ },
+ params: {
+ userAddress: sender.address,
+ fromChainId: fromChainId,
+ fromTokenAddress: fromToken.address,
+ fromAmount: fromAmount.toString(),
+ toChainId: toChainId,
+ toTokenAddress: toToken.address,
+ defaultBridgeSlippage: slippage < 1 ? slippage * 100 : slippage,
+ singleTxOnly: true,
+ uniqueRoutesPerBridge: true,
+ sort: 'output',
+ includeDexes: ['oneinch', 'rainbow'],
+ includeBridges: ['cctp', 'celer', 'connext', 'hop', 'stargate'],
+ },
+ })
+}
+
+async function getTransactionData(route: any): Promise {
+ return axios.post(
+ `${SOCKET_URL}/build-tx`,
+ { route },
+ {
+ headers: {
+ 'API-KEY': SOCKET_API_KEY,
+ Accept: 'application/json',
+ },
+ }
+ )
+}
diff --git a/packages/connectors/test/connext/ConnextConnector.behavior.ts b/packages/connectors/test/connext/ConnextConnector.behavior.ts
index ab4bd6d1..e38f1469 100644
--- a/packages/connectors/test/connext/ConnextConnector.behavior.ts
+++ b/packages/connectors/test/connext/ConnextConnector.behavior.ts
@@ -20,7 +20,7 @@ export function itBehavesLikeConnextConnector(
})
context('when the recipient is not the zero address', async () => {
- const slippage = 0.05
+ const slippage = 0.15
const relayerFee = amount.div(10)
let minAmountOut: BigNumber, amountAfterFees: BigNumber
diff --git a/packages/connectors/test/connext/ConnextConnector.bsc.ts b/packages/connectors/test/connext/ConnextConnector.bsc.ts
index ecaf9a54..7805cd9c 100644
--- a/packages/connectors/test/connext/ConnextConnector.bsc.ts
+++ b/packages/connectors/test/connext/ConnextConnector.bsc.ts
@@ -1,11 +1,10 @@
-import { deploy, fp, toUSDC } from '@mimic-fi/v3-helpers'
+import { deploy, toUSDC } from '@mimic-fi/v3-helpers'
import { itBehavesLikeConnextConnector } from './ConnextConnector.behavior'
/* eslint-disable no-secrets/no-secrets */
const USDC = '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d'
-const WETH = '0x2170Ed0880ac9A755fd29B2688956BD959F933F8'
const WHALE = '0x8894e0a0c962cb723c1976a4421c95949be2d4e3'
const CONNEXT = '0xCd401c10afa37d641d2F594852DA94C700e4F2CE'
@@ -20,8 +19,4 @@ describe('ConnextConnector', () => {
context('USDC', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, USDC, toUSDC(300), CONNEXT, WHALE)
})
-
- context('WETH', () => {
- itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, WETH, fp(0.5), CONNEXT, WHALE)
- })
})
diff --git a/packages/connectors/test/connext/ConnextConnector.optimism.ts b/packages/connectors/test/connext/ConnextConnector.optimism.ts
index 9031e1d9..3b5ffe6e 100644
--- a/packages/connectors/test/connext/ConnextConnector.optimism.ts
+++ b/packages/connectors/test/connext/ConnextConnector.optimism.ts
@@ -1,11 +1,10 @@
-import { deploy, fp, toUSDC } from '@mimic-fi/v3-helpers'
+import { deploy, toUSDC } from '@mimic-fi/v3-helpers'
import { itBehavesLikeConnextConnector } from './ConnextConnector.behavior'
/* eslint-disable no-secrets/no-secrets */
const USDC = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'
-const WETH = '0x4200000000000000000000000000000000000006'
const WHALE = '0x85149247691df622eaf1a8bd0cafd40bc45154a9'
const CONNEXT = '0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA'
@@ -20,8 +19,4 @@ describe('ConnextConnector', () => {
context('USDC', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, USDC, toUSDC(300), CONNEXT, WHALE)
})
-
- context('WETH', () => {
- itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, WETH, fp(2), CONNEXT, WHALE)
- })
})
diff --git a/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-1.json b/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-1.json
new file mode 100644
index 00000000..0c67a011
--- /dev/null
+++ b/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-1.json
@@ -0,0 +1,9 @@
+{
+ "fromChainId": 56,
+ "fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
+ "fromAmount": "50000000000000000000000",
+ "toChainId": 1,
+ "toToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "slippage": 0.02,
+ "data": "0x0000000d52106ce9345fa8d86eeffdd25c389441ff96e05cf90592de8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000100b8d397000068dc"
+}
\ No newline at end of file
diff --git a/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-10.json b/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-10.json
new file mode 100644
index 00000000..1147868a
--- /dev/null
+++ b/packages/connectors/test/helpers/socket/fixtures/56/42144988/USDC-10.json
@@ -0,0 +1,9 @@
+{
+ "fromChainId": 56,
+ "fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
+ "fromAmount": "50000000000000000000000",
+ "toChainId": 10,
+ "toToken": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
+ "slippage": 0.02,
+ "data": "0x0000000d52106ce9345fa8d86eeffdd25c389441ff96e05cf90592de8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000a00b8e5120000620c"
+}
\ No newline at end of file
diff --git a/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-1.json b/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-1.json
new file mode 100644
index 00000000..46aceb2d
--- /dev/null
+++ b/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-1.json
@@ -0,0 +1,9 @@
+{
+ "fromChainId": 56,
+ "fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
+ "fromAmount": "50000000000000000000000",
+ "toChainId": 1,
+ "toToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "slippage": 0.02,
+ "data": "0x0000000d52106ce94a1bf945c2995b53a00df694fab85f453e5e1f5e8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000100d2437d000068dd"
+}
\ No newline at end of file
diff --git a/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-10.json b/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-10.json
new file mode 100644
index 00000000..03af9246
--- /dev/null
+++ b/packages/connectors/test/helpers/socket/fixtures/56/42145576/USDC-10.json
@@ -0,0 +1,9 @@
+{
+ "fromChainId": 56,
+ "fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
+ "fromAmount": "50000000000000000000000",
+ "toChainId": 10,
+ "toToken": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
+ "slippage": 0.02,
+ "data": "0x0000000d52106ce94a1bf945c2995b53a00df694fab85f453e5e1f5e8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000a00d256670000620c"
+}
\ No newline at end of file
diff --git a/packages/connectors/test/helpers/socket/index.ts b/packages/connectors/test/helpers/socket/index.ts
new file mode 100644
index 00000000..a2eebdf8
--- /dev/null
+++ b/packages/connectors/test/helpers/socket/index.ts
@@ -0,0 +1,84 @@
+import { currentBlockNumber } from '@mimic-fi/v3-helpers'
+import { BigNumber, Contract } from 'ethers'
+import fs from 'fs'
+import hre from 'hardhat'
+import { HardhatNetworkConfig } from 'hardhat/types'
+import path from 'path'
+
+import { getSocketBridgeData } from '../../../src/socket'
+
+type Fixture = {
+ fromChainId: number
+ fromToken: string
+ fromAmount: string
+ toChainId: number
+ toToken: string
+ slippage: number
+ data: string
+}
+
+export async function loadOrGetSocketData(
+ sender: Contract,
+ fromChainId: number,
+ fromToken: Contract,
+ fromAmount: BigNumber,
+ toChainId: number,
+ toToken: Contract,
+ slippage: number
+): Promise {
+ const config = hre.network.config as HardhatNetworkConfig
+ const blockNumber = config?.forking?.blockNumber?.toString() || (await currentBlockNumber()).toString()
+
+ const fixture = await readFixture(fromChainId, fromToken, toChainId, toToken, blockNumber)
+ if (fixture) return fixture.data
+
+ const data = await getSocketBridgeData(sender, fromChainId, fromToken, fromAmount, toChainId, toToken, slippage)
+ await saveFixture(fromChainId, fromToken, fromAmount, toChainId, toToken, slippage, data, blockNumber)
+ return data
+}
+
+async function readFixture(
+ fromChainId: number,
+ fromToken: Contract,
+ toChainId: number,
+ toToken: Contract,
+ blockNumber: string
+): Promise {
+ const bridgePath = `${await fromToken.symbol()}-${toChainId}.json`
+ const fixturePath = path.join(__dirname, 'fixtures', fromChainId.toString(), blockNumber, bridgePath)
+ if (!fs.existsSync(fixturePath)) return undefined
+ return JSON.parse(fs.readFileSync(fixturePath).toString())
+}
+
+async function saveFixture(
+ fromChainId: number,
+ fromToken: Contract,
+ fromAmount: BigNumber,
+ toChainId: number,
+ toToken: Contract,
+ slippage: number,
+ data: string,
+ blockNumber: string
+): Promise {
+ const output = {
+ fromChainId: fromChainId,
+ fromToken: fromToken.address,
+ fromAmount: fromAmount.toString(),
+ toChainId: toChainId,
+ toToken: toToken.address,
+ slippage,
+ data,
+ }
+
+ const fixturesPath = path.join(__dirname, 'fixtures')
+ if (!fs.existsSync(fixturesPath)) fs.mkdirSync(fixturesPath)
+
+ const networkPath = path.join(fixturesPath, fromChainId.toString())
+ if (!fs.existsSync(networkPath)) fs.mkdirSync(networkPath)
+
+ const blockNumberPath = path.join(networkPath, blockNumber)
+ if (!fs.existsSync(blockNumberPath)) fs.mkdirSync(blockNumberPath)
+
+ const bridgePath = path.join(blockNumberPath, `${await fromToken.symbol()}-${toChainId}.json`)
+ fs.writeFileSync(bridgePath, JSON.stringify(output, null, 2))
+}
diff --git a/packages/connectors/test/socket/SocketConnector.behavior.ts b/packages/connectors/test/socket/SocketConnector.behavior.ts
new file mode 100644
index 00000000..6380e3ce
--- /dev/null
+++ b/packages/connectors/test/socket/SocketConnector.behavior.ts
@@ -0,0 +1,51 @@
+import { fp, impersonate, instanceAt } from '@mimic-fi/v3-helpers'
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'
+import { expect } from 'chai'
+import { BigNumber, Contract } from 'ethers'
+
+import { loadOrGetSocketData } from '../helpers/socket'
+
+/* eslint-disable no-secrets/no-secrets */
+
+export function itBehavesLikeSocketConnector(
+ fromChainId: number,
+ fromTokenAddress: string,
+ fromAmount: BigNumber,
+ toChainId: number,
+ toTokenAddress: string,
+ whaleAddress: string
+): void {
+ const slippage = 0.02
+ let fromToken: Contract, toToken: Contract, whale: SignerWithAddress
+
+ before('load tokens and accounts', async function () {
+ fromToken = await instanceAt('IERC20Metadata', fromTokenAddress)
+ toToken = await instanceAt('IERC20Metadata', toTokenAddress)
+ whale = await impersonate(whaleAddress, fp(100))
+ })
+
+ it('should send the tokens to the socket gateway', async function () {
+ const previousSenderBalance = await fromToken.balanceOf(whaleAddress)
+ const previousConnectorBalance = await fromToken.balanceOf(this.connector.address)
+
+ await fromToken.connect(whale).transfer(this.connector.address, fromAmount)
+
+ const data = await loadOrGetSocketData(
+ this.connector,
+ fromChainId,
+ fromToken,
+ fromAmount,
+ toChainId,
+ toToken,
+ slippage
+ )
+
+ await this.connector.connect(whale).execute(fromTokenAddress, fromAmount, data)
+
+ const currentSenderBalance = await fromToken.balanceOf(whaleAddress)
+ expect(currentSenderBalance).to.be.equal(previousSenderBalance.sub(fromAmount))
+
+ const currentConnectorBalance = await fromToken.balanceOf(this.connector.address)
+ expect(currentConnectorBalance).to.be.equal(previousConnectorBalance)
+ })
+}
diff --git a/packages/connectors/test/socket/SocketConnector.bsc.ts b/packages/connectors/test/socket/SocketConnector.bsc.ts
new file mode 100644
index 00000000..c77d53b7
--- /dev/null
+++ b/packages/connectors/test/socket/SocketConnector.bsc.ts
@@ -0,0 +1,33 @@
+import { deploy, fp, tokens } from '@mimic-fi/v3-helpers'
+
+import { itBehavesLikeSocketConnector } from './SocketConnector.behavior'
+
+/* eslint-disable no-secrets/no-secrets */
+
+const SOCKET_GATEWAY = '0x3a23F943181408EAC424116Af7b7790c94Cb97a5'
+
+const WHALE = '0x8894e0a0c962cb723c1976a4421c95949be2d4e3'
+
+describe('SocketConnector', () => {
+ const fromChainId = 56
+ const fromToken = tokens.bsc.USDC
+ const fromAmount = fp(50000)
+
+ before('create socket connector', async function () {
+ this.connector = await deploy('SocketConnector', [SOCKET_GATEWAY])
+ })
+
+ context('to mainnet', () => {
+ const toChainId = 1
+ const toToken = tokens.mainnet.USDC
+
+ itBehavesLikeSocketConnector(fromChainId, fromToken, fromAmount, toChainId, toToken, WHALE)
+ })
+
+ context('to optimism', () => {
+ const toChainId = 10
+ const toToken = tokens.optimism.USDC
+
+ itBehavesLikeSocketConnector(fromChainId, fromToken, fromAmount, toChainId, toToken, WHALE)
+ })
+})