diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26accdfd7..9007fcdbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: - main pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: run-linters: name: Run linters diff --git a/.github/workflows/contracts-test.yml b/.github/workflows/contracts-test.yml index 561dc09ec..60b205fbc 100644 --- a/.github/workflows/contracts-test.yml +++ b/.github/workflows/contracts-test.yml @@ -34,7 +34,7 @@ jobs: - uses: ./.github/workflows/setup-pnpm with: - npm_token: ${{ secrets.NPM_TOKEN }} + npm_token: ${{ secrets.NPM_TOKEN }} - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 diff --git a/.github/workflows/vercel-preview.yml b/.github/workflows/vercel-preview.yml deleted file mode 100644 index fb9e0136a..000000000 --- a/.github/workflows/vercel-preview.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Vercel Preview Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - branches-ignore: - - main -jobs: - Deploy-Preview: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Vercel CLI - run: npm install --global vercel@canary - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/vercel-prod.yml b/.github/workflows/vercel-prod.yml deleted file mode 100644 index 3bfc7552a..000000000 --- a/.github/workflows/vercel-prod.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Vercel Production Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - branches: - - main -jobs: - Deploy-Production: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Vercel CLI - run: npm install --global vercel@canary - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.gitignore b/.gitignore index 21ba08173..42089505a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules # Local Netlify folder .netlify .vercel +.turbo diff --git a/apps/composable-cow-api/.env.example b/apps/composable-cow-api/.env.example new file mode 100644 index 000000000..9e667e667 --- /dev/null +++ b/apps/composable-cow-api/.env.example @@ -0,0 +1,3 @@ +PONDER_RPC_URL_MAINNET="" +PONDER_RPC_URL_GNOSIS="" +PONDER_RPC_URL_SEPOLIA="" diff --git a/apps/composable-cow-api/.eslintrc.json b/apps/composable-cow-api/.eslintrc.json new file mode 100644 index 000000000..359e2bbfa --- /dev/null +++ b/apps/composable-cow-api/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "ponder" +} diff --git a/apps/composable-cow-api/.gitignore b/apps/composable-cow-api/.gitignore new file mode 100644 index 000000000..93f7ebe5e --- /dev/null +++ b/apps/composable-cow-api/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.DS_Store + +.env.local +.ponder/ +generated/ \ No newline at end of file diff --git a/apps/composable-cow-api/abis/ComposableCow.ts b/apps/composable-cow-api/abis/ComposableCow.ts new file mode 100644 index 000000000..09cf7e633 --- /dev/null +++ b/apps/composable-cow-api/abis/ComposableCow.ts @@ -0,0 +1,343 @@ +export const composableCowAbi = [ + { + inputs: [{ internalType: "address", name: "_settlement", type: "address" }], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "InterfaceNotSupported", type: "error" }, + { inputs: [], name: "InvalidFallbackHandler", type: "error" }, + { inputs: [], name: "InvalidHandler", type: "error" }, + { inputs: [], name: "ProofNotAuthed", type: "error" }, + { inputs: [], name: "SingleOrderNotAuthed", type: "error" }, + { inputs: [], name: "SwapGuardRestricted", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + indexed: false, + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + ], + name: "ConditionalOrderCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "bytes32", + name: "root", + type: "bytes32", + }, + { + components: [ + { internalType: "uint256", name: "location", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + indexed: false, + internalType: "struct ComposableCoW.Proof", + name: "proof", + type: "tuple", + }, + ], + name: "MerkleRootSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "contract ISwapGuard", + name: "swapGuard", + type: "address", + }, + ], + name: "SwapGuardSet", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "bytes32", name: "", type: "bytes32" }, + ], + name: "cabinet", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + { internalType: "bool", name: "dispatch", type: "bool" }, + ], + name: "create", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + { + internalType: "contract IValueFactory", + name: "factory", + type: "address", + }, + { internalType: "bytes", name: "data", type: "bytes" }, + { internalType: "bool", name: "dispatch", type: "bool" }, + ], + name: "createWithContext", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "domainSeparator", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + { internalType: "bytes", name: "offchainInput", type: "bytes" }, + { internalType: "bytes32[]", name: "proof", type: "bytes32[]" }, + ], + name: "getTradeableOrderWithSignature", + outputs: [ + { + components: [ + { + internalType: "contract IERC20", + name: "sellToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "buyToken", + type: "address", + }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "sellAmount", type: "uint256" }, + { internalType: "uint256", name: "buyAmount", type: "uint256" }, + { internalType: "uint32", name: "validTo", type: "uint32" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + { internalType: "uint256", name: "feeAmount", type: "uint256" }, + { internalType: "bytes32", name: "kind", type: "bytes32" }, + { internalType: "bool", name: "partiallyFillable", type: "bool" }, + { + internalType: "bytes32", + name: "sellTokenBalance", + type: "bytes32", + }, + { internalType: "bytes32", name: "buyTokenBalance", type: "bytes32" }, + ], + internalType: "struct GPv2Order.Data", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + ], + name: "hash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "contract Safe", name: "safe", type: "address" }, + { internalType: "address", name: "sender", type: "address" }, + { internalType: "bytes32", name: "_hash", type: "bytes32" }, + { internalType: "bytes32", name: "_domainSeparator", type: "bytes32" }, + { internalType: "bytes32", name: "", type: "bytes32" }, + { internalType: "bytes", name: "encodeData", type: "bytes" }, + { internalType: "bytes", name: "payload", type: "bytes" }, + ], + name: "isValidSafeSignature", + outputs: [{ internalType: "bytes4", name: "magic", type: "bytes4" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "singleOrderHash", type: "bytes32" }, + ], + name: "remove", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "roots", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "root", type: "bytes32" }, + { + components: [ + { internalType: "uint256", name: "location", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ComposableCoW.Proof", + name: "proof", + type: "tuple", + }, + ], + name: "setRoot", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "root", type: "bytes32" }, + { + components: [ + { internalType: "uint256", name: "location", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ComposableCoW.Proof", + name: "proof", + type: "tuple", + }, + { + internalType: "contract IValueFactory", + name: "factory", + type: "address", + }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "setRootWithContext", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ISwapGuard", + name: "swapGuard", + type: "address", + }, + ], + name: "setSwapGuard", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "bytes32", name: "", type: "bytes32" }, + ], + name: "singleOrders", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "swapGuards", + outputs: [ + { internalType: "contract ISwapGuard", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/apps/composable-cow-api/abis/StandaloneConstantProduct.ts b/apps/composable-cow-api/abis/StandaloneConstantProduct.ts new file mode 100644 index 000000000..7cd4cca0c --- /dev/null +++ b/apps/composable-cow-api/abis/StandaloneConstantProduct.ts @@ -0,0 +1,317 @@ +export const standaloneConstantProductAbi = [ + { + inputs: [ + { + internalType: "contract ISettlement", + name: "_solutionSettler", + type: "address", + }, + { internalType: "contract IERC20", name: "_token0", type: "address" }, + { internalType: "contract IERC20", name: "_token1", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "CommitOutsideOfSettlement", type: "error" }, + { inputs: [], name: "OnlyManagerCanCall", type: "error" }, + { inputs: [], name: "OrderDoesNotMatchCommitmentHash", type: "error" }, + { inputs: [], name: "OrderDoesNotMatchDefaultTradeableOrder", type: "error" }, + { inputs: [], name: "OrderDoesNotMatchMessageHash", type: "error" }, + { + inputs: [{ internalType: "string", name: "", type: "string" }], + name: "OrderNotValid", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "blockNumber", type: "uint256" }, + { internalType: "string", name: "message", type: "string" }, + ], + name: "PollTryAtBlock", + type: "error", + }, + { inputs: [], name: "TradingParamsDoNotMatchHash", type: "error" }, + { anonymous: false, inputs: [], name: "TradingDisabled", type: "event" }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "hash", type: "bytes32" }, + { + components: [ + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + indexed: false, + internalType: "struct ConstantProduct.TradingParams", + name: "params", + type: "tuple", + }, + ], + name: "TradingEnabled", + type: "event", + }, + { + inputs: [], + name: "COMMITMENT_SLOT", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "EMPTY_COMMITMENT", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_ORDER_DURATION", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "NO_TRADING", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "commit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "commitment", + outputs: [{ internalType: "bytes32", name: "value", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "disableTrading", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + internalType: "struct ConstantProduct.TradingParams", + name: "tradingParams", + type: "tuple", + }, + ], + name: "enableTrading", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + internalType: "struct ConstantProduct.TradingParams", + name: "tradingParams", + type: "tuple", + }, + ], + name: "getTradeableOrder", + outputs: [ + { + components: [ + { + internalType: "contract IERC20", + name: "sellToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "buyToken", + type: "address", + }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "sellAmount", type: "uint256" }, + { internalType: "uint256", name: "buyAmount", type: "uint256" }, + { internalType: "uint32", name: "validTo", type: "uint32" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + { internalType: "uint256", name: "feeAmount", type: "uint256" }, + { internalType: "bytes32", name: "kind", type: "bytes32" }, + { internalType: "bool", name: "partiallyFillable", type: "bool" }, + { + internalType: "bytes32", + name: "sellTokenBalance", + type: "bytes32", + }, + { internalType: "bytes32", name: "buyTokenBalance", type: "bytes32" }, + ], + internalType: "struct GPv2Order.Data", + name: "order", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + internalType: "struct ConstantProduct.TradingParams", + name: "tradingParams", + type: "tuple", + }, + ], + name: "hash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "_hash", type: "bytes32" }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + name: "isValidSignature", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "manager", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "solutionSettler", + outputs: [ + { internalType: "contract ISettlement", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "solutionSettlerDomainSeparator", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "token0", + outputs: [{ internalType: "contract IERC20", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "token1", + outputs: [{ internalType: "contract IERC20", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tradingParamsHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + internalType: "struct ConstantProduct.TradingParams", + name: "tradingParams", + type: "tuple", + }, + { + components: [ + { + internalType: "contract IERC20", + name: "sellToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "buyToken", + type: "address", + }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "sellAmount", type: "uint256" }, + { internalType: "uint256", name: "buyAmount", type: "uint256" }, + { internalType: "uint32", name: "validTo", type: "uint32" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + { internalType: "uint256", name: "feeAmount", type: "uint256" }, + { internalType: "bytes32", name: "kind", type: "bytes32" }, + { internalType: "bool", name: "partiallyFillable", type: "bool" }, + { + internalType: "bytes32", + name: "sellTokenBalance", + type: "bytes32", + }, + { internalType: "bytes32", name: "buyTokenBalance", type: "bytes32" }, + ], + internalType: "struct GPv2Order.Data", + name: "order", + type: "tuple", + }, + ], + name: "verify", + outputs: [], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/apps/composable-cow-api/abis/StandaloneContantProductFactory.ts b/apps/composable-cow-api/abis/StandaloneContantProductFactory.ts new file mode 100644 index 000000000..affd9574d --- /dev/null +++ b/apps/composable-cow-api/abis/StandaloneContantProductFactory.ts @@ -0,0 +1,276 @@ +export const standaloneConstantProductFactoryAbi = [ + { + inputs: [ + { + internalType: "contract ISettlement", + name: "_settler", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "OnlyOwnerCanCall", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "", type: "string" }], + name: "OrderNotValid", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + indexed: false, + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + ], + name: "ConditionalOrderCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "contract IERC20", + name: "token0", + type: "address", + }, + { + indexed: false, + internalType: "contract IERC20", + name: "token1", + type: "address", + }, + ], + name: "Deployed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + ], + name: "TradingDisabled", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "ammOwner", type: "address" }, + { internalType: "contract IERC20", name: "token0", type: "address" }, + { internalType: "contract IERC20", name: "token1", type: "address" }, + ], + name: "ammDeterministicAddress", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "contract IERC20", name: "token0", type: "address" }, + { internalType: "uint256", name: "amount0", type: "uint256" }, + { internalType: "contract IERC20", name: "token1", type: "address" }, + { internalType: "uint256", name: "amount1", type: "uint256" }, + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + name: "create", + outputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + { internalType: "uint256", name: "amount0", type: "uint256" }, + { internalType: "uint256", name: "amount1", type: "uint256" }, + ], + name: "deposit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + ], + name: "disableTrading", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + { + components: [ + { + internalType: "contract IConditionalOrder", + name: "handler", + type: "address", + }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "bytes", name: "staticInput", type: "bytes" }, + ], + internalType: "struct IConditionalOrder.ConditionalOrderParams", + name: "params", + type: "tuple", + }, + { internalType: "bytes", name: "", type: "bytes" }, + { internalType: "bytes32[]", name: "", type: "bytes32[]" }, + ], + name: "getTradeableOrderWithSignature", + outputs: [ + { + components: [ + { + internalType: "contract IERC20", + name: "sellToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "buyToken", + type: "address", + }, + { internalType: "address", name: "receiver", type: "address" }, + { internalType: "uint256", name: "sellAmount", type: "uint256" }, + { internalType: "uint256", name: "buyAmount", type: "uint256" }, + { internalType: "uint32", name: "validTo", type: "uint32" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + { internalType: "uint256", name: "feeAmount", type: "uint256" }, + { internalType: "bytes32", name: "kind", type: "bytes32" }, + { internalType: "bool", name: "partiallyFillable", type: "bool" }, + { + internalType: "bytes32", + name: "sellTokenBalance", + type: "bytes32", + }, + { internalType: "bytes32", name: "buyTokenBalance", type: "bytes32" }, + ], + internalType: "struct GPv2Order.Data", + name: "order", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "contract ConstantProduct", name: "", type: "address" }, + ], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "settler", + outputs: [ + { internalType: "contract ISettlement", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + { internalType: "uint256", name: "minTradedToken0", type: "uint256" }, + { + internalType: "contract IPriceOracle", + name: "priceOracle", + type: "address", + }, + { internalType: "bytes", name: "priceOracleData", type: "bytes" }, + { internalType: "bytes32", name: "appData", type: "bytes32" }, + ], + name: "updateParameters", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ConstantProduct", + name: "amm", + type: "address", + }, + { internalType: "uint256", name: "amount0", type: "uint256" }, + { internalType: "uint256", name: "amount1", type: "uint256" }, + ], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/apps/composable-cow-api/abis/erc20.ts b/apps/composable-cow-api/abis/erc20.ts new file mode 100644 index 000000000..d107910de --- /dev/null +++ b/apps/composable-cow-api/abis/erc20.ts @@ -0,0 +1,153 @@ +export const erc20Abi = [ + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [], + name: "name", + outputs: [{ name: "", type: "string" }], + }, + { + constant: false, + payable: false, + stateMutability: "nonpayable", + type: "function", + inputs: [ + { name: "guy", type: "address" }, + { name: "wad", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + }, + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [], + name: "totalSupply", + outputs: [{ name: "", type: "uint256" }], + }, + { + constant: false, + payable: false, + stateMutability: "nonpayable", + type: "function", + inputs: [ + { name: "src", type: "address" }, + { name: "dst", type: "address" }, + { name: "wad", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ name: "", type: "bool" }], + }, + { + constant: false, + payable: false, + stateMutability: "nonpayable", + type: "function", + inputs: [{ name: "wad", type: "uint256" }], + name: "withdraw", + outputs: [], + }, + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [], + name: "decimals", + outputs: [{ name: "", type: "uint8" }], + }, + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [{ name: "", type: "address" }], + name: "balanceOf", + outputs: [{ name: "", type: "uint256" }], + }, + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [], + name: "symbol", + outputs: [{ name: "", type: "string" }], + }, + { + constant: false, + payable: false, + stateMutability: "nonpayable", + type: "function", + inputs: [ + { name: "dst", type: "address" }, + { name: "wad", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + }, + { + constant: false, + payable: true, + stateMutability: "payable", + type: "function", + inputs: [], + name: "deposit", + outputs: [], + }, + { + constant: true, + payable: false, + stateMutability: "view", + type: "function", + inputs: [ + { name: "", type: "address" }, + { name: "", type: "address" }, + ], + name: "allowance", + outputs: [{ name: "", type: "uint256" }], + }, + { payable: true, stateMutability: "payable", type: "fallback" }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "src", type: "address", indexed: true }, + { name: "guy", type: "address", indexed: true }, + { name: "wad", type: "uint256", indexed: false }, + ], + name: "Approval", + }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "src", type: "address", indexed: true }, + { name: "dst", type: "address", indexed: true }, + { name: "wad", type: "uint256", indexed: false }, + ], + name: "Transfer", + }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "dst", type: "address", indexed: true }, + { name: "wad", type: "uint256", indexed: false }, + ], + name: "Deposit", + }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "src", type: "address", indexed: true }, + { name: "wad", type: "uint256", indexed: false }, + ], + name: "Withdrawal", + }, +] as const; diff --git a/apps/composable-cow-api/package.json b/apps/composable-cow-api/package.json new file mode 100644 index 000000000..8b6e36646 --- /dev/null +++ b/apps/composable-cow-api/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "scripts": { + "dev": "ponder dev", + "start": "ponder start", + "codegen": "ponder codegen", + "lint": "eslint ." + }, + "dependencies": { + "@ponder/core": "0.4.38" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "abitype": "^0.8.11", + "eslint": "^8.55.0", + "eslint-config-ponder": "^0.0.92", + "typescript": "^5.4.5", + "viem": "^1.19.15" + } +} diff --git a/apps/composable-cow-api/ponder-env.d.ts b/apps/composable-cow-api/ponder-env.d.ts new file mode 100644 index 000000000..1d5dc7ebe --- /dev/null +++ b/apps/composable-cow-api/ponder-env.d.ts @@ -0,0 +1,27 @@ +// This file enables type checking and editor autocomplete for this Ponder project. +// After upgrading, you may find that changes have been made to this file. +// If this happens, please commit the changes. Do not manually edit this file. +// See https://ponder.sh/docs/guides/typescript for more information. + +declare module "@/generated" { + import type { Virtual } from "@ponder/core"; + + type config = typeof import("./ponder.config.ts").default; + type schema = typeof import("./ponder.schema.ts").default; + + export const ponder: Virtual.Registry; + + export type EventNames = Virtual.EventNames; + export type Event = Virtual.Event< + config, + name + >; + export type Context = Virtual.Context< + config, + schema, + name + >; + export type IndexingFunctionArgs = + Virtual.IndexingFunctionArgs; + export type Schema = Virtual.Schema; +} diff --git a/apps/composable-cow-api/ponder.config.ts b/apps/composable-cow-api/ponder.config.ts new file mode 100644 index 000000000..fd18c4e2a --- /dev/null +++ b/apps/composable-cow-api/ponder.config.ts @@ -0,0 +1,56 @@ +import { createConfig } from "@ponder/core"; +import { http } from "viem"; + +import { composableCowAbi } from "./abis/ComposableCow"; +import { standaloneConstantProductFactoryAbi } from "./abis/StandaloneContantProductFactory"; + +export default createConfig({ + networks: { + sepolia: { + chainId: 11155111, + transport: http(process.env.PONDER_RPC_URL_SEPOLIA), + }, + gnosis: { + chainId: 100, + transport: http(process.env.PONDER_RPC_URL_GNOSIS), + }, + mainnet: { + chainId: 1, + transport: http(process.env.PONDER_RPC_URL_MAINNET), + }, + }, + contracts: { + composable: { + abi: composableCowAbi, + address: "0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74", + network: { + sepolia: { + startBlock: 5245332, + }, + gnosis: { + startBlock: 31005430, + }, + mainnet: { + startBlock: 18937172, + }, + }, + }, + standaloneConstantProductFactoryAbi: { + abi: standaloneConstantProductFactoryAbi, + network: { + sepolia: { + startBlock: 5874562, + address: "0xb808E8183e3a72d196457D127c7fd4bEfa0D7Fd3", + }, + gnosis: { + startBlock: 33874317, + address: "0xdb1Cba3a87f2db53b6E1E6Af48e28Ed877592Ec0", + }, + mainnet: { + startBlock: 19861952, + address: "0x40664207e3375fb4b733d4743ce9b159331fd034", + }, + }, + }, + }, +}); diff --git a/apps/composable-cow-api/ponder.schema.ts b/apps/composable-cow-api/ponder.schema.ts new file mode 100644 index 000000000..ce5f7a49a --- /dev/null +++ b/apps/composable-cow-api/ponder.schema.ts @@ -0,0 +1,68 @@ +import { createSchema } from "@ponder/core"; + +export default createSchema((p) => ({ + Order: p.createTable({ + id: p.string(), + chainId: p.int(), + blockNumber: p.bigint(), + blockTimestamp: p.bigint(), + hash: p.hex().optional(), + txHash: p.hex(), + salt: p.hex(), + staticInput: p.hex(), + handler: p.hex(), + owner: p.string(), + }), + Token: p.createTable({ + id: p.string(), + address: p.hex(), + chainId: p.int(), + name: p.string(), + symbol: p.string(), + decimals: p.int(), + }), + User: p.createTable({ + id: p.string(), + address: p.string(), + chainId: p.int(), + }), + StopLossData: p.createTable({ + id: p.string(), + orderId: p.string().references("Order.id"), + order: p.one("orderId"), + userId: p.string().references("User.id"), + user: p.one("userId"), + tokenInId: p.string().references("Token.id"), + tokenIn: p.one("tokenInId"), + tokenAmountIn: p.bigint(), + tokenOutId: p.string().references("Token.id"), + tokenOut: p.one("tokenOutId"), + tokenAmountOut: p.bigint(), + appData: p.hex(), + to: p.hex(), + isSellOrder: p.boolean(), + isPartiallyFillable: p.boolean(), + validityBucketSeconds: p.bigint(), + sellTokenPriceOracle: p.hex(), + buyTokenPriceOracle: p.hex(), + strike: p.bigint(), + maxTimeSinceLastOracleUpdate: p.bigint(), + }), + ConstantProductData: p.createTable({ + id: p.string(), + orderId: p.string().references("Order.id"), + order: p.one("orderId"), + userId: p.string().references("User.id"), + user: p.one("userId"), + token0Id: p.string().references("Token.id"), + token0: p.one("token0Id"), + token1Id: p.string().references("Token.id"), + token1: p.one("token1Id"), + minTradedToken0: p.bigint(), + priceOracle: p.hex(), + priceOracleData: p.hex(), + appData: p.hex(), + disabled: p.boolean().optional(), + version: p.string(), + }), +})); diff --git a/apps/composable-cow-api/src/ComposableCow.ts b/apps/composable-cow-api/src/ComposableCow.ts new file mode 100644 index 000000000..af819d77f --- /dev/null +++ b/apps/composable-cow-api/src/ComposableCow.ts @@ -0,0 +1,45 @@ +import { ponder } from "@/generated"; + +import { getHandlerHelper } from "./handler"; +import { getHash } from "./utils"; + +ponder.on("composable:ConditionalOrderCreated", async ({ event, context }) => { + const hash = await getHash({ + salt: event.args.params.salt, + staticInput: event.args.params.staticInput, + handler: event.args.params.handler, + context, + }).catch(() => { + return undefined; + }); + + const orderId = `${event.log.id}-${context.network.chainId}`; + try { + const handlerHelper = getHandlerHelper(event.args.params.handler, context); + await context.db.Order.create({ + id: orderId, + data: { + chainId: context.network.chainId, + blockNumber: event.block.number, + blockTimestamp: event.block.timestamp, + hash, + txHash: event.log.transactionHash, + salt: event.args.params.salt, + staticInput: event.args.params.staticInput, + handler: event.args.params.handler, + owner: event.args.owner, + }, + }); + await handlerHelper.decodeAndSaveOrder({ + staticInput: event.args.params.staticInput, + context, + orderId, + handler: event.args.params.handler, + owner: event.args.owner, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + return; + } +}); diff --git a/apps/composable-cow-api/src/StandaloneConstantProductFactory.ts b/apps/composable-cow-api/src/StandaloneConstantProductFactory.ts new file mode 100644 index 000000000..ffffae747 --- /dev/null +++ b/apps/composable-cow-api/src/StandaloneConstantProductFactory.ts @@ -0,0 +1,70 @@ +import { Address } from "viem"; + +import { ponder } from "@/generated"; + +import { standaloneConstantProductFactoryAbi } from "../abis/StandaloneContantProductFactory"; +import { StandaloneProductConstantHandlerHelper } from "./handler"; +import { getHash, getUser } from "./utils"; + +ponder.on( + "standaloneConstantProductFactoryAbi:ConditionalOrderCreated", + async ({ event, context }) => { + const hash = await getHash({ + salt: event.args.params.salt, + staticInput: event.args.params.staticInput, + handler: event.args.params.handler, + context, + }).catch(() => { + return undefined; + }); + + const orderId = `${event.log.id}-${context.network.chainId}`; + try { + await context.db.Order.create({ + id: orderId, + data: { + chainId: context.network.chainId, + blockNumber: event.block.number, + blockTimestamp: event.block.timestamp, + hash, + txHash: event.log.transactionHash, + salt: event.args.params.salt, + staticInput: event.args.params.staticInput, + handler: event.args.params.handler, + owner: event.args.owner, + }, + }); + const handlerHelper = new StandaloneProductConstantHandlerHelper(); + await handlerHelper.decodeAndSaveOrder({ + staticInput: event.args.params.staticInput, + context, + orderId, + handler: event.args.params.handler, + owner: event.args.owner, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + return; + } + }, +); + +ponder.on( + "standaloneConstantProductFactoryAbi:TradingDisabled", + async ({ event, context }) => { + const userAddress = await context.client.readContract({ + abi: standaloneConstantProductFactoryAbi, + address: context.contracts.standaloneConstantProductFactoryAbi + .address as Address, + functionName: "owner", + args: [event.args.amm], + }); + + const user = await getUser(userAddress, context); + await context.db.ConstantProductData.update({ + id: `${event.args.amm}-${user.id}`, + data: { disabled: true }, + }); + }, +); diff --git a/apps/composable-cow-api/src/handler.ts b/apps/composable-cow-api/src/handler.ts new file mode 100644 index 000000000..98fc46496 --- /dev/null +++ b/apps/composable-cow-api/src/handler.ts @@ -0,0 +1,247 @@ +import { Address, decodeAbiParameters } from "viem"; + +import { standaloneConstantProductAbi } from "../abis/StandaloneConstantProduct"; +import { standaloneConstantProductFactoryAbi } from "../abis/StandaloneContantProductFactory"; +import { composableContext } from "./types"; +import { bytes32ToAddress, getToken, getUser } from "./utils"; + +interface IDecodeAndSaveInput { + handler: Address; + staticInput: `0x${string}`; + context: composableContext; + orderId: string; + owner: Address; +} + +export abstract class IHandlerHelper { + abstract decodeAndSaveOrder( + decodeAndSaveInput: IDecodeAndSaveInput, + ): Promise; +} + +export class StopLossHandlerHelper extends IHandlerHelper { + async decodeAndSaveOrder({ + staticInput, + context, + orderId, + owner, + }: IDecodeAndSaveInput) { + const user = await getUser(owner, context); + + const stopLossData = decodeAbiParameters( + [ + { name: "sellToken", type: "address" }, + { name: "buyToken", type: "address" }, + { name: "sellAmount", type: "uint256" }, + { name: "buyAmount", type: "uint256" }, + { name: "appData", type: "bytes32" }, + { name: "receiver", type: "address" }, + { name: "isSellOrder", type: "bool" }, + { name: "isPartiallyFillable", type: "bool" }, + { name: "validityBucketSeconds", type: "uint256" }, + { name: "sellTokenPriceOracle", type: "bytes32" }, + { name: "buyTokenPriceOracle", type: "bytes32" }, + { name: "strike", type: "int256" }, + { name: "maxTimeSinceLastOracleUpdate", type: "uint256" }, + ], + staticInput, + ); + const [tokenIn, tokenOut] = await Promise.all([ + getToken(stopLossData[0], context), + getToken(stopLossData[1], context), + ]); + + await context.db.StopLossData.create({ + id: orderId, + data: { + orderId: orderId, + userId: user.id, + tokenInId: tokenIn.id, + tokenOutId: tokenOut.id, + tokenAmountIn: stopLossData[2], + tokenAmountOut: stopLossData[3], + appData: stopLossData[4], + to: stopLossData[5], + isSellOrder: stopLossData[6], + isPartiallyFillable: stopLossData[7], + validityBucketSeconds: stopLossData[8], + sellTokenPriceOracle: bytes32ToAddress(stopLossData[9]), + buyTokenPriceOracle: bytes32ToAddress(stopLossData[10]), + strike: stopLossData[11], + maxTimeSinceLastOracleUpdate: stopLossData[12], + }, + }); + } +} + +export class ProductConstantHandlerHelper extends IHandlerHelper { + async decodeAndSaveOrder({ + staticInput, + context, + orderId, + owner, + handler, + }: IDecodeAndSaveInput) { + // on the CoW AMM calls, the first 32 bytes aren't used by us, so we will remove them + const usefulStaticInput = `0x${staticInput.slice(66)}` as const; + const cowAmmData = decodeAbiParameters( + [ + { name: "token0", type: "address" }, + { name: "token1", type: "address" }, + { name: "minTradedToken0", type: "uint256" }, + { name: "priceOracle", type: "address" }, + { name: "priceOracleData", type: "bytes" }, + { name: "appData", type: "bytes32" }, + ], + usefulStaticInput, + ); + + const [token0, token1, user] = await Promise.all([ + getToken(cowAmmData[0], context), + getToken(cowAmmData[1], context), + getUser(owner, context), + ]); + + const constantProductData = { + orderId: orderId, + userId: user.id, + token0Id: token0.id, + token1Id: token1.id, + minTradedToken0: cowAmmData[2], + priceOracle: cowAmmData[3], + priceOracleData: cowAmmData[4], + appData: cowAmmData[5], + version: "SafeModule", + }; + + const constantProductDataId = `${handler}-${user.id}`; + + await context.db.ConstantProductData.upsert({ + id: constantProductDataId, + create: constantProductData, + update: constantProductData, + }); + } +} +export class StandaloneProductConstantHandlerHelper extends IHandlerHelper { + async decodeAndSaveOrder({ + staticInput, + context, + orderId, + handler, + owner, // On this AMM owner is the new ProductConstant contract created + }: IDecodeAndSaveInput) { + // on the CoW AMM calls, the first 32 bytes aren't used by us, so we will remove them + const usefulStaticInput = `0x${staticInput.slice(66)}` as const; + const cowAmmData = decodeAbiParameters( + [ + { name: "minTradedToken0", type: "uint256" }, + { name: "priceOracle", type: "address" }, + { name: "priceOracleData", type: "bytes" }, + { name: "appData", type: "bytes32" }, + ], + usefulStaticInput, + ); + + const [token0Address, token1Address, userAddress] = await Promise.all([ + context.client.readContract({ + abi: standaloneConstantProductAbi, + address: owner, + functionName: "token0", + }), + context.client.readContract({ + abi: standaloneConstantProductAbi, + address: owner, + functionName: "token1", + }), + context.client.readContract({ + abi: standaloneConstantProductFactoryAbi, + address: handler, + functionName: "owner", + args: [owner], + }), + ]); + + const [token0, token1, user] = await Promise.all([ + getToken(token0Address, context), + getToken(token1Address, context), + getUser(userAddress, context), + ]); + const constantProductData = { + orderId: orderId, + userId: user.id, + token0Id: token0.id, + token1Id: token1.id, + minTradedToken0: cowAmmData[0], + priceOracle: cowAmmData[1], + priceOracleData: cowAmmData[2], + appData: cowAmmData[3], + disabled: false, + version: "Standalone", + }; + const constantProductDataId = `${owner}-${user.id}`; + + await context.db.ConstantProductData.upsert({ + id: constantProductDataId, + create: constantProductData, + update: constantProductData, + }); + } +} + +export function getHandlerHelper(address: Address, context: composableContext) { + const lowerCaseAddress = address.toLowerCase(); + const chainId = context.network.chainId as number; + if (chainId === 1) { + if (lowerCaseAddress === "0x34323b933096534e43958f6c7bf44f2bb59424da") { + return new ProductConstantHandlerHelper(); + } + if ( + [ + "0xe8212f30c28b4aab467df3725c14d6e89c2eb967", + "0x6a8898f43676d8a3e9a5de286195558c3628a6d4", + ].includes(lowerCaseAddress) + ) { + return new StopLossHandlerHelper(); + } + if (lowerCaseAddress === "0x40664207e3375fb4b733d4743ce9b159331fd034") { + return new StandaloneProductConstantHandlerHelper(); + } + } + + if (chainId === 100) { + if (lowerCaseAddress === "0xb148f40fff05b5ce6b22752cf8e454b556f7a851") { + return new ProductConstantHandlerHelper(); + } + if ( + [ + "0xe8212f30c28b4aab467df3725c14d6e89c2eb967", + "0x5951ebf7dc5ddb9fd2fd6d5c7f4bc7b7509b463b", + ].includes(lowerCaseAddress) + ) { + return new StopLossHandlerHelper(); + } + if (lowerCaseAddress === "0xdb1cba3a87f2db53b6e1e6af48e28ed877592ec0") { + return new StandaloneProductConstantHandlerHelper(); + } + } + + if (chainId === 11155111) { + if (lowerCaseAddress === "0x4bb23bf4802b4bbe9195637289bb4ffc835b221b") { + return new ProductConstantHandlerHelper(); + } + if ( + [ + "0xe8212f30c28b4aab467df3725c14d6e89c2eb967", + "0xb560a403f8450164b8b745ecca41d8ced93c50a1", + ].includes(lowerCaseAddress) + ) { + return new StopLossHandlerHelper(); + } + if (lowerCaseAddress === "0xb808e8183e3a72d196457d127c7fd4befa0d7fd3") { + return new StandaloneProductConstantHandlerHelper(); + } + } + + throw new Error(`Handler not found for contract ${address}`); +} diff --git a/apps/composable-cow-api/src/types.ts b/apps/composable-cow-api/src/types.ts new file mode 100644 index 000000000..ae1a9048f --- /dev/null +++ b/apps/composable-cow-api/src/types.ts @@ -0,0 +1,8 @@ +import { type Context } from "@/generated"; + +export type composableContext = Context<"composable:ConditionalOrderCreated">; +export type standaloneConstantProductFactoryContext = + Context<"standaloneConstantProductFactoryAbi:ConditionalOrderCreated">; +export type contextType = + | composableContext + | standaloneConstantProductFactoryContext; diff --git a/apps/composable-cow-api/src/utils.ts b/apps/composable-cow-api/src/utils.ts new file mode 100644 index 000000000..48f75e0a6 --- /dev/null +++ b/apps/composable-cow-api/src/utils.ts @@ -0,0 +1,92 @@ +import { Address } from "viem"; + +import { composableCowAbi } from "../abis/ComposableCow"; +import { erc20Abi } from "../abis/erc20"; +import { contextType } from "./types"; + +export function callERC20Contract( + address: `0x${string}`, + functionName: "symbol" | "decimals" | "name", + context: contextType, +): Promise { + return context.client.readContract({ + abi: erc20Abi, + address, + functionName, + }) as Promise; +} + +export function getErc20Data(address: `0x${string}`, context: contextType) { + return Promise.all([ + callERC20Contract(address, "symbol", context).catch(() => ""), + callERC20Contract(address, "decimals", context).catch(() => 0), + callERC20Contract(address, "name", context).catch(() => ""), + ]); +} + +export function getHash({ + handler, + salt, + staticInput, + context, +}: { + handler: `0x${string}`; + salt: `0x${string}`; + staticInput: `0x${string}`; + context: contextType; +}): Promise<`0x${string}`> { + return context.client.readContract({ + abi: composableCowAbi, + address: "0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74", + functionName: "hash", + args: [{ handler, salt, staticInput }], + }); +} + +export function bytes32ToAddress(hex: `0x${string}`): Address { + return `0x${hex.slice(26)}`; +} + +export async function getToken(address: `0x${string}`, context: contextType) { + const tokenId = `${address}-${context.network.chainId}`; + let token = await context.db.Token.findUnique({ + id: tokenId, + }); + if (!token) { + const [symbol, decimals, name] = await getErc20Data(address, context); + token = await context.db.Token.create({ + id: `${address}-${context.network.chainId}`, + data: { + address, + chainId: context.network.chainId, + symbol, + decimals, + name, + }, + }); + } + return token; +} + +export async function getUser(address: Address, context: contextType) { + const chainId = context.network.chainId as number; + const userId = `${address}-${chainId}`; + let user = await context.db.User.findUnique({ + id: userId, + }); + + if (!user) { + user = await context.db.User.create({ + id: userId, + data: { + address, + chainId, + }, + }); + } + return user; +} + +export function getConstantProductId(handler: Address, userId: string) { + return `${handler}-${userId}`; +} diff --git a/apps/composable-cow-api/tsconfig.json b/apps/composable-cow-api/tsconfig.json new file mode 100644 index 000000000..592b9a939 --- /dev/null +++ b/apps/composable-cow-api/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // Type checking + "strict": true, + "noUncheckedIndexedAccess": true, + + // Interop constraints + "verbatimModuleSyntax": false, + "esModuleInterop": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + + // Language and environment + "moduleResolution": "bundler", + "module": "ESNext", + "noEmit": true, + "lib": ["ES2022"], + "target": "ES2022", + + // Skip type checking for node modules + "skipLibCheck": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/cow-amm-deployer/codegen.ts b/apps/cow-amm-deployer/codegen.ts deleted file mode 100644 index 161baab76..000000000 --- a/apps/cow-amm-deployer/codegen.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CodegenConfig } from "@graphql-codegen/cli"; - -const config: CodegenConfig = { - overwrite: true, - schema: "https://composable-cow-api.up.railway.app", - documents: "src/**/*.(ts|tsx)", - generates: { - "src/lib/gqlComposableCow/generated.ts": { - plugins: [ - "typescript", - "typescript-operations", - "typescript-graphql-request", - ], - }, - }, -}; - -export default config; diff --git a/apps/cow-amm-deployer/graphql-env.d.ts b/apps/cow-amm-deployer/graphql-env.d.ts new file mode 100644 index 000000000..5adfee9dc --- /dev/null +++ b/apps/cow-amm-deployer/graphql-env.d.ts @@ -0,0 +1,48 @@ +/* eslint-disable */ +/* prettier-ignore */ + +/** An IntrospectionQuery representation of your schema. + * + * @remarks + * This is an introspection of your schema saved as a file by GraphQLSP. + * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents. + * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to + * instead save to a .ts instead of a .d.ts file. + */ +export type introspection = { + name: never; + query: 'Query'; + mutation: never; + subscription: never; + types: { + 'BigInt': unknown; + 'Boolean': unknown; + 'ConstantProductData': { kind: 'OBJECT'; name: 'ConstantProductData'; fields: { 'appData': { name: 'appData'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'disabled': { name: 'disabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'minTradedToken0': { name: 'minTradedToken0'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'order': { name: 'order'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Order'; ofType: null; }; } }; 'orderId': { name: 'orderId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'priceOracle': { name: 'priceOracle'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'priceOracleData': { name: 'priceOracleData'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'token0': { name: 'token0'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Token'; ofType: null; }; } }; 'token0Id': { name: 'token0Id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'token1': { name: 'token1'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Token'; ofType: null; }; } }; 'token1Id': { name: 'token1Id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'User'; ofType: null; }; } }; 'userId': { name: 'userId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + 'ConstantProductDataFilter': { kind: 'INPUT_OBJECT'; name: 'ConstantProductDataFilter'; isOneOf: false; inputFields: [{ name: 'AND'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConstantProductDataFilter'; ofType: null; }; }; defaultValue: null }, { name: 'OR'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConstantProductDataFilter'; ofType: null; }; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'orderId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'orderId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'userId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'userId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'token0Id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'token0Id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token0Id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'token1Id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'token1Id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'token1Id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'minTradedToken0_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'minTradedToken0_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'minTradedToken0_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'priceOracle'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracle_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracle_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'priceOracle_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'priceOracle_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracle_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracle_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracle_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'priceOracleData_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'priceOracleData_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'priceOracleData_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'appData_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'appData_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'disabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'disabled_not'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'disabled_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'disabled_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'version'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'version_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'version_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'version_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'ConstantProductDataPage': { kind: 'OBJECT'; name: 'ConstantProductDataPage'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConstantProductData'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; + 'Int': unknown; + 'Order': { kind: 'OBJECT'; name: 'Order'; fields: { 'blockNumber': { name: 'blockNumber'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'blockTimestamp': { name: 'blockTimestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'chainId': { name: 'chainId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'handler': { name: 'handler'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'hash': { name: 'hash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'owner': { name: 'owner'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'salt': { name: 'salt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'staticInput': { name: 'staticInput'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'txHash': { name: 'txHash'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + 'OrderFilter': { kind: 'INPUT_OBJECT'; name: 'OrderFilter'; isOneOf: false; inputFields: [{ name: 'AND'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'OrderFilter'; ofType: null; }; }; defaultValue: null }, { name: 'OR'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'OrderFilter'; ofType: null; }; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'chainId'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_not'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_gt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_gte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'blockNumber'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockNumber_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockNumber_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'blockNumber_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'blockNumber_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockNumber_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockNumber_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockNumber_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'blockTimestamp_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'blockTimestamp_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'blockTimestamp_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'hash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hash_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hash_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'hash_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'hash_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hash_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hash_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'hash_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'txHash_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'txHash_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'txHash_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'salt_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'salt_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'salt_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'staticInput_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'staticInput_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'staticInput_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'handler_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'handler_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'handler_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'owner_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'owner_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'OrderPage': { kind: 'OBJECT'; name: 'OrderPage'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Order'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; + 'PageInfo': { kind: 'OBJECT'; name: 'PageInfo'; fields: { 'endCursor': { name: 'endCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'hasNextPage': { name: 'hasNextPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'hasPreviousPage': { name: 'hasPreviousPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'startCursor': { name: 'startCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'constantProductData': { name: 'constantProductData'; type: { kind: 'OBJECT'; name: 'ConstantProductData'; ofType: null; } }; 'constantProductDatas': { name: 'constantProductDatas'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConstantProductDataPage'; ofType: null; }; } }; 'order': { name: 'order'; type: { kind: 'OBJECT'; name: 'Order'; ofType: null; } }; 'orders': { name: 'orders'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'OrderPage'; ofType: null; }; } }; 'stopLossData': { name: 'stopLossData'; type: { kind: 'OBJECT'; name: 'StopLossData'; ofType: null; } }; 'stopLossDatas': { name: 'stopLossDatas'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StopLossDataPage'; ofType: null; }; } }; 'token': { name: 'token'; type: { kind: 'OBJECT'; name: 'Token'; ofType: null; } }; 'tokens': { name: 'tokens'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TokenPage'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'OBJECT'; name: 'User'; ofType: null; } }; 'users': { name: 'users'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UserPage'; ofType: null; }; } }; }; }; + 'StopLossData': { kind: 'OBJECT'; name: 'StopLossData'; fields: { 'appData': { name: 'appData'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'buyTokenPriceOracle': { name: 'buyTokenPriceOracle'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'isPartiallyFillable': { name: 'isPartiallyFillable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'isSellOrder': { name: 'isSellOrder'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'maxTimeSinceLastOracleUpdate': { name: 'maxTimeSinceLastOracleUpdate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'order': { name: 'order'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Order'; ofType: null; }; } }; 'orderId': { name: 'orderId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'sellTokenPriceOracle': { name: 'sellTokenPriceOracle'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'strike': { name: 'strike'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'to': { name: 'to'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'tokenAmountIn': { name: 'tokenAmountIn'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'tokenAmountOut': { name: 'tokenAmountOut'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'tokenIn': { name: 'tokenIn'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Token'; ofType: null; }; } }; 'tokenInId': { name: 'tokenInId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'tokenOut': { name: 'tokenOut'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Token'; ofType: null; }; } }; 'tokenOutId': { name: 'tokenOutId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'User'; ofType: null; }; } }; 'userId': { name: 'userId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'validityBucketSeconds': { name: 'validityBucketSeconds'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; }; }; + 'StopLossDataFilter': { kind: 'INPUT_OBJECT'; name: 'StopLossDataFilter'; isOneOf: false; inputFields: [{ name: 'AND'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'StopLossDataFilter'; ofType: null; }; }; defaultValue: null }, { name: 'OR'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'StopLossDataFilter'; ofType: null; }; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'orderId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'orderId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'orderId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'userId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'userId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'userId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'tokenInId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'tokenInId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenInId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'tokenAmountIn_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'tokenAmountIn_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountIn_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'tokenOutId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'tokenOutId_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenOutId_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'tokenAmountOut_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'tokenAmountOut_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'tokenAmountOut_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'appData'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'appData_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'appData_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'appData_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'to_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'to_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'to_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'isSellOrder'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'isSellOrder_not'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'isSellOrder_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'isSellOrder_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'isPartiallyFillable'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'isPartiallyFillable_not'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'isPartiallyFillable_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'isPartiallyFillable_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'validityBucketSeconds'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'validityBucketSeconds_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'validityBucketSeconds_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'validityBucketSeconds_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'validityBucketSeconds_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'validityBucketSeconds_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'validityBucketSeconds_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'validityBucketSeconds_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'sellTokenPriceOracle_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'sellTokenPriceOracle_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sellTokenPriceOracle_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'buyTokenPriceOracle_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'buyTokenPriceOracle_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'buyTokenPriceOracle_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'strike'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'strike_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'strike_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'strike_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'strike_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'strike_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'strike_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'strike_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_not'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_gt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_lt'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'maxTimeSinceLastOracleUpdate_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }]; }; + 'StopLossDataPage': { kind: 'OBJECT'; name: 'StopLossDataPage'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StopLossData'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; + 'String': unknown; + 'Token': { kind: 'OBJECT'; name: 'Token'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'chainId': { name: 'chainId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'decimals': { name: 'decimals'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'symbol': { name: 'symbol'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + 'TokenFilter': { kind: 'INPUT_OBJECT'; name: 'TokenFilter'; isOneOf: false; inputFields: [{ name: 'AND'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TokenFilter'; ofType: null; }; }; defaultValue: null }, { name: 'OR'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TokenFilter'; ofType: null; }; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'address_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'address_gt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_lt'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_gte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_lte'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'chainId'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_not'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_gt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_gte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'name_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'name_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'name_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'symbol_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'symbol_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'symbol_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'decimals'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'decimals_not'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'decimals_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'decimals_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'decimals_gt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'decimals_lt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'decimals_gte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'decimals_lte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; + 'TokenPage': { kind: 'OBJECT'; name: 'TokenPage'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Token'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; + 'User': { kind: 'OBJECT'; name: 'User'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'chainId': { name: 'chainId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + 'UserFilter': { kind: 'INPUT_OBJECT'; name: 'UserFilter'; isOneOf: false; inputFields: [{ name: 'AND'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UserFilter'; ofType: null; }; }; defaultValue: null }, { name: 'OR'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UserFilter'; ofType: null; }; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'id_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'id_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_not'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'address_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'address_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_not_contains'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_not_starts_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'address_not_ends_with'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'chainId'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_not'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_not_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'chainId_gt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_gte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'chainId_lte'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; + 'UserPage': { kind: 'OBJECT'; name: 'UserPage'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'User'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; + }; +}; + +import * as gqlTada from "gql.tada"; + +declare module "gql.tada" { + interface setupSchema { + introspection: introspection; + } +} diff --git a/apps/cow-amm-deployer/next.config.js b/apps/cow-amm-deployer/next.config.js index e73732a6e..42af79bbe 100644 --- a/apps/cow-amm-deployer/next.config.js +++ b/apps/cow-amm-deployer/next.config.js @@ -18,9 +18,11 @@ const moduleExports = { }, ]; }, + experimental: { + reactCompiler: true, + }, transpilePackages: ["@bleu/gql"], reactStrictMode: true, - swcMinify: true, /** * This configuration is following Rainbowkit Migration Guide to Viem * 3. Ensure bundler and polyfill compatibility diff --git a/apps/cow-amm-deployer/package.json b/apps/cow-amm-deployer/package.json index a9fcc47ea..4382b3bcd 100644 --- a/apps/cow-amm-deployer/package.json +++ b/apps/cow-amm-deployer/package.json @@ -9,6 +9,7 @@ "dev": "pnpm npm-run-all -l --parallel dev:**", "dev:next": "next dev", "start": "next start", + "format": "prettier --write .", "lint": "eslint '**/*.{ts,tsx}'", "lint:fix": "eslint '**/*.{ts,tsx}' --fix", "test:jest": "pnpm exec jest --passWithNoTests", @@ -18,11 +19,10 @@ "dependencies": { "@bleu/gql": "workspace:*", "@bleu/tsconfig": "workspace:*", - "@bleu/ui": "^0.1.93", - "@cowprotocol/app-data": "^2.0.2", + "@bleu/ui": "^0.1.108", "@gnosis.pm/safe-apps-react-sdk": "^4.6.2", "@gnosis.pm/safe-apps-sdk": "^7.8.0", - "@hookform/resolvers": "3.3.2", + "@hookform/resolvers": "3.6.0", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", @@ -39,56 +39,68 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "1.0.7", - "@safe-global/api-kit": "^2.2.0", - "@safe-global/protocol-kit": "^3.0.1", - "@safe-global/safe-core-sdk-types": "^4.0.1", - "@safe-global/safe-gateway-typescript-sdk": "^3.18.0", + "@safe-global/api-kit": "^2.4.1", + "@safe-global/protocol-kit": "^4.0.1", + "@safe-global/safe-core-sdk-types": "^5.0.1", + "@safe-global/safe-gateway-typescript-sdk": "^3.21.2", + "@tanstack/react-query": "5.45.1", + "@types/react-plotly.js": "^2.6.3", + "@uniswap/sdk-core": "^5.3.0", + "babel-plugin-react-compiler": "0.0.0-experimental-938cd9a-20240601", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", + "clsx": "^2.1.1", "cmdk": "^0.2.1", "copy-to-clipboard": "^3.3.3", - "date-fns": "^3.3.1", + "date-fns": "^3.6.0", "downshift": "^8.4.0", - "fathom-client": "^3.6.0", + "fathom-client": "^3.7.0", "gql": "^1.1.2", + "gql.tada": "^1.7.6", "graphql-request": "6.1.0", "graphql-tag": "^2.12.6", "lodash": "^4.17.21", "lodash.merge": "^4.6.2", - "next": "^14.1.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-hook-form": "7.43.9", + "lucide-react": "^0.395.0", + "next": "15.0.0-rc.0", + "parse-prometheus-text-format": "^1.1.1", + "plotly.js": "^2.33.0", + "react": "19.0.0-rc-6230622a1a-20240610", + "react-chartjs-2": "^5.2.0", + "react-dom": "19.0.0-rc-6230622a1a-20240610", + "react-hook-form": "7.52.0", + "react-plotly.js": "^2.6.0", "server-only": "^0.0.1", "swr": "^2.2.5", - "tailwind-merge": "^2.2.1", + "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "tiny-invariant": "^1.3.3", - "viem": "^2.7.19", - "zod": "^3.22.4" + "viem": "^2.14.2", + "wagmi": "^2.10.2", + "zod": "^3.23.8" }, "devDependencies": { + "@0no-co/graphqlsp": "^1.12.8", "@bleu/eslint-config": "workspace:^", "@bleu/utils": "workspace:^", "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/typescript-graphql-request": "6.2.0", - "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "^14.2.1", + "@testing-library/jest-dom": "6.4.6", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.12", - "@types/lodash": "^4.14.202", + "@types/lodash": "^4.17.5", "@types/lodash.merge": "^4.6.9", - "@types/node": "^20.12.7", - "@types/react": "^18.3.1", + "@types/node": "^20.14.2", + "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "autoprefixer": "^10.4.18", + "autoprefixer": "^10.4.19", "jest": "^29.7.0", "jest-environment-jsdom": "29.7.0", "npm-run-all": "^4.1.5", - "postcss": "^8.4.35", + "postcss": "^8.4.38", "tailwind-scrollbar": "^3.1.0", - "tailwindcss": "^3.4.1", - "ts-jest": "^29.1.2", + "tailwindcss": "^3.4.4", + "ts-jest": "^29.1.5", "typescript": "5.4.5" } } diff --git a/apps/cow-amm-deployer/public/assets/etherscan-logo.svg b/apps/cow-amm-deployer/public/assets/etherscan-logo.svg new file mode 100644 index 000000000..d1a34a887 --- /dev/null +++ b/apps/cow-amm-deployer/public/assets/etherscan-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/(components)/AmmsTable.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/(components)/AmmsTable.tsx new file mode 100644 index 000000000..ad1ecc1e1 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/(components)/AmmsTable.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { formatNumber } from "@bleu/ui"; +import { useRouter } from "next/navigation"; +import React from "react"; + +import { StatusBadge } from "#/components/StatusBadge"; +import Table from "#/components/Table"; +import { TokenInfo } from "#/components/TokenInfo"; +import { ICowAmm } from "#/lib/fetchAmmData"; + +export function AmmsTable({ + standaloneAmmData, + userId, +}: { + standaloneAmmData: ICowAmm[]; + userId: string; +}) { + const router = useRouter(); + return ( + + + Token pair + Token Pair + Value + Status + Updated at + + + {standaloneAmmData.length === 0 ? ( + + + No AMMs created yet + + + ) : ( + standaloneAmmData.map((amm) => ( + { + router.push(`/${userId}/amms/${amm.id}`); + }} + classNames="hover:cursor-pointer hover:bg-accent" + > + + + + + + + + + $ {formatNumber(amm.totalUsdValue, 2)} + + + + + + + + {new Date( + (amm.order.blockTimestamp as number) * 1000, + ).toLocaleString()} + + + + )) + )} + +
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/DisableAmmButton.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/DisableAmmButton.tsx new file mode 100644 index 000000000..bb232e564 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/DisableAmmButton.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { StopIcon } from "@radix-ui/react-icons"; +import React from "react"; +import { parseUnits } from "viem"; +import { useAccount } from "wagmi"; + +import { Button } from "#/components/Button"; +import { Checkbox } from "#/components/Checkbox"; +import { Dialog } from "#/components/Dialog"; +import { useAmmData } from "#/contexts/ammDataContext"; +import { useTransactionManagerContext } from "#/contexts/transactionManagerContext"; +import { ICowAmm } from "#/lib/fetchAmmData"; +import { + AllTransactionArgs, + TRANSACTION_TYPES, + WithdrawCoWAMMArgs, +} from "#/lib/transactionFactory"; +import { ChainId } from "#/utils/chainsPublicClients"; + +export function DisableAmmButton({ ammData }: { ammData: ICowAmm }) { + const [isOpen, setIsOpen] = React.useState(false); + const { isAmmUpdating } = useAmmData(); + + return ( + + } + isOpen={isOpen} + onClose={() => setIsOpen(false)} + setIsOpen={setIsOpen} + > + + + ); +} + +function DisableTradingDialogContent({ + ammData, +}: { + ammData: ICowAmm; + setIsOpen: (isOpen: boolean) => void; +}) { + const { isAmmUpdating } = useAmmData(); + const [withdrawFunds, setWithdrawFunds] = React.useState(false); + const { chainId } = useAccount(); + + const { + managedTransaction: { + writeContract, + writeContractWithSafe, + isWalletContract, + }, + } = useTransactionManagerContext(); + + function onDisableAMM() { + const txArgs = [ + { + type: TRANSACTION_TYPES.DISABLE_COW_AMM, + amm: ammData.order.owner, + chainId: chainId as ChainId, + hash: ammData.order.hash, + version: ammData.version, + }, + ] as AllTransactionArgs[]; + + if (withdrawFunds) { + txArgs.push({ + type: TRANSACTION_TYPES.WITHDRAW_COW_AMM, + amm: ammData.order.owner, + amount0: parseUnits(ammData.token0.balance, ammData.token0.decimals), + amount1: parseUnits(ammData.token1.balance, ammData.token1.decimals), + chainId: chainId as ChainId, + } as WithdrawCoWAMMArgs); + } + + if (isWalletContract) { + writeContractWithSafe(txArgs); + } else { + // TODO: this will need to be refactored once we have EOAs + // @ts-ignore + writeContract(txArgs); + } + } + + return ( +
+ + This action will halt the automatic rebalancing of your AMM over time. + You retain the ability to withdraw or deposit funds as needed. If + desired, you have the option to withdraw all your funds in one single + transaction. + + setWithdrawFunds(!withdrawFunds)} + checked={withdrawFunds} + id="withdraw-funds-checkbox" + /> + +
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/EditAMMForm.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/EditAMMForm.tsx new file mode 100644 index 000000000..8d515d724 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/EditAMMForm.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { PlayIcon } from "@radix-ui/react-icons"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { Address, formatUnits } from "viem"; +import { z } from "zod"; + +import { Button } from "#/components"; +import { Input } from "#/components/Input"; +import { PriceOracleForm } from "#/components/PriceOracleForm"; +import { TokenInfo } from "#/components/TokenInfo"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "#/components/ui/accordion"; +import { Form } from "#/components/ui/form"; +import { useAmmData } from "#/contexts/ammDataContext"; +import { useTransactionManagerContext } from "#/contexts/transactionManagerContext"; +import { ICowAmm } from "#/lib/fetchAmmData"; +import { ammEditSchema } from "#/lib/schema"; +import { buildTxEditAMMArgs } from "#/lib/transactionFactory"; +import { cn } from "#/lib/utils"; + +import { DisableAmmButton } from "./DisableAmmButton"; + +export function EditAMMForm({ ammData }: { ammData: ICowAmm }) { + const [buttonClicked, setButtonClicked] = React.useState(); + const { isAmmUpdating } = useAmmData(); + const form = useForm>({ + // @ts-ignore + resolver: zodResolver(ammEditSchema), + defaultValues: { + safeAddress: ammData.user.address, + chainId: ammData.chainId, + token0: ammData.token0, + token1: ammData.token1, + minTradedToken0: Number( + formatUnits(ammData.minTradedToken0, ammData.token0.decimals), + ), + priceOracleSchema: ammData.decodedPriceOracleData, + }, + }); + + const { + formState: { errors, isSubmitting }, + } = form; + + const { + managedTransaction: { + writeContract, + writeContractWithSafe, + isWalletContract, + }, + } = useTransactionManagerContext(); + + const onSubmit = async (data: z.output) => { + const txArgs = buildTxEditAMMArgs({ + data: data, + ammAddress: ammData.order.owner as Address, + }); + setButtonClicked("edit"); + if (isWalletContract) { + writeContractWithSafe(txArgs); + } else { + // TODO: this will need to be refactored once we have EOAs + // @ts-ignore + writeContract(txArgs); + } + }; + + const submitButtonText = ammData.disabled ? "Enable AMM" : "Update AMM"; + + return ( + // @ts-ignore +
+
+ Token Pair +
+ + +
+
+ + + + + Advanced Options + + + + + + + +
+ {!ammData.disabled && } + +
+ + ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Header.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Header.tsx new file mode 100644 index 000000000..0937f12f1 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Header.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { Card, CardDescription, CardHeader, CardTitle } from "@bleu/ui"; +import { ArrowLeftIcon, ArrowTopRightIcon } from "@radix-ui/react-icons"; +import Link from "next/link"; +import { Address } from "viem"; + +import { Button } from "#/components/Button"; +import { BlockExplorerLink } from "#/components/ExplorerLink"; +import { LinkComponent } from "#/components/Link"; +import { OldVersionOfAMMAlert } from "#/components/OldVersionOfAmmAlert"; +import { StatusBadge } from "#/components/StatusBadge"; +import { TokenPairLogo } from "#/components/TokenPairLogo"; +import { useAmmData } from "#/contexts/ammDataContext"; +import { getExplorerAddressLink } from "#/lib/cowExplorer"; +import { IToken } from "#/lib/fetchAmmData"; +import { truncateMiddle } from "#/lib/truncateMiddle"; +import { ChainId } from "#/utils/chainsPublicClients"; + +import { PriceInformation } from "./PriceInformation"; + +export function Header() { + const { ammData } = useAmmData(); + const oldVersionOfAmm = ammData.version !== "Standalone"; + const poolName = `${ammData.token0.symbol}/${ammData.token1.symbol}`; + + return ( + + + + + + + + {poolName} + } + identifier={ammData.order.owner} + networkId={ammData.chainId as ChainId} + /> + + + +
+ Price Oracle: +
+
+ Orders: CoW Explorer + + + +
+ + +
+ {oldVersionOfAmm && } +
+
+ ); +} + +export function TokenLink({ + chainId, + token, +}: { + chainId: ChainId; + token: IToken; +}) { + return ( +
+ {token.symbol}: {truncateMiddle(token.address, 5)} + } + identifier={token.address} + networkId={chainId as ChainId} + /> +
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Manage.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Manage.tsx new file mode 100644 index 000000000..8cba0a9fb --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/Manage.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Separator, + TabsContent, + TabsList, + TabsRoot, + TabsTrigger, +} from "@bleu/ui"; + +import { DepositForm } from "#/components/DepositForm"; +import { WithdrawForm } from "#/components/WithdrawForm"; +import { useAmmData } from "#/contexts/ammDataContext"; + +import { EditAMMForm } from "./EditAMMForm"; + +export function Manage() { + const { ammData, walletBalanceToken0, walletBalanceToken1, isAmmUpdating } = + useAmmData(); + const oldVersionOfAmm = ammData.version !== "Standalone"; + + return ( + <> + + + Manage + + Manage your CoW AMM + + + + + + + + Deposit + + + Withdraw + + + Edit Parameters + + + + + + + + + + + + + + + + ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolComposition.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolComposition.tsx new file mode 100644 index 000000000..236b27b41 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolComposition.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@bleu/ui"; +import { formatNumber } from "@bleu/utils/formatNumber"; + +import { useAmmData } from "#/contexts/ammDataContext"; + +import { PoolCompositionTable } from "./PoolCompositionTable"; + +export function PoolComposition() { + const { ammData } = useAmmData(); + return ( + + + Pool composition + + The current pool TVL is{" "} + + ${formatNumber(ammData.totalUsdValue, 2)} + + + + + + + + ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolCompositionTable.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolCompositionTable.tsx new file mode 100644 index 000000000..a5c08cb8c --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PoolCompositionTable.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { formatNumber } from "@bleu/ui"; + +import Table from "#/components/Table"; +import { TokenAmount } from "#/components/TokenAmount"; +import { TokenLogo } from "#/components/TokenLogo"; +import { ICowAmm } from "#/lib/fetchAmmData"; + +export function PoolCompositionTable({ ammData }: { ammData: ICowAmm }) { + const anyTokenWithoutUsdPrice = + !ammData.token0.usdPrice || !ammData.token1.usdPrice; + return ( + + + Balance + Weight + + + {[ammData.token0, ammData.token1].map((token) => { + const valuePct = + (Number(token.usdValue) * 100) / ammData.totalUsdValue; + return ( + + +
+
+ +
+ +
+
+ +
+ <> + {anyTokenWithoutUsdPrice ? "-" : formatNumber(valuePct, 2)}{" "} + {valuePct && !anyTokenWithoutUsdPrice ? "%" : ""} + +
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PriceInformation.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PriceInformation.tsx new file mode 100644 index 000000000..726608ff5 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/(components)/PriceInformation.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { ArrowTopRightIcon } from "@radix-ui/react-icons"; +import Link from "next/link"; + +import { ICowAmm } from "#/lib/fetchAmmData"; +import { PRICE_ORACLES } from "#/lib/types"; + +function PriceInformationComponent({ + label, + urls, +}: { + label: string; + urls: string[]; +}) { + return ( +
+ {label} + {urls.map((url, index) => ( + + + + ))} +
+ ); +} + +export function PriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { + const { decodedPriceOracleData, priceFeedLinks } = cowAmm; + + const labels = { + [PRICE_ORACLES.UNI]: "Uniswap V2", + [PRICE_ORACLES.BALANCER]: "Balancer V2", + [PRICE_ORACLES.SUSHI]: "Sushi V2", + [PRICE_ORACLES.CHAINLINK]: "Chainlink", + [PRICE_ORACLES.CUSTOM]: "Custom contract", + } as const; + + const priceOracle = decodedPriceOracleData.priceOracle; + + const label = labels[priceOracle]; + + return ; +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/layout.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/layout.tsx new file mode 100644 index 000000000..e27728f3f --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/layout.tsx @@ -0,0 +1,15 @@ +import { AmmDataContextProvider } from "#/contexts/ammDataContext"; + +export default function Layout({ + children, + params, +}: React.PropsWithChildren<{ + params: { userId: string; id: string }; +}>) { + return ( + + {/* @ts-ignore */} + {children} + + ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/page.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/page.tsx new file mode 100644 index 000000000..74c71344c --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/[id]/page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Spinner } from "#/components/Spinner"; +import { useAmmData } from "#/contexts/ammDataContext"; + +import { Header } from "./(components)/Header"; +import { Manage } from "./(components)/Manage"; +import { PoolComposition } from "./(components)/PoolComposition"; + +export default function Page() { + const { ammData } = useAmmData(); + + if (!ammData) { + return ; + } + + return ( +
+
+
+
+ +
+
+ +
+
+
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/amms/page.tsx b/apps/cow-amm-deployer/src/app/[userId]/amms/page.tsx new file mode 100644 index 000000000..f535c1021 --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/amms/page.tsx @@ -0,0 +1,40 @@ +import { Button } from "#/components"; +import { LinkComponent } from "#/components/Link"; +import { OldVersionOfAMMAlert } from "#/components/OldVersionOfAmmAlert"; +import { TxPendingAlertCard } from "#/components/TxPendingAlertCard"; +import { fetchUserAmmsData, ICowAmm, validateUserId } from "#/lib/fetchAmmData"; +import { fetchHasAmmTxPending } from "#/lib/fetchHasAmmTxPending"; + +import { AmmsTable } from "./(components)/AmmsTable"; + +export default async function Page({ params }: { params: { userId: string } }) { + const [userAddress, chainId] = validateUserId(params.userId); + + const [standaloneAmmData, hasAmmTxPending] = await Promise.all([ + fetchUserAmmsData(params.userId) as Promise, + fetchHasAmmTxPending({ address: userAddress, chainId }) as Promise, + ]); + + const oldVersionOfAmm = standaloneAmmData.find( + (amm) => amm.version !== "Standalone", + ); + + return ( +
+
+ {oldVersionOfAmm && } + {hasAmmTxPending && } +
+

My CoW AMMs

+ + + +
+ +
+
+ ); +} diff --git a/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx new file mode 100644 index 000000000..8efce79ff --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx @@ -0,0 +1,275 @@ +"use client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect, useState } from "react"; +import { useForm, useWatch } from "react-hook-form"; +import { Address } from "viem"; +import { useAccount } from "wagmi"; +import { z } from "zod"; + +import { Button } from "#/components"; +import { AlertCard } from "#/components/AlertCard"; +import { CreateSuccessDialog } from "#/components/CreateSuccessDialog"; +import { Input } from "#/components/Input"; +import { PriceOracleForm } from "#/components/PriceOracleForm"; +import { TokenAmountInput } from "#/components/TokenAmountInput"; +import { TokenSelect } from "#/components/TokenSelect"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "#/components/ui/accordion"; +import { Form } from "#/components/ui/form"; +import { useTransactionManagerContext } from "#/contexts/transactionManagerContext"; +import { useDebounce } from "#/hooks/useDebounce"; +import { UNBALANCED_USD_DIFF_THRESHOLD } from "#/lib/constants"; +import { IToken } from "#/lib/fetchAmmData"; +import { getAmmId } from "#/lib/getAmmId"; +import { ammFormSchema } from "#/lib/schema"; +import { fetchTokenUsdPrice, getNewMinTradeToken0 } from "#/lib/tokenUtils"; +import { buildTxCreateAMMArgs } from "#/lib/transactionFactory"; +import { cn } from "#/lib/utils"; +import { ChainId } from "#/utils/chainsPublicClients"; + +export function CreateAMMForm({ userId }: { userId: string }) { + const { address: safeAddress, chainId } = useAccount(); + const [ammId, setAmmId] = useState(); + const [ammDialogOpen, setAmmDialogOpen] = useState(false); + + const form = useForm>({ + // @ts-ignore + resolver: zodResolver(ammFormSchema), + defaultValues: { + // TODO: this will need to be changed once we allow EOAs to create AMMs + safeAddress, + chainId, + priceOracleSchema: { + chainId: chainId as ChainId, + }, + }, + }); + + const { + setValue, + control, + formState: { errors, isSubmitting }, + } = form; + + const { + managedTransaction: { + writeContract, + status, + isWalletContract, + writeContractWithSafe, + }, + } = useTransactionManagerContext(); + + const [token0, token1, priceOracle, amount0, amount1] = useWatch({ + control, + name: [ + "token0", + "token1", + "priceOracleSchema.priceOracle", + "amount0", + "amount1", + ], + }); + + async function updateAmmId() { + if (!chainId || !safeAddress || !token0 || !token1) return; + const ammId = await getAmmId({ + chainId: chainId as ChainId, + userAddress: safeAddress as Address, + token0Address: token0.address as Address, + token1Address: token1.address as Address, + }); + setAmmId(ammId); + } + + const onSubmit = (data: z.output) => { + updateAmmId(); + if (isWalletContract) { + writeContractWithSafe(buildTxCreateAMMArgs({ data })); + } else { + // TODO: remove this once we allow EOAs to create AMMs + // @ts-ignore + writeContract(buildTxCreateAMMArgs({ data })); + } + }; + + const [token0UsdPrice, setToken0UsdPrice] = useState(); + const [token1UsdPrice, setToken1UsdPrice] = useState(); + const [amountUsdDiff, setAmountUsdDiff] = useState(); + const debouncedAmountUsdDiff = useDebounce( + amountUsdDiff, + 300, + ); + + useEffect(() => { + setValue("safeAddress", safeAddress as string); + }, [safeAddress, setValue]); + + useEffect(() => { + if (status === "final" || status === "confirmed") { + setAmmDialogOpen(true); + } + }, [status]); + + async function updateTokenUsdPrice( + token: IToken, + setAmountUsd: (value: number) => void, + ) { + const amountUsd = await fetchTokenUsdPrice({ + chainId: chainId as ChainId, + tokenDecimals: token.decimals, + tokenAddress: token.address as Address, + }); + setAmountUsd(amountUsd); + } + + useEffect(() => { + if (token0) { + updateTokenUsdPrice(token0, setToken0UsdPrice); + } + }, [token0]); + + useEffect(() => { + if (token1) { + updateTokenUsdPrice(token1, setToken1UsdPrice); + } + }, [token1]); + + useEffect(() => { + if (token0?.address == token1?.address) return; + if (token0UsdPrice && token1UsdPrice && amount0 && amount1) { + const amount0Usd = amount0 * token0UsdPrice; + const amount1Usd = amount1 * token1UsdPrice; + setAmountUsdDiff(Math.abs(amount0Usd - amount1Usd)); + } + }, [amount0, amount1, token0UsdPrice, token1UsdPrice]); + + return ( + <> + + {/* @ts-ignore */} +
+
+
+
+ Token Pair + { + setValue("token0", { + decimals: token.decimals, + address: token.address, + symbol: token.symbol, + }); + setValue( + "minTradedToken0", + await getNewMinTradeToken0(token, chainId as ChainId), + ); + }} + selectedToken={(token0 as IToken) ?? undefined} + errorMessage={errors.token0?.message} + /> +
+
+
+
+ + Select pair + + { + setValue("token1", { + decimals: token.decimals, + address: token.address, + symbol: token.symbol, + }); + }} + selectedToken={(token1 as IToken) ?? undefined} + errorMessage={errors.token1?.message} + /> +
+
+
+
+
+
+ Token amounts + +
+
+
+
+ + +
+
+
+ {(debouncedAmountUsdDiff || 0) > UNBALANCED_USD_DIFF_THRESHOLD && ( + +

+ The difference between the USD value of the two token amounts is + greater than $5000. This may lead to an unbalanced AMM and result + in loss of funds. +

+
+ )} + {/* @ts-ignore */} + + + + + Advanced Options + + + + + + +
+ +
+ + + ); +} diff --git a/apps/cow-amm-deployer/src/app/createtxprocessing/layout.tsx b/apps/cow-amm-deployer/src/app/[userId]/new/layout.tsx similarity index 100% rename from apps/cow-amm-deployer/src/app/createtxprocessing/layout.tsx rename to apps/cow-amm-deployer/src/app/[userId]/new/layout.tsx diff --git a/apps/cow-amm-deployer/src/app/[userId]/new/page.tsx b/apps/cow-amm-deployer/src/app/[userId]/new/page.tsx new file mode 100644 index 000000000..e189b671f --- /dev/null +++ b/apps/cow-amm-deployer/src/app/[userId]/new/page.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { FormPageWrapper } from "#/components/FormPageWrapper"; + +import { CreateAMMForm } from "./(components)/CreateAMMForm"; + +export default function Page({ params }: { params: { userId: string } }) { + return ( + + + + ); +} diff --git a/apps/cow-amm-deployer/src/app/createtxprocessing/page.tsx b/apps/cow-amm-deployer/src/app/createtxprocessing/page.tsx deleted file mode 100644 index 844c9dd20..000000000 --- a/apps/cow-amm-deployer/src/app/createtxprocessing/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client"; - -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; -import { Address } from "viem"; - -import { Spinner } from "#/components/Spinner"; -import { checkIsAmmRunning, fetchLastAmmInfo } from "#/hooks/useRunningAmmInfo"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export default function Page() { - const { - safe: { safeAddress, chainId }, - } = useSafeAppsSDK(); - const router = useRouter(); - - async function redirectToHomeIfAmmIsRunningAndIndexed() { - const isAmmRunning = await fetchLastAmmInfo({ - chainId: chainId as ChainId, - safeAddress: safeAddress as Address, - }).then((data) => - checkIsAmmRunning(chainId as ChainId, safeAddress as Address, data.hash), - ); - if (isAmmRunning) { - router.push("/manager"); - } - } - useEffect(() => { - const intervalId = setInterval( - redirectToHomeIfAmmIsRunningAndIndexed, - 1000, - ); - return () => clearInterval(intervalId); - }, []); - - return ( -
-
- The transaction is being processed -
- -
- ); -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/BalancerPriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/BalancerPriceInformation.tsx deleted file mode 100644 index cc5f8709e..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/BalancerPriceInformation.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { NetworkFromNetworkChainId } from "@bleu/utils"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { ArrowTopRightIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; - -import { ICowAmm } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export function BalancerPriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - const { safe } = useSafeAppsSDK(); - - const priceOracleLink = getBalancerPoolUrl( - safe.chainId as ChainId, - cowAmm.priceOracleData?.balancerPoolId, - ); - - return ( -
- Using price information from Balancer V2 - {priceOracleLink && ( - - - - )} -
- ); -} - -export function getBalancerPoolUrl(chainId: ChainId, poolId?: string) { - return `https://app.balancer.fi/#/${NetworkFromNetworkChainId[chainId]}-chain/pool/${poolId}`; -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/ChainlinkPriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/ChainlinkPriceInformation.tsx deleted file mode 100644 index 399edce14..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/ChainlinkPriceInformation.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { Address } from "viem"; -import { gnosis, sepolia } from "viem/chains"; - -import { priceFeedAbi } from "#/lib/abis/priceFeed"; -import { ICowAmm } from "#/lib/types"; -import { ChainId, publicClientsFromIds } from "#/utils/chainsPublicClients"; - -export function ChainlinkPriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - const { safe } = useSafeAppsSDK(); - const [priceFeed0Link, setPriceFeed0Link] = useState(); - const [priceFeed1Link, setPriceFeed1Link] = useState(); - - const fetchPriceFeedLinks = async () => { - const [priceFeed0Link, priceFeed1Link] = await Promise.all([ - getPriceFeedLink( - safe.chainId as ChainId, - cowAmm.priceOracleData.chainlinkPriceFeed0, - ), - getPriceFeedLink( - safe.chainId as ChainId, - cowAmm.priceOracleData.chainlinkPriceFeed1, - ), - ]); - setPriceFeed0Link(priceFeed0Link); - setPriceFeed1Link(priceFeed1Link); - }; - - useEffect(() => { - fetchPriceFeedLinks(); - }, []); - return ( -
- Using price information from Chainlink - {priceFeed0Link && ( - - 1 - - )} - {priceFeed1Link && ( - - 2 - - )} -
- ); -} - -export async function getPriceFeedLink(chainId: ChainId, address?: Address) { - if (!address) return; - if (chainId === sepolia.id) return; - const publicClient = publicClientsFromIds[chainId]; - const priceFeedDescription = (await publicClient.readContract({ - address: address, - abi: priceFeedAbi, - functionName: "description", - })) as string; - const priceFeedPageName = priceFeedDescription - .replace(" / ", "-") - .toLowerCase(); - const chainName = chainId === gnosis.id ? "xdai" : "ethereum"; - - return `https://data.chain.link/feeds/${chainName}/mainnet/${priceFeedPageName}`; -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/CustomPriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/CustomPriceInformation.tsx deleted file mode 100644 index de958c513..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/CustomPriceInformation.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { buildBlockExplorerAddressURL } from "@bleu/utils"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { ArrowTopRightIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; - -import { ICowAmm } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export function CustomPriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - const { safe } = useSafeAppsSDK(); - - const priceOracleLink = buildBlockExplorerAddressURL({ - chainId: safe.chainId as ChainId, - address: cowAmm.priceOracleAddress, - }); - - return ( -
- Using price information from custom contract - {priceOracleLink && ( - - - - )} -
- ); -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/PoolCompositionTable.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/PoolCompositionTable.tsx deleted file mode 100644 index 8946f7e21..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/PoolCompositionTable.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { formatNumber } from "@bleu/utils/formatNumber"; -import { tomatoDark } from "@radix-ui/colors"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { formatUnits } from "viem"; - -import Table from "#/components/Table"; -import { Tooltip } from "#/components/Tooltip"; -import { ICowAmm } from "#/lib/types"; - -import { TokenInfo } from "./TokenInfo"; - -export function PoolCompositionTable({ cowAmm }: { cowAmm: ICowAmm }) { - const anyTokenWithoutUsdPrice = - !cowAmm.token0.externalUsdPrice || !cowAmm.token1.externalUsdPrice; - return ( - - - Tokens - Balance - Price - Value - Weight - - - {[cowAmm.token0, cowAmm.token1].map((token) => { - const valuePct = - (Number(token.externalUsdValue) * 100) / cowAmm.totalUsdValue; - return ( - - - - - - {formatNumber( - formatUnits(BigInt(token.balance), token.tokenInfo.decimals), - 4, - )} - - -
- <>$ {formatNumber(token.externalUsdPrice, 4)} - {!token.externalUsdPrice && } -
-
- -
- <> - ${" "} - {formatNumber( - token.externalUsdValue, - 2, - "decimal", - "compact", - 0.01, - )} - - {!token.externalUsdPrice && } -
-
- -
- <> - {anyTokenWithoutUsdPrice ? "-" : formatNumber(valuePct, 2)}{" "} - {valuePct && !anyTokenWithoutUsdPrice ? "%" : ""} - - {!token.externalUsdPrice && } -
-
-
- ); - })} -
-
- ); -} - -function PriceErrorTooltip() { - return ( - - - - ); -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/PriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/PriceInformation.tsx deleted file mode 100644 index 2dbf5f236..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/PriceInformation.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ICowAmm, PRICE_ORACLES } from "#/lib/types"; - -import { BalancerPriceInformation } from "./BalancerPriceInformation"; -import { ChainlinkPriceInformation } from "./ChainlinkPriceInformation"; -import { CustomPriceInformation } from "./CustomPriceInformation"; -import { SushiV2PriceInformation } from "./SushiV2PriceInformation"; -import { UniswapV2PriceInformation } from "./UniswapV2PriceInformation"; - -export function PriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - switch (cowAmm.priceOracle) { - case PRICE_ORACLES.UNI: - return ; - case PRICE_ORACLES.BALANCER: - return ; - case PRICE_ORACLES.SUSHI: - return ; - case PRICE_ORACLES.CHAINLINK: - return ; - default: - return ; - } -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/SushiV2PriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/SushiV2PriceInformation.tsx deleted file mode 100644 index e42744851..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/SushiV2PriceInformation.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { ArrowTopRightIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; - -import { ICowAmm } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export function SushiV2PriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - const { safe } = useSafeAppsSDK(); - - const priceOracleLink = getSushiV2Pair( - safe.chainId as ChainId, - cowAmm.priceOracleData?.sushiSwapPairAddress, - ); - - return ( -
- Using price information from Sushi V2 - {priceOracleLink && ( - - - - )} -
- ); -} - -export function getSushiV2Pair(chainId: ChainId, referencePair?: string) { - return `https://www.sushi.com/pool/${[chainId]}%3A${referencePair}`; -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/TokenInfo.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/TokenInfo.tsx deleted file mode 100644 index c6cda6970..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/TokenInfo.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { formatNumber } from "@bleu/utils/formatNumber"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; - -import { TokenLogo } from "#/components/TokenLogo"; -import { ChainId } from "#/utils/chainsPublicClients"; -import { truncateAddress } from "#/utils/truncate"; - -export function TokenInfo({ - symbol, - id, - amount, - logoUri, -}: { - logoUri: string; - symbol?: string | null; - id?: string; - amount?: number | string; -}) { - const { safe } = useSafeAppsSDK(); - return ( -
-
-
- -
-
- {symbol ? symbol : truncateAddress(id)}{" "} - {amount && `(${formatNumber(amount, 4, "decimal", "compact", 0.001)})`} -
- ); -} diff --git a/apps/cow-amm-deployer/src/app/manager/(components)/UniswapV2PriceInformation.tsx b/apps/cow-amm-deployer/src/app/manager/(components)/UniswapV2PriceInformation.tsx deleted file mode 100644 index 00f93b0c1..000000000 --- a/apps/cow-amm-deployer/src/app/manager/(components)/UniswapV2PriceInformation.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { NetworkFromNetworkChainId } from "@bleu/utils"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { ArrowTopRightIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; -import { gnosis } from "viem/chains"; - -import { ICowAmm } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export function UniswapV2PriceInformation({ cowAmm }: { cowAmm: ICowAmm }) { - const { safe } = useSafeAppsSDK(); - - const priceOracleLink = getUniV2PairUrl( - safe.chainId as ChainId, - cowAmm.priceOracleData?.uniswapV2PairAddress, - ); - - return ( -
- Using price information from Uniswap V2 - {priceOracleLink && ( - - - - )} -
- ); -} - -export function getUniV2PairUrl(chainId: ChainId, referencePair?: string) { - if (chainId === gnosis.id) { - return; - } - return `https://info.uniswap.org/#/${NetworkFromNetworkChainId[chainId]}/pools/${referencePair}`; -} diff --git a/apps/cow-amm-deployer/src/app/manager/layout.tsx b/apps/cow-amm-deployer/src/app/manager/layout.tsx deleted file mode 100644 index c1492b79b..000000000 --- a/apps/cow-amm-deployer/src/app/manager/layout.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Layout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/apps/cow-amm-deployer/src/app/manager/page.tsx b/apps/cow-amm-deployer/src/app/manager/page.tsx deleted file mode 100644 index 1da70265b..000000000 --- a/apps/cow-amm-deployer/src/app/manager/page.tsx +++ /dev/null @@ -1,167 +0,0 @@ -"use client"; - -import { formatNumber } from "@bleu/utils/formatNumber"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { tomatoDark } from "@radix-ui/colors"; -import { - ArrowTopRightIcon, - ExclamationTriangleIcon, - Pencil2Icon, - ResetIcon, -} from "@radix-ui/react-icons"; -import Link from "next/link"; -import { redirect, useRouter } from "next/navigation"; -import { useState } from "react"; -import { Address } from "viem"; - -import { Button } from "#/components/Button"; -import { Dialog } from "#/components/Dialog"; -import { Spinner } from "#/components/Spinner"; -import { Tooltip } from "#/components/Tooltip"; -import WalletNotConnected from "#/components/WalletNotConnected"; -import { useRawTxData } from "#/hooks/useRawTxData"; -import { useRunningAMM } from "#/hooks/useRunningAmmInfo"; -import { buildAccountCowExplorerUrl } from "#/lib/cowExplorer"; -import { TRANSACTION_TYPES } from "#/lib/transactionFactory"; -import { ChainId, supportedChainIds } from "#/utils/chainsPublicClients"; - -import { UnsuportedChain } from "../../components/UnsuportedChain"; -import { PoolCompositionTable } from "./(components)/PoolCompositionTable"; -import { PriceInformation } from "./(components)/PriceInformation"; - -export default function Page() { - const router = useRouter(); - const { sendTransactions } = useRawTxData(); - const { safe, connected } = useSafeAppsSDK(); - const { loaded, cowAmm, isAmmFromModule } = useRunningAMM(); - const [openDialog, setOpenDialog] = useState(false); - - if (!connected) { - return ; - } - - if (!loaded) { - return ; - } - - if (!supportedChainIds.includes(safe.chainId)) { - return ; - } - - if (!cowAmm) { - redirect("/new"); - } - - return ( -
- - - Clicking confirm will make the CoW AMM LP position created stop. - This means that the position will no longer be actively - rebalanced. Don't worry, the tokens will stay on your Safe Wallet. - - -
- } - title="Stop CoW AMM Confirmation" - isOpen={openDialog} - setIsOpen={setOpenDialog} - /> -
-
-
-

- The first MEV-Capturing AMM, - brought to you by CoW DAO{" "} -

- -
-
- Total Value - - ${" "} - {formatNumber( - cowAmm.totalUsdValue, - 2, - "decimal", - "compact", - 0.01, - )} - -
-
-
- - AMM Composition - - - See in CoW Explorer - - -
- -
- - - {!isAmmFromModule && ( - - - - )} -
-
- - ); -} diff --git a/apps/cow-amm-deployer/src/app/new/(components)/AmmForm.tsx b/apps/cow-amm-deployer/src/app/new/(components)/AmmForm.tsx deleted file mode 100644 index 3c9bedeef..000000000 --- a/apps/cow-amm-deployer/src/app/new/(components)/AmmForm.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { Address } from "@bleu/utils"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { brownDark } from "@radix-ui/colors"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { FieldValues, useForm, UseFormReturn } from "react-hook-form"; -import { formatUnits, parseUnits } from "viem"; - -import { Button } from "#/components"; -import { Input } from "#/components/Input"; -import { SelectInput } from "#/components/SelectInput"; -import { Spinner } from "#/components/Spinner"; -import { TokenSelect } from "#/components/TokenSelect"; -import { Tooltip } from "#/components/Tooltip"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "#/components/ui/accordion"; -import { Form, FormMessage } from "#/components/ui/form"; -import { Label } from "#/components/ui/label"; -import { useFallbackState } from "#/hooks/useFallbackState"; -import { useRawTxData } from "#/hooks/useRawTxData"; -import { fetchTokenUsdPrice } from "#/lib/fetchTokenUsdPrice"; -import { ammFormSchema } from "#/lib/schema"; -import { buildTxAMMArgs, TRANSACTION_TYPES } from "#/lib/transactionFactory"; -import { FALLBACK_STATES, IToken, PRICE_ORACLES } from "#/lib/types"; -import { cn } from "#/lib/utils"; -import { ChainId } from "#/utils/chainsPublicClients"; - -import { BalancerWeightedForm } from "./BalancerWeightedForm"; -import { ChainlinkForm } from "./ChainlinkForm"; -import { CustomOracleForm } from "./CustomOracleForm"; -import { FallbackAndDomainWarning } from "./FallbackAndDomainWarning"; -import { SushiForm } from "./SushiForm"; -import { UniswapV2Form } from "./UniswapV2Form"; - -const getNewMinTradeToken0 = async (newToken0: IToken, chainId: ChainId) => { - return fetchTokenUsdPrice({ - tokenAddress: newToken0.address as Address, - tokenDecimals: newToken0.decimals, - chainId, - }) - .then((price) => 10 / price) - .then((amount) => - // Format and parse to round on the right number of decimals - Number( - formatUnits( - parseUnits(String(amount), newToken0.decimals), - newToken0.decimals, - ), - ), - ) - .catch(() => 0); -}; - -export function AmmForm({ - transactionType, - defaultValues, -}: { - transactionType: - | TRANSACTION_TYPES.CREATE_COW_AMM - | TRANSACTION_TYPES.EDIT_COW_AMM; - defaultValues?: FieldValues; -}) { - const { - safe: { safeAddress, chainId }, - } = useSafeAppsSDK(); - const router = useRouter(); - - const form = useForm({ - resolver: zodResolver(ammFormSchema), - defaultValues: { - ...defaultValues, - safeAddress, - chainId, - }, - }); - - const { - setValue, - watch, - formState: { errors, isSubmitting }, - } = form; - const { fallbackState, domainSeparator } = useFallbackState(); - const { sendTransactions } = useRawTxData(); - const [confirmedFallbackSetup, setConfirmedFallbackSetup] = useState(false); - - const formData = watch(); - - const onSubmit = async (data: typeof ammFormSchema._type) => { - await buildTxAMMArgs({ data, transactionType }) - .then((txArgs) => { - sendTransactions(txArgs); - }) - .catch((e: Error) => { - // eslint-disable-next-line no-console - console.error(e); - }) - .then(() => { - router.push("/createtxprocessing"); - }); - }; - - useEffect(() => { - if (fallbackState && domainSeparator) { - setValue("domainSeparator", domainSeparator); - setValue("fallbackSetupState", fallbackState); - } - }, [fallbackState, setValue]); - - useEffect(() => { - setValue("safeAddress", safeAddress); - }, [safeAddress, setValue]); - - if (!fallbackState || !domainSeparator) { - return ; - } - - return ( -
-
-
-
- Token Pair - { - setValue("token0", { - decimals: token.decimals, - address: token.address, - symbol: token.symbol, - }); - setValue( - "minTradedToken0", - await getNewMinTradeToken0(token, chainId as ChainId), - ); - }} - selectedToken={formData?.token0 ?? undefined} - /> - {errors.token0 && ( - - {errors.token0.message} - - )} -
-
-
-
- - Select pair - - { - setValue("token1", { - decimals: token.decimals, - address: token.address, - symbol: token.symbol, - }); - }} - selectedToken={formData?.token1 ?? undefined} - /> - {errors.token1 && ( - - {errors.token1.message} - - )} -
-
-
- - - - - Advanced Options - - - - - - - - {fallbackState !== FALLBACK_STATES.HAS_DOMAIN_VERIFIER && ( - - )} -
- -
- - ); -} - -function PriceOracleFields({ - form, -}: { - form: UseFormReturn; -}) { - const { - setValue, - formState: { errors }, - watch, - } = form; - - const priceOracle = watch("priceOracle"); - - return ( -
-
-
-
- - - - -
- ({ - id: value, - value, - }))} - onValueChange={(priceOracle) => { - setValue("priceOracle", priceOracle as PRICE_ORACLES); - }} - placeholder={priceOracle} - /> - {errors.priceOracle && ( - -

- {errors.priceOracle.message as string} -

-
- )} -
-
- - {priceOracle === PRICE_ORACLES.BALANCER && ( - - )} - {priceOracle === PRICE_ORACLES.UNI && } - {priceOracle === PRICE_ORACLES.CUSTOM && } - {priceOracle === PRICE_ORACLES.SUSHI && } - {priceOracle === PRICE_ORACLES.CHAINLINK && } -
- ); -} diff --git a/apps/cow-amm-deployer/src/app/new/(components)/FallbackAndDomainWarning.tsx b/apps/cow-amm-deployer/src/app/new/(components)/FallbackAndDomainWarning.tsx deleted file mode 100644 index f71295e7c..000000000 --- a/apps/cow-amm-deployer/src/app/new/(components)/FallbackAndDomainWarning.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { InfoCircledIcon } from "@radix-ui/react-icons"; - -import { AlertCard } from "#/components/AlertCard"; -import { Checkbox } from "#/components/Checkbox"; -import { Tooltip } from "#/components/Tooltip"; - -export function FallbackAndDomainWarning({ - confirmedFallbackSetup, - setConfirmedFallbackSetup, -}: { - confirmedFallbackSetup: boolean; - setConfirmedFallbackSetup: (value: boolean) => void; -}) { - return ( - -
- setConfirmedFallbackSetup(!confirmedFallbackSetup)} - label="Approve fallback and domain verifier setup" - /> - - - - - -
-
- ); -} diff --git a/apps/cow-amm-deployer/src/app/new/(components)/FormWrapper.tsx b/apps/cow-amm-deployer/src/app/new/(components)/FormWrapper.tsx deleted file mode 100644 index b96e634a9..000000000 --- a/apps/cow-amm-deployer/src/app/new/(components)/FormWrapper.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ArrowLeftIcon } from "@radix-ui/react-icons"; -import { FieldValues } from "react-hook-form"; - -import { LinkComponent } from "#/components/Link"; -import { TRANSACTION_TYPES } from "#/lib/transactionFactory"; - -import { AmmForm } from "./AmmForm"; - -function ArrowIcon() { - return ( - - ); -} - -export function FormWrapper({ - transactionType, - defaultValues, -}: { - transactionType: - | TRANSACTION_TYPES.CREATE_COW_AMM - | TRANSACTION_TYPES.EDIT_COW_AMM; - defaultValues?: FieldValues; -}) { - const backHref = - transactionType === TRANSACTION_TYPES.CREATE_COW_AMM ? "/" : "/manager"; - return ( -
-
-
- - -
- } - /> -
-
- {transactionType === TRANSACTION_TYPES.CREATE_COW_AMM - ? "Create" - : "Edit"}{" "} - AMM -
-
-
-
- -
-
- - ); -} diff --git a/apps/cow-amm-deployer/src/app/new/layout.tsx b/apps/cow-amm-deployer/src/app/new/layout.tsx deleted file mode 100644 index c1492b79b..000000000 --- a/apps/cow-amm-deployer/src/app/new/layout.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Layout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/apps/cow-amm-deployer/src/app/new/page.tsx b/apps/cow-amm-deployer/src/app/new/page.tsx deleted file mode 100644 index 8b3d7bc26..000000000 --- a/apps/cow-amm-deployer/src/app/new/page.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { useState } from "react"; -import { FieldValues } from "react-hook-form"; -import { formatUnits } from "viem"; - -import { Button } from "#/components/Button"; -import { Spinner } from "#/components/Spinner"; -import WalletNotConnected from "#/components/WalletNotConnected"; -import { useRunningAMM } from "#/hooks/useRunningAmmInfo"; -import { TRANSACTION_TYPES } from "#/lib/transactionFactory"; -import { ICowAmm } from "#/lib/types"; -import { supportedChainIds } from "#/utils/chainsPublicClients"; - -import { UnsuportedChain } from "../../components/UnsuportedChain"; -import { FormWrapper } from "./(components)/FormWrapper"; - -function cowAmmToFormValues(cowAmm: ICowAmm): FieldValues { - return { - token0: cowAmm.token0.tokenInfo, - token1: cowAmm.token1.tokenInfo, - minTradedToken0: formatUnits( - BigInt(cowAmm.minTradedToken0), - cowAmm.token0.tokenInfo.decimals, - ), - priceOracle: cowAmm.priceOracle, - balancerPoolId: cowAmm.priceOracleData.balancerPoolId, - uniswapV2Pair: cowAmm.priceOracleData.uniswapV2PairAddress, - sushiSwapPair: cowAmm.priceOracleData.sushiSwapPairAddress, - chainlinkPriceFeed0: cowAmm.priceOracleData.chainlinkPriceFeed0, - chainlinkPriceFeed1: cowAmm.priceOracleData.chainlinkPriceFeed1, - chainlinkTimeThresholdInHours: - cowAmm.priceOracleData.chainlinkTimeThresholdInHours, - customPriceOracleAddress: cowAmm.priceOracleData.customPriceOracleAddress, - customPriceOracleData: cowAmm.priceOracleData.customPriceOracleData, - }; -} - -export default function Page() { - const { safe, connected } = useSafeAppsSDK(); - const { loaded, isAmmRunning, cowAmm } = useRunningAMM(); - const [goToForm, setGoToForm] = useState(false); - - if (!connected) { - return ; - } - - if (!loaded) { - return ; - } - - const transactionType = isAmmRunning - ? TRANSACTION_TYPES.EDIT_COW_AMM - : TRANSACTION_TYPES.CREATE_COW_AMM; - - const defaultValues = - isAmmRunning && cowAmm ? cowAmmToFormValues(cowAmm) : undefined; - - if (!supportedChainIds.includes(safe.chainId)) { - return ; - } - - if (!goToForm && !isAmmRunning) { - return ( -
-
-

- Attention, deploying a CoW AMM - Liquidity pool requires a safe wallet. Keep in mind, that the{" "} - - Safe used for deploying liquidity should only be used for this - purpose - - , as the tokens held in the safe will be used for the pool creation. - CoW AMM utilizes all available token balances on the Safe for the - token pairs that you have created a liquidity pool, thus,{" "} - - disabling other functionalities a Safe might be used for. - -

- -
-
- ); - } - - return ( - - ); -} diff --git a/apps/cow-amm-deployer/src/app/page.tsx b/apps/cow-amm-deployer/src/app/page.tsx index 06a82cb89..09fbb24bf 100644 --- a/apps/cow-amm-deployer/src/app/page.tsx +++ b/apps/cow-amm-deployer/src/app/page.tsx @@ -1,29 +1,37 @@ "use client"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; +import { useAccount } from "wagmi"; -import { HomeWrapper } from "#/components/HomeWrapper"; +import { HomePageWrapper } from "#/components/HomePageWrapper"; import { Spinner } from "#/components/Spinner"; import { UnsuportedChain } from "#/components/UnsuportedChain"; import WalletNotConnected from "#/components/WalletNotConnected"; -import { useRunningAMM } from "#/hooks/useRunningAmmInfo"; -import { supportedChainIds } from "#/utils/chainsPublicClients"; +import { useAutoConnect } from "#/hooks/tx-manager/useAutoConnect"; +import { ChainId, supportedChainIds } from "#/utils/chainsPublicClients"; export default function Page() { - const { connected, safe } = useSafeAppsSDK(); - const { isAmmRunning, loaded } = useRunningAMM(); + const { + address: safeAddress, + chainId, + isConnected, + isConnecting, + } = useAccount(); - if (!loaded) { + useAutoConnect(); + + if (isConnecting) { return ; } - if (!connected) { + if (!isConnected) { return ; } - if (!supportedChainIds.includes(safe.chainId)) { + if (chainId && !supportedChainIds.includes(chainId as ChainId)) { return ; } - return ; + const userId = `${safeAddress}-${chainId}`; + + return ; } diff --git a/apps/cow-amm-deployer/src/app/stoptxprocessing/layout.tsx b/apps/cow-amm-deployer/src/app/stoptxprocessing/layout.tsx deleted file mode 100644 index c1492b79b..000000000 --- a/apps/cow-amm-deployer/src/app/stoptxprocessing/layout.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Layout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/apps/cow-amm-deployer/src/app/stoptxprocessing/page.tsx b/apps/cow-amm-deployer/src/app/stoptxprocessing/page.tsx deleted file mode 100644 index 08d667718..000000000 --- a/apps/cow-amm-deployer/src/app/stoptxprocessing/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; -import { Address } from "viem"; - -import { Spinner } from "#/components/Spinner"; -import { checkIsAmmRunning, fetchLastAmmInfo } from "#/hooks/useRunningAmmInfo"; -import { ChainId } from "#/utils/chainsPublicClients"; - -export default function Page() { - const { - safe: { safeAddress, chainId }, - } = useSafeAppsSDK(); - const router = useRouter(); - - async function redirectToHomeIfAmmIsNotRunning() { - const isAmmRunning = await fetchLastAmmInfo({ - chainId: chainId as ChainId, - safeAddress: safeAddress as Address, - }).then((data) => { - return checkIsAmmRunning( - chainId as ChainId, - safeAddress as Address, - data.hash, - ); - }); - if (!isAmmRunning) { - router.push("/"); - } - } - useEffect(() => { - const intervalId = setInterval(redirectToHomeIfAmmIsNotRunning, 1000); - return () => clearInterval(intervalId); - }, []); - - return ( -
-
- The transaction is being processed -
- -
- ); -} diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/cowswap-explorer-logo.png b/apps/cow-amm-deployer/src/assets/cow-swap/cowswap-explorer-logo.png new file mode 100644 index 000000000..bc2b0676f Binary files /dev/null and b/apps/cow-amm-deployer/src/assets/cow-swap/cowswap-explorer-logo.png differ diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-blue.svg b/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-blue.svg new file mode 100644 index 000000000..53bac5190 --- /dev/null +++ b/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-white.svg b/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-white.svg new file mode 100644 index 000000000..a9f842947 --- /dev/null +++ b/apps/cow-amm-deployer/src/assets/cow-swap/network-arbitrum-one-logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/network-gnosis-chain-logo.svg b/apps/cow-amm-deployer/src/assets/cow-swap/network-gnosis-chain-logo.svg new file mode 100644 index 000000000..231e85ff2 --- /dev/null +++ b/apps/cow-amm-deployer/src/assets/cow-swap/network-gnosis-chain-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/network-mainnet-logo.svg b/apps/cow-amm-deployer/src/assets/cow-swap/network-mainnet-logo.svg new file mode 100644 index 000000000..507f41089 --- /dev/null +++ b/apps/cow-amm-deployer/src/assets/cow-swap/network-mainnet-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/cow-amm-deployer/src/assets/cow-swap/network-sepolia-logo.svg b/apps/cow-amm-deployer/src/assets/cow-swap/network-sepolia-logo.svg new file mode 100644 index 000000000..c6bd42dd1 --- /dev/null +++ b/apps/cow-amm-deployer/src/assets/cow-swap/network-sepolia-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/cow-amm-deployer/src/components/AddressLabel.tsx b/apps/cow-amm-deployer/src/components/AddressLabel.tsx new file mode 100644 index 000000000..af3e90a73 --- /dev/null +++ b/apps/cow-amm-deployer/src/components/AddressLabel.tsx @@ -0,0 +1,55 @@ +import { Address } from "viem"; + +import { truncateMiddle } from "#/lib/truncateMiddle"; + +import { LinkComponent } from "./Link"; + +export function TruncateMiddle({ + text, + length = 5, +}: { + text?: string; + length?: number; +}) { + if (!text) return ""; + if (text?.length > length * 2 + 1) { + return truncateMiddle(text, length); + } + + return text; +} + +export function AddressLabel({ + address, + label, + truncate = true, +}: { + address: Address; + label: string; + truncate?: boolean; +}) { + return ( +
+ {label} + + {truncate ? : address} + +
+ ); +} + +export function AddressLabelLink({ + address, + label, + href, +}: { + address: Address; + label: string; + href: string; +}) { + return ( + + + + ); +} diff --git a/apps/cow-amm-deployer/src/components/AlertCard.tsx b/apps/cow-amm-deployer/src/components/AlertCard.tsx index a3c0e3c26..7d484feeb 100644 --- a/apps/cow-amm-deployer/src/components/AlertCard.tsx +++ b/apps/cow-amm-deployer/src/components/AlertCard.tsx @@ -1,4 +1,3 @@ -import { capitalize } from "@bleu/utils"; import cn from "clsx"; export function AlertCard({ @@ -18,11 +17,9 @@ export function AlertCard({ style === "error" ? "bg-destructive" : "bg-accent", )} > - {capitalize(style)}: {title} + {title} -
+
{children}
diff --git a/apps/cow-amm-deployer/src/app/new/(components)/BalancerWeightedForm.tsx b/apps/cow-amm-deployer/src/components/BalancerWeightedForm.tsx similarity index 75% rename from apps/cow-amm-deployer/src/app/new/(components)/BalancerWeightedForm.tsx rename to apps/cow-amm-deployer/src/components/BalancerWeightedForm.tsx index 0fb71ff0b..ccae1d961 100644 --- a/apps/cow-amm-deployer/src/app/new/(components)/BalancerWeightedForm.tsx +++ b/apps/cow-amm-deployer/src/components/BalancerWeightedForm.tsx @@ -1,27 +1,30 @@ "use client"; import { toast } from "@bleu/ui"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { UseFormReturn } from "react-hook-form"; +import { UseFormReturn, useWatch } from "react-hook-form"; import { Address } from "viem"; +import { useAccount } from "wagmi"; +import { z } from "zod"; import { Input } from "#/components/Input"; import { pools } from "#/lib/gqlBalancer"; -import { ammFormSchema } from "#/lib/schema"; +import { ammEditSchema, ammFormSchema } from "#/lib/schema"; import { loadDEXPriceCheckerErrorText } from "#/lib/utils"; export function BalancerWeightedForm({ form, }: { - form: UseFormReturn; + form: UseFormReturn< + z.input | z.input + >; }) { - const { register, setValue, watch } = form; - const { - safe: { chainId }, - } = useSafeAppsSDK(); + const { register, setValue, control } = form; + const { chainId } = useAccount(); + + if (!chainId) return null; + + const [token0, token1] = useWatch({ control, name: ["token0", "token1"] }); - const token0 = watch("token0"); - const token1 = watch("token1"); const tokenAddresses = [token0?.address, token1?.address].filter( (address) => address, ) as Address[]; @@ -29,7 +32,7 @@ export function BalancerWeightedForm({
+ +
+ } + > + ); +} diff --git a/apps/cow-amm-deployer/src/app/new/(components)/CustomOracleForm.tsx b/apps/cow-amm-deployer/src/components/CustomOracleForm.tsx similarity index 75% rename from apps/cow-amm-deployer/src/app/new/(components)/CustomOracleForm.tsx rename to apps/cow-amm-deployer/src/components/CustomOracleForm.tsx index 639b1af92..06c364c05 100644 --- a/apps/cow-amm-deployer/src/app/new/(components)/CustomOracleForm.tsx +++ b/apps/cow-amm-deployer/src/components/CustomOracleForm.tsx @@ -1,13 +1,16 @@ import { UseFormReturn } from "react-hook-form"; +import { z } from "zod"; import { AlertCard } from "#/components/AlertCard"; import { Input } from "#/components/Input"; -import { ammFormSchema } from "#/lib/schema"; +import { ammEditSchema, ammFormSchema } from "#/lib/schema"; export function CustomOracleForm({ form, }: { - form: UseFormReturn; + form: UseFormReturn< + z.input | z.input + >; }) { const { register } = form; return ( @@ -20,12 +23,12 @@ export function CustomOracleForm({ diff --git a/apps/cow-amm-deployer/src/components/DepositForm.tsx b/apps/cow-amm-deployer/src/components/DepositForm.tsx new file mode 100644 index 000000000..f7213b760 --- /dev/null +++ b/apps/cow-amm-deployer/src/components/DepositForm.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { formatNumber } from "@bleu/ui"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm, useWatch } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "#/components"; +import { TokenInfo } from "#/components/TokenInfo"; +import { Form, FormMessage } from "#/components/ui/form"; +import { useAmmData } from "#/contexts/ammDataContext"; +import { useTransactionManagerContext } from "#/contexts/transactionManagerContext"; +import { useDebounce } from "#/hooks/useDebounce"; +import { + PRICE_IMPACT_THRESHOLD, + USD_VALUE_FOR_PRICE_IMPACT_WARNING, +} from "#/lib/constants"; +import { ICowAmm } from "#/lib/fetchAmmData"; +import { calculatePriceImpact } from "#/lib/priceImpact"; +import { getDepositSchema } from "#/lib/schema"; +import { buildDepositAmmArgs } from "#/lib/transactionFactory"; + +import { AlertCard } from "./AlertCard"; +import { TokenAmountInput } from "./TokenAmountInput"; + +export function DepositForm({ + ammData, + walletBalanceToken0, + walletBalanceToken1, +}: { + ammData: ICowAmm; + walletBalanceToken0: string; + walletBalanceToken1: string; +}) { + const { isAmmUpdating } = useAmmData(); + const schema = getDepositSchema( + Number(walletBalanceToken0), + Number(walletBalanceToken1), + ); + + const form = useForm>({ + // @ts-ignore + resolver: zodResolver(schema), + }); + + const { + formState: { errors, isSubmitting }, + control, + } = form; + + const { + managedTransaction: { + writeContract, + writeContractWithSafe, + isWalletContract, + }, + } = useTransactionManagerContext(); + + const [amount0, amount1] = useWatch({ + control, + name: ["amount0", "amount1"], + }); + const depositUsdValue = + ammData.token0.usdPrice * amount0 + ammData.token1.usdPrice * amount1; + + const priceImpact = calculatePriceImpact({ + balance0: Number(ammData.token0.balance), + balance1: Number(ammData.token1.balance), + amount0: Number(amount0), + amount1: Number(amount1), + }); + + const debouncedPriceImpact = useDebounce(priceImpact, 300); + const debouncedDepositUsdValue = useDebounce(depositUsdValue, 300); + + const onSubmit = async (data: z.output) => { + const txArgs = buildDepositAmmArgs({ + cowAmm: ammData, + amount0: data.amount0, + amount1: data.amount1, + }); + + if (isWalletContract) { + writeContractWithSafe(txArgs); + } else { + // TODO: remove this once we add EOA support + // @ts-ignore + writeContract(txArgs); + } + }; + + return ( + // @ts-ignore +
+
+
+ +
+ +
+
+
+ +
+ +
+ { + // @ts-ignore + errors?.bothAmountsAreZero && ( + +

+ { + // @ts-ignore + errors.bothAmountsAreZero.message as string + } +

+
+ ) + } + {debouncedDepositUsdValue > USD_VALUE_FOR_PRICE_IMPACT_WARNING && + debouncedPriceImpact > PRICE_IMPACT_THRESHOLD && ( + +

+ The price impact of this deposit is{" "} + {formatNumber(debouncedPriceImpact * 100, 2)}%. Deposits with high + price impact may result in lost funds. +

+
+ )} + + +
+ ); +} diff --git a/apps/cow-amm-deployer/src/components/Dialog.tsx b/apps/cow-amm-deployer/src/components/Dialog.tsx index 06bb3e874..0774ab606 100644 --- a/apps/cow-amm-deployer/src/components/Dialog.tsx +++ b/apps/cow-amm-deployer/src/components/Dialog.tsx @@ -60,7 +60,7 @@ export function Dialog({ + +
+
{formTitle}
+
+ +
+ {children} +
+ + + ); +} diff --git a/apps/cow-amm-deployer/src/components/Header.tsx b/apps/cow-amm-deployer/src/components/Header.tsx index 194cebe7a..44998ef1c 100644 --- a/apps/cow-amm-deployer/src/components/Header.tsx +++ b/apps/cow-amm-deployer/src/components/Header.tsx @@ -1,15 +1,15 @@ "use client"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; import { ArrowTopRightIcon } from "@radix-ui/react-icons"; import Image from "next/image"; import Link from "next/link"; import { ReactNode } from "react"; import { Address } from "viem"; +import { useAccount } from "wagmi"; -import { buildAccountCowExplorerUrl } from "#/lib/cowExplorer"; +import { TruncateMiddle } from "#/components/AddressLabel"; +import { getExplorerAddressLink } from "#/lib/cowExplorer"; import { ChainId } from "#/utils/chainsPublicClients"; -import { truncateAddress } from "#/utils/truncate"; interface IHeader { linkUrl: string; @@ -19,7 +19,8 @@ interface IHeader { } export function Header({ linkUrl, imageSrc, children, onLinkClick }: IHeader) { - const { safe } = useSafeAppsSDK(); + // TODO: rename this once we use EOAs + const { address: safeAddress, chainId } = useAccount(); return (
@@ -40,16 +41,17 @@ export function Header({ linkUrl, imageSrc, children, onLinkClick }: IHeader) { className="hover:text-highlight inline-flex items-center gap-1" href={ new URL( - buildAccountCowExplorerUrl({ - chainId: safe.chainId as ChainId, - address: safe.safeAddress as Address, - }), + getExplorerAddressLink( + chainId as ChainId, + safeAddress as Address, + ), ) } rel="noreferrer noopener" target="_blank" > - {truncateAddress(safe.safeAddress)} + +
diff --git a/apps/cow-amm-deployer/src/components/HomeWrapper.tsx b/apps/cow-amm-deployer/src/components/HomePageWrapper.tsx similarity index 56% rename from apps/cow-amm-deployer/src/components/HomeWrapper.tsx rename to apps/cow-amm-deployer/src/components/HomePageWrapper.tsx index e1a28d973..006958da1 100644 --- a/apps/cow-amm-deployer/src/components/HomeWrapper.tsx +++ b/apps/cow-amm-deployer/src/components/HomePageWrapper.tsx @@ -1,25 +1,16 @@ -"use client"; - import Image from "next/image"; import { Button } from "./Button"; import Fathom from "./Fathom"; import { LinkComponent } from "./Link"; -export function HomeWrapper({ - isAmmRunning, - goToSafe = false, +export function HomePageWrapper({ + disableButton = false, + userId, }: { - isAmmRunning: boolean; - goToSafe?: boolean; + disableButton?: boolean; + userId?: string; }) { - const href = isAmmRunning ? "/manager" : "/new"; - const title = goToSafe - ? "Open App in Safe" - : isAmmRunning - ? "Manage your CoW AMM" - : "Create a CoW AMM"; - return (
@@ -38,22 +29,18 @@ export function HomeWrapper({ supplies liquidity for trades made on CoW Protocol. Solvers compete with each other for the right to trade against the AMM - - {title} - - } - /> + + +
diff --git a/apps/cow-amm-deployer/src/components/Input.tsx b/apps/cow-amm-deployer/src/components/Input.tsx index 9f94bc1fe..50ec86974 100644 --- a/apps/cow-amm-deployer/src/components/Input.tsx +++ b/apps/cow-amm-deployer/src/components/Input.tsx @@ -46,22 +46,28 @@ export const Input = React.forwardRef( return (
- + )} setIsLoading(true), - children: isLoading ? : content.props.children, + const ClonedElement = React.cloneElement(children, { + children: isLoading ? : children.props.children, }); + if (children.props.disabled) { + return
{ClonedElement}
; + } + return ( - + { + setIsLoading(true); + }} + > {ClonedElement} ); diff --git a/apps/cow-amm-deployer/src/components/OldVersionOfAmmAlert.tsx b/apps/cow-amm-deployer/src/components/OldVersionOfAmmAlert.tsx new file mode 100644 index 000000000..569d00744 --- /dev/null +++ b/apps/cow-amm-deployer/src/components/OldVersionOfAmmAlert.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Address } from "viem"; + +import { Button } from "#/components"; +import { AlertCard } from "#/components/AlertCard"; +import { useTransactionManagerContext } from "#/contexts/transactionManagerContext"; +import { ICowAmm } from "#/lib/fetchAmmData"; +import { getAmmId } from "#/lib/getAmmId"; +import { buildMigrateToStandaloneVersionArgs } from "#/lib/transactionFactory"; +import { ChainId } from "#/utils/chainsPublicClients"; + +import { CreateSuccessDialog } from "./CreateSuccessDialog"; + +export function OldVersionOfAMMAlert({ ammData }: { ammData: ICowAmm }) { + const [ammId, setAmmId] = useState(); + const [ammDialogOpen, setAmmDialogOpen] = useState(false); + + const { + managedTransaction: { writeContractWithSafe, status }, + } = useTransactionManagerContext(); + async function updateAmmId() { + const ammId = await getAmmId({ + chainId: ammData.chainId as ChainId, + userAddress: ammData.user.address as Address, + token0Address: ammData.token0.address as Address, + token1Address: ammData.token1.address as Address, + }); + setAmmId(ammId); + } + + useEffect(() => { + updateAmmId(); + }, []); + + useEffect(() => { + if (status === "final" || status === "confirmed") { + setAmmDialogOpen(true); + } + }, [status]); + + return ( + <> + +
+ +

+ A old version of the CoW AMM was detected. There is more gas + efficient version of the CoW AMM available. Migrate the funds of + this AMM to the new version clicking on the button bellow. +

+ +
+
+ + ); +} diff --git a/apps/cow-amm-deployer/src/components/PriceOracleForm.tsx b/apps/cow-amm-deployer/src/components/PriceOracleForm.tsx new file mode 100644 index 000000000..b7eac0243 --- /dev/null +++ b/apps/cow-amm-deployer/src/components/PriceOracleForm.tsx @@ -0,0 +1,84 @@ +import { brownDark } from "@radix-ui/colors"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; +import { UseFormReturn, useWatch } from "react-hook-form"; +import { z } from "zod"; + +import { ammEditSchema, ammFormSchema } from "#/lib/schema"; +import { PRICE_ORACLES, PriceOraclesValue } from "#/lib/types"; + +import { BalancerWeightedForm } from "./BalancerWeightedForm"; +import { ChainlinkForm } from "./ChainlinkForm"; +import { CustomOracleForm } from "./CustomOracleForm"; +import { SelectInput } from "./SelectInput"; +import { SushiForm } from "./SushiForm"; +import { Tooltip } from "./Tooltip"; +import { FormMessage } from "./ui/form"; +import { Label } from "./ui/label"; +import { UniswapV2Form } from "./UniswapV2Form"; + +export function PriceOracleForm({ + form, +}: { + form: UseFormReturn< + z.input | z.input + >; +}) { + const { + setValue, + formState: { errors }, + control, + } = form; + + const priceOracle = useWatch({ + control, + name: "priceOracleSchema.priceOracle", + }); + + return ( +
+
+
+
+ + + + +
+ ({ + id: value, + value, + }))} + onValueChange={(priceOracle) => { + setValue( + "priceOracleSchema.priceOracle", + priceOracle as PriceOraclesValue, + ); + }} + placeholder={priceOracle} + /> + {errors.priceOracleSchema?.priceOracle && ( + +

+ {errors.priceOracleSchema?.priceOracle?.message as string} +

+
+ )} +
+
+ + {priceOracle === PRICE_ORACLES.BALANCER && ( + + )} + {priceOracle === PRICE_ORACLES.UNI && } + {priceOracle === PRICE_ORACLES.CUSTOM && } + {priceOracle === PRICE_ORACLES.SUSHI && } + {priceOracle === PRICE_ORACLES.CHAINLINK && } +
+ ); +} diff --git a/apps/cow-amm-deployer/src/components/RootLayout.tsx b/apps/cow-amm-deployer/src/components/RootLayout.tsx index 49d640ad6..6ed6e54b8 100644 --- a/apps/cow-amm-deployer/src/components/RootLayout.tsx +++ b/apps/cow-amm-deployer/src/components/RootLayout.tsx @@ -2,27 +2,46 @@ import { Toaster } from "@bleu/ui"; import SafeProvider from "@gnosis.pm/safe-apps-react-sdk"; -import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { useState } from "react"; +import { WagmiProvider } from "wagmi"; + +import { TokenSelectContextProvider } from "#/contexts/tokenSelectContext"; +import { TransactionManagerContextProvider } from "#/contexts/transactionManagerContext"; +import { config } from "#/wagmi"; import Fathom from "./Fathom"; import { Footer } from "./Footer"; import { Header } from "./Header"; -import { HomeWrapper } from "./HomeWrapper"; +import { HomePageWrapper } from "./HomePageWrapper"; export function RootLayout({ children }: React.PropsWithChildren) { + const [queryClient] = useState(() => new QueryClient()); + return ( - }> - -
-
-
-
- {children} -
-
- -
-
+ }> + + + + + +
+
+
+
+ {children} + +
+
+
+
+
+
+
+
); } diff --git a/apps/cow-amm-deployer/src/components/Spinner.tsx b/apps/cow-amm-deployer/src/components/Spinner.tsx index b60d14e74..7277b7973 100644 --- a/apps/cow-amm-deployer/src/components/Spinner.tsx +++ b/apps/cow-amm-deployer/src/components/Spinner.tsx @@ -10,7 +10,7 @@ export function Spinner({ size = "md" }: { size?: keyof typeof SpinnerSize }) { return (
+ {disabled ? "Paused" : "Active"} + + ); +} diff --git a/apps/cow-amm-deployer/src/app/new/(components)/SushiForm.tsx b/apps/cow-amm-deployer/src/components/SushiForm.tsx similarity index 74% rename from apps/cow-amm-deployer/src/app/new/(components)/SushiForm.tsx rename to apps/cow-amm-deployer/src/components/SushiForm.tsx index c068b0b0a..5fd84b3fd 100644 --- a/apps/cow-amm-deployer/src/app/new/(components)/SushiForm.tsx +++ b/apps/cow-amm-deployer/src/components/SushiForm.tsx @@ -1,25 +1,27 @@ import { toast } from "@bleu/ui"; -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { UseFormReturn } from "react-hook-form"; +import { UseFormReturn, useWatch } from "react-hook-form"; import { Address } from "viem"; +import { useAccount } from "wagmi"; +import { z } from "zod"; import { Input } from "#/components/Input"; import { pairs } from "#/lib/gqlSushi"; -import { ammFormSchema } from "#/lib/schema"; +import { ammEditSchema, ammFormSchema } from "#/lib/schema"; import { loadDEXPriceCheckerErrorText } from "#/lib/utils"; export function SushiForm({ form, }: { - form: UseFormReturn; + form: UseFormReturn< + z.input | z.input + >; }) { - const { register, setValue, watch } = form; - const { - safe: { chainId }, - } = useSafeAppsSDK(); + const { register, setValue, control } = form; + const { chainId } = useAccount(); + if (!chainId) return null; + + const [token0, token1] = useWatch({ control, name: ["token0", "token1"] }); - const token0 = watch("token0"); - const token1 = watch("token1"); const tokenAddresses = [token0?.address, token1?.address].filter( (address) => address, ) as Address[]; @@ -27,7 +29,7 @@ export function SushiForm({