diff --git a/.github/workflows/clean-up-pr-caches.yml b/.github/workflows/clean-up-pr.yml
similarity index 79%
rename from .github/workflows/clean-up-pr-caches.yml
rename to .github/workflows/clean-up-pr.yml
index b31bacc14a..003bb35714 100644
--- a/.github/workflows/clean-up-pr-caches.yml
+++ b/.github/workflows/clean-up-pr.yml
@@ -13,9 +13,6 @@ jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- - name: Check out code
- uses: actions/checkout@v4
-
- name: Cleanup caches
run: |
gh extension install actions/gh-actions-cache
@@ -36,3 +33,10 @@ jobs:
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Delete closed PR branch
+ uses: dawidd6/action-delete-branch@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ numbers: ${{github.event.pull_request.number}}
+ soft_fail: true
diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml
index d36e8d6c6b..d07e7e555c 100644
--- a/.github/workflows/close-stale.yml
+++ b/.github/workflows/close-stale.yml
@@ -2,16 +2,20 @@ name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
+ workflow_dispatch:
+ push:
+ paths:
+ - '.github/workflows/close-stale.yml'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v4
+ - uses: actions/stale@v8
with:
stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
exempt-pr-labels: exempt-stale
days-before-issue-stale: 999
days-before-pr-stale: 14
days-before-close: 5
- repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/fail-on-needs-generate.yaml b/.github/workflows/fail-on-needs-generate.yaml
index 25f42e7838..a66264a3f4 100644
--- a/.github/workflows/fail-on-needs-generate.yaml
+++ b/.github/workflows/fail-on-needs-generate.yaml
@@ -10,7 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- name: Fail on needs-go-generate
run: echo Failed, needs go generate. && exit 1
check-generate-yarn:
@@ -18,6 +17,5 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- name: Fail on needs-go-yarn
run: echo Failed, needs yarn install. && exit 1
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 02c138595e..5252a0f98d 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -23,29 +23,29 @@ jobs:
name: Change Detection
runs-on: ubuntu-latest
outputs:
- # Expose matched filters as job 'packages' output variable
- packages_deps: ${{ steps.filter_go_deps.outputs.changed_modules }}
- packages_nodeps: ${{ steps.filter_go_nodeps.outputs.changed_modules }}
+ # Expose matched filters as job 'packages' output variable with a trailing _deps for dependencies
+ packages_deps: ${{ steps.filter_go.outputs.changed_modules_deps }}
package_count_deps: ${{ steps.length.outputs.FILTER_LENGTH_DEPS }}
+
+ unchanged_deps: ${{ steps.filter_go.outputs.unchanged_modules_deps }}
+ unchanged_package_count_deps: ${{ steps.length.outputs.FILTER_LENGTH_UNCHANGED_DEPS }}
+
+ # These have no dependencies included in the change outputs
+ packages_nodeps: ${{ steps.filter_go_nodeps.outputs.changed_modules }}
package_count_nodeps: ${{ steps.length.outputs.FILTER_LENGTH_NODEPS }}
solidity_changes: ${{ steps.filter_solidity.outputs.any_changed }}
steps:
- uses: actions/checkout@v4
with:
+ # TODO: this can be shorter, maybe 500-1,000? This may be slower (see: https://github.com/CocoaPods/CocoaPods/issues/4989#issuecomment-193772935)
fetch-depth: 0
+ # TODO: is this neccesary?
submodules: 'recursive'
+ # note: after this action is pushed, whatever this ends up being should go back to latest.
- uses: docker://ghcr.io/synapsecns/sanguine/git-changes-action:latest
- id: filter_go_deps
- with:
- include_deps: true
- github_token: ${{ secrets.WORKFLOW_PAT }}
- timeout: '10m'
-
- - uses: docker://ghcr.io/synapsecns/sanguine/git-changes-action:latest
- id: filter_go_nodeps
+ id: filter_go
with:
- include_deps: false
github_token: ${{ secrets.WORKFLOW_PAT }}
timeout: '10m'
@@ -65,11 +65,15 @@ jobs:
export FILTER_LENGTH_DEPS=$(echo $FILTERED_PATHS_DEPS | jq '. | length')
echo "##[set-output name=FILTER_LENGTH_DEPS;]$(echo $FILTER_LENGTH_DEPS)"
+ export FILTER_LENGTH_UNCHANGED_DEPS=$(echo $UNCHANGED_DEPS | jq '. | length')
+ echo "##[set-output name=FILTER_LENGTH_UNCHANGED_DEPS;]$(echo $FILTER_LENGTH_UNCHANGED_DEPS)"
+
export FILTER_LENGTH_NODEPS=$(echo $FILTERED_PATHS_NODEPS | jq '. | length')
echo "##[set-output name=FILTER_LENGTH_NODEPS;]$(echo $FILTER_LENGTH_NODEPS)"
env:
- FILTERED_PATHS_DEPS: ${{ steps.filter_go_deps.outputs.changed_modules }}
- FILTERED_PATHS_NODEPS: ${{ steps.filter_go_nodeps.outputs.changed_modules }}
+ FILTERED_PATHS_DEPS: ${{ steps.filter_go.outputs.changed_modules_deps }}
+ UNCHANGED_DEPS: ${{ steps.filter_go.outputs.unchanged_modules_deps }}
+ FILTERED_PATHS_NODEPS: ${{ steps.filter_go.outputs.changed_modules }}
test:
name: Go Coverage
@@ -106,7 +110,7 @@ jobs:
submodules: 'recursive'
- name: Cache Docker images.
- uses: ScribeMD/docker-cache@0.2.6
+ uses: ScribeMD/docker-cache@0.3.6
with:
key: docker-test-${{ runner.os }}-${{ matrix.package }}
@@ -114,6 +118,7 @@ jobs:
uses: actions/cache@v3
with:
# see https://github.com/mvdan/github-actions-golang
+ # also: https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/ w/ go build (workaround now is having a cache that just gets expired at night)
path: |
~/go/pkg/mod
~/.cache/go-build
@@ -124,7 +129,7 @@ jobs:
${{ runner.os }}-test-${{matrix.package}}
- name: Install Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
@@ -259,7 +264,7 @@ jobs:
name: Build
needs: changes
runs-on: ${{ matrix.platform }}
- if: ${{ needs.changes.outputs.package_count_deps > 0 }}
+ if: ${{ needs.changes.outputs.package_count_deps > 0 && github.event_name != 'pull_request' }}
strategy:
fail-fast: false
matrix:
@@ -272,7 +277,7 @@ jobs:
with:
fetch-depth: 1
- name: Install Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
@@ -280,6 +285,7 @@ jobs:
uses: actions/cache@v3
with:
# see https://github.com/mvdan/github-actions-golang
+ # also: https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/ w/ go build (workaround now is having a cache that just gets expired at night)
path: |
~/go/pkg/mod
~/.cache/go-build
@@ -305,12 +311,13 @@ jobs:
if: ${{ needs.changes.outputs.package_count_nodeps > 0 }}
strategy:
fail-fast: false
+ max-parallel: 8
matrix:
# Parse JSON array containing names of all filters matching any of changed files
# e.g. ['package1', 'package2'] if both package folders contains changes
package: ${{ fromJSON(needs.changes.outputs.packages_nodeps) }}
steps:
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
# see: https://github.com/golangci/golangci-lint/issues/3420, moving to go 1.20 requires a new golangci-lint version
# TODO: with this being 3 behind this should be done sooner rather than later.
@@ -338,26 +345,49 @@ jobs:
GOMEMLIMIT: 6GiB
GOGC: -1
- issue_number:
+ pr_metadata:
# this is needed to prevent us from hitting the github api rate limit
- name: Get The Issue Number
- needs: changes
+ name: Get PR Metadata
runs-on: ubuntu-latest
+ # not stricly true, but this job is fast enough to not block and we want to prioritize canceling outdated because downstream jobs can use many workers
+ needs: cancel-outdated
# currently, this matches the logic in the go generate check. If we ever add more checks that run on all packages, we should
# change this to run on those pushes
- if: ${{ github.event_name != 'pull_request' && (needs.changes.outputs.solidity_changes == 'true' || needs.changes.outputs.package_count_deps > 0 ) }}
+ if: ${{ github.event_name != 'pull_request' && format('refs/heads/{0}', github.event.repository.default_branch) != github.ref }}
outputs:
issue_number: ${{ steps.find_pr.outputs.pr }}
+ metadata: ${{ steps.metadata.outputs.METADATA }}
+ labels: ${{ steps.metadata.outputs.LABELS }}
steps:
- uses: jwalton/gh-find-current-pr@v1
id: find_pr
+ # TODO: https://stackoverflow.com/a/75429845 consider splitting w/ gql to reduce limit hit
+ - run: |
+ # Fetch the metadata
+ metadata="$(gh api repos/$OWNER/$REPO_NAME/pulls/$PULL_REQUEST_NUMBER)"
+
+ # Extract the labels in JSON format from the metadata
+ labels_json="$(echo "$metadata" | jq -r -c '[.labels[].name]')"
+
+ # Set the full metadata including the labels as the GitHub Actions step output
+ echo "::set-output name=METADATA::$metadata"
+ echo "::set-output name=LABELS::$labels_json"
+
+ id: metadata
+ shell: bash
+ continue-on-error: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OWNER: ${{ github.repository_owner }}
+ REPO_NAME: ${{ github.event.repository.name }}
+ PULL_REQUEST_NUMBER: ${{ steps.find_pr.outputs.pr }}
# check if we need to rerun go generate as a result of solidity changes. Note, this will only run on solidity changes.
# TODO: consolidate w/ go change check. This will run twice on agents
check-generation-solidity:
name: Go Generate (Solidity Only)
runs-on: ubuntu-latest
- needs: [changes, issue_number]
+ needs: [changes, pr_metadata]
if: ${{ github.event_name != 'pull_request' && needs.changes.outputs.solidity_changes == 'true' }}
strategy:
fail-fast: false
@@ -371,7 +401,7 @@ jobs:
submodules: 'recursive'
- name: Cache Docker images.
- uses: ScribeMD/docker-cache@0.2.6
+ uses: ScribeMD/docker-cache@0.3.6
with:
key: docker-generate-${{ runner.os }}-${{ matrix.package }}
@@ -389,7 +419,7 @@ jobs:
run: npx lerna exec npm run build:go --parallel
# Setup Go
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
go-version: 1.21.x
@@ -397,6 +427,7 @@ jobs:
uses: actions/cache@v3
with:
# see https://github.com/mvdan/github-actions-golang
+ # also: https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/ w/ go build (workaround now is having a cache that just gets expired at night)
path: |
~/go/pkg/mod
~/.cache/go-build
@@ -438,26 +469,50 @@ jobs:
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
# Fail if files need regeneration
+ # TODO: this can run into a bit of a race condition if any other label is removed/added while this is run, look into fixing this by dispatching another workflow
- name: Add Label
- if: steps.verify-changed-files.outputs.files_changed == 'true'
+ if: ${{ !contains(fromJson(needs.pr_metadata.outputs.labels), format('needs-go-generate-{0}', matrix.package)) && steps.verify-changed-files.outputs.files_changed == 'true' }}
uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260
with:
add-labels: 'needs-go-generate-${{matrix.package}}'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- issue-number: ${{ needs.issue_number.outputs.issue_number }}
+ issue-number: ${{ needs.pr_metadata.outputs.issue_number }}
- name: Remove Label
- if: steps.verify-changed-files.outputs.files_changed != 'true'
+ if: ${{ contains(fromJson(needs.pr_metadata.outputs.labels), format('needs-go-generate-{0}', matrix.package)) && steps.verify-changed-files.outputs.files_changed != 'true' }}
uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260
with:
remove-labels: 'needs-go-generate-${{matrix.package}}'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- issue-number: ${{ needs.issue_number.outputs.issue_number }}
+ issue-number: ${{ needs.pr_metadata.outputs.issue_number }}
+
+ remove-label-generation:
+ name: Remove Generate Label From Unused Jobs
+ runs-on: ubuntu-latest
+ needs: [changes, pr_metadata]
+ if: ${{ github.event_name != 'pull_request' && needs.changes.outputs.unchanged_package_count_deps > 0 && contains(needs.pr_metadata.outputs.labels, 'needs-go-generate') }}
+ strategy:
+ fail-fast: false
+ max-parallel: 1
+ matrix:
+ # only do on agents for now. Anything that relies on solidity in a package should do this
+ package: ${{ fromJSON(needs.changes.outputs.unchanged_deps) }}
+ steps:
+ - name: Remove Label
+ if: ${{ contains(fromJson(needs.pr_metadata.outputs.labels), format('needs-go-generate-{0}', matrix.package)) }}
+ # labels can't be removed in parallel
+ uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260
+ with:
+ remove-labels: 'needs-go-generate-${{matrix.package}}'
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-number: ${{ needs.pr_metadata.outputs.issue_number }}
+
+
check-generation:
name: Go Generate (Module Changes)
runs-on: ubuntu-latest
- needs: [changes, issue_number]
+ needs: [changes, pr_metadata]
if: ${{ github.event_name != 'pull_request' && needs.changes.outputs.package_count_deps > 0 }}
strategy:
fail-fast: false
@@ -471,7 +526,7 @@ jobs:
submodules: 'recursive'
- name: Cache Docker images.
- uses: ScribeMD/docker-cache@0.2.6
+ uses: ScribeMD/docker-cache@0.3.6
with:
key: docker-generate-${{ runner.os }}-${{ matrix.package }}
@@ -495,7 +550,7 @@ jobs:
if: ${{ contains(matrix.package, 'agents') }}
# Setup Go
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
go-version: 1.21.x
@@ -504,6 +559,7 @@ jobs:
if: ${{ !contains(matrix.package, 'services/cctp-relayer') }}
with:
# see https://github.com/mvdan/github-actions-golang
+ # also: https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/ w/ go build (workaround now is having a cache that just gets expired at night)
path: |
~/go/pkg/mod
~/.cache/go-build
@@ -576,18 +632,19 @@ jobs:
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
# Fail if files need regeneration
+ # TODO: this can run into a bit of a race condition if any other label is removed/added while this is run, look into fixing this by dispatching another workflow
- name: Add Label
- if: steps.verify-changed-files.outputs.files_changed == 'true'
+ if: ${{ !contains(fromJson(needs.pr_metadata.outputs.labels), format('needs-go-generate-{0}', matrix.package)) && steps.verify-changed-files.outputs.files_changed == 'true' }}
uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260
with:
add-labels: 'needs-go-generate-${{matrix.package}}'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- issue-number: ${{ needs.issue_number.outputs.issue_number }}
+ issue-number: ${{ needs.pr_metadata.outputs.issue_number }}
- name: Remove Label
- if: steps.verify-changed-files.outputs.files_changed != 'true'
+ if: ${{ contains(fromJson(needs.pr_metadata.outputs.labels), format('needs-go-generate-{0}', matrix.package)) && steps.verify-changed-files.outputs.files_changed != 'true' }}
uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260
with:
remove-labels: 'needs-go-generate-${{matrix.package}}'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- issue-number: ${{ needs.issue_number.outputs.issue_number }}
+ issue-number: ${{ needs.pr_metadata.outputs.issue_number }}
diff --git a/.github/workflows/goreleaser-actions.yml b/.github/workflows/goreleaser-actions.yml
index 446ec1b2a6..816e2125b4 100644
--- a/.github/workflows/goreleaser-actions.yml
+++ b/.github/workflows/goreleaser-actions.yml
@@ -24,7 +24,7 @@ jobs:
- name: Cache Docker images.
- uses: ScribeMD/docker-cache@0.2.6
+ uses: ScribeMD/docker-cache@0.3.6
with:
key: docker-release-${{ runner.os }}-${{ matrix.package }}
@@ -87,7 +87,7 @@ jobs:
if: ${{ format('refs/heads/{0}', github.event.repository.default_branch) == github.ref || contains(github.event.head_commit.message, '[goreleaser]') }}
outputs:
# Expose matched filters as job 'packages' output variable
- packages: ${{ steps.filter_go.outputs.changed_modules }}
+ packages: ${{ steps.filter_go.outputs.changed_modules_deps }}
package_count: ${{ steps.length.outputs.FILTER_LENGTH }}
steps:
- uses: actions/checkout@v4
@@ -98,7 +98,6 @@ jobs:
- uses: docker://ghcr.io/synapsecns/sanguine/git-changes-action:latest
id: filter_go
with:
- include_deps: true
github_token: ${{ secrets.WORKFLOW_PAT }}
- id: length
@@ -106,7 +105,7 @@ jobs:
export FILTER_LENGTH=$(echo $FILTERED_PATHS | jq '. | length')
echo "##[set-output name=FILTER_LENGTH;]$(echo $FILTER_LENGTH)"
env:
- FILTERED_PATHS: ${{ steps.filter_go.outputs.changed_modules }}
+ FILTERED_PATHS: ${{ steps.filter_go.outputs.changed_modules_deps }}
# TODO: we may want to dry run this on prs
run-goreleaser:
@@ -168,7 +167,7 @@ jobs:
passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
go-version: 1.20.x
diff --git a/.github/workflows/helm-test.yml b/.github/workflows/helm-test.yml
index 640ffd4b46..92f37ec9b8 100644
--- a/.github/workflows/helm-test.yml
+++ b/.github/workflows/helm-test.yml
@@ -7,6 +7,7 @@ on:
push:
paths:
- 'charts/**'
+ - '.github/workflows/helm-test.yml'
# TODO: it'd be nice to eventually work this into release process on new images
# definitely not a right now thing
@@ -38,7 +39,7 @@ jobs:
with:
version: v3.9.2
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v4
with:
python-version: 3.7
diff --git a/.github/workflows/jaeger-ui.yml b/.github/workflows/jaeger-ui.yml
index a11d13d127..5daf747662 100644
--- a/.github/workflows/jaeger-ui.yml
+++ b/.github/workflows/jaeger-ui.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v4
- name: Cache Docker images.
- uses: ScribeMD/docker-cache@0.2.6
+ uses: ScribeMD/docker-cache@0.3.6
with:
key: docker-release-jaeger
diff --git a/contrib/git-changes-action/README.md b/contrib/git-changes-action/README.md
index 7fd78bc2db..6cacbbe1dd 100644
--- a/contrib/git-changes-action/README.md
+++ b/contrib/git-changes-action/README.md
@@ -14,7 +14,7 @@ This GitHub Action exports a variable that contains the list of Go modules chang
```yaml
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: 'recursive'
@@ -26,14 +26,12 @@ This GitHub Action exports a variable that contains the list of Go modules chang
- uses: docker://ghcr.io/synapsecns/sanguine/git-changes-action:latest
id: filter_go
with:
- include_deps: true
github_token: ${{ secrets.github_token }}
timeout: "1m" # optional, defaults to 1m
```
You can customize the behavior of the git-changes script by using the following inputs:
- - `include_deps`: A boolean that controls whether dependent modules are included in the list of changed modules. Set to true by default.
- `github_token`: The token to use for authentication with the GitHub API. This is required to fetch information about the current pull request.
- `timeout`: The maximum time to wait for the GitHub API to respond. Defaults to 1 minute.
@@ -41,6 +39,9 @@ The output of the git-changes script is a comma-separated list of Go module path
```yaml
- run: echo "Changed modules: ${{ steps.filter_go.outputs.changed_modules }}"
+ - run: echo "Unchanged modules: ${{ steps.filter_go.outputs.unchanged_modules }}"
+ - run: echo "Changed modules (including dependencies): ${{ steps.filter_go.outputs.changed_modules_deps }}"
+ - run: echo "Unchanged modules (including dependencies): ${{ steps.filter_go.outputs.unchanged_modules_deps }}"
```
## Example
@@ -67,7 +68,6 @@ jobs:
- uses: docker://ghcr.io/synapsecns/sanguine/git-changes-action:latest
id: filter_go
with:
- include_deps: true
github_token: ${{ secrets.github_token }}
timeout: "1m"
@@ -97,30 +97,31 @@ Each module in the `go.work` is visited. If any changes were detected by the pre
```mermaid
sequenceDiagram
- participant GW as go.work
- participant M as Module
- participant CML as Changed_Module_List
- participant ID as include_dependencies
- participant D as Dependency
-
- GW->>M: Visit Module
- Note over M: Check for changes
- M-->>GW: Changes Detected?
- alt Changes Detected
- GW->>CML: Add Module to Changed_Module_List
- else No Changes Detected
- GW-->>M: Skip Module
+ participant GW as go.work
+ participant M as Module
+ participant CML as Changed_Module_List
+ participant UML as Unchanged_Module_List
+ participant D as Dependency
+
+ GW->>M: Visit Module
+ Note over M: Check for changes
+ M-->>GW: Changes Detected?
+ alt Changes Detected
+ GW->>CML: Add Module to Changed_Module_List
+ M->>D: Has Dependency in go.work?
+ alt Has Dependency
+ GW->>CML: Add Dependency to Changed_Module_List
+ else No Dependency
+ M-->>GW: No Dependency to Add
end
- GW->>ID: include_dependencies On?
- alt include_dependencies On
- M->>D: Has Dependency in go.work?
- alt Has Dependency
- GW->>CML: Add Dependency to Changed_Module_List
- else No Dependency
- M-->>GW: No Dependency to Add
- end
- else include_dependencies Off
- GW-->>M: Skip Dependency Check
+ else No Changes Detected
+ GW->>UML: Add Module to Unchanged_Module_List
+ M->>D: Has Dependency in go.work?
+ alt Has Dependency
+ GW->>UML: Add Dependency to Unchanged_Module_List
+ else No Dependency
+ M-->>GW: No Dependency to Add
end
- GW->>GW: Continue Until All Modules Visited
+ end
+ GW->>GW: Continue Until All Modules Visited
```
diff --git a/contrib/git-changes-action/main.go b/contrib/git-changes-action/main.go
index 01cbf82280..60e2ea6883 100644
--- a/contrib/git-changes-action/main.go
+++ b/contrib/git-changes-action/main.go
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "github.com/synapsecns/sanguine/contrib/git-changes-action/detector/tree"
"os"
"sort"
"strings"
@@ -32,8 +33,6 @@ func main() {
panic(fmt.Errorf("failed to parse timeout: %w", err))
}
- includeDeps := getIncludeDeps()
-
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
@@ -42,44 +41,57 @@ func main() {
panic(err)
}
- modules, err := detector.DetectChangedModules(workingDirectory, ct, includeDeps)
+ noDepChanged, noDepUnchanged, err := outputModuleChanges(workingDirectory, ct, false)
+ if err != nil {
+ panic(err)
+ }
+
+ depChanged, depUnchanged, err := outputModuleChanges(workingDirectory, ct, true)
if err != nil {
panic(err)
}
- var changedModules []string
+ githubactions.SetOutput("changed_modules", noDepChanged)
+ githubactions.SetOutput("unchanged_modules", noDepUnchanged)
+
+ githubactions.SetOutput("changed_modules_deps", depChanged)
+ githubactions.SetOutput("unchanged_modules_deps", depUnchanged)
+}
+
+// outputModuleChanges outputs the changed modules.
+// this wraps detector.DetectChangedModules and handles the output formatting to be parsable by github actions.
+// the final output is a json array of strings.
+func outputModuleChanges(workingDirectory string, ct tree.Tree, includeDeps bool) (changedJSON string, unchangedJson string, err error) {
+ modules, err := detector.DetectChangedModules(workingDirectory, ct, includeDeps)
+ if err != nil {
+ return changedJSON, unchangedJson, fmt.Errorf("failed to detect changed modules w/ include deps set to %v: %w", includeDeps, err)
+ }
+
+ var changedModules, unchangedModules []string
for module, changed := range modules {
- if !changed {
- continue
- }
+ modName := strings.TrimPrefix(module, "./")
- changedModules = append(changedModules, strings.TrimPrefix(module, "./"))
+ if changed {
+ changedModules = append(changedModules, modName)
+ } else {
+ unchangedModules = append(unchangedModules, modName)
+ }
}
sort.Strings(changedModules)
- marshalledJSON, err := json.Marshal(changedModules)
+ sort.Strings(unchangedModules)
+
+ marshalledChanged, err := json.Marshal(changedModules)
if err != nil {
- panic(err)
+ return changedJSON, unchangedJson, fmt.Errorf("failed to marshall changed module json w/ include deps set to %v: %w", includeDeps, err)
}
- if len(changedModules) == 0 {
- fmt.Println("no modules changed")
- } else {
- fmt.Printf("setting output to %s\n", marshalledJSON)
+ marshalledUnchanged, err := json.Marshal(unchangedModules)
+ if err != nil {
+ return changedJSON, unchangedJson, fmt.Errorf("failed to marshall unchanged module json w/ include deps set to %v: %w", includeDeps, err)
}
- githubactions.SetOutput("changed_modules", string(marshalledJSON))
-}
-// getIncludeDeps gets the include deps setting.
-// If it is not set, it defaults to false.
-func getIncludeDeps() (includeDeps bool) {
- rawIncludeDeps := githubactions.GetInput("include_deps")
-
- includeDeps = false
- if rawIncludeDeps == "true" {
- includeDeps = true
- }
- return
+ return string(marshalledChanged), string(marshalledUnchanged), nil
}
// getTimeout gets the timeout setting. If it is not set, it defaults to 1 minute.
diff --git a/packages/synapse-interface/CHANGELOG.md b/packages/synapse-interface/CHANGELOG.md
index c12a86fd8f..e7e5afd7ac 100644
--- a/packages/synapse-interface/CHANGELOG.md
+++ b/packages/synapse-interface/CHANGELOG.md
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.1.134](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.1.133...@synapsecns/synapse-interface@0.1.134) (2023-09-26)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
+## [0.1.133](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.1.132...@synapsecns/synapse-interface@0.1.133) (2023-09-25)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
## [0.1.132](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.1.131...@synapsecns/synapse-interface@0.1.132) (2023-09-15)
**Note:** Version bump only for package @synapsecns/synapse-interface
diff --git a/packages/synapse-interface/components/ActionCardFooter.tsx b/packages/synapse-interface/components/ActionCardFooter.tsx
deleted file mode 100644
index fc3f509ecc..0000000000
--- a/packages/synapse-interface/components/ActionCardFooter.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { ArrowSmRightIcon, ChartSquareBarIcon } from '@heroicons/react/outline'
-
-import { EXPLORER_PATH } from '@/constants/urls'
-
-export const ActionCardFooter = ({ link }: { link: string }) => {
- return (
-
- )
-}
diff --git a/packages/synapse-interface/components/ChainLabel.tsx b/packages/synapse-interface/components/ChainLabel.tsx
deleted file mode 100644
index 7b15a532d7..0000000000
--- a/packages/synapse-interface/components/ChainLabel.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import { ChevronDownIcon } from '@heroicons/react/outline'
-import { CHAINS_BY_ID, ORDERED_CHAINS_BY_ID } from '@constants/chains'
-import * as CHAINS from '@constants/chains/master'
-import { getNetworkButtonBorder } from '@/styles/chains'
-import { getOrderedChains } from '@utils/getOrderedChains'
-import Image from 'next/image'
-import Tooltip from '@tw/Tooltip'
-import { useEffect, useState } from 'react'
-import { DisplayType } from '@/pages/bridge/DisplayType'
-
-export const ChainLabel = ({
- isOrigin,
- chains,
- chainId,
- titleText,
- connectedChainId,
- labelClassNameOverride,
- onChangeChain,
- setDisplayType,
-}: {
- isOrigin: boolean
- chains: string[] | undefined
- chainId: number
- titleText?: string
- connectedChainId: number
- labelClassNameOverride?: string
- onChangeChain: (chainId: number, flip: boolean, type: 'from' | 'to') => void
- setDisplayType: (v: string) => void
-}) => {
- const labelClassName = 'text-sm'
- const displayType = isOrigin ? DisplayType.FROM_CHAIN : DisplayType.TO_CHAIN
- const dataId = isOrigin ? 'bridge-origin-chain' : 'bridge-destination-chain'
- const title = titleText ?? (isOrigin ? 'Origin' : 'Dest.')
- const [orderedChains, setOrderedChains] = useState([])
- useEffect(() => {
- setOrderedChains(
- chainOrderBySwapSide(connectedChainId, isOrigin, chainId, chains)
- )
- }, [chainId, connectedChainId, chains])
-
- return (
-
-
- {title}
-
-
- {orderedChains.map((id) =>
- Number(id) === chainId ? (
-
- ) : (
-
- )
- )}
-
-
-
- )
-}
-
-const PossibleChain = ({
- chainId,
- onChangeChain,
- isOrigin,
-}: {
- chainId: number
- onChangeChain: (chainId: number, flip: boolean, type: 'from' | 'to') => void
- isOrigin: boolean
-}) => {
- const chain = CHAINS_BY_ID[chainId]
- return chain ? (
-
- ) : null
-}
-
-const SelectedChain = ({ chainId }: { chainId: number }) => {
- const chain = CHAINS_BY_ID[chainId]
- return chain ? (
-
-
-
-
- {chain.name === 'Boba Network' ? 'Boba' : chain.name}
-
-
-
- ) : null
-}
-
-const chainOrderBySwapSide = (
- connectedChain: number,
- isOrigin: boolean,
- chainId: number,
- chains: string[] | undefined
-) => {
- let orderedChains
- if (isOrigin) {
- orderedChains = ORDERED_CHAINS_BY_ID.filter((e) => e !== String(chainId))
- orderedChains = orderedChains.slice(0, 5)
- orderedChains.unshift(chainId)
- return orderedChains
- } else {
- return getOrderedChains(connectedChain, chainId, chains)
- }
-}
diff --git a/packages/synapse-interface/components/StateManagedBridge/ConnectionIndicators.tsx b/packages/synapse-interface/components/ConnectionIndicators.tsx
similarity index 100%
rename from packages/synapse-interface/components/StateManagedBridge/ConnectionIndicators.tsx
rename to packages/synapse-interface/components/ConnectionIndicators.tsx
diff --git a/packages/synapse-interface/components/Popup.tsx b/packages/synapse-interface/components/Popup.tsx
deleted file mode 100644
index bef8575530..0000000000
--- a/packages/synapse-interface/components/Popup.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { useState, useEffect } from 'react'
-import { IMPAIRED_CHAINS } from '@/constants/impairedChains'
-const Popup = ({ chainId }) => {
- const [active, setActive] = useState(false)
-
- useEffect(() => {
- if (chainId && IMPAIRED_CHAINS[chainId]) {
- setActive(true)
- } else {
- setActive(false)
- }
- }, [chainId])
-
- if (!active) return null
- return (
-
-
-
- {IMPAIRED_CHAINS[chainId] && IMPAIRED_CHAINS[chainId].content()}
-
-
-
-
-
-
- )
-}
-
-export default Popup
diff --git a/packages/synapse-interface/components/StateManagedBridge/FromChainSelector.tsx b/packages/synapse-interface/components/StateManagedBridge/FromChainSelector.tsx
index a55118bf7b..61a377a876 100644
--- a/packages/synapse-interface/components/StateManagedBridge/FromChainSelector.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/FromChainSelector.tsx
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'
import { setShowFromChainListOverlay } from '@/slices/bridgeDisplaySlice'
import { useBridgeState } from '@/slices/bridge/hooks'
import { CHAINS_BY_ID } from '@/constants/chains'
-import { DropDownArrowSvg } from './components/DropDownArrowSvg'
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
import {
getNetworkButtonBgClassNameActive,
getNetworkButtonBorderActive,
diff --git a/packages/synapse-interface/components/StateManagedBridge/FromTokenSelector.tsx b/packages/synapse-interface/components/StateManagedBridge/FromTokenSelector.tsx
index bed5ec2f94..20ce214938 100644
--- a/packages/synapse-interface/components/StateManagedBridge/FromTokenSelector.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/FromTokenSelector.tsx
@@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'
import { setShowFromTokenListOverlay } from '@/slices/bridgeDisplaySlice'
import { useBridgeState } from '@/slices/bridge/hooks'
-import { DropDownArrowSvg } from './components/DropDownArrowSvg'
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
import {
getBorderStyleForCoinHover,
getMenuItemHoverBgForCoin,
diff --git a/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx b/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx
index 3b31daced6..ae7f1294a6 100644
--- a/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx
@@ -10,7 +10,7 @@ import {
ConnectToNetworkButton,
ConnectWalletButton,
ConnectedIndicator,
-} from './ConnectionIndicators'
+} from '@/components/ConnectionIndicators'
import { FromChainSelector } from './FromChainSelector'
import { FromTokenSelector } from './FromTokenSelector'
import { useBridgeState } from '@/slices/bridge/hooks'
diff --git a/packages/synapse-interface/components/StateManagedBridge/ToChainSelector.tsx b/packages/synapse-interface/components/StateManagedBridge/ToChainSelector.tsx
index 0038eb3f3c..3934fe8d17 100644
--- a/packages/synapse-interface/components/StateManagedBridge/ToChainSelector.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/ToChainSelector.tsx
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'
import { setShowToChainListOverlay } from '@/slices/bridgeDisplaySlice'
import { useBridgeState } from '@/slices/bridge/hooks'
import { CHAINS_BY_ID } from '@/constants/chains'
-import { DropDownArrowSvg } from './components/DropDownArrowSvg'
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
import {
getNetworkButtonBgClassNameActive,
getNetworkButtonBorderActive,
diff --git a/packages/synapse-interface/components/StateManagedBridge/ToTokenSelector.tsx b/packages/synapse-interface/components/StateManagedBridge/ToTokenSelector.tsx
index 6d2159d908..47166d0b78 100644
--- a/packages/synapse-interface/components/StateManagedBridge/ToTokenSelector.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/ToTokenSelector.tsx
@@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'
import { setShowToTokenListOverlay } from '@/slices/bridgeDisplaySlice'
import { useBridgeState } from '@/slices/bridge/hooks'
-import { DropDownArrowSvg } from './components/DropDownArrowSvg'
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
import {
getBorderStyleForCoinHover,
getMenuItemHoverBgForCoin,
diff --git a/packages/synapse-interface/components/StateManagedBridge/components/SelectSpecificNetworkButton.tsx b/packages/synapse-interface/components/StateManagedBridge/components/SelectSpecificNetworkButton.tsx
index f12b7c8b4a..1852838824 100644
--- a/packages/synapse-interface/components/StateManagedBridge/components/SelectSpecificNetworkButton.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/components/SelectSpecificNetworkButton.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef } from 'react'
+import { useEffect, useMemo, useRef, useState } from 'react'
import { CHAINS_BY_ID } from '@constants/chains'
import Image from 'next/image'
import {
@@ -10,6 +10,11 @@ import {
getNetworkButtonBorderActive,
getMenuItemStyleForChain,
} from '@/styles/chains'
+import { usePortfolioState } from '@/slices/portfolio/hooks'
+import {
+ TokenWithBalanceAndAllowances,
+ sortTokensByBalanceDescending,
+} from '@/utils/actions/fetchPortfolioBalances'
export const SelectSpecificNetworkButton = ({
itemChainId,
@@ -50,7 +55,7 @@ export const SelectSpecificNetworkButton = ({
ref={ref}
tabIndex={active ? 1 : 0}
className={`
- flex items-center
+ flex items-center justify-between
transition-all duration-75
w-full
px-2 py-4
@@ -74,17 +79,105 @@ export const SelectSpecificNetworkButton = ({
function ButtonContent({ chainId }: { chainId: number }) {
const chain = CHAINS_BY_ID[chainId]
+ const { balancesAndAllowances } = usePortfolioState()
+
+ const balanceTokens =
+ balancesAndAllowances &&
+ balancesAndAllowances[chainId] &&
+ sortTokensByBalanceDescending(
+ balancesAndAllowances[chainId].filter((bt) => bt.balance > 0n)
+ )
return chain ? (
<>
-
-
-
{chain.name}
+
+ {balanceTokens && balanceTokens.length > 0 ? (
+
+ ) : null}
>
) : null
}
+
+const ChainTokens = ({
+ balanceTokens = [],
+}: {
+ balanceTokens: TokenWithBalanceAndAllowances[]
+}) => {
+ const [isHovered, setIsHovered] = useState(false)
+ const hasOneToken = useMemo(
+ () => balanceTokens && balanceTokens.length > 0,
+ [balanceTokens]
+ )
+ const hasTwoTokens = useMemo(
+ () => balanceTokens && balanceTokens.length > 1,
+ [balanceTokens]
+ )
+ const numOverTwoTokens = useMemo(
+ () =>
+ balanceTokens && balanceTokens.length - 2 > 0
+ ? balanceTokens.length - 2
+ : 0,
+ [balanceTokens]
+ )
+
+ return (
+
setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {hasOneToken && (
+
+ )}
+ {hasTwoTokens && (
+
+ )}
+ {numOverTwoTokens > 0 && (
+
+ {numOverTwoTokens}
+ )}
+
+ {isHovered && (
+
+ {balanceTokens.map(
+ (token: TokenWithBalanceAndAllowances, key: number) => {
+ const tokenSymbol = token.token.symbol
+ const balance = token.parsedBalance
+ return (
+
+ {balance} {tokenSymbol}
+
+ )
+ }
+ )}
+
+ )}
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedBridge/helpers/sortByBalance.ts b/packages/synapse-interface/components/StateManagedBridge/helpers/sortByBalance.ts
index c7f1fc31f3..e8447b81f3 100644
--- a/packages/synapse-interface/components/StateManagedBridge/helpers/sortByBalance.ts
+++ b/packages/synapse-interface/components/StateManagedBridge/helpers/sortByBalance.ts
@@ -1,11 +1,12 @@
import _ from 'lodash'
import { Token } from '@/utils/types'
+import { NetworkTokenBalancesAndAllowances } from '@/utils/actions/fetchPortfolioBalances'
export const hasBalance = (
t: Token,
chainId: number,
- portfolioBalances: any
+ portfolioBalances: NetworkTokenBalancesAndAllowances
) => {
if (!chainId) {
return false
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapChainListOverlay.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapChainListOverlay.tsx
new file mode 100644
index 0000000000..bb98d7d435
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapChainListOverlay.tsx
@@ -0,0 +1,194 @@
+import _ from 'lodash'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import Fuse from 'fuse.js'
+import { useKeyPress } from '@hooks/useKeyPress'
+import * as ALL_CHAINS from '@constants/chains/master'
+import SlideSearchBox from '@pages/bridge/SlideSearchBox'
+import { CHAINS_BY_ID, sortChains } from '@constants/chains'
+import { useDispatch } from 'react-redux'
+import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
+import { setShowFromChainListOverlay } from '@/slices/bridgeDisplaySlice'
+import { SelectSpecificNetworkButton } from './components/SelectSpecificNetworkButton'
+import useCloseOnOutsideClick from '@/utils/hooks/useCloseOnOutsideClick'
+import { CloseButton } from './components/CloseButton'
+import { SearchResults } from './components/SearchResults'
+import { setShowSwapChainListOverlay } from '@/slices/swapDisplaySlice'
+import { setSwapChainId } from '@/slices/swap/reducer'
+import { useSwapState } from '@/slices/swap/hooks'
+
+export const SwapChainListOverlay = () => {
+ const { swapChainId, swapFromChainIds } = useSwapState()
+ const [currentIdx, setCurrentIdx] = useState(-1)
+ const [searchStr, setSearchStr] = useState('')
+ const dispatch = useDispatch()
+ const dataId = 'swap-origin-chain-list'
+ const overlayRef = useRef(null)
+
+ let possibleChains = sortChains(
+ _(ALL_CHAINS)
+ .pickBy((value) => _.includes(swapFromChainIds, value.id))
+ .values()
+ .value()
+ )
+
+ let remainingChains = swapFromChainIds
+ ? sortChains(
+ _.difference(
+ Object.keys(CHAINS_BY_ID).map((id) => CHAINS_BY_ID[id]),
+ swapFromChainIds.map((id) => CHAINS_BY_ID[id])
+ )
+ )
+ : []
+
+ const possibleChainsWithSource = possibleChains.map((chain) => ({
+ ...chain,
+ source: 'possibleChains',
+ }))
+
+ const remainingChainsWithSource = remainingChains.map((chain) => ({
+ ...chain,
+ source: 'remainingChains',
+ }))
+
+ const masterList = [...possibleChainsWithSource, ...remainingChainsWithSource]
+
+ const fuseOptions = {
+ includeScore: true,
+ threshold: 0.0,
+ keys: [
+ {
+ name: 'name',
+ weight: 2,
+ },
+ 'id',
+ 'nativeCurrency.symbol',
+ ],
+ }
+
+ const fuse = new Fuse(masterList, fuseOptions)
+
+ if (searchStr?.length > 0) {
+ const results = fuse.search(searchStr).map((i) => i.item)
+
+ possibleChains = results.filter((item) => item.source === 'possibleChains')
+ remainingChains = results.filter(
+ (item) => item.source === 'remainingChains'
+ )
+ }
+
+ const escPressed = useKeyPress('Escape')
+ const arrowUp = useKeyPress('ArrowUp')
+ const arrowDown = useKeyPress('ArrowDown')
+
+ const onClose = useCallback(() => {
+ setCurrentIdx(-1)
+ setSearchStr('')
+ dispatch(setShowSwapChainListOverlay(false))
+ }, [dispatch])
+
+ const escFunc = () => {
+ if (escPressed) {
+ onClose()
+ }
+ }
+ const arrowDownFunc = () => {
+ const nextIdx = currentIdx + 1
+ if (arrowDown && nextIdx < masterList.length) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ const arrowUpFunc = () => {
+ const nextIdx = currentIdx - 1
+ if (arrowUp && -1 < nextIdx) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ const onSearch = (str: string) => {
+ setSearchStr(str)
+ setCurrentIdx(-1)
+ }
+
+ useEffect(arrowDownFunc, [arrowDown])
+ useEffect(escFunc, [escPressed])
+ useEffect(arrowUpFunc, [arrowUp])
+ useCloseOnOutsideClick(overlayRef, onClose)
+
+ const handleSetSwapChainId = (chainId) => {
+ const eventTitle = `[Swap User Action] Sets new fromChainId`
+ const eventData = {
+ previousFromChainId: swapChainId,
+ newFromChainId: chainId,
+ }
+
+ segmentAnalyticsEvent(eventTitle, eventData)
+ dispatch(setSwapChainId(chainId))
+ onClose()
+ }
+
+ return (
+
+
+
+ {possibleChains && possibleChains.length > 0 && (
+ <>
+
From…
+ {possibleChains.map(({ id: mapChainId }, idx) => {
+ return (
+
{
+ if (swapChainId === mapChainId) {
+ onClose()
+ } else {
+ handleSetSwapChainId(mapChainId)
+ }
+ }}
+ dataId={dataId}
+ />
+ )
+ })}
+ >
+ )}
+ {remainingChains && remainingChains.length > 0 && (
+ <>
+
+ All chains
+
+ {remainingChains.map(({ id: mapChainId }, idx) => {
+ return (
+ handleSetSwapChainId(mapChainId)}
+ dataId={dataId}
+ alternateBackground={true}
+ />
+ )
+ })}
+ >
+ )}
+
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapChainSelector.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapChainSelector.tsx
new file mode 100644
index 0000000000..b0204ecbaf
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapChainSelector.tsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import { useDispatch } from 'react-redux'
+
+import { CHAINS_BY_ID } from '@/constants/chains'
+import { DropDownArrowSvg } from '@/components/icons/DropDownArrowSvg'
+import {
+ getNetworkButtonBgClassNameActive,
+ getNetworkButtonBorderActive,
+ getNetworkButtonBorderHover,
+ getNetworkHover,
+} from '@/styles/chains'
+import { useSwapState } from '@/slices/swap/hooks'
+import { setShowSwapChainListOverlay } from '@/slices/swapDisplaySlice'
+
+export const SwapChainSelector = () => {
+ const dispatch = useDispatch()
+ const { swapChainId } = useSwapState()
+ const chain = CHAINS_BY_ID[swapChainId]
+
+ const buttonContent = swapChainId ? (
+
+
+
+
+
+
+
+ ) : (
+
+ )
+
+ return (
+
+ )
+}
diff --git a/packages/synapse-interface/components/SwapExchangeRateInfo.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapExchangeRateInfo.tsx
similarity index 100%
rename from packages/synapse-interface/components/SwapExchangeRateInfo.tsx
rename to packages/synapse-interface/components/StateManagedSwap/SwapExchangeRateInfo.tsx
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenListOverlay.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenListOverlay.tsx
new file mode 100644
index 0000000000..d97b6318a1
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenListOverlay.tsx
@@ -0,0 +1,223 @@
+import _ from 'lodash'
+
+import { useEffect, useRef, useState } from 'react'
+import { useDispatch } from 'react-redux'
+import Fuse from 'fuse.js'
+
+import { useKeyPress } from '@hooks/useKeyPress'
+import SlideSearchBox from '@pages/bridge/SlideSearchBox'
+import { Token } from '@/utils/types'
+import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
+import { usePortfolioBalances } from '@/slices/portfolio/hooks'
+import SelectSpecificTokenButton from './components/SelectSpecificTokenButton'
+
+import { hasBalance } from './helpers/sortByBalance'
+import { sortByPriorityRank } from './helpers/sortByPriorityRank'
+import useCloseOnOutsideClick from '@/utils/hooks/useCloseOnOutsideClick'
+import { CloseButton } from './components/CloseButton'
+import { SearchResults } from './components/SearchResults'
+import { useSwapState } from '@/slices/swap/hooks'
+import { setShowSwapFromTokenListOverlay } from '@/slices/swapDisplaySlice'
+import { setSwapFromToken } from '@/slices/swap/reducer'
+import { getSwapPossibilities } from '@/utils/swapFinder/generateSwapPossibilities'
+import { CHAINS_BY_ID } from '@/constants/chains'
+
+export const SwapFromTokenListOverlay = () => {
+ const [currentIdx, setCurrentIdx] = useState(-1)
+ const [searchStr, setSearchStr] = useState('')
+ const dispatch = useDispatch()
+ const overlayRef = useRef(null)
+
+ const { swapFromTokens, swapChainId, swapFromToken } = useSwapState()
+ const portfolioBalances = usePortfolioBalances()
+
+ let possibleTokens = sortByPriorityRank(swapFromTokens)
+
+ possibleTokens = [
+ ...possibleTokens.filter((t) =>
+ hasBalance(t, swapChainId, portfolioBalances)
+ ),
+ ...possibleTokens.filter(
+ (t) => !hasBalance(t, swapChainId, portfolioBalances)
+ ),
+ ]
+
+ const { fromTokens: allSwapChainTokens } = getSwapPossibilities({
+ fromChainId: swapChainId,
+ fromToken: null,
+ toChainId: swapChainId,
+ toToken: null,
+ })
+
+ let remainingTokens = sortByPriorityRank(
+ _.difference(allSwapChainTokens, swapFromTokens)
+ )
+
+ remainingTokens = [
+ ...remainingTokens.filter((t) =>
+ hasBalance(t, swapChainId, portfolioBalances)
+ ),
+ ...remainingTokens.filter(
+ (t) => !hasBalance(t, swapChainId, portfolioBalances)
+ ),
+ ]
+
+ const possibleTokensWithSource = possibleTokens.map((token) => ({
+ ...token,
+ source: 'possibleTokens',
+ }))
+ const remainingTokensWithSource = remainingTokens.map((token) => ({
+ ...token,
+ source: 'remainingTokens',
+ }))
+
+ const masterList = [...possibleTokensWithSource, ...remainingTokensWithSource]
+
+ const fuseOptions = {
+ ignoreLocation: true,
+ includeScore: true,
+ threshold: 0.0,
+ keys: [
+ {
+ name: 'symbol',
+ weight: 2,
+ },
+ 'routeSymbol',
+ `addresses.${swapChainId}`,
+ 'name',
+ ],
+ }
+
+ const fuse = new Fuse(masterList, fuseOptions)
+
+ if (searchStr?.length > 0) {
+ const results = fuse.search(searchStr).map((i) => i.item)
+ possibleTokens = results.filter((item) => item.source === 'possibleTokens')
+ remainingTokens = results.filter(
+ (item) => item.source === 'remainingTokens'
+ )
+ }
+
+ const escPressed = useKeyPress('Escape')
+ const arrowUp = useKeyPress('ArrowUp')
+ const arrowDown = useKeyPress('ArrowDown')
+
+ function onClose() {
+ setCurrentIdx(-1)
+ setSearchStr('')
+ dispatch(setShowSwapFromTokenListOverlay(false))
+ }
+
+ function escFunc() {
+ if (escPressed) {
+ onClose()
+ }
+ }
+
+ function arrowDownFunc() {
+ const nextIdx = currentIdx + 1
+ if (arrowDown && nextIdx < masterList.length) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ function arrowUpFunc() {
+ const nextIdx = currentIdx - 1
+ if (arrowUp && -1 < nextIdx) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ function onSearch(str: string) {
+ setSearchStr(str)
+ setCurrentIdx(-1)
+ }
+
+ useEffect(escFunc, [escPressed])
+ useEffect(arrowDownFunc, [arrowDown])
+ useEffect(arrowUpFunc, [arrowUp])
+ useCloseOnOutsideClick(overlayRef, onClose)
+
+ const handleSetFromToken = (oldToken: Token, newToken: Token) => {
+ const eventTitle = '[Swap User Action] Sets new fromToken'
+ const eventData = {
+ previousFromToken: oldToken?.symbol,
+ newFromToken: newToken?.symbol,
+ }
+ segmentAnalyticsEvent(eventTitle, eventData)
+ dispatch(setSwapFromToken(newToken))
+ onClose()
+ }
+
+ return (
+
+
+ {possibleTokens && possibleTokens.length > 0 && (
+ <>
+
+ Swap…
+
+
+ {possibleTokens.map((token, idx) => {
+ return (
+ {
+ if (token === swapFromToken) {
+ onClose()
+ } else {
+ handleSetFromToken(swapFromToken, token)
+ }
+ }}
+ />
+ )
+ })}
+
+ >
+ )}
+ {remainingTokens && remainingTokens.length > 0 && (
+ <>
+
+ {swapChainId
+ ? `More on ${CHAINS_BY_ID[swapChainId]?.name}`
+ : 'All swappable tokens'}
+
+
+ {remainingTokens.map((token, idx) => {
+ return (
+ handleSetFromToken(swapFromToken, token)}
+ />
+ )
+ })}
+
+ >
+ )}
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenSelector.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenSelector.tsx
new file mode 100644
index 0000000000..1dcca30170
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapFromTokenSelector.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import { useDispatch } from 'react-redux'
+
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
+import {
+ getBorderStyleForCoinHover,
+ getMenuItemHoverBgForCoin,
+} from '@/styles/tokens'
+import { useSwapState } from '@/slices/swap/hooks'
+import { setShowSwapFromTokenListOverlay } from '@/slices/swapDisplaySlice'
+
+export const SwapFromTokenSelector = () => {
+ const dispatch = useDispatch()
+
+ const { swapFromToken } = useSwapState()
+
+ const buttonContent = swapFromToken ? (
+
+
+
+
+
+
+ {swapFromToken?.symbol}
+
+
+
+
+ ) : (
+
+ )
+
+ return (
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx
new file mode 100644
index 0000000000..82eb3a047c
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx
@@ -0,0 +1,172 @@
+import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'
+import { useDispatch } from 'react-redux'
+import { useAccount, useNetwork } from 'wagmi'
+
+import MiniMaxButton from '../buttons/MiniMaxButton'
+import { formatBigIntToString, stringToBigInt } from '@/utils/bigint/format'
+import { cleanNumberInput } from '@/utils/cleanNumberInput'
+import {
+ ConnectToNetworkButton,
+ ConnectWalletButton,
+ ConnectedIndicator,
+} from '@/components/ConnectionIndicators'
+import { SwapChainSelector } from './SwapChainSelector'
+import { SwapFromTokenSelector } from './SwapFromTokenSelector'
+import { usePortfolioState } from '@/slices/portfolio/hooks'
+import { updateSwapFromValue } from '@/slices/swap/reducer'
+import { useSwapState } from '@/slices/swap/hooks'
+
+export const SwapInputContainer = () => {
+ const inputRef = useRef
(null)
+ const { swapChainId, swapFromToken, swapToToken, swapFromValue } =
+ useSwapState()
+ const [showValue, setShowValue] = useState('')
+
+ const [hasMounted, setHasMounted] = useState(false)
+
+ const { balancesAndAllowances } = usePortfolioState()
+
+ useEffect(() => {
+ setHasMounted(true)
+ }, [])
+
+ const { isConnected } = useAccount()
+ const { chain } = useNetwork()
+
+ const dispatch = useDispatch()
+
+ const tokenData = balancesAndAllowances[swapChainId]?.find(
+ (token) => token.tokenAddress === swapFromToken?.addresses[swapChainId]
+ )
+
+ const parsedBalance = tokenData?.parsedBalance
+
+ const balance = tokenData?.balance
+
+ useEffect(() => {
+ if (
+ swapFromToken &&
+ swapFromToken.decimals[swapChainId] &&
+ stringToBigInt(swapFromValue, swapFromToken.decimals[swapChainId]) !== 0n
+ ) {
+ setShowValue(swapFromValue)
+ }
+ }, [swapFromValue, swapChainId, swapFromToken])
+
+ const handleFromValueChange = (
+ event: React.ChangeEvent
+ ) => {
+ const swapFromValueString: string = cleanNumberInput(event.target.value)
+ try {
+ dispatch(updateSwapFromValue(swapFromValueString))
+ setShowValue(swapFromValueString)
+ } catch (error) {
+ console.error('Invalid value for conversion to BigInteger')
+ const inputValue = event.target.value
+ const regex = /^[0-9]*[.,]?[0-9]*$/
+
+ if (regex.test(inputValue) || inputValue === '') {
+ dispatch(updateSwapFromValue(''))
+ setShowValue(inputValue)
+ }
+ }
+ }
+
+ const onMaxBalance = useCallback(() => {
+ dispatch(
+ updateSwapFromValue(
+ formatBigIntToString(balance, swapFromToken.decimals[swapChainId])
+ )
+ )
+ }, [balance, swapChainId, swapFromToken])
+
+ const connectedStatus = useMemo(() => {
+ if (hasMounted && isConnected) {
+ if (swapChainId === chain.id) {
+ return
+ } else if (swapChainId !== chain.id) {
+ return
+ }
+ } else if (hasMounted && !isConnected) {
+ return
+ }
+ }, [chain, swapChainId, isConnected, hasMounted])
+
+ return (
+
+
+
+ {connectedStatus}
+
+
+
+
+
+
+
+
+
+ {hasMounted && isConnected && (
+
+ )}
+
+
+
+ {hasMounted && isConnected && (
+
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapOutputContainer.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapOutputContainer.tsx
new file mode 100644
index 0000000000..ee35082cf7
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapOutputContainer.tsx
@@ -0,0 +1,64 @@
+import { useEffect, useState } from 'react'
+import { Address, useAccount } from 'wagmi'
+
+import LoadingSpinner from '../ui/tailwind/LoadingSpinner'
+import { SwapToTokenSelector } from './SwapToTokenSelector'
+import { useSwapState } from '@/slices/swap/hooks'
+
+export const SwapOutputContainer = ({}) => {
+ const { swapQuote, isLoading, swapToToken } = useSwapState()
+
+ const { address: isConnectedAddress } = useAccount()
+ const [address, setAddress] = useState()
+
+ useEffect(() => {
+ setAddress(isConnectedAddress)
+ }, [isConnectedAddress])
+
+ return (
+
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapToTokenListOverlay.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapToTokenListOverlay.tsx
new file mode 100644
index 0000000000..77bf22b49e
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapToTokenListOverlay.tsx
@@ -0,0 +1,254 @@
+import _ from 'lodash'
+import { useEffect, useRef, useState } from 'react'
+import { useDispatch } from 'react-redux'
+import Fuse from 'fuse.js'
+
+import { useKeyPress } from '@hooks/useKeyPress'
+import SlideSearchBox from '@pages/bridge/SlideSearchBox'
+import { Token } from '@/utils/types'
+import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
+import SelectSpecificTokenButton from './components/SelectSpecificTokenButton'
+import { getRoutePossibilities } from '@/utils/routeMaker/generateRoutePossibilities'
+
+import { sortByPriorityRank } from './helpers/sortByPriorityRank'
+import { CHAINS_BY_ID } from '@/constants/chains'
+import useCloseOnOutsideClick from '@/utils/hooks/useCloseOnOutsideClick'
+import { CloseButton } from './components/CloseButton'
+import { SearchResults } from './components/SearchResults'
+import { setShowSwapToTokenListOverlay } from '@/slices/swapDisplaySlice'
+import { setSwapToToken } from '@/slices/swap/reducer'
+import { useSwapState } from '@/slices/swap/hooks'
+import { getSwapPossibilities } from '@/utils/swapFinder/generateSwapPossibilities'
+
+export const SwapToTokenListOverlay = () => {
+ const { swapChainId, swapToTokens, swapToToken } = useSwapState()
+
+ const [currentIdx, setCurrentIdx] = useState(-1)
+ const [searchStr, setSearchStr] = useState('')
+ const dispatch = useDispatch()
+ const overlayRef = useRef(null)
+
+ let possibleTokens = sortByPriorityRank(swapToTokens)
+
+ const { toTokens: allToChainTokens } = getSwapPossibilities({
+ fromChainId: swapChainId,
+ fromToken: null,
+ toChainId: swapChainId,
+ toToken: null,
+ })
+
+ let remainingChainTokens = swapChainId
+ ? sortByPriorityRank(_.difference(allToChainTokens, swapToTokens))
+ : []
+
+ const { toTokens: allTokens } = getSwapPossibilities({
+ fromChainId: null,
+ fromToken: null,
+ toChainId: null,
+ toToken: null,
+ })
+
+ let allOtherToTokens = swapChainId
+ ? sortByPriorityRank(_.difference(allTokens, allToChainTokens))
+ : sortByPriorityRank(allTokens)
+
+ const possibleTokenswithSource = possibleTokens.map((token) => ({
+ ...token,
+ source: 'possibleTokens',
+ }))
+
+ const remainingChainTokensWithSource = remainingChainTokens.map((token) => ({
+ ...token,
+ source: 'remainingChainTokens',
+ }))
+
+ const allOtherToTokensWithSource = allOtherToTokens.map((token) => ({
+ ...token,
+ source: 'allOtherToTokens',
+ }))
+
+ const masterList = [
+ ...possibleTokenswithSource,
+ ...remainingChainTokensWithSource,
+ ...allOtherToTokensWithSource,
+ ]
+
+ const fuseOptions = {
+ ignoreLocation: true,
+ includeScore: true,
+ threshold: 0.0,
+ keys: [
+ {
+ name: 'symbol',
+ weight: 2,
+ },
+ 'routeSymbol',
+ `addresses.${swapChainId}`,
+ 'name',
+ ],
+ }
+ const fuse = new Fuse(masterList, fuseOptions)
+
+ if (searchStr?.length > 0) {
+ const results = fuse.search(searchStr).map((i) => i.item)
+
+ possibleTokens = results.filter((item) => item.source === 'possibleTokens')
+ remainingChainTokens = results.filter(
+ (item) => item.source === 'remainingChainTokens'
+ )
+ allOtherToTokens = results.filter(
+ (item) => item.source === 'allOtherToTokens'
+ )
+ }
+
+ const escPressed = useKeyPress('Escape')
+ const arrowUp = useKeyPress('ArrowUp')
+ const arrowDown = useKeyPress('ArrowDown')
+
+ function onClose() {
+ setCurrentIdx(-1)
+ setSearchStr('')
+ dispatch(setShowSwapToTokenListOverlay(false))
+ }
+
+ function escFunc() {
+ if (escPressed) {
+ onClose()
+ }
+ }
+
+ function arrowDownFunc() {
+ const nextIdx = currentIdx + 1
+ if (arrowDown && nextIdx < masterList.length) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ function arrowUpFunc() {
+ const nextIdx = currentIdx - 1
+ if (arrowUp && -1 < nextIdx) {
+ setCurrentIdx(nextIdx)
+ }
+ }
+
+ function onSearch(str: string) {
+ setSearchStr(str)
+ setCurrentIdx(-1)
+ }
+
+ useEffect(escFunc, [escPressed])
+ useEffect(arrowDownFunc, [arrowDown])
+ useEffect(arrowUpFunc, [arrowUp])
+ useCloseOnOutsideClick(overlayRef, onClose)
+
+ const handleSetToToken = (oldToken: Token, newToken: Token) => {
+ const eventTitle = `[Swap User Action] Sets new toToken`
+ const eventData = {
+ previousToToken: oldToken?.symbol,
+ newToToken: newToken?.symbol,
+ }
+ segmentAnalyticsEvent(eventTitle, eventData)
+ dispatch(setSwapToToken(newToken))
+ onClose()
+ }
+
+ return (
+
+
+ {possibleTokens && possibleTokens.length > 0 && (
+ <>
+
+ Receive…
+
+
+ {possibleTokens.map((token, idx) => {
+ return (
+ {
+ if (token === swapToToken) {
+ onClose()
+ } else {
+ handleSetToToken(swapToToken, token)
+ }
+ }}
+ />
+ )
+ })}
+
+ >
+ )}
+ {remainingChainTokens && remainingChainTokens.length > 0 && (
+ <>
+
+ {swapChainId
+ ? `More on ${CHAINS_BY_ID[swapChainId]?.name}`
+ : 'All swapable tokens'}
+
+
+ {remainingChainTokens.map((token, idx) => {
+ return (
+ handleSetToToken(swapToToken, token)}
+ />
+ )
+ })}
+
+ >
+ )}
+ {allOtherToTokens && allOtherToTokens.length > 0 && (
+ <>
+
+ All swapable tokens
+
+
+ {allOtherToTokens.map((token, idx) => {
+ return (
+ handleSetToToken(swapToToken, token)}
+ alternateBackground={true}
+ />
+ )
+ })}
+
+ >
+ )}
+
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapToTokenSelector.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapToTokenSelector.tsx
new file mode 100644
index 0000000000..e0fe621320
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapToTokenSelector.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import { useDispatch } from 'react-redux'
+
+import { DropDownArrowSvg } from '../icons/DropDownArrowSvg'
+import {
+ getBorderStyleForCoinHover,
+ getMenuItemHoverBgForCoin,
+} from '@/styles/tokens'
+import { setShowSwapToTokenListOverlay } from '@/slices/swapDisplaySlice'
+import { useSwapState } from '@/slices/swap/hooks'
+
+export const SwapToTokenSelector = () => {
+ const dispatch = useDispatch()
+
+ const { swapToToken } = useSwapState()
+
+ const buttonContent = swapToToken ? (
+
+
+
+
+
+
+ {swapToToken?.symbol}
+
+
+
+
+ ) : (
+
+ )
+
+ return (
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapTransactionButton.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapTransactionButton.tsx
new file mode 100644
index 0000000000..baa2654f04
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/SwapTransactionButton.tsx
@@ -0,0 +1,127 @@
+import { useEffect, useMemo, useState } from 'react'
+import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi'
+import { useConnectModal } from '@rainbow-me/rainbowkit'
+
+import { TransactionButton } from '@/components/buttons/TransactionButton'
+import { EMPTY_SWAP_QUOTE, EMPTY_SWAP_QUOTE_ZERO } from '@/constants/swap'
+import { stringToBigInt } from '@/utils/bigint/format'
+import { usePortfolioBalances } from '@/slices/portfolio/hooks'
+import { useSwapState } from '@/slices/swap/hooks'
+import { SWAP_CHAIN_IDS } from '@/constants/existingSwapRoutes'
+
+export const SwapTransactionButton = ({
+ approveTxn,
+ executeSwap,
+ isApproved,
+}) => {
+ const [isConnected, setIsConnected] = useState(false)
+ const { openConnectModal } = useConnectModal()
+
+ const { chain } = useNetwork()
+ const { chains, switchNetwork } = useSwitchNetwork()
+
+ const { isConnected: isConnectedInit } = useAccount({
+ onDisconnect() {
+ setIsConnected(false)
+ },
+ })
+
+ useEffect(() => {
+ setIsConnected(isConnectedInit)
+ }, [isConnectedInit])
+
+ const {
+ swapChainId,
+ swapFromToken,
+ swapToToken,
+ swapFromValue,
+ isLoading,
+ swapQuote,
+ } = useSwapState()
+
+ const balances = usePortfolioBalances()
+ const balancesForChain = balances[swapChainId]
+ const balanceForToken = balancesForChain?.find(
+ (t) => t.tokenAddress === swapFromToken?.addresses[swapChainId]
+ )?.balance
+
+ const sufficientBalance = useMemo(() => {
+ if (!swapChainId || !swapFromToken || !swapToToken) return false
+ return (
+ stringToBigInt(swapFromValue, swapFromToken?.decimals[swapChainId]) <=
+ balanceForToken
+ )
+ }, [balanceForToken, swapFromValue, swapChainId, swapFromToken, swapToToken])
+
+ const isButtonDisabled =
+ isLoading ||
+ swapQuote === EMPTY_SWAP_QUOTE_ZERO ||
+ swapQuote === EMPTY_SWAP_QUOTE ||
+ (isConnected && !sufficientBalance)
+
+ let buttonProperties
+
+ const fromTokenDecimals: number | undefined =
+ swapFromToken && swapFromToken.decimals[swapChainId]
+
+ const fromValueBigInt = useMemo(() => {
+ return fromTokenDecimals
+ ? stringToBigInt(swapFromValue, fromTokenDecimals)
+ : 0
+ }, [swapFromValue, fromTokenDecimals, swapChainId, swapFromToken])
+
+ if (!swapChainId) {
+ buttonProperties = {
+ label: 'Please select Origin network',
+ onClick: null,
+ }
+ } else if (!SWAP_CHAIN_IDS.includes(swapChainId)) {
+ buttonProperties = {
+ label: 'Swaps are not available on this network',
+ onClick: null,
+ }
+ } else if (!swapFromToken) {
+ buttonProperties = {
+ label: `Please select token`,
+ onClick: null,
+ }
+ } else if (!isConnected && fromValueBigInt > 0) {
+ buttonProperties = {
+ label: `Connect Wallet to Swap`,
+ onClick: openConnectModal,
+ }
+ } else if (isConnected && !sufficientBalance) {
+ buttonProperties = {
+ label: 'Insufficient balance',
+ onClick: null,
+ }
+ } else if (chain?.id != swapChainId && fromValueBigInt > 0) {
+ buttonProperties = {
+ label: `Switch to ${chains.find((c) => c.id === swapChainId).name}`,
+ onClick: () => switchNetwork(swapChainId),
+ pendingLabel: 'Switching chains',
+ }
+ } else if (!isApproved) {
+ buttonProperties = {
+ onClick: approveTxn,
+ label: `Approve ${swapFromToken?.symbol}`,
+ pendingLabel: 'Approving',
+ }
+ } else {
+ buttonProperties = {
+ onClick: executeSwap,
+ label: `Swap ${swapFromToken?.symbol} for ${swapToToken?.symbol}`,
+ pendingLabel: 'Swapping',
+ }
+ }
+
+ return (
+ buttonProperties && (
+
+ )
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/components/CloseButton.tsx b/packages/synapse-interface/components/StateManagedSwap/components/CloseButton.tsx
new file mode 100644
index 0000000000..c9a0c25099
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/components/CloseButton.tsx
@@ -0,0 +1,17 @@
+import { XIcon } from '@heroicons/react/outline'
+
+export const CloseButton = ({ onClick }: { onClick: () => void }) => {
+ return (
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/components/SearchResults.tsx b/packages/synapse-interface/components/StateManagedSwap/components/SearchResults.tsx
new file mode 100644
index 0000000000..1f52ec4356
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/components/SearchResults.tsx
@@ -0,0 +1,21 @@
+export const SearchResults = ({
+ searchStr,
+ type,
+}: {
+ searchStr: string
+ type: string
+}) => {
+ return (
+
+ {searchStr ? (
+
+ No other results found for{' '}
+
{searchStr}.
+
+ Want to see a {type} supported on Synapse? Let us know!
+
+
+ ) : null}
+
+ )
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificNetworkButton.tsx b/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificNetworkButton.tsx
new file mode 100644
index 0000000000..f12b7c8b4a
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificNetworkButton.tsx
@@ -0,0 +1,90 @@
+import { useEffect, useRef } from 'react'
+import { CHAINS_BY_ID } from '@constants/chains'
+import Image from 'next/image'
+import {
+ getNetworkHover,
+ getNetworkButtonBorder,
+ getNetworkButtonBorderHover,
+ getNetworkButtonBgClassName,
+ getNetworkButtonBgClassNameActive,
+ getNetworkButtonBorderActive,
+ getMenuItemStyleForChain,
+} from '@/styles/chains'
+
+export const SelectSpecificNetworkButton = ({
+ itemChainId,
+ isCurrentChain,
+ active,
+ onClick,
+ dataId,
+ alternateBackground = false,
+}: {
+ itemChainId: number
+ isCurrentChain: boolean
+ active: boolean
+ onClick: () => void
+ dataId: string
+ alternateBackground?: boolean
+}) => {
+ const ref = useRef(null)
+ const chain = CHAINS_BY_ID[itemChainId]
+
+ useEffect(() => {
+ if (active) {
+ ref?.current?.focus()
+ }
+ }, [active])
+
+ let bgClassName
+
+ if (isCurrentChain) {
+ bgClassName = `
+ ${getNetworkButtonBgClassName(chain.color)}
+ ${getNetworkButtonBorder(chain.color)}
+ bg-opacity-30
+ `
+ }
+
+ return (
+
+ )
+}
+
+function ButtonContent({ chainId }: { chainId: number }) {
+ const chain = CHAINS_BY_ID[chainId]
+
+ return chain ? (
+ <>
+
+
+ >
+ ) : null
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificTokenButton.tsx b/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificTokenButton.tsx
new file mode 100644
index 0000000000..9f56f08a3e
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/components/SelectSpecificTokenButton.tsx
@@ -0,0 +1,207 @@
+import {
+ getBorderStyleForCoin,
+ getBorderStyleForCoinHover,
+ getMenuItemBgForCoin,
+ getMenuItemStyleForCoin,
+} from '@styles/tokens'
+import { memo, useEffect, useRef, useState } from 'react'
+import { Token } from '@/utils/types'
+import { usePortfolioBalances } from '@/slices/portfolio/hooks'
+import { CHAINS_BY_ID } from '@/constants/chains'
+import { useSwapState } from '@/slices/swap/hooks'
+
+const SelectSpecificTokenButton = ({
+ showAllChains,
+ isOrigin,
+ token,
+ active,
+ selectedToken,
+ onClick,
+ alternateBackground = false,
+}: {
+ showAllChains?: boolean
+ isOrigin: boolean
+ token: Token
+ active: boolean
+ selectedToken: Token
+ onClick: () => void
+ alternateBackground?: boolean
+}) => {
+ const ref = useRef(null)
+ const isCurrentlySelected = selectedToken?.routeSymbol === token?.routeSymbol
+ const { swapChainId, swapFromToken, swapToToken } = useSwapState()
+
+ useEffect(() => {
+ if (active) {
+ ref?.current?.focus()
+ }
+ }, [active])
+
+ const chainId = swapChainId
+
+ let bgClassName
+
+ const classNameForMenuItemStyle = getMenuItemStyleForCoin(token?.color)
+
+ if (isCurrentlySelected) {
+ bgClassName = `${getMenuItemBgForCoin(
+ token?.color
+ )} ${getBorderStyleForCoin(token?.color)}`
+ } else {
+ bgClassName = getBorderStyleForCoinHover(token?.color)
+ }
+
+ return (
+
+ )
+}
+
+const ButtonContent = memo(
+ ({
+ token,
+ chainId,
+ isOrigin,
+ showAllChains,
+ }: {
+ token: Token
+ chainId: number
+ isOrigin: boolean
+ showAllChains: boolean
+ }) => {
+ const portfolioBalances = usePortfolioBalances()
+
+ const parsedBalance = portfolioBalances[chainId]?.find(
+ (tb) => tb.token.addresses[chainId] === token.addresses[chainId]
+ )?.parsedBalance
+
+ return (
+
+
+
+ {isOrigin && (
+
+ )}
+
+ )
+ }
+)
+
+const Coin = ({ token, showAllChains }: { token; showAllChains: boolean }) => {
+ return (
+
+
{token?.symbol}
+
+
{token?.name}
+ {showAllChains &&
}
+
+
+ )
+}
+
+const TokenBalance = ({
+ token,
+ chainId,
+ parsedBalance,
+}: {
+ token: Token
+ chainId: number
+ parsedBalance?: string
+}) => {
+ return (
+
+ {parsedBalance && parsedBalance !== '0.0' && (
+
+ {parsedBalance}
+
+ {' '}
+ {token ? token.symbol : ''}
+
+
+ )}
+
+ )
+}
+
+const AvailableChains = ({ token }: { token: Token }) => {
+ const [isHovered, setIsHovered] = useState(false)
+ const chainIds = Object.keys(token.addresses)
+ const hasOneChain = chainIds.length > 0
+ const hasMultipleChains = chainIds.length > 1
+ const numOverTwoChains = chainIds.length - 2 > 0 ? chainIds.length - 2 : 0
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {hasOneChain && (
+
+ )}
+ {hasMultipleChains && (
+
+ )}
+ {numOverTwoChains > 0 && (
+
+ {numOverTwoChains}
+ )}
+
+ {isHovered && (
+
+ {chainIds.map((chainId) => {
+ const chainName = CHAINS_BY_ID[chainId].name
+ return
{chainName}
+ })}
+
+ )}
+
+
+ )
+}
+
+export default SelectSpecificTokenButton
diff --git a/packages/synapse-interface/components/StateManagedSwap/helpers/sortByBalance.ts b/packages/synapse-interface/components/StateManagedSwap/helpers/sortByBalance.ts
new file mode 100644
index 0000000000..8f03dfd6b2
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/helpers/sortByBalance.ts
@@ -0,0 +1,25 @@
+import _ from 'lodash'
+
+import { Token } from '@/utils/types'
+import { NetworkTokenBalancesAndAllowances } from '@/utils/actions/fetchPortfolioBalances'
+
+export const hasBalance = (
+ t: Token,
+ chainId: number,
+ portfolioBalances: NetworkTokenBalancesAndAllowances
+) => {
+ if (!chainId) {
+ return false
+ }
+ const pb = portfolioBalances[chainId]
+ if (!pb) {
+ return false
+ }
+ const token = _(pb)
+ .pickBy((value, _key) => value.token === t)
+ .value()
+
+ const tokenWithPb = Object.values(token)[0]
+
+ return tokenWithPb && tokenWithPb.balance !== 0n
+}
diff --git a/packages/synapse-interface/components/StateManagedSwap/helpers/sortByPriorityRank.ts b/packages/synapse-interface/components/StateManagedSwap/helpers/sortByPriorityRank.ts
new file mode 100644
index 0000000000..57925085b7
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedSwap/helpers/sortByPriorityRank.ts
@@ -0,0 +1,11 @@
+import _ from 'lodash'
+
+import { Token } from '@/utils/types'
+
+export const sortByPriorityRank = (tokens: Token[]) => {
+ return _.orderBy(
+ tokens,
+ [(token) => token.priorityRank, (token) => token.symbol.toLowerCase()],
+ ['asc', 'asc']
+ )
+}
diff --git a/packages/synapse-interface/components/TransactionItems.tsx b/packages/synapse-interface/components/TransactionItems.tsx
deleted file mode 100644
index 05c43d3988..0000000000
--- a/packages/synapse-interface/components/TransactionItems.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { CHAINS_BY_ID } from '@constants/chains'
-import { getNetworkLinkTextColor } from '@styles/chains'
-import { Chain } from '@types'
-
-export const CheckingConfPlaceholder = ({ chain }: { chain: Chain }) => {
- return (
-
-
-
-
-
- Confirmations left on {chain?.name}
-
-
-
-
-
-
- )
-}
-
-export const PendingCreditTransactionItem = ({
- chainId,
-}: {
- chainId: number
-}) => {
- const chain = CHAINS_BY_ID[chainId]
-
- return (
-
-
-
-
-
-
-
-
- Waiting to be credited on
-
- {chain?.name}
-
-
-
-
-
-
- )
-}
-
-export const EmptySubTransactionItem = ({ chainId }: { chainId: number }) => {
- const chain = CHAINS_BY_ID[chainId]
- return (
-
-
-
-
-
- )
-}
-
-export const CreditedTransactionItem = ({ chainId }: { chainId: number }) => {
- const chain = CHAINS_BY_ID[chainId]
- return (
-
-
-
-
-
-
-
-
- Bridging Completed on
-
- {chain?.name}
-
-
-
-
-
-
- )
-}
diff --git a/packages/synapse-interface/components/StateManagedBridge/components/DropDownArrowSvg.tsx b/packages/synapse-interface/components/icons/DropDownArrowSvg.tsx
similarity index 100%
rename from packages/synapse-interface/components/StateManagedBridge/components/DropDownArrowSvg.tsx
rename to packages/synapse-interface/components/icons/DropDownArrowSvg.tsx
diff --git a/packages/synapse-interface/components/input/TokenAmountInput/SelectTokenDropdown.tsx b/packages/synapse-interface/components/input/TokenAmountInput/SelectTokenDropdown.tsx
deleted file mode 100644
index 1c38bcf012..0000000000
--- a/packages/synapse-interface/components/input/TokenAmountInput/SelectTokenDropdown.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { ChevronDownIcon } from '@heroicons/react/outline'
-import React from 'react'
-import Image from 'next/image'
-import {
- getBorderStyleForCoinHover,
- getMenuItemHoverBgForCoin,
-} from '@styles/tokens'
-import { Token } from '@/utils/types'
-import { QuestionMarkCircleIcon } from '@heroicons/react/outline'
-import { CHAINS_BY_ID } from '@/constants/chains'
-import { Chain } from '@/utils/types'
-
-const SelectTokenDropdown = ({
- chainId,
- selectedToken,
- onClick,
- isOrigin,
-}: {
- chainId: number
- selectedToken: Token
- onClick: () => void
- isOrigin: boolean
-}) => {
- const currentChain: Chain = CHAINS_BY_ID[chainId]
- const isUnsupportedChain: boolean = currentChain ? false : true
- const symbol = selectedToken ? selectedToken.symbol : ''
- const dataId = isOrigin ? 'bridge-origin-token' : 'bridge-destination-token'
-
- return (
-
- )
-}
-export default SelectTokenDropdown
diff --git a/packages/synapse-interface/components/input/TokenAmountInput/index.tsx b/packages/synapse-interface/components/input/TokenAmountInput/index.tsx
deleted file mode 100644
index 399f557e0f..0000000000
--- a/packages/synapse-interface/components/input/TokenAmountInput/index.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { formatBigIntToString } from '@/utils/bigint/format'
-import React, { useMemo } from 'react'
-import SwitchButton from '@components/buttons/SwitchButton'
-import MiniMaxButton from '@components/buttons/MiniMaxButton'
-import Spinner from '@/components/icons/Spinner'
-import { cleanNumberInput } from '@utils/cleanNumberInput'
-
-import { Token } from '@/utils/types'
-import { ChainLabel } from '@components/ChainLabel'
-import { DisplayType } from '@/pages/bridge/DisplayType'
-import SelectTokenDropdown from './SelectTokenDropdown'
-
-const BridgeInputContainer = ({
- address,
- isOrigin,
- isSwap,
- chains,
- chainId,
- inputString,
- selectedToken,
- connectedChainId,
- onChangeChain,
- onChangeAmount,
- setDisplayType,
- fromTokenBalance,
- isQuoteLoading = false,
-}: {
- address: `0x${string}`
- isOrigin: boolean
- isSwap: boolean
- chains: string[]
- chainId: number
- inputString: string
- selectedToken: Token
- connectedChainId: number
- setDisplayType: (v: DisplayType) => void
- onChangeAmount?: (v: string) => void
- onChangeChain: (chainId: number, flip: boolean, type: 'from' | 'to') => void
- fromTokenBalance?: bigint
- isQuoteLoading?: boolean
-}) => {
- const formattedBalance = useMemo(() => {
- if (!fromTokenBalance) return '0.0'
- return formatBigIntToString(
- fromTokenBalance,
- selectedToken?.decimals[chainId as keyof Token['decimals']],
- 3
- )
- }, [fromTokenBalance])
-
- const isConnected = address !== null
- const isMaxDisabled = formattedBalance === '0.0'
-
- const onClickBalance = () => {
- onChangeAmount(
- formatBigIntToString(
- fromTokenBalance,
- selectedToken?.decimals[chainId as keyof Token['decimals']]
- )
- )
- }
-
- return (
-
-
-
- {!isOrigin && !isSwap && (
-
-
-
- onChangeChain(chainId, true, isOrigin ? 'from' : 'to')
- }
- />
-
-
- )}
- {!(isSwap && !isOrigin) && (
-
- )}
-
-
-
-
-
{
- setDisplayType(isOrigin ? DisplayType.FROM : DisplayType.TO)
- }}
- />
-
- onChangeAmount(cleanNumberInput(e.target.value))
- : () => null
- }
- value={inputString === '0' ? null : inputString}
- name="inputRow"
- autoComplete="off"
- />
- {isOrigin && isConnected && (
-
- )}
-
- {isOrigin && isConnected && (
-
-
-
- )}
-
-
-
- )
-}
-
-export default BridgeInputContainer
diff --git a/packages/synapse-interface/components/pairTxKappa.tsx b/packages/synapse-interface/components/pairTxKappa.tsx
deleted file mode 100644
index 868a3a8c21..0000000000
--- a/packages/synapse-interface/components/pairTxKappa.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import _ from 'lodash'
-
-function checkTxIn(tx) {
- if (tx.args?.chainId) {
- // if (tx.args?.chainId ?? false) { //
- return true
- } else {
- return false
- }
-}
-
-/**
- * @param {Transaction[]} transactions
- * @return {Transaction[][]}
- */
-export function pairTxKappa(transactions) {
- const transactionsByHash = {}
- for (const tx of transactions) {
- transactionsByHash[tx.transactionHash] = tx
- }
-
- const inputTxns = transactions.filter((tx) => tx.kekTxSig).filter(checkTxIn)
- const outputTxns = transactions
- .filter((tx) => tx.kekTxSig)
- .filter((tx) => !checkTxIn(tx))
-
- const outputTxnsDict = {}
-
- for (const tx of outputTxns) {
- outputTxnsDict[tx.args?.kappa] = tx
- }
-
- const pairSetsByChain = []
-
- for (const inTx of inputTxns) {
- const outTx = outputTxnsDict[inTx.kekTxSig]
- if (outTx) {
- pairSetsByChain.push([inTx, outTx])
- } else {
- pairSetsByChain.push([inTx, undefined])
- }
- }
- const outTxnKeys = pairSetsByChain.map(
- ([inTx, outTx]) => outTx?.transactionHash
- )
- const remainingOuts = outputTxns.filter(
- (tx) => !outTxnKeys.includes(tx.transactionHash)
- )
-
- for (const outTx of remainingOuts) {
- pairSetsByChain.push([undefined, outTx])
- }
-
- return _.sortBy(pairSetsByChain, ([inTx, outTx]) => {
- return -(outTx?.timestamp ?? inTx?.timestamp)
- })
-}
diff --git a/packages/synapse-interface/components/ui/tailwind/Modal.tsx b/packages/synapse-interface/components/ui/tailwind/Modal.tsx
deleted file mode 100644
index 34bb136ba1..0000000000
--- a/packages/synapse-interface/components/ui/tailwind/Modal.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { useEffect } from 'react'
-import { useKeyPress } from '@hooks/useKeyPress'
-
-export default function Modal({
- isOpen,
- onClose,
- children,
-}: {
- isOpen: boolean
- onClose: () => void
- children: any
-}) {
- const escPressed = useKeyPress('Escape')
-
- function escEffect() {
- if (escPressed) {
- onClose()
- }
- }
-
- useEffect(escEffect, [escPressed])
-
- if (isOpen) {
- return (
- <>
-
-
- >
- )
- } else {
- return null
- }
-}
diff --git a/packages/synapse-interface/components/ui/tailwind/ModalHeadline.tsx b/packages/synapse-interface/components/ui/tailwind/ModalHeadline.tsx
deleted file mode 100644
index 20207f107f..0000000000
--- a/packages/synapse-interface/components/ui/tailwind/ModalHeadline.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { twMerge } from 'tailwind-merge'
-
-const baseClassname = `
- mb-3 text-sm text-secondaryTextColor text-opacity-50
-`
-
-export default function ModalHeadline({
- title,
- subtitle,
- onClose,
- titleClassName,
-}: {
- title: string
- subtitle: string
- onClose: any
- titleClassName?: string
-}) {
- const mergedTitleClassName = twMerge(`${baseClassname} ${titleClassName}`)
-
- return (
-
- )
-}
diff --git a/packages/synapse-interface/constants/chains/master.tsx b/packages/synapse-interface/constants/chains/master.tsx
index c01268700c..55f7f24ce0 100644
--- a/packages/synapse-interface/constants/chains/master.tsx
+++ b/packages/synapse-interface/constants/chains/master.tsx
@@ -406,7 +406,7 @@ export const DOGE: Chain = {
}
export const BASE: Chain = {
- priorityRank: 1,
+ priorityRank: 90,
id: 8453,
chainSymbol: 'ETH',
name: 'Base',
diff --git a/packages/synapse-interface/constants/existingSwapRoutes.ts b/packages/synapse-interface/constants/existingSwapRoutes.ts
new file mode 100644
index 0000000000..e7f6a7e7a3
--- /dev/null
+++ b/packages/synapse-interface/constants/existingSwapRoutes.ts
@@ -0,0 +1,52 @@
+import _ from 'lodash'
+import { zeroAddress } from 'viem'
+
+import { BRIDGE_MAP } from './bridgeMap'
+import { findTokenByAddressAndChain } from '@/utils/findTokenByAddressAndChainId'
+import { ETHEREUM_ADDRESS } from '.'
+
+export const FILTERED = _(BRIDGE_MAP)
+ .mapValues((chainObj) => {
+ return _(chainObj)
+ .pickBy(
+ (tokenObj: any) =>
+ Array.isArray(tokenObj.swappable) && tokenObj.swappable.length > 0
+ )
+ .value()
+ })
+ .pickBy((value, _key) => Object.values(value).length > 0)
+ .value()
+
+export const SWAP_CHAIN_IDS = Object.keys(FILTERED).map(Number)
+
+export const EXISTING_SWAP_ROUTES = _(FILTERED)
+ .map((tokens, chainId) => {
+ return _(tokens)
+ .map((info, tokenAddress) => {
+ if (tokenAddress.toLowerCase() === ETHEREUM_ADDRESS.toLowerCase()) {
+ tokenAddress = zeroAddress
+ }
+
+ const symbol = findTokenByAddressAndChain(
+ tokenAddress,
+ chainId
+ )?.routeSymbol
+ const key = `${symbol}-${chainId}`
+ const swappable = info.swappable.map((address) => {
+ if (address.toLowerCase() === ETHEREUM_ADDRESS.toLowerCase()) {
+ address = zeroAddress
+ }
+
+ const symbol = findTokenByAddressAndChain(
+ address,
+ chainId
+ )?.routeSymbol
+ return `${symbol}-${chainId}`
+ })
+ return [key, swappable]
+ })
+ .value()
+ })
+ .flatten()
+ .fromPairs()
+ .value()
diff --git a/packages/synapse-interface/constants/index.ts b/packages/synapse-interface/constants/index.ts
index 6fcfb54de0..85e1dee41c 100644
--- a/packages/synapse-interface/constants/index.ts
+++ b/packages/synapse-interface/constants/index.ts
@@ -1,2 +1,4 @@
export const MAX_UINT256 =
115792089237316195423570985008687907853269984665640564039457584007913129639935n
+
+export const ETHEREUM_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
diff --git a/packages/synapse-interface/constants/tokens/deprecated.ts b/packages/synapse-interface/constants/tokens/deprecated.ts
index b5ab9eae64..e807d1e8e5 100644
--- a/packages/synapse-interface/constants/tokens/deprecated.ts
+++ b/packages/synapse-interface/constants/tokens/deprecated.ts
@@ -1,5 +1,5 @@
-import usdbLogo from '@assets/icons/usdb.png'
-import fusdtLogo from '@assets/icons/fusdt.svg'
+import usdbLogo from '@assets/icons/usdc.svg'
+import fusdtLogo from '@assets/icons/usdt.svg'
import { Token } from '@/utils/types'
import * as CHAINS from '@/constants/chains/master'
diff --git a/packages/synapse-interface/constants/tokens/index.ts b/packages/synapse-interface/constants/tokens/index.ts
index 5fe0d98f28..73e70c077a 100644
--- a/packages/synapse-interface/constants/tokens/index.ts
+++ b/packages/synapse-interface/constants/tokens/index.ts
@@ -16,19 +16,7 @@ interface TokensByChain {
interface TokenByKey {
[cID: string]: Token
}
-interface BridgeChainsByType {
- [swapableType: string]: string[]
-}
-
-interface BridgeTypeByChain {
- [cID: string]: string[]
-}
-interface SwapableTokensByType {
- [cID: string]: {
- [swapableType: string]: Token[]
- }
-}
export const sortTokens = (tokens: Token[]) =>
Object.values(tokens).sort((a, b) => b.visibilityRank - a.visibilityRank)
@@ -81,83 +69,6 @@ const getBridgeableTokens = (): TokensByChain => {
return bridgeableTokens
}
-const getBridgeChainsByType = (): BridgeChainsByType => {
- const bridgeChainsByType: BridgeChainsByType = {}
- Object.entries(all).map(([key, token]) => {
- const swapableType = String(token?.swapableType)
- const keys = Object.keys(token.addresses).filter((cID) => {
- // Skip if the token is paused on the current chain
- if (PAUSED_TOKENS_BY_CHAIN[cID]?.includes(key)) return false
-
- return !bridgeChainsByType[swapableType]?.includes(cID)
- })
-
- if (bridgeChainsByType[swapableType]) {
- bridgeChainsByType[swapableType] = [
- ...bridgeChainsByType[swapableType],
- ...keys,
- ]
- } else {
- bridgeChainsByType[swapableType] = keys
- }
- })
- return bridgeChainsByType
-}
-
-const getBridgeTypeByChain = (): BridgeTypeByChain => {
- const bridgeChainByType = getBridgeChainsByType()
- const bridgeTypeByChain: BridgeTypeByChain = {}
- Object.keys(bridgeChainByType).forEach((key) => {
- bridgeChainByType[key].forEach((value) => {
- if (bridgeTypeByChain[value]) {
- bridgeTypeByChain[value].push(key)
- } else {
- bridgeTypeByChain[value] = [key]
- }
- })
- })
- return bridgeTypeByChain
-}
-
-const convertArrayToObject = (array: any) => {
- return array.reduce((obj: any, value: any) => {
- obj[value] = []
- return obj
- }, {})
-}
-
-const getBridgeableTokensByType = (): SwapableTokensByType => {
- const bridgeTypeByChain = getBridgeTypeByChain()
- const bridgeSwapableTokensByType = Object.fromEntries(
- Object.entries(bridgeTypeByChain).map(([k, v]) => [
- k,
- convertArrayToObject(v),
- ])
- )
-
- Object.entries(all).map(([key, token]) => {
- const swapableType = String(token?.swapableType)
-
- for (const cID of Object.keys(token.addresses)) {
- // Skip if the token is paused on the current chain
- if (PAUSED_TOKENS_BY_CHAIN[cID]?.includes(key)) continue
-
- if (bridgeSwapableTokensByType[cID][swapableType].length === 0) {
- bridgeSwapableTokensByType[cID][swapableType] = [token]
- } else if (
- !bridgeSwapableTokensByType[cID][swapableType]?.includes(token)
- ) {
- bridgeSwapableTokensByType[cID][swapableType] = [
- ...bridgeSwapableTokensByType[cID][swapableType],
- token,
- ]
- }
- }
- })
-
- return bridgeSwapableTokensByType
-}
-
const getTokenHashMap = () => {
const tokenHashMap = {}
@@ -182,9 +93,7 @@ export const TOKENS_SORTED_BY_SYMBOL = Array.from(
new Set(sortedTokens.map((token) => token.symbol))
)
export const BRIDGABLE_TOKENS = getBridgeableTokens()
-export const BRIDGE_CHAINS_BY_TYPE = getBridgeChainsByType()
-export const BRIDGE_TYPES_BY_CHAIN = getBridgeTypeByChain()
-export const BRIDGE_SWAPABLE_TOKENS_BY_TYPE = getBridgeableTokensByType()
+
export const tokenSymbolToToken = (chainId: number, symbol: string) => {
if (chainId) {
const token = BRIDGABLE_TOKENS[chainId].find((token) => {
@@ -213,40 +122,7 @@ export const TOKEN_HASH_MAP = getTokenHashMap()
// SWAPS
const allTokensWithSwap = [...Object.values(all), ...Object.values(allSwap)]
-const getSwapableTokens = (): TokensByChain => {
- const swapTokens: TokensByChain = {}
- allTokensWithSwap.map((token) => {
- if (!(token?.swapableOn?.length > 0)) return
- for (const cID of token.swapableOn) {
- if (!swapTokens[cID]) {
- swapTokens[cID] = [token]
- } else if (!swapTokens[cID]?.includes(token)) {
- swapTokens[cID] = [...swapTokens[cID], token]
- }
- }
- })
- return swapTokens
-}
-const getSwapableTokensByType = (): SwapableTokensByType => {
- const swapTokens: SwapableTokensByType = {}
- allTokensWithSwap.map((token) => {
- if (!(token?.swapableOn?.length > 0)) return
- for (const cID of token.swapableOn) {
- if (!swapTokens[cID]) {
- swapTokens[cID] = { [token.swapableType]: [token] }
- } else if (!swapTokens[cID][token.swapableType]) {
- swapTokens[cID][token.swapableType] = [token]
- } else if (!swapTokens[cID][token.swapableType].includes(token)) {
- swapTokens[cID][token.swapableType] = [
- ...swapTokens[cID][token.swapableType],
- token,
- ]
- }
- }
- })
- return swapTokens
-}
const getSwapPriorityRanking = () => {
const swapPriorityRanking = {}
allTokensWithSwap.map((token) => {
@@ -262,8 +138,6 @@ const getSwapPriorityRanking = () => {
})
return swapPriorityRanking
}
-export const SWAPABLE_TOKENS = getSwapableTokens()
-export const SWAPABLE_TOKENS_BY_TYPE = getSwapableTokensByType()
export const POOL_PRIORITY_RANKING = getSwapPriorityRanking()
// POOLS
diff --git a/packages/synapse-interface/contexts/WalletAnalyticsProvider.tsx b/packages/synapse-interface/contexts/UserProvider.tsx
similarity index 59%
rename from packages/synapse-interface/contexts/WalletAnalyticsProvider.tsx
rename to packages/synapse-interface/contexts/UserProvider.tsx
index f32e9f6a2f..a610ffae42 100644
--- a/packages/synapse-interface/contexts/WalletAnalyticsProvider.tsx
+++ b/packages/synapse-interface/contexts/UserProvider.tsx
@@ -2,14 +2,20 @@ import { createContext, useContext, useEffect, useRef } from 'react'
import { Chain, useAccount, useNetwork } from 'wagmi'
import { segmentAnalyticsEvent } from './SegmentAnalyticsProvider'
import { useRouter } from 'next/router'
+import { setSwapChainId } from '@/slices/swap/reducer'
+
+import { fetchAndStorePortfolioBalances } from '@/slices/portfolio/hooks'
+import { useAppDispatch } from '@/store/hooks'
+import { resetPortfolioState } from '@/slices/portfolio/actions'
const WalletStatusContext = createContext(undefined)
-export const WalletAnalyticsProvider = ({ children }) => {
+export const UserProvider = ({ children }) => {
+ const dispatch = useAppDispatch()
const { chain } = useNetwork()
const router = useRouter()
const { query, pathname } = router
- const { connector } = useAccount({
+ const { address, connector } = useAccount({
onConnect() {
segmentAnalyticsEvent(`[Wallet Analytics] connects`, {
walletId: connector?.id,
@@ -30,10 +36,16 @@ export const WalletAnalyticsProvider = ({ children }) => {
const prevChain = prevChainRef.current
useEffect(() => {
+ if (chain) {
+ dispatch(setSwapChainId(chain.id))
+ }
+
if (!chain) {
return
}
if (prevChain && chain !== prevChain) {
+ dispatch(setSwapChainId(chain.id))
+
segmentAnalyticsEvent(`[Wallet Analytics] connected to new chain`, {
previousNetworkName: prevChain.name,
previousChainId: prevChain.id,
@@ -46,6 +58,22 @@ export const WalletAnalyticsProvider = ({ children }) => {
}
}, [chain])
+ useEffect(() => {
+ ;(async () => {
+ if (address && chain?.id) {
+ try {
+ await dispatch(fetchAndStorePortfolioBalances(address))
+ } catch (error) {
+ console.error('Failed to fetch and store portfolio balances:', error)
+ }
+ }
+
+ if (!address) {
+ dispatch(resetPortfolioState())
+ }
+ })()
+ }, [chain, address])
+
return (
{children}
@@ -53,4 +81,4 @@ export const WalletAnalyticsProvider = ({ children }) => {
)
}
-export const useWalletStatus = () => useContext(WalletStatusContext)
+export const useUserStatus = () => useContext(WalletStatusContext)
diff --git a/packages/synapse-interface/package.json b/packages/synapse-interface/package.json
index 14535c1c28..e8c72c8078 100644
--- a/packages/synapse-interface/package.json
+++ b/packages/synapse-interface/package.json
@@ -1,6 +1,6 @@
{
"name": "@synapsecns/synapse-interface",
- "version": "0.1.132",
+ "version": "0.1.134",
"private": true,
"engines": {
"node": ">=16.0.0"
diff --git a/packages/synapse-interface/pages/_app.tsx b/packages/synapse-interface/pages/_app.tsx
index 19cef6e443..bb2844fed9 100644
--- a/packages/synapse-interface/pages/_app.tsx
+++ b/packages/synapse-interface/pages/_app.tsx
@@ -46,7 +46,7 @@ import { SegmentAnalyticsProvider } from '@/contexts/SegmentAnalyticsProvider'
import { Provider } from 'react-redux'
import { store } from '@/store/store'
-import { WalletAnalyticsProvider } from '@/contexts/WalletAnalyticsProvider'
+import { UserProvider } from '@/contexts/UserProvider'
import PortfolioUpdater from '@/slices/portfolio/updater'
import TransactionsUpdater from '@/slices/transactions/updater'
@@ -146,16 +146,16 @@ const App = ({ Component, pageProps }: AppProps) => {
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/BlockCountdown.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/BlockCountdown.tsx
deleted file mode 100644
index fe604a6721..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/BlockCountdown.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import _ from 'lodash'
-import { useEffect, useState, memo } from 'react'
-import { fetchBlockNumber } from '@wagmi/core'
-import {
- ChevronRightIcon,
- ChevronDoubleRightIcon,
-} from '@heroicons/react/outline'
-import { Arc } from '@visx/shape'
-import { Chord } from '@visx/chord'
-import { BridgeWatcherTx } from '@types'
-import { getNetworkTextColor } from '@styles/chains'
-import { CHAINS_BY_ID } from '@/constants/chains'
-import { BRIDGE_REQUIRED_CONFIRMATIONS } from '@constants/bridge'
-import {
- EmptySubTransactionItem,
- CheckingConfPlaceholder,
-} from '@components/TransactionItems'
-
-const BlockCountdown = memo(
- ({
- fromEvent,
- toEvent,
- setCompletedConf,
- }: {
- fromEvent: BridgeWatcherTx
- toEvent?: BridgeWatcherTx
- setCompletedConf: (bool: boolean) => void
- }) => {
- const chain = fromEvent?.chainId ? CHAINS_BY_ID[fromEvent.chainId] : null
- const [confirmationDelta, setConfirmationDelta] = useState(-1)
- const [time, setTime] = useState(Date.now())
-
- useEffect(() => {
- const interval = setInterval(() => {
- setTime(Date.now())
- }, 5000)
-
- return () => {
- clearInterval(interval)
- }
- }, [])
-
- useEffect(() => {
- if (confirmationDelta === 0 || toEvent) {
- return
- }
- fetchBlockNumber({
- chainId: fromEvent?.chainId,
- }).then((newestBlockNumber) => {
- // if error with rpc getting block number, don't run the following code
- if (!newestBlockNumber) {
- return
- }
- // get number of blocks since from event blocknumber
- const blockDifference = newestBlockNumber - BigInt(fromEvent.blockNumber)
-
- // get number of blocks since event block number - required confirmations
- const blocksSinceConfirmed =
- blockDifference - BigInt(BRIDGE_REQUIRED_CONFIRMATIONS[fromEvent?.chainId])
-
- // if blocks since confirmed is less than 0, thats how many blocks left to confirm
- setConfirmationDelta(
- blocksSinceConfirmed >= 0 ? 0 : Number(blocksSinceConfirmed) * -1
- )
- if (blocksSinceConfirmed >= 0) {
- setCompletedConf(true)
- }
- })
- }, [time])
-
- return (
- <>
-
-
- {fromEvent?.toChainId && confirmationDelta > 0 && (
- <>
-
-
-
-
-
- >
- )}
-
-
- >
- )
- }
-)
-
-const BlockCountdownCircle = ({
- clampedDiff,
- fromChainConfirmations,
- coloring,
-}) => {
- const dataMatrix = [
- [fromChainConfirmations - clampedDiff, 0, 0, 0],
- [clampedDiff, 0, 0, 0],
- ]
- return (
-
- )
-}
-
-export default BlockCountdown
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/BridgeEvent.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/BridgeEvent.tsx
deleted file mode 100644
index 87cb0bb049..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/BridgeEvent.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import EventCard from './EventCard'
-import DestinationTx from './DestinationTx'
-import { BridgeWatcherTx } from '@types'
-import Link from 'next/link'
-import { EXPLORER_KAPPA } from '@urls'
-import { memo } from 'react'
-
-const BridgeEvent = memo((fromEvent: BridgeWatcherTx) => {
- // Saving Event Link for when indexing occurs faster
- const EventLink = (
-
-
- View on Explorer
- {' '}
-
- )
- return (
-
-
-
- {fromEvent && }
-
-
- {fromEvent && }
-
-
- {/* {EventLink} */}
-
- )
-})
-export default BridgeEvent
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/DestinationTx.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/DestinationTx.tsx
deleted file mode 100644
index de6f5087d8..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/DestinationTx.tsx
+++ /dev/null
@@ -1,243 +0,0 @@
-import _ from 'lodash'
-import { useEffect, useState, memo, useMemo } from 'react'
-import { useWalletClient } from 'wagmi'
-import { fetchBlockNumber } from '@wagmi/core'
-import { Contract, Signer } from 'ethers'
-import { Interface } from '@ethersproject/abi'
-import {
- ChevronRightIcon,
- ChevronDoubleRightIcon,
-} from '@heroicons/react/outline'
-import { BridgeWatcherTx } from '@types'
-import { getNetworkTextColor } from '@/styles/chains'
-import SYNAPSE_BRIDGE_ABI from '@abis/synapseBridge.json'
-import { BRIDGE_CONTRACTS } from '@constants/bridge'
-import { CHAINS_BY_ID } from '@/constants/chains'
-import { GETLOGS_SIZE } from '@constants/bridgeWatcher'
-import { useSynapseContext } from '@/utils/providers/SynapseProvider'
-import {
- getLogs,
- getBlock,
- getTransactionReceipt,
- generateBridgeTx,
- checkTxIn,
-} from '@utils/bridgeWatcher'
-import { remove0xPrefix } from '@/utils/remove0xPrefix'
-import EventCard from './EventCard'
-import BlockCountdown from './BlockCountdown'
-import { CreditedTransactionItem } from '@components/TransactionItems'
-import { Address } from 'viem'
-import SYNAPSE_CCTP_ABI from '@abis/synapseCCTP.json'
-import { SYNAPSE_CCTP_CONTRACTS } from '@constants/bridge'
-
-const DestinationTx = (fromEvent: BridgeWatcherTx) => {
- const [toEvent, setToEvent] = useState(undefined)
- const [toSynapseContract, setToSynapseContract] =
- useState(undefined)
- const [toCCTPContract, setToCCTPContract] = useState(undefined)
- const [toSigner, setToSigner] = useState()
- const { data: toSignerRaw } = useWalletClient({
- chainId: fromEvent.toChainId,
- })
- const [completedConf, setCompletedConf] = useState(false)
- const [attempted, setAttempted] = useState(false)
- const { providerMap } = useSynapseContext()
-
- const networkTextColorClass: string = useMemo(() => {
- const networkChainById = CHAINS_BY_ID[fromEvent.chainId]
- return getNetworkTextColor(networkChainById?.color)
- }, [fromEvent.toChainId])
-
- const getToBridgeEvent = async (): Promise => {
- const headOnDestination = await fetchBlockNumber({
- chainId: fromEvent.toChainId,
- })
- const provider = providerMap[fromEvent.toChainId]
-
- const isCCTP =
- typeof fromEvent.contractEmittedFrom === 'string' &&
- typeof SYNAPSE_CCTP_CONTRACTS[fromEvent.chainId] === 'string' &&
- fromEvent.contractEmittedFrom.toLowerCase() ===
- SYNAPSE_CCTP_CONTRACTS[fromEvent.chainId].toLowerCase()
- ? true
- : false
-
- const iface = new Interface(isCCTP ? SYNAPSE_CCTP_ABI : SYNAPSE_BRIDGE_ABI)
-
- let allToEvents = []
- let i = 0
- let afterOrginTx = true
- while (afterOrginTx) {
- const startBlock = Number(headOnDestination) - GETLOGS_SIZE * i
-
- // get timestamp from from block
- const blockRaw = await getBlock(startBlock - (GETLOGS_SIZE + 1), provider)
- const blockTimestamp = blockRaw?.timestamp
-
- // Exit loop if destination block was mined before the block for the origin tx
- if (blockTimestamp < fromEvent.timestamp) {
- afterOrginTx = false
- }
-
- const fromEventsBridge = await getLogs(
- startBlock,
- provider,
- toSynapseContract,
- fromEvent.toAddress
- )
-
- const fromEventsCCTP = await getLogs(
- startBlock,
- provider,
- toCCTPContract,
- fromEvent.toAddress
- )
-
- allToEvents.push(fromEventsBridge, fromEventsCCTP)
- i++
-
- // Break if cannot find tx
- if (i > 30) {
- afterOrginTx = false
- }
- }
- const flattendEvents = _.flatten(allToEvents)
- let parsedLogs
- if (!isCCTP) {
- parsedLogs = flattendEvents
- .map((log) => {
- return {
- ...iface.parseLog(log).args,
- transactionHash: log.transactionHash,
- blockNumber: Number(log.blockNumber),
- }
- })
- .filter((log: any) => {
- const convertedKappa = remove0xPrefix(log.kappa)
- return !checkTxIn(log) && convertedKappa === fromEvent.kappa
- })
- } else {
- parsedLogs = flattendEvents
- .map((log) => {
- return {
- ...iface.parseLog(log).args,
- transactionHash: log.transactionHash,
- blockNumber: Number(log.blockNumber),
- }
- })
- .filter((log: any) => {
- return log.requestID === fromEvent.kappa
- })
- }
-
- const parsedLog = parsedLogs?.[0]
- if (parsedLog) {
- const [inputTimestamp, transactionReceipt] = await Promise.all([
- getBlock(parsedLog.blockNumber, provider),
- getTransactionReceipt(parsedLog.transactionHash, provider),
- ])
-
- const destBridgeTx = generateBridgeTx(
- false,
- fromEvent.toAddress,
- fromEvent.toChainId,
- parsedLog,
- inputTimestamp,
- transactionReceipt,
- fromEvent.toAddress
- )
- setAttempted(true)
- return destBridgeTx
- }
-
- setAttempted(true)
- return null
- }
-
- useEffect(() => {
- if (toSigner && fromEvent) {
- const toSynapseContract = new Contract(
- BRIDGE_CONTRACTS[fromEvent.toChainId],
- SYNAPSE_BRIDGE_ABI,
- providerMap[fromEvent.toChainId]
- )
- setToSynapseContract(toSynapseContract)
-
- // Initialize CCTP Contract when signer and fromEvent are available
- if (SYNAPSE_CCTP_CONTRACTS[fromEvent.toChainId]) {
- const toCCTPContract = new Contract(
- SYNAPSE_CCTP_CONTRACTS[fromEvent.toChainId],
- SYNAPSE_CCTP_ABI,
- providerMap[fromEvent.toChainId]
- )
- setToCCTPContract(toCCTPContract)
- }
- }
- }, [fromEvent, toSigner])
-
- // Listens for confirmations to complete and if so, recheck destination chain for logs
- useEffect(() => {
- if (completedConf && (toSynapseContract || toCCTPContract) && attempted) {
- getToBridgeEvent().then((tx) => {
- setToEvent(tx)
- })
- }
- }, [completedConf, toEvent, fromEvent, toSynapseContract, toCCTPContract])
-
- // Listens for SynapseContract to be set and if so, will check destination chain for logs if there is no toEvent
- useEffect(() => {
- if ((toSynapseContract || toCCTPContract) && !toEvent) {
- getToBridgeEvent().then((tx) => {
- setToEvent(tx)
- return
- })
- }
- return
- }, [toSynapseContract, toCCTPContract])
-
- useEffect(() => {
- if (toSignerRaw != undefined) {
- setToSigner(toSignerRaw.account.address)
- }
- }, [toSignerRaw])
-
- return (
-
-
- {toEvent ? (
-
- ) : (
-
- )}
-
-
- {toEvent ? (
-
-
-
- ) : (
-
-
-
- )}
-
- )
-}
-export default DestinationTx
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/EventCard.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/EventCard.tsx
deleted file mode 100644
index 75e9f0eaa7..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/EventCard.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { BridgeWatcherTx } from '@types'
-import { CHAINS_BY_ID } from '@constants/chains'
-import { ETH } from '@constants/tokens/bridgeable'
-import ExplorerLink from './ExplorerLink'
-import { getNetworkLinkTextColor } from '@styles/chains'
-import { AddToWalletMiniButton } from '@components/buttons/AddToWalletButton'
-import { getCoinTextColorCombined } from '@styles/tokens'
-import { memo } from 'react'
-import { formatTimestampToDate } from '@utils/time'
-import { commify, formatBigIntToString } from '@/utils/bigint/format'
-
-const EventCard = memo((event: BridgeWatcherTx) => {
- const chain = CHAINS_BY_ID[event.chainId]
- let showAddBtn
- if (event.token?.symbol == ETH.symbol) {
- showAddBtn = false
- } else {
- showAddBtn = true
- }
-
- return (
- <>
-
- {event?.timestamp && formatTimestampToDate(event.timestamp)}
-
-
-
-
-
-
-
-
- {event && (
-
- )}
-
-
-
-
-
- {event?.amount
- ? commify(
- formatBigIntToString(
- BigInt(event.amount.toString()),
- event.token?.decimals[event.chainId],
- 8
- )
- )
- : '0'}
-
-
- {event.token && (
- <>
-
- {' '}
- {event.token.symbol}{' '}
-
-
- >
- )}
-
-
-
-
- {showAddBtn && event.isFrom && (
-
- )}
-
-
- >
- )
-})
-
-export default EventCard
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/ExplorerLink.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/ExplorerLink.tsx
deleted file mode 100644
index 2e610abc26..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/ExplorerLink.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ExternalLinkIcon } from '@heroicons/react/outline'
-import { getExplorerTxUrl } from '@urls'
-
-const ExplorerLink = ({
- transactionHash,
- chainId,
- className,
- overrideExistingClassname = false,
- showIcon = false,
-}) => {
- const explorerTxUrl = getExplorerTxUrl({ hash: transactionHash, chainId })
- const len = transactionHash?.length
-
- return (
-
- {transactionHash?.slice(0, 6)}...{transactionHash?.slice(len - 4, len)}
- {showIcon && }
-
- )
-}
-export default ExplorerLink
diff --git a/packages/synapse-interface/pages/bridge/BridgeWatcher/index.tsx b/packages/synapse-interface/pages/bridge/BridgeWatcher/index.tsx
deleted file mode 100644
index 4979acb4e6..0000000000
--- a/packages/synapse-interface/pages/bridge/BridgeWatcher/index.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-import { fetchBlockNumber } from '@wagmi/core'
-import { useWalletClient, useAccount } from 'wagmi'
-import SYNAPSE_BRIDGE_ABI from '@abis/synapseBridge.json'
-import { Contract, Signer } from 'ethers'
-import { BRIDGE_CONTRACTS, SYNAPSE_CCTP_CONTRACTS } from '@constants/bridge'
-import { useEffect, useState } from 'react'
-import { Interface } from '@ethersproject/abi'
-import _ from 'lodash'
-import Grid from '@tw/Grid'
-import Card from '@tw/Card'
-import BridgeEvent from './BridgeEvent'
-import { BridgeWatcherTx } from '@types'
-import { GETLOGS_SIZE, GETLOGS_REQUEST_COUNT } from '@constants/bridgeWatcher'
-import { useSynapseContext } from '@/utils/providers/SynapseProvider'
-import { useSelector } from 'react-redux'
-import { RootState } from '@/store/store'
-import { Address } from 'viem'
-import { walletClientToSigner } from '@/ethers'
-import SYNAPSE_CCTP_ABI from '@abis/synapseCCTP.json'
-import * as CHAINS from '@constants/chains/master'
-import { ChainId } from '@/constants/chains'
-
-import {
- getLogs,
- getBlock,
- getTransactionReceipt,
- generateBridgeTx,
- checkTxIn,
-} from '@utils/bridgeWatcher'
-
-const BridgeWatcher = ({
- fromChainId,
- toChainId,
- address,
- destinationAddress,
-}: {
- fromChainId: number
- toChainId: number
- address: string
- destinationAddress: string
-}) => {
- const bridgeTxHashes = useSelector((state: RootState) => state.bridge)
- const [fromTransactions, setFromTransactions] = useState([])
- const [fromSynapseContract, setFromSynapseContract] = useState()
- const [fromCCTPContract, setFromCCTPContract] = useState()
-
- const [fromSigner, setFromSigner] = useState()
- const { address: fromSignerRaw, isConnecting, isDisconnected } = useAccount()
- const { providerMap } = useSynapseContext()
-
- const createContractsAndInterfaces = (chainId, provider) => {
- const bridgeAddress = BRIDGE_CONTRACTS[chainId]
- const synapseCCTPAddress = SYNAPSE_CCTP_CONTRACTS[chainId]
-
- const validBridgeContract = BRIDGE_CONTRACTS[fromChainId]
- ? BRIDGE_CONTRACTS[fromChainId]
- : BRIDGE_CONTRACTS[CHAINS.ETH.id]
- const bridgeContract = new Contract(
- validBridgeContract,
- SYNAPSE_BRIDGE_ABI,
- provider
- )
-
- const bridgeInterface = new Interface(SYNAPSE_BRIDGE_ABI)
-
- const synapseCCTPContract = synapseCCTPAddress
- ? new Contract(synapseCCTPAddress, SYNAPSE_CCTP_ABI, provider)
- : null
-
- const synapseCCTPInterface = synapseCCTPAddress
- ? new Interface(SYNAPSE_CCTP_ABI)
- : null
-
- return {
- bridgeContract,
- bridgeInterface,
- synapseCCTPContract,
- synapseCCTPInterface,
- }
- }
-
- const fetchFromBridgeEvents = async (
- currentFromBlock: number,
- provider: any,
- adjustedAddress: string
- ) => {
- const {
- bridgeContract,
- bridgeInterface,
- synapseCCTPContract,
- synapseCCTPInterface,
- } = createContractsAndInterfaces(provider.network.chainId, provider)
- let allFromEvents = []
- let retryCount = 0
- const maxRetries = 5 // Adjust this as needed
-
- // fetch bridge logs
- for (let i = 0; i < GETLOGS_REQUEST_COUNT; i++) {
- let successful = false
- while (!successful && retryCount < maxRetries) {
- try {
- const fromEvents = await getLogs(
- currentFromBlock - GETLOGS_SIZE * i,
- provider,
- bridgeContract,
- adjustedAddress
- )
- allFromEvents.push(fromEvents)
- successful = true
- } catch (error) {
- retryCount++
- console.log(
- `getLogs failed, retrying in ${Math.pow(2, retryCount)} seconds...`
- )
- await new Promise((resolve) =>
- setTimeout(resolve, Math.pow(2, retryCount) * 1000)
- )
- }
- }
- if (retryCount === maxRetries) {
- console.error('getLogs failed after maximum retries')
- break
- }
- }
- // fetch synapseCCTP logs if the contract exists for the chain
- if (synapseCCTPContract) {
- for (let i = 0; i < GETLOGS_REQUEST_COUNT; i++) {
- let successful = false
- while (!successful && retryCount < maxRetries) {
- try {
- const fromEvents = await getLogs(
- currentFromBlock - GETLOGS_SIZE * i,
- provider,
- synapseCCTPContract,
- adjustedAddress
- )
- allFromEvents.push(fromEvents)
- successful = true
- } catch (error) {
- retryCount++
- console.log(
- `getLogs failed, retrying in ${Math.pow(
- 2,
- retryCount
- )} seconds...`
- )
- await new Promise((resolve) =>
- setTimeout(resolve, Math.pow(2, retryCount) * 1000)
- )
- }
- }
- if (retryCount === maxRetries) {
- console.error('getLogs failed after maximum retries')
- break
- }
- }
- }
-
- return _.flatten(allFromEvents)
- }
-
- const parseLogs = (
- fromEvents: any[],
- bridgeInterface: Interface,
- synapseCCTPInterface: Interface,
- bridgeAddress: string,
- synapseCCTPAddress?: string
- ) => {
- return fromEvents
- .map((log) => {
- // Select the correct interface based on the contract address
- const iface =
- log.address.toLowerCase() === bridgeAddress.toLowerCase()
- ? bridgeInterface
- : synapseCCTPInterface
-
- return {
- ...iface.parseLog(log).args,
- transactionHash: log.transactionHash,
- blockNumber: Number(log.blockNumber),
- contractEmittedFrom: log.address.toLowerCase(),
- }
- })
- .filter((log) => checkTxIn(log))
- }
-
- const fetchTimestampsAndReceipts = (parsedLogs: any[], provider: any) => {
- return Promise.all([
- Promise.all(parsedLogs.map((log) => getBlock(log.blockNumber, provider))),
- Promise.all(
- parsedLogs.map((log) =>
- getTransactionReceipt(log.transactionHash, provider)
- )
- ),
- ])
- }
-
- const generateBridgeTransactions = (
- parsedLogs: any[],
- inputTimestamps: any[],
- transactionReceipts: any[],
- address: string,
- fromChainId: number,
- destinationAddress: string
- ) => {
- return _.zip(parsedLogs, inputTimestamps, transactionReceipts).map(
- ([parsedLog, timestampObj, txReceipt]) => {
- return generateBridgeTx(
- true,
- address,
- fromChainId,
- parsedLog,
- timestampObj,
- txReceipt,
- destinationAddress
- )
- }
- )
- }
-
- const getFromBridgeEvents = async (): Promise => {
- const currentFromBlock = await fetchBlockNumber({ chainId: fromChainId })
- const provider = providerMap[fromChainId] ?? providerMap[ChainId.ETH]
- const iface = new Interface(SYNAPSE_BRIDGE_ABI)
- const adjustedAddress = destinationAddress ? destinationAddress : address
-
- // Define the contracts and interfaces here
- const {
- bridgeContract,
- bridgeInterface,
- synapseCCTPContract,
- synapseCCTPInterface,
- } = createContractsAndInterfaces(provider?.network?.chainId, provider)
-
- const fromEvents = await fetchFromBridgeEvents(
- Number(currentFromBlock),
- provider,
- adjustedAddress
- )
- // Use the correct interfaces and addresses when parsing the logs
- const parsedLogs = parseLogs(
- fromEvents,
- bridgeInterface,
- synapseCCTPInterface,
- bridgeContract.address,
- synapseCCTPContract?.address
- )
-
- const [inputTimestamps, transactionReceipts] =
- await fetchTimestampsAndReceipts(parsedLogs, provider)
- const txObjects = generateBridgeTransactions(
- parsedLogs,
- inputTimestamps,
- transactionReceipts,
- address,
- fromChainId,
- destinationAddress
- )
-
- return txObjects
- }
-
- useEffect(() => {
- if (fromSigner && fromChainId && toChainId && address) {
- const validBridgeContract = BRIDGE_CONTRACTS[fromChainId]
- ? BRIDGE_CONTRACTS[fromChainId]
- : BRIDGE_CONTRACTS[1]
- const fromSynapseContract = new Contract(
- validBridgeContract,
- SYNAPSE_BRIDGE_ABI,
- providerMap[fromChainId]
- )
- setFromSynapseContract(fromSynapseContract)
- }
- }, [fromChainId, fromSigner])
-
- useEffect(() => {
- if (fromSynapseContract) {
- getFromBridgeEvents().then((txs) => {
- setFromTransactions(txs)
- })
- }
-
- return () => setFromTransactions([...fromTransactions])
- }, [fromSynapseContract, bridgeTxHashes])
-
- useEffect(() => {
- setFromSigner(fromSignerRaw)
- }, [fromSignerRaw])
-
- return (
-
- {fromTransactions?.length > 0 && (
-
-
- {fromTransactions.map((fromEvent, i) => {
- return
- })}
-
-
- )}
-
- )
-}
-
-export default BridgeWatcher
diff --git a/packages/synapse-interface/pages/state-managed-bridge/index.tsx b/packages/synapse-interface/pages/state-managed-bridge/index.tsx
index f3a0aa165a..b5da17b74c 100644
--- a/packages/synapse-interface/pages/state-managed-bridge/index.tsx
+++ b/packages/synapse-interface/pages/state-managed-bridge/index.tsx
@@ -3,7 +3,6 @@ import { useSelector } from 'react-redux'
import { RootState } from '../../store/store'
import toast from 'react-hot-toast'
import { animated } from 'react-spring'
-import BridgeWatcher from '@/pages/bridge/BridgeWatcher'
import { useRouter } from 'next/router'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
@@ -562,14 +561,6 @@ const StateManagedBridge = () => {
- {/*
-
-
*/}
)
}
diff --git a/packages/synapse-interface/pages/swap/NoSwapCard.tsx b/packages/synapse-interface/pages/swap/NoSwapCard.tsx
deleted file mode 100644
index 0fcb7a59d9..0000000000
--- a/packages/synapse-interface/pages/swap/NoSwapCard.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import Card from '@tw/Card'
-import { CHAINS_BY_ID } from '@constants/chains'
-import { getNetworkTextColor } from '@styles/chains'
-import { useMemo } from 'react'
-
-const NoSwapCard = ({ chainId }: { chainId: number }) => {
- const chain = useMemo(() => CHAINS_BY_ID[chainId], [chainId])
- return (
-
-
- No swaps available on{' '}
-
- {chain?.name ?? 'current network'}
-
-
-
- )
-}
-
-export default NoSwapCard
diff --git a/packages/synapse-interface/pages/swap/SwapCard.tsx b/packages/synapse-interface/pages/swap/SwapCard.tsx
deleted file mode 100644
index a0d87cc686..0000000000
--- a/packages/synapse-interface/pages/swap/SwapCard.tsx
+++ /dev/null
@@ -1,844 +0,0 @@
-import Grid from '@tw/Grid'
-import { useEffect, useState, useMemo, useCallback } from 'react'
-import { useRouter } from 'next/router'
-import { getWalletClient, switchNetwork } from '@wagmi/core'
-import { Address } from 'wagmi'
-import { sortByTokenBalance, sortByVisibilityRank } from '@utils/sortTokens'
-import { calculateExchangeRate } from '@utils/calculateExchangeRate'
-import SwapExchangeRateInfo from '@components/SwapExchangeRateInfo'
-import { TransactionButton } from '@/components/buttons/TransactionButton'
-import BridgeInputContainer from '../../components/input/TokenAmountInput/index'
-import { approveToken } from '@/utils/approveToken'
-import { validateAndParseAddress } from '@utils/validateAndParseAddress'
-import { commify } from '@ethersproject/units'
-import { subtractSlippage } from '@utils/slippage'
-import { ChainSlideOver } from '@/components/misc/ChainSlideOver'
-import { TokenSlideOver } from '@/components/misc/TokenSlideOver'
-import { Token } from '@/utils/types'
-import { SWAP_PATH } from '@/constants/urls'
-import { stringToBigInt } from '@/utils/bigint/format'
-import { useSynapseContext } from '@/utils/providers/SynapseProvider'
-import { checkStringIfOnlyZeroes } from '@/utils/regex'
-import { timeout } from '@/utils/timeout'
-import { Transition } from '@headlessui/react'
-import { COIN_SLIDE_OVER_PROPS } from '@styles/transitions'
-import Card from '@tw/Card'
-import { SwapQuote, Query } from '@types'
-import { IMPAIRED_CHAINS } from '@/constants/impairedChains'
-import { CHAINS_BY_ID } from '@constants/chains'
-import { toast } from 'react-hot-toast'
-import { txErrorHandler } from '@/utils/txErrorHandler'
-import ExplorerToastLink from '@/components/ExplorerToastLink'
-import { zeroAddress } from 'viem'
-
-import {
- DEFAULT_FROM_TOKEN,
- DEFAULT_TO_TOKEN,
- EMPTY_SWAP_QUOTE,
- QUOTE_POLLING_INTERVAL,
- EMPTY_SWAP_QUOTE_ZERO,
-} from '@/constants/swap'
-import {
- SWAPABLE_TOKENS,
- BRIDGE_SWAPABLE_TOKENS_BY_TYPE,
- SWAPABLE_TOKENS_BY_TYPE,
- tokenSymbolToToken,
-} from '@constants/tokens'
-import { formatBigIntToString } from '@/utils/bigint/format'
-import { getErc20TokenAllowance } from '@/actions/getErc20TokenAllowance'
-
-const SwapCard = ({
- address,
- connectedChainId,
-}: {
- address: Address | undefined
- connectedChainId: number
-}) => {
- const router = useRouter()
- const { synapseSDK } = useSynapseContext()
- const [time, setTime] = useState(Date.now())
- const [fromToken, setFromToken] = useState(DEFAULT_FROM_TOKEN)
- const [fromTokens, setFromTokens] = useState([])
- const [fromInput, setFromInput] = useState({ string: '', bigInt: 0n })
- const [toToken, setToToken] = useState(DEFAULT_TO_TOKEN)
- const [toTokens, setToTokens] = useState([]) //add default
- const [isQuoteLoading, setIsQuoteLoading] = useState(false)
- const [error, setError] = useState(undefined)
- const [destinationAddress, setDestinationAddress] = useState('')
- const [swapQuote, setSwapQuote] = useState(EMPTY_SWAP_QUOTE)
- const [displayType, setDisplayType] = useState(undefined)
- const [fromTokenBalance, setFromTokenBalance] = useState(0n)
- const [validChainId, setValidChainId] = useState(true)
- const [swapTxnHash, setSwapTxnHash] = useState('')
- const [approveTx, setApproveTx] = useState(null)
-
- let pendingPopup: any
- let successPopup: any
- let errorPopup: string
-
- /*
- useEffect Trigger: onMount
- - Gets current network connected and sets it as the state.
- - Initializes polling (setInterval) func to re-retrieve quotes.
- */
- useEffect(() => {
- const interval = setInterval(
- () => setTime(Date.now()),
- QUOTE_POLLING_INTERVAL
- )
- return () => {
- clearInterval(interval)
- }
- }, [])
-
- /*
- useEffect Trigger: fromInput
- - Resets approve txn status if user input changes after amount is approved
- */
-
- useEffect(() => {
- if (approveTx) {
- setApproveTx(null)
- }
- }, [fromInput])
-
- /*
- useEffect Trigger: fromToken, fromTokens
- - When either the from token or list of from tokens are mutated, the selected token's balance is set in state
- this is for checking max bridge possible as well as for producing the option to select max bridge
- */
- useEffect(() => {
- if (fromTokens && fromToken) {
- setFromTokenBalance(
- fromTokens.filter((token) => token.token === fromToken)[0]?.balance
- ? fromTokens.filter((token) => token.token === fromToken)[0]?.balance
- : 0n
- )
- }
- }, [fromToken, fromTokens])
-
- useEffect(() => {
- if (!router.isReady || !SWAPABLE_TOKENS[connectedChainId]) {
- return
- }
- const {
- inputCurrency: fromTokenSymbolUrl,
- outputCurrency: toTokenSymbolUrl,
- } = router.query
-
- let tempFromToken: Token = getMostCommonSwapableType(connectedChainId)
-
- if (fromTokenSymbolUrl) {
- let token = tokenSymbolToToken(
- connectedChainId,
- String(fromTokenSymbolUrl)
- )
- if (token) {
- tempFromToken = token
- }
- }
- const { swapableToken, swapableTokens } = handleNewFromToken(
- tempFromToken,
- toTokenSymbolUrl ? String(toTokenSymbolUrl) : undefined,
- connectedChainId
- )
- resetTokenPermutation(
- tempFromToken,
- swapableToken,
- swapableTokens,
- tempFromToken.symbol,
- swapableToken.symbol
- )
- updateUrlParams({
- inputCurrency: fromToken.symbol,
- outputCurrency: swapableToken.symbol,
- })
- }, [router.isReady])
-
- /*
- useEffect Trigger: connectedChain
- - when the connected chain changes (wagmi hook), update the state
- */
- useEffect(() => {
- if (address === undefined) {
- return
- }
- handleChainChange(connectedChainId, undefined, undefined)
-
- sortByTokenBalance(
- SWAPABLE_TOKENS[connectedChainId],
- connectedChainId,
- address
- ).then((tokens) => {
- setFromTokens(tokens)
- })
- return
- }, [connectedChainId, swapTxnHash, address])
-
- /*
- useEffect Triggers: toToken, fromInput, toChainId, time
- - Gets a quote when the polling function is executed or any of the bridge attributes are altered.
- - Debounce quote call by calling quote price AFTER user has stopped typing for 1s or 1000ms
- */
- useEffect(() => {
- let isCancelled = false
-
- const handleChange = async () => {
- // await timeout(1000)
- if (
- connectedChainId &&
- String(fromToken.addresses[connectedChainId]) &&
- fromInput &&
- fromInput.bigInt > 0n
- ) {
- // TODO this needs to be debounced or throttled somehow to prevent spam and lag in the ui
- getQuote()
- } else {
- setSwapQuote(EMPTY_SWAP_QUOTE)
- }
- }
- handleChange()
-
- return () => {
- isCancelled = true
- }
- }, [toToken, fromInput, time, connectedChainId])
-
- /*
- Helper Function: resetTokenPermutation
- - Handles when theres a new from token/chain and all other parts of the bridge arrangement needs to be updated
- - Updates url params.
- */
- const resetTokenPermutation = (
- newFromToken: Token,
- newToToken: Token,
- newSwapableTokens: Token[],
- newFromTokenSymbol: string,
- newSwapableTokenSymbol: string
- ) => {
- setFromToken(newFromToken)
- setToToken(newToToken)
- setToTokens(newSwapableTokens)
- // resetRates()
- updateUrlParams({
- inputCurrency: newFromTokenSymbol,
- outputCurrency: newSwapableTokenSymbol,
- })
- }
-
- /*
- Helper Function: resetRates
- - Called when switching from chain/token so that the from input isn't populated with stale data.
- */
- const resetRates = () => {
- setSwapQuote(EMPTY_SWAP_QUOTE)
- setFromInput({ string: '', bigInt: 0n })
- }
-
- /*
- Helper Function: onChangeFromAmount
- - Ensures inputted data isn't too long and then sets state with the input.
- - Calculates BigNum from the input and stores in state as well (for quotes)
- */
- const onChangeFromAmount = (value: string) => {
- if (
- !(
- value.split('.')[1]?.length >
- fromToken[connectedChainId as keyof Token['decimals']]
- )
- ) {
- let bigInt =
- stringToBigInt(value, fromToken.decimals[connectedChainId]) ?? 0n
- setFromInput({
- string: value,
- bigInt: bigInt,
- })
- }
- }
-
- /*
- Helper Function: getMostCommonSwapableType
- - Returns the default token to display when switching chains. Usually returns stables or eth/wrapped eth.
- */
- const getMostCommonSwapableType = (chainId: number) => {
- const fromChainTokensByType = Object.values(
- SWAPABLE_TOKENS_BY_TYPE[chainId]
- )
- let maxTokenLength = 0
- let mostCommonSwapableType: Token[] = fromChainTokensByType[0]
- fromChainTokensByType.map((tokenArr, i) => {
- if (tokenArr.length > maxTokenLength) {
- maxTokenLength = tokenArr.length
- mostCommonSwapableType = tokenArr
- }
- })
-
- return sortByVisibilityRank(mostCommonSwapableType)[0]
- }
-
- /*
- Helper Function: updateUrlParams
- - Pushes chain and token changes to url
- NOTE: did not alter any variable names in case previous users have saved links of different bridging permutations.
- */
- const updateUrlParams = ({
- inputCurrency,
- outputCurrency,
- }: {
- inputCurrency: string
- outputCurrency: string
- }) => {
- router.replace(
- {
- pathname: SWAP_PATH,
- query: {
- inputCurrency,
- outputCurrency,
- },
- },
- undefined,
- { shallow: true }
- )
- }
-
- /*
- Function: handleNewFromToken
- - Handles all the changes that occur when selecting a new "from token", such as generating lists of potential chains/tokens
- to bridge to and handling if the current "to chain/token" are incompatible.
- */
- const handleNewFromToken = (
- token: Token,
- positedToSymbol: string | undefined,
- fromChainId: number
- ) => {
- const swapExceptionsArr: number[] =
- token?.swapExceptions?.[fromChainId as keyof Token['swapExceptions']]
-
- const positedToToken = positedToSymbol
- ? tokenSymbolToToken(fromChainId, positedToSymbol)
- : tokenSymbolToToken(fromChainId, token.symbol)
-
- let swapableTokens: Token[] = sortByVisibilityRank(
- BRIDGE_SWAPABLE_TOKENS_BY_TYPE[fromChainId][String(token.swapableType)]
- ).filter((toToken) => toToken !== token)
- if (swapExceptionsArr?.length > 0) {
- swapableTokens = swapableTokens.filter(
- (toToken) => toToken.symbol === token.symbol
- )
- }
- let swapableToken: Token = positedToToken
- if (!swapableTokens.includes(positedToToken)) {
- swapableToken = swapableTokens[0]
- }
-
- return {
- swapableToken,
- swapableTokens,
- }
- }
-
- /*
- useEffect triggers: address, popup
- - will dismiss toast asking user to connect wallet once wallet has been connected
- */
- useEffect(() => {
- if (address && errorPopup) {
- toast.dismiss(errorPopup)
- }
- }, [address, errorPopup])
-
- /*
- Function: handleChainChange
- - Produces and alert if chain not connected (upgrade to toaster)
- - Handles flipping to and from chains if flag is set to true
- - Handles altering the chain state for origin or destination depending on the type specified.
- */
- const handleChainChange = useCallback(
- async (chainId: number, flip: boolean, type: 'from' | 'to') => {
- if (address === undefined) {
- errorPopup = toast.error('Please connect your wallet', {
- id: 'bridge-connect-wallet',
- duration: 20000,
- })
- return errorPopup
- }
- const desiredChainId = Number(chainId)
-
- const res = await switchNetwork({ chainId: desiredChainId })
- .then((res) => {
- if (fromInput.string !== '') {
- setIsQuoteLoading(true)
- }
- return res
- })
- .catch((error) => {
- return error && undefined
- })
-
- if (res === undefined) {
- console.log("can't switch network, chainId: ", chainId)
- return
- }
- if (!SWAPABLE_TOKENS[desiredChainId]) {
- return
- }
- setValidChainId(true)
-
- const swapableFromTokens: Token[] = sortByVisibilityRank(
- BRIDGE_SWAPABLE_TOKENS_BY_TYPE[chainId][String(fromToken.swapableType)]
- )
- let tempFromToken: Token = fromToken
-
- if (swapableFromTokens?.length > 0) {
- tempFromToken = getMostCommonSwapableType(chainId)
- }
- const { swapableToken, swapableTokens } = handleNewFromToken(
- tempFromToken,
- toToken.symbol,
- desiredChainId
- )
- resetTokenPermutation(
- tempFromToken,
- swapableToken,
- swapableTokens,
- tempFromToken.symbol,
- swapableToken?.symbol
- )
- return
- },
- [
- fromToken,
- toToken,
- connectedChainId,
- address,
- isQuoteLoading,
- handleNewFromToken,
- switchNetwork,
- ]
- )
-
- /*
- Function:handleTokenChange
- - Handles when the user selects a new token from either the origin or destination
- */
- const handleTokenChange = (token: Token, type: 'from' | 'to') => {
- switch (type) {
- case 'from':
- const { swapableToken, swapableTokens } = handleNewFromToken(
- token,
- toToken.symbol,
- connectedChainId
- )
- resetTokenPermutation(
- token,
- swapableToken,
- swapableTokens,
- token.symbol,
- swapableToken.symbol
- )
- if (fromInput.string !== '') {
- setIsQuoteLoading(true)
- }
- return
- case 'to':
- setToToken(token)
- if (fromInput.string !== '') {
- setIsQuoteLoading(true)
- }
- updateUrlParams({
- inputCurrency: fromToken.symbol,
- outputCurrency: token.symbol,
- })
- return
- }
- }
-
- /*
- Function: getQuote
- - Gets quote data from the Synapse SDK (from the imported provider)
- - Calculates slippage by subtracting fee from input amount (checks to ensure proper num of decimals are in use - ask someone about stable swaps if you want to learn more)
- */
- const getQuote = async () => {
- try {
- if (swapQuote === EMPTY_SWAP_QUOTE) {
- setIsQuoteLoading(true)
- }
- const { routerAddress, maxAmountOut, query } = await synapseSDK.swapQuote(
- connectedChainId,
- fromToken.addresses[connectedChainId],
- toToken.addresses[connectedChainId],
- fromInput.bigInt
- )
- if (!(query && maxAmountOut)) {
- setSwapQuote(EMPTY_SWAP_QUOTE_ZERO)
- setIsQuoteLoading(false)
- return
- }
-
- const toValueBigInt = BigInt(maxAmountOut.toString()) ?? 0n
-
- const allowance =
- fromToken.addresses[connectedChainId] === zeroAddress ||
- address === undefined
- ? 0n
- : await getErc20TokenAllowance({
- address,
- chainId: connectedChainId,
- tokenAddress: fromToken.addresses[connectedChainId] as Address,
- spender: routerAddress,
- })
-
- const minWithSlippage = subtractSlippage(
- query?.minAmountOut ?? 0n,
- 'ONE_TENTH',
- null
- )
- // TODO 1) make dynamic 2) clean up
- let newOriginQuery = { ...query }
- newOriginQuery.minAmountOut = minWithSlippage
-
- setSwapQuote({
- outputAmount: toValueBigInt,
- outputAmountString: commify(
- formatBigIntToString(
- toValueBigInt,
- toToken.decimals[connectedChainId],
- 8
- )
- ),
- routerAddress,
- allowance: BigInt(allowance.toString()),
- exchangeRate: calculateExchangeRate(
- fromInput.bigInt - 0n, // this needs to be changed once we can get fee data from router.
- fromToken.decimals[connectedChainId],
- toValueBigInt,
- toToken.decimals[connectedChainId]
- ),
- delta: toValueBigInt,
- quote: newOriginQuery,
- })
- setIsQuoteLoading(false)
- return
- } catch (error) {
- setIsQuoteLoading(false)
- console.log(`Quote failed with error: ${error}`)
- return
- }
- }
-
- /*
- Function: approveToken
- - Gets raw unsigned tx data from sdk and then execute it with ethers.
- - Only executes if token has already been approved.
- */
- const executeSwap = async () => {
- const currentChainName = CHAINS_BY_ID[connectedChainId]?.name
- pendingPopup = toast(
- `Initiating swap from ${fromToken.symbol} to ${toToken.symbol} on ${currentChainName}`,
- { id: 'swap-in-progress-popup', duration: Infinity }
- )
-
- try {
- const wallet = await getWalletClient({
- chainId: connectedChainId,
- })
-
- const data = await synapseSDK.swap(
- connectedChainId,
- address,
- fromToken.addresses[connectedChainId],
- fromInput.bigInt,
- swapQuote.quote
- )
-
- const payload =
- fromToken.addresses[connectedChainId as keyof Token['addresses']] ===
- zeroAddress ||
- fromToken.addresses[connectedChainId as keyof Token['addresses']] === ''
- ? { data: data.data, to: data.to, value: fromInput.bigInt }
- : data
-
- const tx = await wallet.sendTransaction(payload)
-
- try {
- const successTx = await tx
-
- setSwapTxnHash(successTx)
-
- toast.dismiss(pendingPopup)
-
- console.log(`Transaction mined successfully: ${tx}`)
-
- const successToastContent = (
-
-
- Successfully swapped from {fromToken.symbol} to {toToken.symbol}{' '}
- on {currentChainName}
-
-
-
- )
-
- successPopup = toast.success(successToastContent, {
- id: 'swap-successful-popup',
- duration: 10000,
- })
-
- resetRates()
- return tx
- } catch (error) {
- toast.dismiss(pendingPopup)
- console.log(`Transaction failed with error: ${error}`)
- }
- } catch (error) {
- console.log(`Swap Execution failed with error: ${error}`)
- toast.dismiss(pendingPopup)
- txErrorHandler(error)
- }
- }
-
- const transitionProps = {
- ...COIN_SLIDE_OVER_PROPS,
- className: `
- origin-bottom absolute
- w-full h-full
- md:w-[95%] md:h-[95%]
- -ml-0 md:-ml-3
- bg-bgBase
- z-20 rounded-lg
- `,
- }
-
- const isFromBalanceEnough = fromTokenBalance >= fromInput.bigInt
- let destAddrNotValid: boolean
-
- const getButtonProperties = () => {
- let properties = {
- label: `Enter amount to swap`,
- pendingLabel: 'Swapping funds...',
- className: '',
- disabled: true,
- buttonAction: () => executeSwap(),
- postButtonAction: () => resetRates(),
- }
-
- const isInputZero = checkStringIfOnlyZeroes(fromInput?.string)
-
- if (error) {
- properties.label = error
- properties.disabled = true
- return properties
- }
-
- if (isInputZero || fromInput?.bigInt === 0n) {
- properties.label = `Enter amount to swap`
- properties.disabled = true
- return properties
- }
-
- if (!isFromBalanceEnough) {
- properties.label = `Insufficient ${fromToken.symbol} Balance`
- properties.disabled = true
- return properties
- }
-
- if (IMPAIRED_CHAINS[connectedChainId]?.disabled) {
- properties.label = `${CHAINS_BY_ID[connectedChainId]?.name} is currently paused`
- properties.disabled = true
- return properties
- }
-
- if (fromInput.bigInt === 0n) {
- properties.label = `Amount must be greater than fee`
- properties.disabled = true
- return properties
- }
-
- if (
- fromInput?.bigInt !== 0n &&
- fromToken?.addresses[connectedChainId] !== '' &&
- fromToken?.addresses[connectedChainId] !== zeroAddress &&
- !swapQuote?.allowance &&
- swapQuote?.allowance < fromInput.bigInt &&
- !approveTx
- ) {
- properties.buttonAction = () =>
- approveToken(
- swapQuote.routerAddress,
- connectedChainId,
- fromToken.addresses[connectedChainId],
- fromInput.bigInt
- )
- properties.label = `Approve ${fromToken.symbol}`
- properties.pendingLabel = `Approving ${fromToken.symbol}`
- properties.className = 'from-[#feba06] to-[#FEC737]'
- properties.disabled = false
- properties.postButtonAction = () => {
- setApproveTx('approved')
- }
- return properties
- }
-
- if (destinationAddress && !validateAndParseAddress(destinationAddress)) {
- destAddrNotValid = true
- properties.label = 'Invalid Destination Address'
- properties.disabled = true
- return properties
- }
-
- // default case
- properties.label = 'Swap your funds'
- properties.disabled = false
-
- const numExchangeRate = swapQuote?.exchangeRate
- ? Number(formatBigIntToString(swapQuote.exchangeRate, 18, 4))
- : 0
-
- if (
- fromInput.bigInt !== 0n &&
- numExchangeRate !== 0 &&
- (numExchangeRate < 0.95 || numExchangeRate > 1.05)
- ) {
- properties.className = 'from-[#fe064a] to-[#fe5281]'
- properties.label = 'Slippage High - Swap Anyway?'
- }
-
- return properties
- }
-
- const {
- label: btnLabel,
- pendingLabel,
- className: btnClassName,
- buttonAction,
- postButtonAction,
- disabled,
- } = useMemo(getButtonProperties, [
- isFromBalanceEnough,
- address,
- fromInput,
- fromToken,
- swapQuote,
- isQuoteLoading,
- destinationAddress,
- error,
- approveTx,
- ])
-
- const ActionButton = useMemo(() => {
- return (
- buttonAction()}
- disabled={disabled || destAddrNotValid}
- className={btnClassName}
- label={btnLabel}
- pendingLabel={pendingLabel}
- chainId={connectedChainId}
- onSuccess={() => {
- postButtonAction()
- }}
- />
- )
- }, [fromInput, time, swapQuote, error, approveTx])
-
- /*
- useEffect Triggers: fromInput
- - Checks that user input is not zero. When input changes,
- - isQuoteLoading state is set to true for loading state interactions
- */
- useEffect(() => {
- const { string, bigInt } = fromInput
- const isInvalid = checkStringIfOnlyZeroes(string)
- isInvalid ? () => null : setIsQuoteLoading(true)
-
- return () => {
- setIsQuoteLoading(false)
- }
- }, [fromInput])
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{ActionButton}
-
-
- )
-}
-
-export default SwapCard
diff --git a/packages/synapse-interface/pages/swap/index.tsx b/packages/synapse-interface/pages/swap/index.tsx
index cb1c8c450d..9f0870ae0c 100644
--- a/packages/synapse-interface/pages/swap/index.tsx
+++ b/packages/synapse-interface/pages/swap/index.tsx
@@ -1,73 +1,438 @@
-import { useEffect, useState } from 'react'
import { useAccount, useNetwork } from 'wagmi'
-import { PageHeader } from '@components/PageHeader'
-import { SWAPABLE_TOKENS } from '@constants/tokens'
-import { DEFAULT_FROM_CHAIN } from '@/constants/swap'
-import { LandingPageWrapper } from '@layouts/LandingPageWrapper'
-import StandardPageContainer from '@layouts/StandardPageContainer'
-import Grid from '@tw/Grid'
-import SwapCard from './SwapCard'
-import NoSwapCard from './NoSwapCard'
+import { useSelector } from 'react-redux'
+import { RootState } from '../../store/store'
+import toast from 'react-hot-toast'
+import { animated } from 'react-spring'
import { useRouter } from 'next/router'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
-import { Address } from 'wagmi'
-const SwapPage = () => {
- const { address: currentAddress } = useAccount()
+import { setIsLoading } from '@/slices/swap/reducer'
+
+import { useSynapseContext } from '@/utils/providers/SynapseProvider'
+import { getErc20TokenAllowance } from '@/actions/getErc20TokenAllowance'
+import { subtractSlippage } from '@/utils/slippage'
+import { commify } from '@ethersproject/units'
+import { formatBigIntToString } from '@/utils/bigint/format'
+import { calculateExchangeRate } from '@/utils/calculateExchangeRate'
+import { useEffect, useRef, useState } from 'react'
+import { Token } from '@/utils/types'
+import { getWalletClient } from '@wagmi/core'
+import { txErrorHandler } from '@/utils/txErrorHandler'
+import { AcceptedChainId, CHAINS_ARR, CHAINS_BY_ID } from '@/constants/chains'
+import { approveToken } from '@/utils/approveToken'
+import { PageHeader } from '@/components/PageHeader'
+import Card from '@/components/ui/tailwind/Card'
+import { Transition } from '@headlessui/react'
+import {
+ SECTION_TRANSITION_PROPS,
+ TRANSITION_PROPS,
+} from '@/styles/transitions'
+import ExplorerToastLink from '@/components/ExplorerToastLink'
+import { Address, zeroAddress } from 'viem'
+import { stringToBigInt } from '@/utils/bigint/format'
+import { useAppDispatch } from '@/store/hooks'
+import {
+ fetchAndStoreSingleTokenAllowance,
+ fetchAndStoreSingleTokenBalance,
+} from '@/slices/portfolio/hooks'
+import {
+ usePortfolioBalances,
+ useFetchPortfolioBalances,
+} from '@/slices/portfolio/hooks'
+import { FetchState } from '@/slices/portfolio/actions'
+import { updateSingleTokenAllowance } from '@/slices/portfolio/actions'
+import { SwapTransactionButton } from '@/components/StateManagedSwap/SwapTransactionButton'
+import SwapExchangeRateInfo from '@/components/StateManagedSwap/SwapExchangeRateInfo'
+import { useSwapState } from '@/slices/swap/hooks'
+import { SwapChainListOverlay } from '@/components/StateManagedSwap/SwapChainListOverlay'
+import { SwapFromTokenListOverlay } from '@/components/StateManagedSwap/SwapFromTokenListOverlay'
+import { SwapInputContainer } from '@/components/StateManagedSwap/SwapInputContainer'
+import { SwapOutputContainer } from '@/components/StateManagedSwap/SwapOutputContainer'
+import { setSwapQuote, updateSwapFromValue } from '@/slices/swap/reducer'
+import { DEFAULT_FROM_CHAIN, EMPTY_SWAP_QUOTE_ZERO } from '@/constants/swap'
+import { SwapToTokenListOverlay } from '@/components/StateManagedSwap/SwapToTokenListOverlay'
+import { LandingPageWrapper } from '@/components/layouts/LandingPageWrapper'
+
+const StateManagedSwap = () => {
+ const { address } = useAccount()
const { chain } = useNetwork()
- const [connectedChainId, setConnectedChainId] = useState(0)
- const [address, setAddress] = useState(undefined)
+ const { synapseSDK } = useSynapseContext()
+ const swapDisplayRef = useRef(null)
+ const currentSDKRequestID = useRef(0)
const router = useRouter()
+ const { query, pathname } = router
+
+ const { balancesAndAllowances: portfolioBalances, status: portfolioStatus } =
+ useFetchPortfolioBalances()
+
+ const { swapChainId, swapFromToken, swapToToken, swapFromValue, swapQuote } =
+ useSwapState()
+
+ const {
+ showSwapFromTokenListOverlay,
+ showSwapChainListOverlay,
+ showSwapToTokenListOverlay,
+ } = useSelector((state: RootState) => state.swapDisplay)
+
+ let pendingPopup
+ let successPopup
+
+ const [isApproved, setIsApproved] = useState(false)
+
+ const dispatch = useAppDispatch()
useEffect(() => {
- segmentAnalyticsEvent(`[Swap] arrives`, {
- fromChainId: chain?.id,
- query: router.query,
- pathname: router.pathname,
+ segmentAnalyticsEvent(`[Swap page] arrives`, {
+ swapChainId,
+ query,
+ pathname,
})
- }, [])
+ }, [query])
useEffect(() => {
- setConnectedChainId(chain?.id ?? DEFAULT_FROM_CHAIN)
- }, [chain])
+ if (
+ swapFromToken &&
+ swapToToken &&
+ swapFromToken?.decimals[swapChainId] &&
+ stringToBigInt(swapFromValue, swapFromToken.decimals[swapChainId]) > 0n
+ ) {
+ console.log('trying to set swap quote')
+ getAndSetSwapQuote()
+ } else {
+ dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO))
+ dispatch(setIsLoading(false))
+ }
+ }, [
+ swapChainId,
+ swapFromToken,
+ swapToToken,
+ swapFromValue,
+ address,
+ portfolioBalances,
+ ])
useEffect(() => {
- setAddress(currentAddress)
- }, [currentAddress])
+ if (
+ swapFromToken &&
+ swapFromToken?.addresses[swapChainId] === zeroAddress
+ ) {
+ setIsApproved(true)
+ } else {
+ if (
+ swapFromToken &&
+ swapQuote?.allowance &&
+ stringToBigInt(swapFromValue, swapFromToken.decimals[swapChainId]) <=
+ swapQuote.allowance
+ ) {
+ setIsApproved(true)
+ } else {
+ setIsApproved(false)
+ }
+ }
+ }, [swapQuote, swapFromToken, swapFromValue, swapChainId])
+
+ let quoteToast
+
+ const getAndSetSwapQuote = async () => {
+ currentSDKRequestID.current += 1
+ const thisRequestId = currentSDKRequestID.current
+ try {
+ dispatch(setIsLoading(true))
+
+ const { routerAddress, maxAmountOut, query } = await synapseSDK.swapQuote(
+ swapChainId,
+ swapFromToken.addresses[swapChainId],
+ swapToToken.addresses[swapChainId],
+ stringToBigInt(swapFromValue, swapFromToken.decimals[swapChainId])
+ )
+
+ if (!(query && maxAmountOut)) {
+ dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO))
+ dispatch(setIsLoading(true))
+ return
+ }
+
+ const toValueBigInt = BigInt(maxAmountOut.toString()) ?? 0n
+
+ const allowance =
+ swapFromToken.addresses[swapChainId] === zeroAddress ||
+ address === undefined
+ ? 0n
+ : await getErc20TokenAllowance({
+ address,
+ chainId: swapChainId,
+ tokenAddress: swapFromToken.addresses[swapChainId] as Address,
+ spender: routerAddress,
+ })
+
+ const minWithSlippage = subtractSlippage(
+ query?.minAmountOut ?? 0n,
+ 'ONE_TENTH',
+ null
+ )
+
+ let newOriginQuery = { ...query }
+ newOriginQuery.minAmountOut = minWithSlippage
+
+ if (thisRequestId === currentSDKRequestID.current) {
+ dispatch(
+ setSwapQuote({
+ outputAmount: toValueBigInt,
+ outputAmountString: commify(
+ formatBigIntToString(
+ toValueBigInt,
+ swapToToken.decimals[swapChainId],
+ 8
+ )
+ ),
+ routerAddress,
+ allowance: BigInt(allowance.toString()),
+ exchangeRate: calculateExchangeRate(
+ stringToBigInt(
+ swapFromValue,
+ swapFromToken.decimals[swapChainId]
+ ),
+ swapFromToken.decimals[swapChainId],
+ toValueBigInt,
+ swapToToken.decimals[swapChainId]
+ ),
+ delta: toValueBigInt,
+ quote: newOriginQuery,
+ })
+ )
+
+ dispatch(setIsLoading(false))
+ toast.dismiss(quoteToast)
+ const message = `Route found for swapping ${swapFromValue} ${swapFromToken.symbol} on ${CHAINS_BY_ID[swapChainId]?.name} to ${swapToToken.symbol}`
+ console.log(message)
+ quoteToast = toast(message, { duration: 3000 })
+ }
+ } catch (err) {
+ console.log(err)
+ if (thisRequestId === currentSDKRequestID.current) {
+ toast.dismiss(quoteToast)
+ let message
+ if (!swapChainId) {
+ message = 'Please select an origin chain'
+ } else if (!swapFromToken) {
+ message = 'Please select an origin token'
+ } else if (!swapToToken) {
+ message = 'Please select a destination token'
+ } else {
+ message = `No route found for swapping ${swapFromValue} ${swapFromToken.symbol} on ${CHAINS_BY_ID[swapChainId]?.name} to ${swapToToken.symbol}`
+ }
+ console.log(message)
+ quoteToast = toast(message, { duration: 3000 })
+
+ dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO))
+ return
+ }
+ } finally {
+ if (thisRequestId === currentSDKRequestID.current) {
+ dispatch(setIsLoading(false))
+ }
+ }
+ }
+
+ const approveTxn = async () => {
+ try {
+ const tx = approveToken(
+ swapQuote?.routerAddress,
+ swapChainId,
+ swapFromToken?.addresses[swapChainId]
+ ).then(() => {
+ dispatch(
+ fetchAndStoreSingleTokenAllowance({
+ routerAddress: swapQuote?.routerAddress as Address,
+ tokenAddress: swapFromToken?.addresses[swapChainId] as Address,
+ address: address,
+ chainId: swapChainId,
+ })
+ )
+ })
+
+ try {
+ await tx
+ setIsApproved(true)
+ } catch (error) {
+ return txErrorHandler(error)
+ }
+ } catch (error) {
+ return txErrorHandler(error)
+ }
+ }
+
+ const executeSwap = async () => {
+ const currentChainName = CHAINS_BY_ID[swapChainId]?.name
+
+ pendingPopup = toast(
+ `Initiating swap from ${swapFromToken.symbol} to ${swapToToken.symbol} on ${currentChainName}`,
+ { id: 'swap-in-progress-popup', duration: Infinity }
+ )
+ segmentAnalyticsEvent(`[Swap] initiates swap`, {
+ address,
+ chainId: swapChainId,
+ swapFromToken: swapFromToken.symbol,
+ swapToToken: swapToToken.symbol,
+ inputAmount: swapFromValue,
+ expectedReceivedAmount: swapQuote.outputAmountString,
+ exchangeRate: swapQuote.exchangeRate,
+ })
+ try {
+ const wallet = await getWalletClient({
+ chainId: swapChainId,
+ })
+
+ const data = await synapseSDK.swap(
+ swapChainId,
+ address,
+ swapFromToken.addresses[swapChainId],
+ stringToBigInt(swapFromValue, swapFromToken.decimals[swapChainId]),
+ swapQuote.quote
+ )
+
+ const payload =
+ swapFromToken.addresses[swapChainId as keyof Token['addresses']] ===
+ zeroAddress ||
+ swapFromToken.addresses[swapChainId as keyof Token['addresses']] === ''
+ ? {
+ data: data.data,
+ to: data.to,
+ value: stringToBigInt(
+ swapFromValue,
+ swapFromToken.decimals[swapChainId]
+ ),
+ }
+ : data
+
+ const tx = await wallet.sendTransaction(payload)
+
+ const originChainName = CHAINS_BY_ID[swapChainId]?.name
+ pendingPopup = toast(
+ `Swapping ${swapFromToken.symbol} on ${originChainName} to ${swapToToken.symbol}`,
+ { id: 'swap-in-progress-popup', duration: Infinity }
+ )
+
+ try {
+ const successTx = await tx
+
+ segmentAnalyticsEvent(`[Swap] swaps successfully`, {
+ address,
+ originChainId: swapChainId,
+ inputAmount: swapFromValue,
+ expectedReceivedAmount: swapQuote.outputAmountString,
+ exchangeRate: swapQuote.exchangeRate,
+ })
+
+ toast.dismiss(pendingPopup)
+
+ const successToastContent = (
+
+
+ Successfully swapped from {swapFromToken.symbol} to{' '}
+ {swapToToken.symbol} on {currentChainName}
+
+
+
+ )
+
+ successPopup = toast.success(successToastContent, {
+ id: 'swap-successful-popup',
+ duration: 10000,
+ })
+
+ dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO))
+ dispatch(updateSwapFromValue())
+ return tx
+ } catch (error) {
+ toast.dismiss(pendingPopup)
+ console.log(`Transaction failed with error: ${error}`)
+ }
+ } catch (error) {
+ console.log(`Swap Execution failed with error: ${error}`)
+ toast.dismiss(pendingPopup)
+ txErrorHandler(error)
+ }
+ }
+
+ const springClass =
+ '-mt-4 fixed z-50 w-full h-full bg-opacity-50 bg-[#343036]'
return (
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- {SWAPABLE_TOKENS[connectedChainId]?.length > 0 ? (
-
+
+
- ) : (
-
- )}
+
-
+
-
+
)
}
-export default SwapPage
+export default StateManagedSwap
diff --git a/packages/synapse-interface/slices/bridge/reducer.ts b/packages/synapse-interface/slices/bridge/reducer.ts
index 798cb57a14..ec6b367527 100644
--- a/packages/synapse-interface/slices/bridge/reducer.ts
+++ b/packages/synapse-interface/slices/bridge/reducer.ts
@@ -21,6 +21,7 @@ import {
updatePendingBridgeTransaction,
updatePendingBridgeTransactions,
} from './actions'
+import { findValidToken } from '@/utils/findValidToken'
export interface BridgeState {
fromChainId: number
@@ -525,14 +526,3 @@ export const {
} = bridgeSlice.actions
export default bridgeSlice.reducer
-
-const findValidToken = (
- tokens: Token[],
- routeSymbol: string,
- swapableType: string
-) => {
- const matchingToken = tokens?.find((t) => t.routeSymbol === routeSymbol)
- const swapableToken = tokens?.find((t) => t.swapableType === swapableType)
-
- return matchingToken ? matchingToken : swapableToken ? swapableToken : null
-}
diff --git a/packages/synapse-interface/slices/swap/hooks.ts b/packages/synapse-interface/slices/swap/hooks.ts
new file mode 100644
index 0000000000..13168dc2f4
--- /dev/null
+++ b/packages/synapse-interface/slices/swap/hooks.ts
@@ -0,0 +1,6 @@
+import { RootState } from '@/store/store'
+import { useAppSelector } from '@/store/hooks'
+
+export const useSwapState = (): RootState['swap'] => {
+ return useAppSelector((state) => state.swap)
+}
diff --git a/packages/synapse-interface/slices/swap/reducer.ts b/packages/synapse-interface/slices/swap/reducer.ts
new file mode 100644
index 0000000000..13599a0a76
--- /dev/null
+++ b/packages/synapse-interface/slices/swap/reducer.ts
@@ -0,0 +1,295 @@
+import _ from 'lodash'
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+
+import { DAI, USDC } from '@/constants/tokens/bridgeable'
+import { EMPTY_SWAP_QUOTE } from '@/constants/swap'
+import { ETH as ETHEREUM } from '@/constants/chains/master'
+import { getSwapPossibilities } from '@/utils/swapFinder/generateSwapPossibilities'
+import { SwapQuote, Token } from '@/utils/types'
+import { getSwapFromTokens } from '@/utils/swapFinder/getSwapFromTokens'
+import { getSymbol } from '@/utils/getSymbol'
+import { findTokenByRouteSymbol } from '@/utils/findTokenByRouteSymbol'
+import { getSwapToTokens } from '@/utils/swapFinder/getSwapToTokens'
+import { getSwapFromChainIds } from '@/utils/swapFinder/getSwapFromChainIds'
+import { findValidToken } from '@/utils/findValidToken'
+import { flattenPausedTokens } from '@/utils/flattenPausedTokens'
+
+export interface SwapState {
+ swapChainId: number
+ swapFromToken: Token
+ swapToToken: Token
+ swapFromChainIds: number[]
+ swapFromTokens: Token[]
+ swapToTokens: Token[]
+
+ swapFromValue: string
+ swapQuote: SwapQuote
+ isLoading: boolean
+}
+
+const { fromChainId, fromToken, toToken, fromChainIds, fromTokens, toTokens } =
+ getSwapPossibilities({
+ fromChainId: ETHEREUM.id,
+ fromToken: USDC,
+ toChainId: ETHEREUM.id,
+ toToken: DAI,
+ })
+
+export const initialState: SwapState = {
+ swapChainId: fromChainId,
+ swapFromToken: fromToken,
+ swapToToken: toToken,
+ swapFromChainIds: fromChainIds,
+ swapFromTokens: fromTokens,
+ swapToTokens: toTokens,
+
+ swapFromValue: '',
+ swapQuote: EMPTY_SWAP_QUOTE,
+ isLoading: false,
+}
+
+export const swapSlice = createSlice({
+ name: 'swap',
+ initialState,
+ reducers: {
+ setIsLoading: (state, action: PayloadAction) => {
+ state.isLoading = action.payload
+ },
+ setSwapChainId: (state, action: PayloadAction) => {
+ const incomingFromChainId = action.payload
+
+ const validFromTokens = _(
+ getSwapFromTokens({
+ fromChainId: incomingFromChainId ?? null,
+ fromTokenRouteSymbol: state.swapFromToken?.routeSymbol ?? null,
+ toChainId: incomingFromChainId ?? null,
+ toTokenRouteSymbol: null,
+ })
+ )
+ .difference(flattenPausedTokens())
+ ?.map(getSymbol)
+ .map((s) => findTokenByRouteSymbol(s))
+ .filter(Boolean)
+ .value()
+
+ const validToTokens = _(
+ getSwapToTokens({
+ fromChainId: incomingFromChainId ?? null,
+ fromTokenRouteSymbol: state.swapFromToken?.routeSymbol ?? null,
+ toChainId: incomingFromChainId ?? null,
+ toTokenRouteSymbol: null,
+ })
+ )
+ .difference(flattenPausedTokens())
+ ?.map(getSymbol)
+ .map((s) => findTokenByRouteSymbol(s))
+ .filter(Boolean)
+ .value()
+
+ let validFromToken
+ let validToToken
+
+ if (
+ validFromTokens?.some(
+ (token) => token?.routeSymbol === state.swapFromToken?.routeSymbol
+ )
+ ) {
+ validFromToken = state.swapFromToken
+ } else {
+ validFromToken = findValidToken(
+ validFromTokens,
+ state.swapToToken?.routeSymbol,
+ state.swapToToken?.swapableType
+ )
+ }
+
+ if (
+ validToTokens?.some(
+ (token) => token?.routeSymbol === state.swapToToken?.routeSymbol
+ )
+ ) {
+ validToToken = state.swapToToken
+ } else {
+ validToToken = findValidToken(
+ validToTokens,
+ state.swapFromToken?.routeSymbol,
+ state.swapFromToken?.swapableType
+ )
+ }
+
+ const {
+ fromChainId,
+ fromToken,
+ toToken,
+ fromChainIds,
+ fromTokens,
+ toTokens,
+ } = getSwapPossibilities({
+ fromChainId: incomingFromChainId,
+ fromToken: validFromToken,
+ toChainId: incomingFromChainId,
+ toToken: validToToken,
+ })
+
+ state.swapChainId = fromChainId
+ state.swapFromToken = fromToken
+ state.swapToToken = toToken
+ state.swapFromChainIds = fromChainIds
+ state.swapFromTokens = fromTokens
+ state.swapToTokens = toTokens
+ },
+ setSwapFromToken: (state, action: PayloadAction) => {
+ const incomingFromToken = action.payload
+
+ const validFromChainIds = getSwapFromChainIds({
+ fromChainId: state.swapChainId ?? null,
+ fromTokenRouteSymbol: incomingFromToken?.routeSymbol ?? null,
+ toChainId: null,
+ toTokenRouteSymbol: null,
+ })
+
+ const validToTokens = _(
+ getSwapToTokens({
+ fromChainId: state.swapChainId ?? null,
+ fromTokenRouteSymbol: incomingFromToken?.routeSymbol ?? null,
+ toChainId: state.swapChainId ?? null,
+ toTokenRouteSymbol: null,
+ })
+ )
+ .difference(flattenPausedTokens())
+ ?.map(getSymbol)
+ .map((s) => findTokenByRouteSymbol(s))
+ .filter(Boolean)
+ .value()
+
+ let validFromChainId
+ let validToToken
+
+ if (validFromChainIds?.includes(state.swapChainId)) {
+ validFromChainId = state.swapChainId
+ } else {
+ validFromChainId = null
+ }
+
+ if (
+ validToTokens?.some(
+ (token) => token?.routeSymbol === state.swapToToken?.routeSymbol
+ )
+ ) {
+ validToToken = state.swapToToken
+ } else {
+ validToToken = findValidToken(
+ validToTokens,
+ incomingFromToken?.routeSymbol,
+ incomingFromToken?.swapableType
+ )
+ }
+
+ const {
+ fromChainId,
+ fromToken,
+ toToken,
+ fromChainIds,
+ fromTokens,
+ toTokens,
+ } = getSwapPossibilities({
+ fromChainId: validFromChainId,
+ fromToken: incomingFromToken,
+ toChainId: validFromChainId,
+ toToken: validToToken,
+ })
+
+ state.swapChainId = fromChainId
+ state.swapFromToken = fromToken
+ state.swapToToken = toToken
+ state.swapFromChainIds = fromChainIds
+ state.swapFromTokens = fromTokens
+ state.swapToTokens = toTokens
+ },
+ setSwapToToken: (state, action: PayloadAction) => {
+ const incomingToToken = action.payload
+
+ const validFromChainIds = getSwapFromChainIds({
+ fromChainId: state.swapChainId ?? null,
+ fromTokenRouteSymbol: null,
+ toChainId: state.swapChainId ?? null,
+ toTokenRouteSymbol: incomingToToken?.routeSymbol ?? null,
+ })
+
+ const validFromTokens = _(
+ getSwapFromTokens({
+ fromChainId: state.swapChainId ?? null,
+ fromTokenRouteSymbol: state.swapFromToken?.routeSymbol ?? null,
+ toChainId: state.swapChainId ?? null,
+ toTokenRouteSymbol: incomingToToken?.routeSymbol ?? null,
+ })
+ )
+ .difference(flattenPausedTokens())
+ ?.map(getSymbol)
+ .map((s) => findTokenByRouteSymbol(s))
+ .filter(Boolean)
+ .value()
+
+ let validFromChainId
+ let validFromToken
+
+ if (validFromChainIds?.includes(state.swapChainId)) {
+ validFromChainId = state.swapChainId
+ } else {
+ validFromChainId = null
+ }
+
+ if (
+ validFromTokens?.some(
+ (token) => token?.routeSymbol === state.swapFromToken?.routeSymbol
+ )
+ ) {
+ validFromToken = state.swapFromToken
+ } else {
+ validFromToken = findValidToken(
+ validFromTokens,
+ incomingToToken?.routeSymbol,
+ incomingToToken?.swapableType
+ )
+ }
+
+ const {
+ fromChainId,
+ fromToken,
+ toToken,
+ fromChainIds,
+ fromTokens,
+ toTokens,
+ } = getSwapPossibilities({
+ fromChainId: validFromChainId,
+ fromToken: validFromToken,
+ toChainId: validFromChainId,
+ toToken: incomingToToken,
+ })
+
+ state.swapChainId = fromChainId
+ state.swapFromToken = fromToken
+ state.swapToToken = toToken
+ state.swapFromChainIds = fromChainIds
+ state.swapFromTokens = fromTokens
+ state.swapToTokens = toTokens
+ },
+ setSwapQuote: (state, action: PayloadAction) => {
+ state.swapQuote = action.payload
+ },
+ updateSwapFromValue: (state, action: PayloadAction) => {
+ state.swapFromValue = action.payload
+ },
+ },
+})
+
+export const {
+ setSwapChainId,
+ setSwapFromToken,
+ setSwapToToken,
+ updateSwapFromValue,
+ setSwapQuote,
+ setIsLoading,
+} = swapSlice.actions
+
+export default swapSlice.reducer
diff --git a/packages/synapse-interface/slices/swapDisplaySlice.ts b/packages/synapse-interface/slices/swapDisplaySlice.ts
new file mode 100644
index 0000000000..7984f94c97
--- /dev/null
+++ b/packages/synapse-interface/slices/swapDisplaySlice.ts
@@ -0,0 +1,40 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+
+export interface SwapDisplayState {
+ showSwapChainListOverlay: boolean
+ showSwapFromTokenListOverlay: boolean
+ showSwapToTokenListOverlay: boolean
+}
+
+const initialState: SwapDisplayState = {
+ showSwapChainListOverlay: false,
+ showSwapFromTokenListOverlay: false,
+ showSwapToTokenListOverlay: false,
+}
+
+export const swapDisplaySlice = createSlice({
+ name: 'swapDisplay',
+ initialState,
+ reducers: {
+ setShowSwapFromTokenListOverlay: (
+ state,
+ action: PayloadAction
+ ) => {
+ state.showSwapFromTokenListOverlay = action.payload
+ },
+ setShowSwapToTokenListOverlay: (state, action: PayloadAction) => {
+ state.showSwapToTokenListOverlay = action.payload
+ },
+ setShowSwapChainListOverlay: (state, action: PayloadAction) => {
+ state.showSwapChainListOverlay = action.payload
+ },
+ },
+})
+
+export const {
+ setShowSwapChainListOverlay,
+ setShowSwapFromTokenListOverlay,
+ setShowSwapToTokenListOverlay,
+} = swapDisplaySlice.actions
+
+export default swapDisplaySlice.reducer
diff --git a/packages/synapse-interface/store/store.ts b/packages/synapse-interface/store/store.ts
index 986fa041cd..0beaa7318e 100644
--- a/packages/synapse-interface/store/store.ts
+++ b/packages/synapse-interface/store/store.ts
@@ -9,6 +9,8 @@ import poolUserDataReducer from '@/slices/poolUserDataSlice'
import poolDepositReducer from '@/slices/poolDepositSlice'
import poolWithdrawReducer from '@/slices/poolWithdrawSlice'
import portfolioReducer from '@/slices/portfolio/reducer'
+import swapReducer from '@/slices/swap/reducer'
+import swapDisplayReducer from '@/slices/swapDisplaySlice'
import transactionsReducer from '@/slices/transactions/reducer'
import { api } from '@/slices/api/slice'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
@@ -22,6 +24,8 @@ export const store = configureStore({
poolDeposit: poolDepositReducer,
poolWithdraw: poolWithdrawReducer,
portfolio: portfolioReducer,
+ swap: swapReducer,
+ swapDisplay: swapDisplayReducer,
transactions: transactionsReducer,
[api.reducerPath]: api.reducer,
},
diff --git a/packages/synapse-interface/utils/bridgeWatcher.ts b/packages/synapse-interface/utils/bridgeWatcher.ts
deleted file mode 100644
index ad8af31509..0000000000
--- a/packages/synapse-interface/utils/bridgeWatcher.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import { JsonRpcProvider } from '@ethersproject/providers'
-import { hexZeroPad } from '@ethersproject/bytes'
-import { Contract } from '@ethersproject/contracts'
-import { getAddress, isAddress } from '@ethersproject/address'
-import { id } from '@ethersproject/hash'
-import { toHexStr } from '@utils/toHexStr'
-import { BridgeWatcherTx } from '@types'
-import { GETLOGS_SIZE } from '@constants/bridgeWatcher'
-import { TOKEN_HASH_MAP } from '@constants/tokens'
-import * as CHAINS from '@constants/chains/master'
-import {
- SYN,
- NUSD,
- NETH,
- FRAX,
- SYNFRAX,
- WBTC,
- DOG,
- LINK,
- GOHM,
- HIGH,
- JUMP,
- NFD,
- NEWO,
- VSTA,
- GMX,
- SDT,
- UNIDX,
- SFI,
- H2O,
- L2DAO,
- PLS,
- AGEUR,
- NOTE,
- USDC,
- SUSD,
- WETH,
-} from '@constants/tokens/bridgeable'
-
-export const getTransactionReceipt = async (
- txHash: string,
- provider: JsonRpcProvider
-) => {
- const receipt = await provider.getTransactionReceipt(txHash)
- return receipt
-}
-export const getBlock = async (
- blockNumber: number,
- provider: JsonRpcProvider
-) => {
- const block = await provider.getBlock(blockNumber)
- return block
-}
-export const getLogs = async (
- currentBlock: number,
- provider: JsonRpcProvider,
- contract: Contract,
- address: string
-) => {
- const filter = {
- address: contract?.address,
- topics: [null, hexZeroPad(address, 32)],
- fromBlock: toHexStr(currentBlock - GETLOGS_SIZE),
- toBlock: toHexStr(currentBlock),
- }
- try {
- const logs = await provider.send('eth_getLogs', [filter])
- return logs
- } catch (e) {
- console.log('getLogs error', e)
- return []
- }
-}
-
-export const checkTxIn = (tx) => {
- return tx?.chainId ? true : false
-}
-
-export const generateBridgeTx = (
- isFrom,
- address,
- chainId,
- parsedLog,
- timestampObj,
- txReceipt,
- destinationAddress
-): BridgeWatcherTx => {
- const swapTokenAddr = getAddress(parsedLog.token)
-
- let tokenAddr
- if (isFrom) {
- if (txReceipt.logs[1].address === GMX.addresses[CHAINS.AVALANCHE.id]) {
- tokenAddr = GMX.addresses[CHAINS.AVALANCHE.id]
- } else {
- tokenAddr = txReceipt.logs[0].address
- }
- } else {
- if (
- [
- SYN,
- LINK,
- HIGH,
- DOG,
- JUMP,
- FRAX,
- NFD,
- GOHM,
- AGEUR,
- H2O,
- L2DAO,
- PLS,
- NEWO,
- VSTA,
- SFI,
- SDT,
- UNIDX,
- GMX,
- WBTC,
- NOTE,
- SUSD,
- ]
- .map((t) => t.addresses[chainId])
- .includes(swapTokenAddr)
- ) {
- tokenAddr = TOKEN_HASH_MAP[chainId][swapTokenAddr].addresses[chainId]
- } else if (swapTokenAddr === SYNFRAX.addresses[chainId]) {
- tokenAddr = FRAX.addresses[chainId]
- } else if (swapTokenAddr === GMX.wrapperAddresses[chainId]) {
- tokenAddr = GMX.addresses[chainId]
- } else if (swapTokenAddr === NETH.addresses[chainId]) {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 2].address
- } else if (swapTokenAddr === WETH.addresses[chainId]) {
- if (chainId === CHAINS.ETH.id) {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 2].address
- } else {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 1].address
- }
- } else if (swapTokenAddr === NUSD.addresses[chainId]) {
- if (chainId === CHAINS.ETH.id) {
- if (parsedLog.event === 'TokenWithdraw') {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 1].address
- } else {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 2].address
- }
- } else if (chainId === CHAINS.POLYGON.id) {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 3].address
- } else {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 2].address
- }
- } else if (
- !isFrom &&
- swapTokenAddr === USDC.addresses[chainId] &&
- [CHAINS.ARBITRUM.id, CHAINS.ETH.id, CHAINS.AVALANCHE.id].includes(chainId)
- ) {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 3].address
- } else {
- tokenAddr = txReceipt.logs[txReceipt.logs.length - 2].address
- }
- }
- const token = TOKEN_HASH_MAP[chainId][tokenAddr]
-
- let inputTokenAmount
- if (
- getAddress(txReceipt.logs[0]?.address) ===
- GMX.addresses[CHAINS.ARBITRUM.id] ||
- getAddress(txReceipt.logs[1]?.address) ===
- GMX.addresses[CHAINS.AVALANCHE.id]
- ) {
- inputTokenAmount = txReceipt.logs[1].data
- } else {
- inputTokenAmount = txReceipt.logs[0].data
- }
-
- return {
- isFrom,
- amount: isFrom ? inputTokenAmount : parsedLog.amount,
- timestamp: timestampObj.timestamp,
- blockNumber: parsedLog.blockNumber,
- chainId,
- address,
- txHash: txReceipt.transactionHash,
- txReceipt,
- token,
- kappa: parsedLog.requestID
- ? parsedLog.requestID
- : removePrefix(id(parsedLog.transactionHash)),
- toChainId: isFrom ? Number(parsedLog.chainId.toString()) : chainId,
- toAddress: isAddress(destinationAddress) ? destinationAddress : address,
- contractEmittedFrom: parsedLog.contractEmittedFrom,
- }
-}
-
-export const getHighestBlock = async (
- chainId: number,
- provider: JsonRpcProvider
-) => {
- const highestBlock = await provider.getBlockNumber()
- return highestBlock
-}
-
-const removePrefix = (str: string): string => {
- if (str.startsWith('0x')) {
- return str.substring(2)
- }
- return str
-}
diff --git a/packages/synapse-interface/utils/findTokenByAddressAndChainId.ts b/packages/synapse-interface/utils/findTokenByAddressAndChainId.ts
new file mode 100644
index 0000000000..d6f18f5713
--- /dev/null
+++ b/packages/synapse-interface/utils/findTokenByAddressAndChainId.ts
@@ -0,0 +1,21 @@
+import { Address } from 'viem'
+
+import { ALL_TOKENS } from '@/constants/tokens/master'
+
+export const findTokenByAddressAndChain = (
+ address: Address | string,
+ chainId: string
+) => {
+ for (const [, token] of Object.entries(ALL_TOKENS)) {
+ const chainAddresses = token.addresses
+ if (
+ chainAddresses &&
+ Object.keys(chainAddresses).length > 0 &&
+ chainAddresses[chainId] &&
+ chainAddresses[chainId].toLowerCase() === address.toLowerCase()
+ ) {
+ return token
+ }
+ }
+ return null
+}
diff --git a/packages/synapse-interface/utils/findValidToken.ts b/packages/synapse-interface/utils/findValidToken.ts
new file mode 100644
index 0000000000..12c6fe6d25
--- /dev/null
+++ b/packages/synapse-interface/utils/findValidToken.ts
@@ -0,0 +1,12 @@
+import { Token } from './types'
+
+export const findValidToken = (
+ tokens: Token[],
+ routeSymbol: string,
+ swapableType: string
+): Token | null => {
+ const matchingToken = tokens?.find((t) => t.routeSymbol === routeSymbol)
+ const swapableToken = tokens?.find((t) => t.swapableType === swapableType)
+
+ return matchingToken ? matchingToken : swapableToken ? swapableToken : null
+}
diff --git a/packages/synapse-interface/utils/generateChainIdAddressMapping.ts b/packages/synapse-interface/utils/generateChainIdAddressMapping.ts
index c698d541d8..6788bbd479 100644
--- a/packages/synapse-interface/utils/generateChainIdAddressMapping.ts
+++ b/packages/synapse-interface/utils/generateChainIdAddressMapping.ts
@@ -1,7 +1,9 @@
import { zeroAddress } from 'viem'
import _ from 'lodash'
-import * as BRIDGEABLE from '@constants/tokens/bridgeable'
-import { BRIDGE_MAP } from '@constants/bridgeMap'
+
+import * as BRIDGEABLE from '@/constants/tokens/bridgeable'
+import { BRIDGE_MAP } from '@/constants/bridgeMap'
+import { ETHEREUM_ADDRESS } from '@/constants'
export const generateChainIdAddressMapping = (routeSymbol: string) => {
const result: { [key: number]: string } = {}
@@ -10,9 +12,7 @@ export const generateChainIdAddressMapping = (routeSymbol: string) => {
Object.entries(tokens).forEach(([address, token]) => {
if (token.symbol === routeSymbol) {
result[Number(chainId)] =
- address === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
- ? zeroAddress
- : address
+ address === ETHEREUM_ADDRESS ? zeroAddress : address
}
})
})
diff --git a/packages/synapse-interface/utils/getSymbol.ts b/packages/synapse-interface/utils/getSymbol.ts
new file mode 100644
index 0000000000..ff6057d86e
--- /dev/null
+++ b/packages/synapse-interface/utils/getSymbol.ts
@@ -0,0 +1,3 @@
+export const getSymbol = (tokenAndChainId: string): string => {
+ return tokenAndChainId.split('-')[0]
+}
diff --git a/packages/synapse-interface/utils/swapFinder/generateSwapPossibilities.ts b/packages/synapse-interface/utils/swapFinder/generateSwapPossibilities.ts
new file mode 100644
index 0000000000..0c80ee0c7c
--- /dev/null
+++ b/packages/synapse-interface/utils/swapFinder/generateSwapPossibilities.ts
@@ -0,0 +1,82 @@
+import _ from 'lodash'
+
+import { flattenPausedTokens } from '../flattenPausedTokens'
+import { Token } from '../types'
+import { getSwapFromChainIds } from './getSwapFromChainIds'
+import { getSwapFromTokens } from './getSwapFromTokens'
+import { getSwapToTokens } from './getSwapToTokens'
+import { PAUSED_TO_CHAIN_IDS } from '@/constants/chains'
+import { findTokenByRouteSymbol } from '../findTokenByRouteSymbol'
+import { getSymbol } from '@/utils/getSymbol'
+
+export interface RouteQueryFields {
+ fromChainId?: number
+ fromTokenRouteSymbol?: string
+ toChainId?: number
+ toTokenRouteSymbol?: string
+}
+
+export const getSwapPossibilities = ({
+ fromChainId,
+ fromToken,
+ toChainId,
+ toToken,
+}: {
+ fromChainId?: number
+ fromToken?: Token
+ toChainId?: number
+ toToken?: Token
+}) => {
+ const fromTokenRouteSymbol = fromToken && fromToken.routeSymbol
+ const toTokenRouteSymbol = toToken && toToken.routeSymbol
+
+ const fromChainIds: number[] = getSwapFromChainIds({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+ })
+
+ const fromTokens: Token[] = _(
+ getSwapFromTokens({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+ })
+ )
+ .difference(flattenPausedTokens())
+ .map(getSymbol)
+ .uniq()
+ .map((symbol) => findTokenByRouteSymbol(symbol))
+ .compact()
+ .value()
+
+ const toTokens: Token[] = _(
+ getSwapToTokens({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+ })
+ )
+ .difference(flattenPausedTokens())
+ .filter((token) => {
+ return !PAUSED_TO_CHAIN_IDS.some((value) => token.endsWith(`-${value}`))
+ })
+ .map(getSymbol)
+ .uniq()
+ .map((symbol) => findTokenByRouteSymbol(symbol))
+ .compact()
+ .value()
+
+ return {
+ fromChainId,
+ fromToken,
+ toChainId,
+ toToken,
+ fromChainIds,
+ fromTokens,
+ toTokens,
+ }
+}
diff --git a/packages/synapse-interface/utils/swapFinder/getSwapFromChainIds.ts b/packages/synapse-interface/utils/swapFinder/getSwapFromChainIds.ts
new file mode 100644
index 0000000000..8012eb044e
--- /dev/null
+++ b/packages/synapse-interface/utils/swapFinder/getSwapFromChainIds.ts
@@ -0,0 +1,283 @@
+import _ from 'lodash'
+
+import {
+ EXISTING_SWAP_ROUTES,
+ SWAP_CHAIN_IDS,
+} from '@/constants/existingSwapRoutes'
+import { RouteQueryFields } from './generateSwapPossibilities'
+import { getTokenAndChainId } from './getTokenAndChainId'
+
+export const getAllFromChainIds = () => SWAP_CHAIN_IDS
+
+export const getSwapFromChainIds = ({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+}: RouteQueryFields) => {
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .filter((key) => {
+ const { symbol } = getTokenAndChainId(key)
+ return symbol === fromTokenRouteSymbol
+ })
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .filter((key) => {
+ const { symbol } = getTokenAndChainId(key)
+ return symbol === fromTokenRouteSymbol
+ })
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .entries()
+ .filter(([_key, values]) =>
+ values.some((v) => v.endsWith(`-${toChainId}`))
+ )
+ .map(([key]) => getTokenAndChainId(key).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .entries()
+ .filter(([_key, values]) =>
+ values.some((v) => v.endsWith(`-${toChainId}`))
+ )
+ .map(([key]) => getTokenAndChainId(key).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .entries()
+ .filter(([key, _values]) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .filter(([_key, values]) =>
+ values.some((v) => getTokenAndChainId(v).chainId === toChainId)
+ )
+ .map(([key, _values]) => key)
+ .filter((token) => token.endsWith(`-${toChainId}`))
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .keys()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .filter((chainId) => chainId !== toChainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .chain()
+ .filter((values, _key) => {
+ return values.some((v) => {
+ const { symbol } = getTokenAndChainId(v)
+ return symbol === toTokenRouteSymbol
+ })
+ })
+ .flatten()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .chain()
+ .filter((values, _key) => {
+ return values.some((v) => {
+ const { symbol } = getTokenAndChainId(v)
+ return symbol === toTokenRouteSymbol
+ })
+ })
+ .flatten()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .entries()
+ .filter(([key, _values]) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .map(([key, _values]) => key)
+ .flatten()
+ .filter((token) => token.startsWith(`${toTokenRouteSymbol}-`))
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .chain()
+ .filter((values, key) => {
+ return (
+ values.some((v) => {
+ const { symbol } = getTokenAndChainId(v)
+ return symbol === toTokenRouteSymbol
+ }) && key.startsWith(`${fromTokenRouteSymbol}-`)
+ )
+ })
+ .flatten()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .filter((k) => k.startsWith(`${fromTokenRouteSymbol}-`))
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+
+ if (fromChainId && fromTokenRouteSymbol && toChainId && toTokenRouteSymbol) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .filter((k) => k.startsWith(`${fromTokenRouteSymbol}-`))
+ .map((token) => getTokenAndChainId(token).chainId)
+ .uniq()
+ .value()
+ }
+}
diff --git a/packages/synapse-interface/utils/swapFinder/getSwapFromTokens.ts b/packages/synapse-interface/utils/swapFinder/getSwapFromTokens.ts
new file mode 100644
index 0000000000..fc755330ec
--- /dev/null
+++ b/packages/synapse-interface/utils/swapFinder/getSwapFromTokens.ts
@@ -0,0 +1,235 @@
+import _ from 'lodash'
+
+import { EXISTING_SWAP_ROUTES } from '@/constants/existingSwapRoutes'
+import { RouteQueryFields } from './generateSwapPossibilities'
+import { getTokenAndChainId } from './getTokenAndChainId'
+
+export const getSwapFromTokens = ({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+}: RouteQueryFields) => {
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES).keys().uniq().value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .filter((token) => token.endsWith(`-${fromChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES).keys().uniq().value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .keys()
+ .filter((key) => getTokenAndChainId(key).chainId === fromChainId)
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => values.some((v) => v.endsWith(`-${toChainId}`)))
+ .keys()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => values.some((v) => v.endsWith(`-${toChainId}`)))
+ .keys()
+ .filter((key) => key.endsWith(`-${fromChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .entries()
+ .filter(([key, _values]) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .filter(([_key, values]) =>
+ values.some((v) => getTokenAndChainId(v).chainId === toChainId)
+ )
+ .map(([key, _values]) => key)
+ .filter((token) => token.endsWith(`-${toChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => values.some((v) => v.endsWith(`-${toChainId}`)))
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .keys()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .chain()
+ .filter((values, _key) =>
+ values.some((v) => getTokenAndChainId(v).symbol === toTokenRouteSymbol)
+ )
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) =>
+ values.some((v) => getTokenAndChainId(v).symbol === toTokenRouteSymbol)
+ )
+ .keys()
+ .filter((k) => k.endsWith(`-${fromChainId}`))
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) =>
+ values.some((v) => v.startsWith(`${toTokenRouteSymbol}-`))
+ )
+ .keys()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .chain()
+ .filter((values, _key) => {
+ return values.some((v) => {
+ const { symbol } = getTokenAndChainId(v)
+ return symbol === toTokenRouteSymbol
+ })
+ })
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) => {
+ return _.includes(values, `${toTokenRouteSymbol}-${toChainId}`)
+ })
+ .keys()
+ .value()
+ }
+
+ if (fromChainId && fromTokenRouteSymbol && toChainId && toTokenRouteSymbol) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((values, _key) =>
+ values.some((v) => {
+ return v === `${toTokenRouteSymbol}-${toChainId}`
+ })
+ )
+ .keys()
+ .filter((key) => key.endsWith(`-${fromChainId}`))
+ .value()
+ }
+}
diff --git a/packages/synapse-interface/utils/swapFinder/getSwapToTokens.ts b/packages/synapse-interface/utils/swapFinder/getSwapToTokens.ts
new file mode 100644
index 0000000000..cc773f7078
--- /dev/null
+++ b/packages/synapse-interface/utils/swapFinder/getSwapToTokens.ts
@@ -0,0 +1,213 @@
+import _ from 'lodash'
+
+import { EXISTING_SWAP_ROUTES } from '@/constants/existingSwapRoutes'
+import { RouteQueryFields } from './generateSwapPossibilities'
+
+export const getSwapToTokens = ({
+ fromChainId,
+ fromTokenRouteSymbol,
+ toChainId,
+ toTokenRouteSymbol,
+}: RouteQueryFields) => {
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES).values().flatten().uniq().value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol === null
+ ) {
+ return EXISTING_SWAP_ROUTES[`${fromTokenRouteSymbol}-${fromChainId}`]
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .values()
+ .flatten()
+ .filter((token) => token.endsWith(`-${toChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .values()
+ .flatten()
+ .filter((value) => value.endsWith(`-${toChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .values()
+ .flatten()
+ .filter((token) => token.endsWith(`-${toChainId}`))
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol === null
+ ) {
+ return EXISTING_SWAP_ROUTES[
+ `${fromTokenRouteSymbol}-${fromChainId}`
+ ]?.filter((token) => token.endsWith(`-${toChainId}`))
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES).values().flatten().uniq().value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .pickBy((_values, key) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol &&
+ toChainId === null &&
+ toTokenRouteSymbol
+ ) {
+ return EXISTING_SWAP_ROUTES[`${fromTokenRouteSymbol}-${fromChainId}`]
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .mapValues((values) =>
+ values.filter((token) => token === `${toTokenRouteSymbol}-${toChainId}`)
+ )
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+ if (
+ fromChainId &&
+ fromTokenRouteSymbol === null &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .mapValues((values) =>
+ values.filter((token) => token === `${toTokenRouteSymbol}-${toChainId}`)
+ )
+ .pickBy((_values, key) => key.endsWith(`-${fromChainId}`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (
+ fromChainId === null &&
+ fromTokenRouteSymbol &&
+ toChainId &&
+ toTokenRouteSymbol
+ ) {
+ return _(EXISTING_SWAP_ROUTES)
+ .mapValues((values) =>
+ values.filter((token) => token === `${toTokenRouteSymbol}-${toChainId}`)
+ )
+ .pickBy((_values, key) => key.startsWith(`${fromTokenRouteSymbol}-`))
+ .values()
+ .flatten()
+ .uniq()
+ .value()
+ }
+
+ if (fromChainId && fromTokenRouteSymbol && toChainId && toTokenRouteSymbol) {
+ return EXISTING_SWAP_ROUTES[
+ `${fromTokenRouteSymbol}-${fromChainId}`
+ ]?.filter((value) => value.endsWith(`-${toChainId}`))
+ }
+}
diff --git a/packages/synapse-interface/utils/swapFinder/getTokenAndChainId.ts b/packages/synapse-interface/utils/swapFinder/getTokenAndChainId.ts
new file mode 100644
index 0000000000..2be2f57df9
--- /dev/null
+++ b/packages/synapse-interface/utils/swapFinder/getTokenAndChainId.ts
@@ -0,0 +1,5 @@
+export const getTokenAndChainId = (tokenAndChainId: string) => {
+ const [symbol, chainId] = tokenAndChainId.split('-')
+
+ return { symbol, chainId: Number(chainId) }
+}