diff --git a/.changeset/config.json b/.changeset/config.json index 85d08b049..66e2a1434 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,6 +11,6 @@ "@terminallyonline/plug-docs", "@terminallyonline/plug-app", "@terminallyonline/plug-pitch", - "@terminallyonline/plug-solver" + "@terminallyonline/plug-licenses" ] } diff --git a/.changeset/perfect-bobcats-shout.md b/.changeset/perfect-bobcats-shout.md deleted file mode 100644 index 7771a23a1..000000000 --- a/.changeset/perfect-bobcats-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@terminallyonline/plug-core": patch ---- - -feat: include action contracts in generated deployment scripts diff --git a/.changeset/wild-elephants-sit.md b/.changeset/wild-elephants-sit.md new file mode 100644 index 000000000..2e85762f8 --- /dev/null +++ b/.changeset/wild-elephants-sit.md @@ -0,0 +1,5 @@ +--- +"@terminallyonline/plug-core": minor +--- + +feat: turing complete architecture with jumps diff --git a/.github/workflows/app-main.yml b/.github/workflows/app-main.yml index bbec32c7f..5eb795f5c 100644 --- a/.github/workflows/app-main.yml +++ b/.github/workflows/app-main.yml @@ -1,68 +1,78 @@ -name: Build and Deploy App +name: App - Deploy on: push: branches: ["**"] paths: + - ".github/workflows/app-main.yml" - "packages/app/**" + - "packages/core/**" - "packages/posts/**" + - "packages/sdk/**" workflow_dispatch: -concurrency: - cancel-in-progress: false - group: ${{ github.workflow }}-${{ github.ref }} - permissions: contents: write pages: write id-token: write + pull-requests: write jobs: build: - name: ๐Ÿ—๏ธ Build runs-on: ubuntu-latest steps: - name: ๐Ÿ›’ Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: submodules: recursive - name: โš™๏ธ Setup Action - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: ๐Ÿ“š Setup Node - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 18 + - name: โœจ Install Foundry + uses: foundry-rs/foundry-toolchain@50d5a8956f2e319df19e6b57539d7e2acb9f8c1e # v1.5.0 + with: + version: nightly + + - name: ๐Ÿ™ Install Forge dependencies + run: | + cd packages/core + forge install + - name: ๐Ÿ“ฆ Install dependencies if: github.ref != 'refs/heads/main' run: pnpm install --filter=@terminallyonline/plug-app... - - name: ๐Ÿ”ฅ Build Next (Not Deploying) + - name: ๐Ÿ“ Write .env file if: github.ref != 'refs/heads/main' run: | - cd packages/app - ENCRYPTION_KEY=github-action DATABASE_URL=postgresql://postgres:postgres@localhost:5434/postgres NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=github-action OPENSEA_KEY=github-action ZERION_KEY=github-action NEXT_PUBLIC_EARLY_ACCESS=false NEXT_PUBLIC_APP_URL=http://localhost:3000 NEXT_PUBLIC_WS_URL=ws://localhost:3001 NEXT_PUBLIC_WALLETCONNECT_ID=github-action NEXT_PUBLIC_ALCHEMY_KEY=github-action pnpm build - - - name: ๐Ÿ”„ Install Vercel CLI - if: github.ref == 'refs/heads/main' - run: pnpm install --global vercel@latest - - - name: ๐Ÿ”— Pull Vercel Environment Information - if: github.ref == 'refs/heads/main' - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.APP_VERCEL_PROJECT_ID }} + cat < packages/app/.env + ENCRYPTION_KEY=github-action + DATABASE_URL=postgresql://postgres:postgres@localhost:5434/postgres + NEXTAUTH_URL=http://localhost:3000 + NEXTAUTH_SECRET=github-action + ANTHROPIC_KEY=github-action + OPENSEA_KEY=github-action + ZERION_KEY=github-action + NEXT_PUBLIC_EARLY_ACCESS=false + NEXT_PUBLIC_APP_URL=http://localhost:3000 + NEXT_PUBLIC_WS_URL=ws://localhost:3001 + NEXT_PUBLIC_WALLETCONNECT_ID=github-action + NEXT_PUBLIC_ALCHEMY_KEY=github-action + SOLVER_DELEGATE_ADDRESS=0x62180042606624f02d8a130da8a3171e9b33894d + NEXT_PUBLIC_QUICKNODE_NAME=github-action + NEXT_PUBLIC_QUICKNODE_KEY=github-action + EOF - - name: ๐Ÿš€ Deploy Project Artifacts to Vercel - if: github.ref == 'refs/heads/main' - run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.APP_VERCEL_PROJECT_ID }} + - name: ๐Ÿ”ฅ Build Next (Not Deploying) + if: github.ref != 'refs/heads/main' + run: pnpm build --filter=@terminallyonline/plug-app... - name: โ˜ข๏ธ Purge CloudFlare if: github.ref == 'refs/heads/main' @@ -71,3 +81,14 @@ jobs: -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ -H "Content-Type: application/json" \ --data '{"purge_everything":true}' + + - name: ๐Ÿš€ Create Release Pull Request or Publish + id: changesets + uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3 + with: + publish: pnpm changeset publish + commit: "chore: release package" + title: "chore: release package" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/audit-gh-actions.yml b/.github/workflows/audit-gh-actions.yml deleted file mode 100644 index 223731a4f..000000000 --- a/.github/workflows/audit-gh-actions.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: Audit GitHub Actions - -on: - # Run on demand - workflow_dispatch: - # Run on pull requests that modify workflow files - pull_request: - paths: - - '.github/workflows/**' - -jobs: - audit: - name: Audit Actions for Pin Compliance - runs-on: ubuntu-latest - permissions: - contents: read - issues: write # To create issues for unpinned actions - - steps: - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Check for unpinned GitHub Actions - id: audit - run: | - echo "Scanning workflow files for unpinned GitHub Actions..." - - # Use a simpler and more direct approach - # Create a flag file to track if we found any unpinned actions - touch /tmp/found_unpinned - > /tmp/found_unpinned - - # Create a file to collect results - > /tmp/unpinned_actions.txt - - # Process each workflow file - for file in $(find .github/workflows -name "*.yml" -o -name "*.yaml"); do - echo "Checking $file" - - # Extract lines with 'uses:' directives - grep -n "uses:" "$file" | while read -r match; do - line_num=$(echo "$match" | cut -d: -f1) - line_content=$(echo "$match" | cut -d: -f2-) - - # Skip if line contains a SHA hash or "No pin needed" - if echo "$line_content" | grep -q -E '@[0-9a-f]{40}' || echo "$line_content" | grep -q "# No pin needed"; then - continue - fi - - # Extract the action reference (more robust regex) - # This handles various formats and quotation styles - action_ref=$(echo "$line_content" | grep -o -E 'uses:[ ]*['\''"]?[a-zA-Z0-9_\-\.\/]+(@[a-zA-Z0-9\.\-]+)?['\''"]?' | sed 's/uses:[ ]*//g' | sed 's/['\''"]//g') - - # Skip empty matches or local actions - if [ -z "$action_ref" ] || [[ "$action_ref" == "./"* ]] || [[ "$action_ref" == ".github/"* ]]; then - continue - fi - - # Debug output - echo "DEBUG: Found unpinned action at line $line_num: $action_ref" >&2 - - # Record the unpinned action - echo "โš ๏ธ Found unpinned action in $file:" >> /tmp/unpinned_actions.txt - echo " - Line $line_num: $action_ref" >> /tmp/unpinned_actions.txt - echo "" >> /tmp/unpinned_actions.txt - - # Mark that we found an unpinned action - echo "true" > /tmp/found_unpinned - done - done - - # Check if we found any unpinned actions - FOUND_UNPINNED=$(cat /tmp/found_unpinned) - - # Debug output - echo "DEBUG: FOUND_UNPINNED = $FOUND_UNPINNED" >&2 - echo "DEBUG: Content of unpinned_actions.txt:" >&2 - cat /tmp/unpinned_actions.txt >&2 - - # Set outputs based on whether we found unpinned actions - if [ "$FOUND_UNPINNED" = "true" ]; then - echo "UNPINNED_ACTIONS<> $GITHUB_ENV - cat /tmp/unpinned_actions.txt >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "has_unpinned=true" >> $GITHUB_OUTPUT - - echo "Unpinned actions found:" - cat /tmp/unpinned_actions.txt - else - echo "UNPINNED_ACTIONS=No unpinned actions found." >> $GITHUB_ENV - echo "has_unpinned=false" >> $GITHUB_OUTPUT - echo "โœ… All GitHub Actions are properly pinned." - fi - - - name: Create Issue for Unpinned Actions - if: steps.audit.outputs.has_unpinned == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const unpinnedActions = process.env.UNPINNED_ACTIONS; - - // Check if there's an open issue about unpinned actions - const { data: issues } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - labels: ['critical', 'github_actions', 'devops'] - }); - - // Format the issue body - const body = `## Security Alert: Unpinned GitHub Actions - - The following GitHub Actions are not pinned to specific commit hashes in your workflows: - - ${unpinnedActions} - - ### Why is this important? - - Using version tags like \`@v3\` instead of commit SHAs makes your workflows vulnerable to supply chain attacks. If the action repository is compromised, malicious code could be executed in your CI/CD pipeline. - - ### How to fix: - - 1. Find the full commit SHA for the version you're using - 2. Replace the version tag with the commit SHA - 3. Add a comment with the version for readability - - This issue was automatically generated by the GitHub Actions audit workflow.`; - - if (issues.length === 0) { - // Create a new issue - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: '๐Ÿ”’ Security: Unpinned GitHub Actions detected', - body: body, - labels: ['critical', 'github_actions', 'devops'] - }); - console.log('Created new issue for unpinned actions'); - } else { - // Update existing issue - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issues[0].number, - body: body - }); - console.log(`Updated existing issue #${issues[0].number}`); - } - - - name: Report Audit Status - run: | - if [ "${{ steps.audit.outputs.has_unpinned }}" == "true" ]; then - echo "โš ๏ธ WARNING: Unpinned GitHub Actions found!" - echo "$UNPINNED_ACTIONS" - echo "" - echo "Please pin these actions to specific commit hashes to improve security." - - # For pull requests, we might want to fail the check - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - exit 1 - fi - else - echo "โœ… All GitHub Actions are properly pinned to commit hashes. Good job!" - fi \ No newline at end of file diff --git a/.github/workflows/cdn-main.yml b/.github/workflows/cdn-main.yml index d4866cdb8..4324df736 100644 --- a/.github/workflows/cdn-main.yml +++ b/.github/workflows/cdn-main.yml @@ -28,39 +28,32 @@ jobs: steps: - name: ๐Ÿ›’ Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: submodules: recursive - name: โš™๏ธ Setup Action - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - with: - version: 9.14.4 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: ๐Ÿ“š Setup Node - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 18 - cache: "pnpm" - name: ๐Ÿ“ฆ Install dependencies run: pnpm install --filter=@terminallyonline/plug-cdn - name: Deploy to Cloudflare R2 run: | - # Try to create bucket, ignore error if it already exists pnpm exec wrangler r2 bucket create plug-cdn || true - # Create a temporary file list find . -type f -not -path "./node_modules/*" -not -name "package.json" > files_to_upload.txt - # Upload files in parallel using xargs (30 concurrent uploads) cat files_to_upload.txt | xargs -P 30 -I {} bash -c ' file="$1" pnpm exec wrangler r2 object put plug-cdn/"${file:2}" --file="$file" ' -- {} - # Cleanup rm files_to_upload.txt env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.github/workflows/core-main.yml b/.github/workflows/core-main.yml deleted file mode 100644 index 6396d5022..000000000 --- a/.github/workflows/core-main.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Core - Build - -on: - push: - branches: - - "**" - paths: - - "packages/core/**" - -concurrency: - cancel-in-progress: true - group: ${{ github.workflow }}-${{ github.ref }} - -env: - FOUNDRY_PROFILE: ci - -defaults: - run: - working-directory: packages/core - -jobs: - build: - name: ๐Ÿ‘๏ธ Build and Keep Artifacts - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›’ Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - submodules: recursive - - - name: โš™๏ธ Setup Action - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - with: - version: 9.14.4 - - - name: ๐Ÿ“š Setup Node - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 - with: - node-version: 18 - cache: "pnpm" - - - name: ๐Ÿ“‚ Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v 4.2.3 - name: ๐Ÿซ™ Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: โœจ Install Foundry - uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # v1.3.1 - with: - version: nightly - - - name: โฌ‡๏ธ Install dependencies - run: pnpm install --filter=@terminallyonline/plug-core... - - - name: ๐Ÿ™ Install Forge dependencies - run: forge install - - - name: ๐Ÿ“ฆ Build package - run: pnpm build - - - name: ๐Ÿงน Lint - run: pnpm build:lint - - - name: ๐Ÿงช Run Forge Test - run: | - forge test diff --git a/.github/workflows/docs-main.yml b/.github/workflows/docs-main.yml index 8a3c790d4..e20559a87 100644 --- a/.github/workflows/docs-main.yml +++ b/.github/workflows/docs-main.yml @@ -1,4 +1,4 @@ -name: Docs - Build and Deploy +name: Docs - Deploy on: push: @@ -24,19 +24,18 @@ defaults: jobs: build: runs-on: ubuntu-latest - name: ๐Ÿ—๏ธ Build steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: ๐Ÿซณ Install Node - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 18 - name: ๐Ÿ“ฅ Install pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 with: - version: 9.14.4 + version: 10.11.1 - name: ๐Ÿ“š Get pnpm store directory shell: bash @@ -44,7 +43,7 @@ jobs: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: ๐Ÿ—ณ๏ธ Setup pnpm cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v 4.2.3 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v 5.0.1 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} @@ -63,18 +62,16 @@ jobs: pnpm build - name: โ›… Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: packages/docs/.vitepress/dist - # Deployment job deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} needs: build runs-on: ubuntu-latest - name: ๐Ÿš€ Deploy steps: - name: ๐Ÿš€ Deploy to GitHub Pages id: deployment diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml new file mode 100644 index 000000000..4a084392f --- /dev/null +++ b/.github/workflows/maintenance.yml @@ -0,0 +1,269 @@ +name: Maintenance + +on: + push: + branches: + - staging + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + upstream-merge: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Check if staging is behind main + id: check-branches + run: | + # Fetch all branches + git fetch origin + + # Check if staging exists + if git show-ref --verify --quiet refs/remotes/origin/staging; then + # Check if staging is behind main (needs updates from main) + if git rev-list --count origin/staging..origin/main > 0; then + echo "Staging is behind main, merge needed" + echo "needs_merge=true" >> $GITHUB_OUTPUT + else + echo "Staging is up-to-date with main or ahead, no merge needed" + echo "needs_merge=false" >> $GITHUB_OUTPUT + fi + else + echo "Staging branch doesn't exist, creating it from main" + echo "needs_merge=true" >> $GITHUB_OUTPUT + fi + + - name: Update staging with main changes + if: steps.check-branches.outputs.needs_merge == 'true' + run: | + git checkout staging + git merge origin/main -m "chore: sync staging with main" || { + echo "โš ๏ธ Merge conflict occurred! This requires manual resolution." + echo "The staging branch has changes that conflict with main." + exit 1 + } + + git push origin staging + + upstream-pr: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Check if staging is ahead of main + id: check-branches + run: | + # Fetch all branches + git fetch origin main + + # Check if staging is ahead of main (has changes not in main) + COMMITS_AHEAD=$(git rev-list --count origin/main..HEAD) + echo "Commits ahead of main: $COMMITS_AHEAD" + if [ $COMMITS_AHEAD -gt 0 ]; then + echo "Staging is ahead of main, PR creation needed" + echo "needs_pr=true" >> $GITHUB_OUTPUT + else + echo "Staging is not ahead of main, no PR needed" + echo "needs_pr=false" >> $GITHUB_OUTPUT + fi + + - name: Check for existing PR + if: steps.check-branches.outputs.needs_pr == 'true' + id: check_pr + run: | + # Use GitHub CLI to check for PRs + PR_COUNT=$(gh pr list --head staging --base main --state open --json number | jq length) + if [ "$PR_COUNT" -gt 0 ]; then + echo "PR already exists from staging to main" + echo "pr_exists=true" >> $GITHUB_OUTPUT + else + echo "No PR exists from staging to main" + echo "pr_exists=false" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create PR + if: steps.check-branches.outputs.needs_pr == 'true' && steps.check_pr.outputs.pr_exists == 'false' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'chore: staging to main', + body: 'This PR was automatically created to merge changes from staging into main.', + head: 'staging', + base: 'main' + }); + console.log(`PR created: ${pr.html_url}`); + audit: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Check for unpinned GitHub Actions + id: audit + run: | + echo "Scanning workflow files for unpinned GitHub Actions..." + + # Use a simpler and more direct approach + # Create a flag file to track if we found any unpinned actions + touch /tmp/found_unpinned + > /tmp/found_unpinned + + # Create a file to collect results + > /tmp/unpinned_actions.txt + + # Process each workflow file + for file in $(find .github/workflows -name "*.yml" -o -name "*.yaml"); do + echo "Checking $file" + + # Extract lines with 'uses:' directives + grep -n "uses:" "$file" | while read -r match; do + line_num=$(echo "$match" | cut -d: -f1) + line_content=$(echo "$match" | cut -d: -f2-) + + # Skip if line contains a SHA hash or "No pin needed" + if echo "$line_content" | grep -q -E '@[0-9a-f]{40}' || echo "$line_content" | grep -q "# No pin needed"; then + continue + fi + + # Extract the action reference (more robust regex) + # This handles various formats and quotation styles + action_ref=$(echo "$line_content" | grep -o -E 'uses:[ ]*['\''"]?[a-zA-Z0-9_\-\.\/]+(@[a-zA-Z0-9\.\-]+)?['\''"]?' | sed 's/uses:[ ]*//g' | sed 's/['\''"]//g') + + # Skip empty matches or local actions + if [ -z "$action_ref" ] || [[ "$action_ref" == "./"* ]] || [[ "$action_ref" == ".github/"* ]]; then + continue + fi + + # Debug output + echo "DEBUG: Found unpinned action at line $line_num: $action_ref" >&2 + + # Record the unpinned action + echo "โš ๏ธ Found unpinned action in $file:" >> /tmp/unpinned_actions.txt + echo " - Line $line_num: $action_ref" >> /tmp/unpinned_actions.txt + echo "" >> /tmp/unpinned_actions.txt + + # Mark that we found an unpinned action + echo "true" > /tmp/found_unpinned + done + done + + # Check if we found any unpinned actions + FOUND_UNPINNED=$(cat /tmp/found_unpinned) + + # Debug output + echo "DEBUG: FOUND_UNPINNED = $FOUND_UNPINNED" >&2 + echo "DEBUG: Content of unpinned_actions.txt:" >&2 + cat /tmp/unpinned_actions.txt >&2 + + # Set outputs based on whether we found unpinned actions + if [ "$FOUND_UNPINNED" = "true" ]; then + echo "UNPINNED_ACTIONS<> $GITHUB_ENV + cat /tmp/unpinned_actions.txt >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + echo "has_unpinned=true" >> $GITHUB_OUTPUT + + echo "Unpinned actions found:" + cat /tmp/unpinned_actions.txt + else + echo "UNPINNED_ACTIONS=No unpinned actions found." >> $GITHUB_ENV + echo "has_unpinned=false" >> $GITHUB_OUTPUT + echo "โœ… All GitHub Actions are properly pinned." + fi + + - name: Create Issue for Unpinned Actions + if: steps.audit.outputs.has_unpinned == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const unpinnedActions = process.env.UNPINNED_ACTIONS; + + // Check if there's an open issue about unpinned actions + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: ['critical', 'github_actions', 'devops'] + }); + + // Format the issue body + const body = `## Security Alert: Unpinned GitHub Actions + + The following GitHub Actions are not pinned to specific commit hashes in your workflows: + + ${unpinnedActions} + + ### Why is this important? + + Using version tags like \`@v3\` instead of commit SHAs makes your workflows vulnerable to supply chain attacks. If the action repository is compromised, malicious code could be executed in your CI/CD pipeline. + + ### How to fix: + + 1. Find the full commit SHA for the version you're using + 2. Replace the version tag with the commit SHA + 3. Add a comment with the version for readability + + This issue was automatically generated by the GitHub Actions audit workflow.`; + + if (issues.length === 0) { + // Create a new issue + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '๐Ÿ”’ Security: Unpinned GitHub Actions detected', + body: body, + labels: ['critical', 'github_actions', 'devops'] + }); + console.log('Created new issue for unpinned actions'); + } else { + // Update existing issue + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issues[0].number, + body: body + }); + console.log(`Updated existing issue #${issues[0].number}`); + } + + - name: Report Audit Status + run: | + if [ "${{ steps.audit.outputs.has_unpinned }}" == "true" ]; then + echo "โš ๏ธ WARNING: Unpinned GitHub Actions found!" + echo "$UNPINNED_ACTIONS" + echo "" + echo "Please pin these actions to specific commit hashes to improve security." + + # For pull requests, we might want to fail the check + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + exit 1 + fi + else + echo "โœ… All GitHub Actions are properly pinned to commit hashes. Good job!" + fi diff --git a/.github/workflows/monorepo-release.yml b/.github/workflows/monorepo-release.yml deleted file mode 100644 index 32da35158..000000000 --- a/.github/workflows/monorepo-release.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Monorepo - Release - -on: - push: - branches: - - main - paths: - - "packages/core/**" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - release: - name: โ›… Release - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›’ Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.head_ref }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: โš™๏ธ Setup Action - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - - - name: ๐Ÿ“š Setup Node - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 - with: - node-version: 18 - cache: "pnpm" - - - name: ๐Ÿ“‚ Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v 4.2.3 - name: ๐Ÿซ™ Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: โœจ Install Foundry - uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # v1.3.1 - with: - version: nightly - - - name: ๐Ÿ“ฆ Install dependencies - run: pnpm install --filter=@terminallyonline/plug-core... - - - name: ๐Ÿ™ Install Forge dependencies - run: forge install - - - name: ๐Ÿ“ฆ Build Packages - run: pnpm build --filter=@terminallyonline/plug-core - - - name: ๐Ÿš€ Create Release Pull Request or Publish - id: changesets - uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10 - with: - publish: pnpm changeset publish - commit: "chore: release package" - title: "chore: release package" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml deleted file mode 100644 index dd5f34789..000000000 --- a/.github/workflows/pr-to-main.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: PR from Staging to Main - -on: - push: - branches: - - staging - -permissions: - contents: read - pull-requests: write - -jobs: - check-and-create-pr: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - - name: Check if staging is ahead of main - id: check-branches - run: | - # Fetch all branches - git fetch origin main - - # Check if staging is ahead of main (has changes not in main) - COMMITS_AHEAD=$(git rev-list --count origin/main..HEAD) - echo "Commits ahead of main: $COMMITS_AHEAD" - if [ $COMMITS_AHEAD -gt 0 ]; then - echo "Staging is ahead of main, PR creation needed" - echo "needs_pr=true" >> $GITHUB_OUTPUT - else - echo "Staging is not ahead of main, no PR needed" - echo "needs_pr=false" >> $GITHUB_OUTPUT - fi - - - name: Check for existing PR - if: steps.check-branches.outputs.needs_pr == 'true' - id: check_pr - run: | - # Use GitHub CLI to check for PRs - PR_COUNT=$(gh pr list --head staging --base main --state open --json number | jq length) - if [ "$PR_COUNT" -gt 0 ]; then - echo "PR already exists from staging to main" - echo "pr_exists=true" >> $GITHUB_OUTPUT - else - echo "No PR exists from staging to main" - echo "pr_exists=false" >> $GITHUB_OUTPUT - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create PR - if: steps.check-branches.outputs.needs_pr == 'true' && steps.check_pr.outputs.pr_exists == 'false' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data: pr } = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: 'chore: staging to main', - body: 'This PR was automatically created to merge changes from staging into main.', - head: 'staging', - base: 'main' - }); - console.log(`PR created: ${pr.html_url}`); diff --git a/.github/workflows/solver-main.yml b/.github/workflows/solver-main.yml deleted file mode 100644 index 971a60ea6..000000000 --- a/.github/workflows/solver-main.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Solver - Build - -on: - push: - branches: ["**"] - paths: - - "packages/solver/**" - -concurrency: - cancel-in-progress: false - group: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: read - pages: write - id-token: write - -defaults: - run: - working-directory: packages/solver - -jobs: - build: - name: Build - runs-on: ubuntu-latest - env: - ALCHEMY_API_KEY: github-action - steps: - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Set up Go - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 - with: - go-version: "1.23.x" - check-latest: true - - name: Cache Go modules - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v 4.2.3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Get dependencies - run: go mod download - - name: Build - run: go build -v ./... diff --git a/.github/workflows/sync-staging-to-main.yml b/.github/workflows/sync-staging-to-main.yml deleted file mode 100644 index 348d45e43..000000000 --- a/.github/workflows/sync-staging-to-main.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Sync Staging to Main - -on: - push: - branches: - - main - -permissions: - contents: write - -jobs: - merge-main-to-staging: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - - name: Check if staging is behind main - id: check-branches - run: | - # Fetch all branches - git fetch origin - - # Check if staging exists - if git show-ref --verify --quiet refs/remotes/origin/staging; then - # Check if staging is behind main (needs updates from main) - if git rev-list --count origin/staging..origin/main > 0; then - echo "Staging is behind main, merge needed" - echo "needs_merge=true" >> $GITHUB_OUTPUT - else - echo "Staging is up-to-date with main or ahead, no merge needed" - echo "needs_merge=false" >> $GITHUB_OUTPUT - fi - else - echo "Staging branch doesn't exist, creating it from main" - echo "needs_merge=true" >> $GITHUB_OUTPUT - fi - - - name: Merge main into staging - if: steps.check-branches.outputs.needs_merge == 'true' - run: | - # Check if staging branch exists - if git show-ref --verify --quiet refs/remotes/origin/staging; then - git checkout staging - else - # Create staging branch if it doesn't exist - git checkout -b staging - fi - - git merge --no-ff origin/main -m "Auto-merge main into staging" || { - echo "Merge conflict occurred. Manual intervention required." - exit 1 - } - git push origin staging diff --git a/.gitignore b/.gitignore index dadc11648..abe806ad9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ **/build **/dist **/outputs +packages/app/public/sw.js # misc .DS_Store @@ -52,7 +53,7 @@ next-env.d.ts # Smart contract coverage core/cache -.vitepress/ +**/cache artifacts create2crunch --branch-coverage @@ -102,6 +103,7 @@ go.work.sum node_modules/ build abigenBindings +posts.ts # Shared definitions diff --git a/.gitmodules b/.gitmodules index 3d0ef2d3d..34a6e4c63 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "packages/core/lib/interop-lib"] path = packages/core/lib/interop-lib url = https://github.com/ethereum-optimism/interop-lib +[submodule "packages/core/lib/solady"] + path = packages/core/lib/solady + url = https://github.com/Vectorized/solady diff --git a/README.md b/README.md index 72c341350..7d5670ab0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Plug banner](/plug.png) +![Plug banner](https://raw.githubusercontent.com/Terminally-Online/plug/refs/heads/staging/plug.png) `@terminally-online/plug` is the only everything aggregator for all onchain activity in the EVM ecosystem, providing a seamless interface between users and the decentralized world. @@ -19,7 +19,5 @@ โ”œโ”€ licenses - "Automatic license generation and distribution for each package of Plug." โ”œโ”€ pitch - "Automatic generation of pitch deck and supporting materials." โ”œโ”€ posts - "Primitive CMS to manage post state on Plug website." -โ”œโ”€ science - "Singular housing location for all of the code-supported research of the ecosystem." -โ”œโ”€ solver โ€” "Instant transaction and route building for intents within Plug." -โ””โ”€ status โ€” "Monitor the health of all the systems that power the Plug ecosystem." +โ””โ”€ sdk - "Typescript React SDK that manages all Plug interfacing in web applications." ``` diff --git a/nixpacks.toml b/nixpacks.toml new file mode 100644 index 000000000..d4a61ac31 --- /dev/null +++ b/nixpacks.toml @@ -0,0 +1,24 @@ +providers = ["node"] + +[phases.setup] +nixpkgs = ["curl", "git"] +cmds = [ + "curl -L https://foundry.paradigm.xyz | bash" +] +paths = ["/root/.foundry/bin"] + +[phases.install] +dependsOn = ['setup'] +cmds = [ + "foundryup --install nightly && forge --version", + "pnpm install" +] + +[phases.build] +dependsOn = ['install'] +cmds = [ + "export PATH=\"$HOME/.foundry/bin:$PATH\" && pnpm build --filter=@terminallyonline/plug-app..." +] + +[start] + cmd = "pnpm start --filter=@terminallyonline/plug-app" diff --git a/package.json b/package.json index 4829463b4..34b89166e 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,29 @@ { - "name": "plug", - "version": "0.0.1", - "description": "Monorepo that contains all of the key packages of Plug.", + "name": "@terminallyonline/plug", "scripts": { - "build": "turbo run build", - "dev": "pnpm i && turbo run dev", + "build": "pnpm i && dotenv -- turbo run build", + "dev": "pnpm i && dotenv -- turbo run dev", "lint": "turbo run lint", "clean": "turbo run clean", - "format": "prettier --write \"**/*.{ts,tsx,md,json}\"", "changeset": "changeset", "changeset:version": "changeset version", - "changeset:release": "turbo run build --filter=!@terminallyonline/plug-docs && changeset publish" + "changeset:release": "turbo run build --filter=!@terminallyonline/plug-docs && changeset publish", + "start": "pnpm i && dotenv -- turbo run start" }, - "author": "nftchance", "dependencies": { - "fs-extra": "^11.1.1", - "tsx": "^4.19.2" + "dotenv-cli": "^8.0.0" }, "devDependencies": { "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.1", - "@types/fs-extra": "^11.0.4", - "@types/node": "^22.13.10", "turbo": "^2.4.4" }, + "version": "0.0.1", + "description": "Monorepo that contains all of the key packages of Plug.", + "author": "nftchance", "private": true, "workspaces": [ "packages/*" ], - "packageManager": "pnpm@9.14.4" + "packageManager": "pnpm@10.11.1" } diff --git a/packages/api/constants.go b/packages/api/constants.go new file mode 100644 index 000000000..2155f5743 --- /dev/null +++ b/packages/api/constants.go @@ -0,0 +1,48 @@ +package api + +// Call methods - how the plug executes against the target +const ( + CALL uint8 = 0x00 // Standard external call + DELEGATE_CALL uint8 = 0x01 // Execute in caller's context + STATIC_CALL uint8 = 0x02 // Read-only call + VALUE_CALL uint8 = 0x03 // Call with ETH value + EXTRACT_CALL uint8 = 0x04 // Extract element from tuple in state (no external call) + JUMP uint8 = 0x05 // Unconditional jump to destination index + JUMP_IF uint8 = 0x06 // Conditional jump based on boolean in state + MASK_CALL uint8 = 0x07 // Bitwise mask and shift extraction (no external call) +) + +// Index space encoding - where function arguments come from +const ( + IDX_STATIC_MAX uint8 = 0x7F // 0x00-0x7F: static state slots + IDX_VARIABLE_LENGTH uint8 = 0x80 // 0x80-0xBF: dynamic state slots + IDX_DYNAMIC_MAX uint8 = 0xBF // Upper bound for dynamic state + IDX_LITERAL_BASE uint8 = 0xC0 // 0xC0-0xFD: literal slots + IDX_LITERAL_MAX uint8 = 0xFD // Upper bound for literals + IDX_USE_STATE uint8 = 0xFE // Encode entire state array as arg + IDX_END_OF_ARGS uint8 = 0xFF // Signals end of parameter list +) + +// StaticStateIndex returns the index byte for a static state slot (32-byte value) +func StaticStateIndex(slot uint8) uint8 { + if slot > IDX_STATIC_MAX { + panic("static state slot must be <= 0x7F") + } + return slot +} + +// DynamicStateIndex returns the index byte for a dynamic state slot (variable-length) +func DynamicStateIndex(slot uint8) uint8 { + if slot > 0x3F { + panic("dynamic state slot must be <= 0x3F") + } + return IDX_VARIABLE_LENGTH | slot +} + +// LiteralIndex returns the index byte for a literal slot +func LiteralIndex(slot uint8) uint8 { + if slot > (IDX_LITERAL_MAX - IDX_LITERAL_BASE) { + panic("literal slot out of range") + } + return IDX_LITERAL_BASE + slot +} diff --git a/packages/api/constants_test.go b/packages/api/constants_test.go new file mode 100644 index 000000000..9edbba751 --- /dev/null +++ b/packages/api/constants_test.go @@ -0,0 +1,85 @@ +package api + +import "testing" + +func TestStaticStateIndex(t *testing.T) { + tests := []struct { + slot uint8 + expected uint8 + }{ + {0, 0x00}, + {1, 0x01}, + {63, 0x3F}, + {127, 0x7F}, + } + + for _, tt := range tests { + got := StaticStateIndex(tt.slot) + if got != tt.expected { + t.Errorf("StaticStateIndex(%d) = 0x%02x, want 0x%02x", tt.slot, got, tt.expected) + } + } +} + +func TestStaticStateIndex_Panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("StaticStateIndex(128) should panic") + } + }() + StaticStateIndex(128) +} + +func TestDynamicStateIndex(t *testing.T) { + tests := []struct { + slot uint8 + expected uint8 + }{ + {0, 0x80}, + {1, 0x81}, + {63, 0xBF}, + } + + for _, tt := range tests { + got := DynamicStateIndex(tt.slot) + if got != tt.expected { + t.Errorf("DynamicStateIndex(%d) = 0x%02x, want 0x%02x", tt.slot, got, tt.expected) + } + } +} + +func TestDynamicStateIndex_Panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("DynamicStateIndex(64) should panic") + } + }() + DynamicStateIndex(64) +} + +func TestLiteralIndex(t *testing.T) { + tests := []struct { + slot uint8 + expected uint8 + }{ + {0, 0xC0}, + {1, 0xC1}, + {61, 0xFD}, + } + + for _, tt := range tests { + got := LiteralIndex(tt.slot) + if got != tt.expected { + t.Errorf("LiteralIndex(%d) = 0x%02x, want 0x%02x", tt.slot, got, tt.expected) + } + } +} + +func TestLiteralIndex_Panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("LiteralIndex(62) should panic") + } + }() + LiteralIndex(62) +} diff --git a/packages/api/contracts/addresses.go b/packages/api/contracts/addresses.go new file mode 100644 index 000000000..ed03603ca --- /dev/null +++ b/packages/api/contracts/addresses.go @@ -0,0 +1,139 @@ +package contracts + +import ( + "errors" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + ethcommon "github.com/ethereum/go-ethereum/common" +) + +type AbiJson string + +func (a AbiJson) Parse() (*abi.ABI, error) { + if a == "" { + return nil, errors.New("ABI is empty") + } + contract, err := abi.JSON(strings.NewReader(string(a))) + if err != nil { + return nil, err + } + return &contract, nil +} + +func (a AbiJson) MustParse() *abi.ABI { + parsed, err := a.Parse() + if err != nil { + panic(err) + } + return parsed +} + +type Contract struct { + Address ethcommon.Address `swaggertype:"string" json:"-"` + BlockCreated uint64 `json:"-"` + Bytecode string `json:"-"` + BytecodeHash string `json:"-"` + DeployedBytecode string `json:"-"` + DeployedBytecodeHash string `json:"-"` + ABI AbiJson `json:"-"` +} + +var ( + Factory = Contract{ + Address: ethcommon.HexToAddress("0x0000000075d7d37E5fAB59A26A8Ed88da1540D35"), + Bytecode: "0x608080604052346015576105ac908161001b8239f35b600080fdfe608080604052600436101561001357600080fd5b600090813560e01c908162774360146101735750806375fd9f281461012e57637ac4ed641461004157600080fd5b3461012b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b576020906100f161007e610507565b90604051917fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076604052616009602052601e5268603d3d8160223d3973600a52605f602120916040526000606052565b60ff82536035523060601b600152602435601552605581209060355273ffffffffffffffffffffffffffffffffffffffff60405191168152f35b80fd5b503461012b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b57602061016b61007e610507565b604051908152f35b8260207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b576004359167ffffffffffffffff8311610503573660238401121561050357826004013567ffffffffffffffff811161043c57366024828601011161043c576060116104a7575060448201359173ffffffffffffffffffffffffffffffffffffffff83169073ffffffffffffffffffffffffffffffffffffffff606482013516938415801561049f575b61046f577fffffffffffffffffffffffffffffffffffffffff000000000000000000000000604051917fffffffffffffffffffffffff000000000000000000000000000000000000000060246020850195013560a01b16845260601b16602c8201526020815261029860408261052f565b5190209180916040517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207660405261600960205285601e5268603d3d8160223d3973600a52605f60212060358201523060581b815260ff81538460158201526055812093341590853b15610452575060019061044d578338818034895af115610440575b8160405283606052159485610375575b604080518715815273ffffffffffffffffffffffffffffffffffffffff87166020820152f35b957f32c7564be3bbeabe66360c08d019367b0d744fcb948046d92552c00c9743dd106020838699869552a373ffffffffffffffffffffffffffffffffffffffff8316803b1561043c576024839260405194859384927fc4d66de800000000000000000000000000000000000000000000000000000000845260048401525af18015610431579373ffffffffffffffffffffffffffffffffffffffff9291604095610421575b829361034f565b8161042b9161052f565b8561041a565b6040513d86823e3d90fd5b8280fd5b63b12d13eb84526004601cfd5b61033f565b94505084605f602134f5938461033f57633011642584526004601cfd5b50506044927f54f044c4000000000000000000000000000000000000000000000000000000008352600452602452fd5b508215610227565b807f08c379a0000000000000000000000000000000000000000000000000000000006064925260206004820152601760248201527f506c7567436f72653a73616c742d6d616c666f726d65640000000000000000006044820152fd5b5080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361052a57565b600080fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761057057604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c634300081e000a", + BytecodeHash: "0xb643e73d0dac356d258ca1f865d440975889caf1680567e908dbcd26b50c2dd7", + DeployedBytecode: "0x608080604052600436101561001357600080fd5b600090813560e01c908162774360146101735750806375fd9f281461012e57637ac4ed641461004157600080fd5b3461012b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b576020906100f161007e610507565b90604051917fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076604052616009602052601e5268603d3d8160223d3973600a52605f602120916040526000606052565b60ff82536035523060601b600152602435601552605581209060355273ffffffffffffffffffffffffffffffffffffffff60405191168152f35b80fd5b503461012b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b57602061016b61007e610507565b604051908152f35b8260207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012b576004359167ffffffffffffffff8311610503573660238401121561050357826004013567ffffffffffffffff811161043c57366024828601011161043c576060116104a7575060448201359173ffffffffffffffffffffffffffffffffffffffff83169073ffffffffffffffffffffffffffffffffffffffff606482013516938415801561049f575b61046f577fffffffffffffffffffffffffffffffffffffffff000000000000000000000000604051917fffffffffffffffffffffffff000000000000000000000000000000000000000060246020850195013560a01b16845260601b16602c8201526020815261029860408261052f565b5190209180916040517fcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f36060527f5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e207660405261600960205285601e5268603d3d8160223d3973600a52605f60212060358201523060581b815260ff81538460158201526055812093341590853b15610452575060019061044d578338818034895af115610440575b8160405283606052159485610375575b604080518715815273ffffffffffffffffffffffffffffffffffffffff87166020820152f35b957f32c7564be3bbeabe66360c08d019367b0d744fcb948046d92552c00c9743dd106020838699869552a373ffffffffffffffffffffffffffffffffffffffff8316803b1561043c576024839260405194859384927fc4d66de800000000000000000000000000000000000000000000000000000000845260048401525af18015610431579373ffffffffffffffffffffffffffffffffffffffff9291604095610421575b829361034f565b8161042b9161052f565b8561041a565b6040513d86823e3d90fd5b8280fd5b63b12d13eb84526004601cfd5b61033f565b94505084605f602134f5938461033f57633011642584526004601cfd5b50506044927f54f044c4000000000000000000000000000000000000000000000000000000008352600452602452fd5b508215610227565b807f08c379a0000000000000000000000000000000000000000000000000000000006064925260206004820152601760248201527f506c7567436f72653a73616c742d6d616c666f726d65640000000000000000006044820152fd5b5080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361052a57565b600080fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761057057604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fdfea164736f6c634300081e000a", + DeployedBytecodeHash: "0x6e5d6ac0d9dbfe5cca83fda08d8dc14013754a05610aaa932db34e0ad72a2bc9", + ABI: `[{"type":"function","name":"deploy","inputs":[{"name":"$salt","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"$alreadyDeployed","type":"bool","internalType":"bool"},{"name":"$socketAddress","type":"address","internalType":"address"}],"stateMutability":"payable"},{"type":"function","name":"getAddress","inputs":[{"name":"$implementation","type":"address","internalType":"address"},{"name":"$salt","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"$vault","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"initCodeHash","inputs":[{"name":"$implementation","type":"address","internalType":"address"}],"outputs":[{"name":"$initCodeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"event","name":"SocketDeployed","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"},{"name":"vault","type":"address","indexed":true,"internalType":"address"},{"name":"salt","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"error","name":"SaltInvalid","inputs":[{"name":"$implementation","type":"address","internalType":"address"},{"name":"$admin","type":"address","internalType":"address"}]}]`, + } + Router = Contract{ + Address: ethcommon.HexToAddress("0x00000000aa8916bd282d4A7147A0f306F483A265"), + Bytecode: "0x60808060405234603757600080546001600160a01b0319166f75d7d37e5fab59a26a8ed88da1540d35179055610f5f908161003d8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c806306fdde03146100675780631d447b441461006257806324f87a611461005d57806395d89b41146100585763f72060d51461005357600080fd5b610343565b6102e4565b6101de565b61014c565b346100c25760006003193601126100c2576100be604080519061008a818361047c565b600482527f506c7567000000000000000000000000000000000000000000000000000000006020830152519182918261011a565b0390f35b600080fd5b60009103126100c257565b60005b8381106100e55750506000910152565b81810151838201526020016100d5565b90601f19601f602093610113815180928187528780880191016100d2565b0116010190565b90602061012b9281815201906100f5565b90565b73ffffffffffffffffffffffffffffffffffffffff8116036100c257565b346100c25760406003193601126100c2576004356101698161012e565b60243567ffffffffffffffff81116100c257366023820112156100c257806004013567ffffffffffffffff81116100c25736602482840101116100c2576100be9260246101b793019061089f565b60405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b60206003193601126100c25760043567ffffffffffffffff81116100c257366023820112156100c25780600401359067ffffffffffffffff82116100c2573660248360051b830101116100c257906102346104e6565b506000917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9d81360301925b8260ff821610156102e25760006024611fe08360051b168401013590858212156102df5750907f6dc3c3a4e13e1e712e839686c373ae75e0388a5fe963d3bc521e0d84b16500506102b6602460ff94860101610c6e565b6102c66040519283928684610540565b0390a11660ff81146102da5760010161025f565b610511565b80fd5b005b346100c25760006003193601126100c2576100be6040805190610307818361047c565b600482527f504c5547000000000000000000000000000000000000000000000000000000006020830152519182916020835260208301906100f5565b346100c25760206003193601126100c25760043567ffffffffffffffff81116100c257806004019060a060031982360301126100c2578173ffffffffffffffffffffffffffffffffffffffff6103b66020936103b060846103f29735926103a98461012e565b0185610580565b9161089f565b1660405180809581947ff72060d50000000000000000000000000000000000000000000000000000000083528660048401526024830190610704565b03915afa8015610448576100be91600091610419575b506040519081529081906020820190565b61043b915060203d602011610441575b610433818361047c565b8101906105d1565b38610408565b503d610429565b61085c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f601f19910116810190811067ffffffffffffffff82111761049f57604052565b61044d565b604051906104b360408361047c565b565b67ffffffffffffffff811161049f57601f01601f191660200190565b604051906104e060208361047c565b60008252565b604051906040820182811067ffffffffffffffff82111761049f576040526060602083600081520152565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9161012b9360209160ff60a094168552828501526060604085015260ff81511660608501520151916040608082015201906100f5565b3561012b8161012e565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156100c2570180359067ffffffffffffffff82116100c2576020019181360383136100c257565b908160209103126100c2575190565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1823603018112156100c257016020813591019167ffffffffffffffff82116100c2578160051b360383136100c257565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1823603018112156100c257016020813591019167ffffffffffffffff82116100c25781360383136100c257565b601f8260209493601f19938186528686013760008582860101520116010190565b90602083828152019260208260051b82010193836000925b8484106106cc5750505050505090565b9091929394956020806106f483601f1986600196030188526106ee8b88610633565b90610683565b98019401940192949391906106bc565b60a08201929173ffffffffffffffffffffffffffffffffffffffff823561072a8161012e565b16815261073a60208301836105e0565b809560a060208501525260c0820160c08660051b84010195826000907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc181360301935b8383106107d3575050505050506107c56107b061012b94956107a260408601866105e0565b9085830360408701526106a4565b92606081013560608401526080810190610633565b916080818503910152610683565b9091929394987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff408782030182528935868112156100c257602061084c600193868394019061083f6108356108278480610633565b604085526040850191610683565b9285810190610633565b9185818503910152610683565b9b0192019301919094939261077d565b6040513d6000823e3d90fd5b91908260409103126100c257815180151581036100c25760209092015161012b8161012e565b91602061012b938181520191610683565b9190823b156108c4575b505073ffffffffffffffffffffffffffffffffffffffff1690565b61093c916040916109066108ed60005473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b90600084518096819582947e7743600000000000000000000000000000000000000000000000000000000084526004840161088e565b03925af1908115610448576000916109d2575b5073ffffffffffffffffffffffffffffffffffffffff821673ffffffffffffffffffffffffffffffffffffffff82160361098957806108a9565b7f5a4be8f40000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff9081166004521660245260446000fd5b6109f4915060403d6040116109fc575b6109ec818361047c565b810190610868565b90503861094f565b503d6109e2565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61813603018212156100c2570190565b90602082528035917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61823603018312156100c257610a92610a8761012b946040602085015284606085019101610704565b926020810190610633565b916040601f1982860301910152610683565b60009060033d11610ab157565b905060046000803e60005160e01c90565b600060443d1061012b576040516003193d016004823e8051913d602484011167ffffffffffffffff841117610b31578282019283519167ffffffffffffffff8311610b29576003193d85010160208487010111610b29575061012b9291016020019061047c565b949350505050565b92915050565b6000809160233d11610b4557565b9150506020600460003e600051600191565b3d15610b82573d90610b68826104b5565b91610b76604051938461047c565b82523d6000602084013e565b606090565b60405190610b9660408361047c565b600f82527f506c75673a656d7074792d6461746100000000000000000000000000000000006020830152565b60405190610bd160408361047c565b601582527f506c75673a756e6b6e6f776e2d73656c6563746f7200000000000000000000006020830152565b91906040838203126100c257825160ff811681036100c2579260208101519067ffffffffffffffff82116100c2570181601f820112156100c2578051610c42816104b5565b92610c50604051948561047c565b818452602082840101116100c25761012b91602080850191016100d2565b610c766104e6565b91610ca66108ed610c8f610c8a8580610a03565b610576565b6103b0610c9c8680610a03565b6080810190610580565b91604051907f2e66fb7b00000000000000000000000000000000000000000000000000000000825260208280610cdf8460048301610a36565b0381875afa91821561044857600092610f31575b508193803b156100c257610d3b6000929183926040519485809481937fccc2040100000000000000000000000000000000000000000000000000000000835260048301610a36565b03925af19081610f16575b50610eff576001610d55610aa4565b806308c379a014610ed357634e487b7114610e77575b610d725750565b909250610d7d610b57565b906004825110610e5f57507fb921d87d000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000060208301511603610e40576040519080519060031982019081845260005b828110610e2a5750505090601c610e0e9282010160405260208082518301019101610bfd565b610e22610e196104a4565b60ff9093168352565b602082015291565b8060246020928401015182828801015201610de8565b5090610e4a6104a4565b60fc8152610e56610bc2565b60208201529190565b92915050610e6b6104a4565b60fb8152610e56610b87565b610e7f610b37565b90610e8b575b50610d6b565b945050610eab610eb9600095604051928391602083019190602083019252565b03601f19810183528261047c565b610ec16104a4565b60fd8152905b60208201529338610e85565b50610edc610ac2565b80610ee75750610d6b565b945050600093610ef56104a4565b60fe815290610ec7565b509150610f0a6104a4565b60ff8152610e226104d1565b80610f256000610f2b9361047c565b806100c7565b38610d46565b610f4b91925060203d60201161044157610433818361047c565b9038610cf356fea164736f6c634300081e000a", + BytecodeHash: "0x062e116a7209290a1207eb77a6e748c612c796c3e9a54729d49d8a24a997d9ae", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c806306fdde03146100675780631d447b441461006257806324f87a611461005d57806395d89b41146100585763f72060d51461005357600080fd5b610343565b6102e4565b6101de565b61014c565b346100c25760006003193601126100c2576100be604080519061008a818361047c565b600482527f506c7567000000000000000000000000000000000000000000000000000000006020830152519182918261011a565b0390f35b600080fd5b60009103126100c257565b60005b8381106100e55750506000910152565b81810151838201526020016100d5565b90601f19601f602093610113815180928187528780880191016100d2565b0116010190565b90602061012b9281815201906100f5565b90565b73ffffffffffffffffffffffffffffffffffffffff8116036100c257565b346100c25760406003193601126100c2576004356101698161012e565b60243567ffffffffffffffff81116100c257366023820112156100c257806004013567ffffffffffffffff81116100c25736602482840101116100c2576100be9260246101b793019061089f565b60405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b60206003193601126100c25760043567ffffffffffffffff81116100c257366023820112156100c25780600401359067ffffffffffffffff82116100c2573660248360051b830101116100c257906102346104e6565b506000917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9d81360301925b8260ff821610156102e25760006024611fe08360051b168401013590858212156102df5750907f6dc3c3a4e13e1e712e839686c373ae75e0388a5fe963d3bc521e0d84b16500506102b6602460ff94860101610c6e565b6102c66040519283928684610540565b0390a11660ff81146102da5760010161025f565b610511565b80fd5b005b346100c25760006003193601126100c2576100be6040805190610307818361047c565b600482527f504c5547000000000000000000000000000000000000000000000000000000006020830152519182916020835260208301906100f5565b346100c25760206003193601126100c25760043567ffffffffffffffff81116100c257806004019060a060031982360301126100c2578173ffffffffffffffffffffffffffffffffffffffff6103b66020936103b060846103f29735926103a98461012e565b0185610580565b9161089f565b1660405180809581947ff72060d50000000000000000000000000000000000000000000000000000000083528660048401526024830190610704565b03915afa8015610448576100be91600091610419575b506040519081529081906020820190565b61043b915060203d602011610441575b610433818361047c565b8101906105d1565b38610408565b503d610429565b61085c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f601f19910116810190811067ffffffffffffffff82111761049f57604052565b61044d565b604051906104b360408361047c565b565b67ffffffffffffffff811161049f57601f01601f191660200190565b604051906104e060208361047c565b60008252565b604051906040820182811067ffffffffffffffff82111761049f576040526060602083600081520152565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9161012b9360209160ff60a094168552828501526060604085015260ff81511660608501520151916040608082015201906100f5565b3561012b8161012e565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156100c2570180359067ffffffffffffffff82116100c2576020019181360383136100c257565b908160209103126100c2575190565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1823603018112156100c257016020813591019167ffffffffffffffff82116100c2578160051b360383136100c257565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1823603018112156100c257016020813591019167ffffffffffffffff82116100c25781360383136100c257565b601f8260209493601f19938186528686013760008582860101520116010190565b90602083828152019260208260051b82010193836000925b8484106106cc5750505050505090565b9091929394956020806106f483601f1986600196030188526106ee8b88610633565b90610683565b98019401940192949391906106bc565b60a08201929173ffffffffffffffffffffffffffffffffffffffff823561072a8161012e565b16815261073a60208301836105e0565b809560a060208501525260c0820160c08660051b84010195826000907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc181360301935b8383106107d3575050505050506107c56107b061012b94956107a260408601866105e0565b9085830360408701526106a4565b92606081013560608401526080810190610633565b916080818503910152610683565b9091929394987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff408782030182528935868112156100c257602061084c600193868394019061083f6108356108278480610633565b604085526040850191610683565b9285810190610633565b9185818503910152610683565b9b0192019301919094939261077d565b6040513d6000823e3d90fd5b91908260409103126100c257815180151581036100c25760209092015161012b8161012e565b91602061012b938181520191610683565b9190823b156108c4575b505073ffffffffffffffffffffffffffffffffffffffff1690565b61093c916040916109066108ed60005473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b90600084518096819582947e7743600000000000000000000000000000000000000000000000000000000084526004840161088e565b03925af1908115610448576000916109d2575b5073ffffffffffffffffffffffffffffffffffffffff821673ffffffffffffffffffffffffffffffffffffffff82160361098957806108a9565b7f5a4be8f40000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff9081166004521660245260446000fd5b6109f4915060403d6040116109fc575b6109ec818361047c565b810190610868565b90503861094f565b503d6109e2565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61813603018212156100c2570190565b90602082528035917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61823603018312156100c257610a92610a8761012b946040602085015284606085019101610704565b926020810190610633565b916040601f1982860301910152610683565b60009060033d11610ab157565b905060046000803e60005160e01c90565b600060443d1061012b576040516003193d016004823e8051913d602484011167ffffffffffffffff841117610b31578282019283519167ffffffffffffffff8311610b29576003193d85010160208487010111610b29575061012b9291016020019061047c565b949350505050565b92915050565b6000809160233d11610b4557565b9150506020600460003e600051600191565b3d15610b82573d90610b68826104b5565b91610b76604051938461047c565b82523d6000602084013e565b606090565b60405190610b9660408361047c565b600f82527f506c75673a656d7074792d6461746100000000000000000000000000000000006020830152565b60405190610bd160408361047c565b601582527f506c75673a756e6b6e6f776e2d73656c6563746f7200000000000000000000006020830152565b91906040838203126100c257825160ff811681036100c2579260208101519067ffffffffffffffff82116100c2570181601f820112156100c2578051610c42816104b5565b92610c50604051948561047c565b818452602082840101116100c25761012b91602080850191016100d2565b610c766104e6565b91610ca66108ed610c8f610c8a8580610a03565b610576565b6103b0610c9c8680610a03565b6080810190610580565b91604051907f2e66fb7b00000000000000000000000000000000000000000000000000000000825260208280610cdf8460048301610a36565b0381875afa91821561044857600092610f31575b508193803b156100c257610d3b6000929183926040519485809481937fccc2040100000000000000000000000000000000000000000000000000000000835260048301610a36565b03925af19081610f16575b50610eff576001610d55610aa4565b806308c379a014610ed357634e487b7114610e77575b610d725750565b909250610d7d610b57565b906004825110610e5f57507fb921d87d000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000060208301511603610e40576040519080519060031982019081845260005b828110610e2a5750505090601c610e0e9282010160405260208082518301019101610bfd565b610e22610e196104a4565b60ff9093168352565b602082015291565b8060246020928401015182828801015201610de8565b5090610e4a6104a4565b60fc8152610e56610bc2565b60208201529190565b92915050610e6b6104a4565b60fb8152610e56610b87565b610e7f610b37565b90610e8b575b50610d6b565b945050610eab610eb9600095604051928391602083019190602083019252565b03601f19810183528261047c565b610ec16104a4565b60fd8152905b60208201529338610e85565b50610edc610ac2565b80610ee75750610d6b565b945050600093610ef56104a4565b60fe815290610ec7565b509150610f0a6104a4565b60ff8152610e226104d1565b80610f256000610f2b9361047c565b806100c7565b38610d46565b610f4b91925060203d60201161044157610433818361047c565b9038610cf356fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x0b6b9234672da0af7b8a22f965cd33c38abfcdd920876385908d22fc7689b26c", + ABI: `[{"type":"function","name":"digest","inputs":[{"name":"$plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$digest","type":"bytes32","internalType":"bytes32"}],"stateMutability":"nonpayable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"$name","type":"string","internalType":"string"}],"stateMutability":"pure"},{"type":"function","name":"plug","inputs":[{"name":"$livePlugs","type":"tuple[]","internalType":"struct PlugTypesLib.LivePlugs[]","components":[{"name":"plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"socket","inputs":[{"name":"$socketAddress","type":"address","internalType":"address"},{"name":"$salt","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"$socket","type":"address","internalType":"contract PlugSocketInterface"}],"stateMutability":"nonpayable"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"$version","type":"string","internalType":"string"}],"stateMutability":"pure"},{"type":"event","name":"PlugResult","inputs":[{"name":"index","type":"uint8","indexed":false,"internalType":"uint8"},{"name":"plugsHash","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"reason","type":"tuple","indexed":false,"internalType":"struct PlugTypesLib.Result","components":[{"name":"index","type":"uint8","internalType":"uint8"},{"name":"error","type":"string","internalType":"string"}]}],"anonymous":false},{"type":"error","name":"SocketAddressInvalid","inputs":[{"name":"$intended","type":"address","internalType":"address"},{"name":"$socket","type":"address","internalType":"address"}]}]`, + } + Socket = Contract{ + Address: ethcommon.HexToAddress("0x00000000f290cFAc2F220ae2f0306f6052611806"), + Bytecode: "0x60a06040523460745730608052638b78c6d819546066576001638b78c6d81955600160007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3604051612a04908161007a82396080518181816109100152610a860152f35b630dc149f06000526004601cfd5b600080fdfe60806040526004361015610015575b3661179357005b60003560e01c806306fdde03146101f557806312715922146101f057806325692962146101eb5780632cfa37cb146101e65780632e66fb7b146101e157806334bb3700146101dc578063453004ed146101d75780634e9e7760146101d25780634f1ef286146101cd57806352d1902d146101c857806354d1f13d146101c357806354fd4d50146101be578063598993a0146101b9578063649f069b146101b45780636899a9d6146101af578063715018a6146101aa5780637976a334146101a55780637be6a1b5146101a05780638da5cb5b1461019b57806395d89b4114610196578063a2bff4cf14610191578063c2fb26a61461018c578063c4d66de814610187578063ccc2040114610182578063dfe86ac51461017d578063f04e283e14610178578063f2fde38b14610173578063f72060d51461016e578063fd5508e7146101695763fee81cf40361000e57611156565b611138565b611121565b6110e5565b611095565b611072565b610ee5565b610e67565b610de9565b610d7b565b610d1c565b610ce4565b610ca9565b610c6e565b610c03565b610b8a565b610b77565b610b38565b610b1c565b610ad4565b610a72565b6108d8565b610892565b61084d565b610727565b610706565b6106bb565b61063c565b61061c565b610242565b60005b83811061020d5750506000910152565b81810151838201526020016101fd565b90601f19601f60209361023b815180928187528780880191016101fa565b0116010190565b346102765760006003193601126102765761027261025e611189565b60405191829160208352602083019061021d565b0390f35b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176102c657604052565b61027b565b6080810190811067ffffffffffffffff8211176102c657604052565b90601f601f19910116810190811067ffffffffffffffff8211176102c657604052565b6040519061031960a0836102e7565b565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361027657565b359073ffffffffffffffffffffffffffffffffffffffff8216820361027657565b67ffffffffffffffff81116102c65760051b60200190565b67ffffffffffffffff81116102c657601f01601f191660200190565b92919261039f82610377565b916103ad60405193846102e7565b829481845281830111610276578281602093846000960137010152565b9080601f83011215610276578160206103e593359101610393565b90565b91906040838203126102765760405190610401826102aa565b8193803567ffffffffffffffff811161027657826104209183016103ca565b835260208101359167ffffffffffffffff83116102765760209261044492016103ca565b910152565b9291906104558161035f565b9361046360405195866102e7565b602085838152019160051b8101918383116102765781905b838210610489575050505050565b813567ffffffffffffffff8111610276576020916104aa87849387016103e8565b81520191019061047b565b9080601f83011215610276578160206103e593359101610449565b9291906104dc8161035f565b936104ea60405195866102e7565b602085838152019160051b8101918383116102765781905b838210610510575050505050565b813567ffffffffffffffff81116102765760209161053187849387016103ca565b815201910190610502565b9080601f83011215610276578160206103e5933591016104d0565b91909160a0818403126102765761056c61030a565b926105768261033e565b8452602082013567ffffffffffffffff811161027657816105989184016104b5565b6020850152604082013567ffffffffffffffff811161027657816105bd91840161053c565b604085015260608201356060850152608082013567ffffffffffffffff8111610276576105ea92016103ca565b6080830152565b6020600319820112610276576004359067ffffffffffffffff8211610276576103e591600401610557565b3461027657602061063461062f366105f1565b6111c4565b604051908152f35b60006003193601126102765763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b6020600319820112610276576004359067ffffffffffffffff8211610276576003198260a0920301126102765760040190565b34610276576106d16106cc36610688565b6112be565b005b6020600319820112610276576004359067ffffffffffffffff821161027657600319826040920301126102765760040190565b346102765760206106346107223661071d366106d3565b6107ea565b611559565b346102765760206003193601126102765760043567ffffffffffffffff8111610276576080600319823603011261027657604051610764816102cb565b816004013567ffffffffffffffff81116102765761078890600436918501016103ca565b815260248201359067ffffffffffffffff8211610276576107d06064610272946107bb6107da95600436918401016103ca565b6020850152604481013560408501520161033e565b6060820152611300565b6040519081529081906020820190565b91906040838203126102765760405190610803826102aa565b8193803567ffffffffffffffff81116102765782610420918301610557565b6020600319820112610276576004359067ffffffffffffffff8211610276576103e5916004016107ea565b3461027657602061087461086036610822565b8261086b8251611387565b91015190611a22565b73ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346102765760206106346108a5366105f1565b611387565b9181601f840112156102765782359167ffffffffffffffff8311610276576020838186019501011161027657565b6040600319360112610276576108ec61031b565b60243567ffffffffffffffff81116102765761090c9036906004016108aa565b90917f00000000000000000000000000000000000000000000000000000000000000003014610a645761093e33611b37565b15610a145773ffffffffffffffffffffffffffffffffffffffff163d6000526352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602060016004601d855afa5103610a0657807fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a281817f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc556109e757005b816000926040519485378338925af4156109fd57005b3d6000823e3d90fd5b6355299b496001526004601dfd5b610a60610a1f611650565b6040519182917fb921d87d00000000000000000000000000000000000000000000000000000000835260ff600484015260406024840152604483019061021d565b0390fd5b639f03a0266000526004601cfd5b3461027657600060031936011261027657307f000000000000000000000000000000000000000000000000000000000000000003610a645760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b60006003193601126102765763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b346102765760006003193601126102765761027261025e61140d565b346102765760406003193601126102765760043567ffffffffffffffff811161027657610634610b6e602092369060040161053c565b602435906114c5565b3461027657602061063461072236610822565b346102765760206003193601126102765760043567ffffffffffffffff811161027657610bbb9036906004016108aa565b810190600090602081840312610bff5780359167ffffffffffffffff8311610bfc576106d1610bec85858501610557565b60406020820151910151906118dc565b80fd5b5080fd5b600060031936011261027657610c17611ac8565b6000638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a37f8000000000000000000000000000000000000000000000000000000000000000638b78c6d81955005b346102765760206003193601126102765760043567ffffffffffffffff811161027657610634610ca460209236906004016104b5565b6115b4565b346102765760206003193601126102765760043567ffffffffffffffff811161027657610634610cdf60209236906004016103e8565b6115f7565b34610276576000600319360112610276576020638b78c6d8195473ffffffffffffffffffffffffffffffffffffffff60405191168152f35b34610276576000600319360112610276576102726040805190610d3f81836102e7565b600582527f504c55475300000000000000000000000000000000000000000000000000000060208301525191829160208352602083019061021d565b610d8436610688565b3068929eee149b4bd212685414610ddb573068929eee149b4bd2126855610daa33611b37565b15610a1457610dc890610dc061062f3683610557565b6001556112be565b60006001553868929eee149b4bd2126855005b63ab143c066000526004601cfd5b3461027657600060031936011261027657610e026116a3565b60405180916020825273ffffffffffffffffffffffffffffffffffffffff6060610e51610e3b84516080602088015260a087019061021d565b6020850151601f1987830301604088015261021d565b9260408101518286015201511660808301520390f35b3461027657602060031936011261027657610e8061031b565b638b78c6d81954610ed75773ffffffffffffffffffffffffffffffffffffffff16801560ff1b8117638b78c6d8195560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3005b630dc149f06000526004601cfd5b610eee366106d3565b3068929eee149b4bd212685414610ddb573068929eee149b4bd2126855610f41610f246108a536610f1f8580611732565b610557565b610f3b610f346020850185611b6c565b3691610393565b90611a22565b610f9f610f8e610f88610f61610f578680611732565b6080810190611b6c565b50357fffffffffffffffffffffffff00000000000000000000000000000000000000001690565b60a01c90565b6bffffffffffffffffffffffff1690565b6001610fc3826001908060081c600052600060205260ff60406000205491161c1690565b15151461106757610fed908060081c6000526000602052600160ff604060002092161b8154179055565b73ffffffffffffffffffffffffffffffffffffffff80638b78c6d81954921691160361102a5761101c90611765565b3868929eee149b4bd2126855005b610a606110356116f7565b6040519182917fb921d87d0000000000000000000000000000000000000000000000000000000083526004830161168b565b610a60611035611bbd565b346102765760006003193601126102765760206106346110906116a3565b611300565b6020600319360112610276576110a961031b565b6110b1611ac8565b63389a75e1600c52806000526020600c2090815442116110d75760006106d19255611ae5565b636f5e88186000526004601cfd5b6020600319360112610276576110f961031b565b611101611ac8565b8060601b15611113576106d190611ae5565b637448fbae6000526004601cfd5b346102765760206106346108a536610f1f36610688565b34610276576000600319360112610276576020600154604051908152f35b346102765760206003193601126102765761116f61031b565b63389a75e1600c52600052602080600c2054604051908152f35b604051906111986040836102e7565b600b82527f506c756720536f636b65740000000000000000000000000000000000000000006020830152565b73ffffffffffffffffffffffffffffffffffffffff815116906111ea60208201516115b4565b90604081015190608061120360608301938451906114c5565b925191015160208151910120916040519360208501957f5e753b799be998b96f5989c4585d8cf47a8e46682fc527f23d88e0d6a09048cf875260408601526060850152608084015260a083015260c082015260c0815261126460e0826102e7565b51902090565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610276570180359067ffffffffffffffff821161027657602001918160051b3603831361027657565b6112fd906112f76112e56112ef6112d8602085018561126a565b929094604081019061126a565b9490923691610449565b9236916104d0565b906118dc565b50565b805160208151910120906020810151602081519101209073ffffffffffffffffffffffffffffffffffffffff6060604083015192015116906040519260208401947f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f865260408501526060840152608083015260a082015260a0815261126460c0826102e7565b6113c86113c2611395611189565b61139d61140d565b604051916113aa836102cb565b82526020820152466040820152306060820152611300565b916111c4565b6040519060208201927f1901000000000000000000000000000000000000000000000000000000000000845260228301526042820152604281526112646062826102e7565b6040519061141c6040836102e7565b600582527f302e302e310000000000000000000000000000000000000000000000000000006020830152565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b805182101561148b5760209160051b010190565b611448565b6020806103199294939460405195816114b288935180928680870191016101fa565b82019083820152030180855201836102e7565b6000929160605b825185101561154b5760019082861c821682036115295761152090600060206114f860405191826102e7565b527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090611490565b945b01936114cc565b611545906115378786611477565b516020815191012090611490565b94611522565b805160209091012093505050565b602061156582516111c4565b910151602081519101206040519060208201927feb79b94df94230950addab3b4e85da31ca18ec7ce505cc225a455e8e31773a1d845260408301526060820152606081526112646080826102e7565b60009060605b81518310156115ea576115e26001916115dc6115d68686611477565b516115f7565b90611490565b9201916115ba565b9150506020815191012090565b60208151818151910120910151602081519101206040519060208201927f4a5c402b142b42f7a370c0499cf5ed6a0a39a73d479eb129087b5b45e608ccc6845260408301526060820152606081526112646080826102e7565b6040519061165f6040836102e7565b601782527f506c7567436f72653a73656e6465722d696e76616c69640000000000000000006020830152565b9060406103e59260ff8152816020820152019061021d565b600060606040516116b3816102cb565b81815281602082015282604082015201526116cc611189565b6116d461140d565b604051916116e1836102cb565b8252602082015246604082015230606082015290565b604051906117066040836102e7565b601a82527f506c7567436f72653a7369676e61747572652d696e76616c69640000000000006020830152565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6181360301821215610276570190565b6106cc61178c9161178361062f61177c8380611732565b3690610557565b60015580611732565b6000600155565b60003560e01c63bc197c81811463f23a6e6182141763150b7a028214176117c257633c10b94e6000526004601cfd5b6020526020603cf35b80516002101561148b5760220190565b80516006101561148b5760260190565b80511561148b5760200190565b80516001101561148b5760210190565b80516005101561148b5760250190565b90815181101561148b570160200190565b604051906118386040836102e7565b601482527f506c7567436f72653a706c75672d6661696c65640000000000000000000000006020830152565b6040906103e593928152816020820152019061021d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146118d75760010190565b61187b565b906000905b8251821015611a1c5761195561194f61194f8361193a61193461190e611907898b611477565b51516117cb565b517fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b6020611946888a611477565b51015190611c3f565b60ff1690565b60058114611a03576006146119e557611978816119728486611477565b51611e65565b9190156119a65761199d91611997916119918587611477565b51612040565b916118aa565b925b92906118e1565b826119af611829565b90610a606040519283927fb921d87d00000000000000000000000000000000000000000000000000000000845260048401611864565b90816119f46119fd9285611477565b51845190611d59565b9261199f565b509081611a136119fd9285611477565b51845190611ceb565b91505090565b91909160405192805180604014611a8c57604114611a4d57505050505b638baa579f6000526004601cfd5b602091606082015160001a835260408201516060525b60005201516040526020600160806000825afa519160006060526040523d610319575050611a3f565b506020917f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040830151601b8160ff1c01855216606052611a63565b638b78c6d819543303611ad757565b6382b429006000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff1680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3801560ff1b17638b78c6d81955565b73ffffffffffffffffffffffffffffffffffffffff80638b78c6d81954169116908114908115611b65575090565b9050301490565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610276570180359067ffffffffffffffff82116102765760200191813603831361027657565b60405190611bcc6040836102e7565b601682527f506c7567436f72653a6e6f6e63652d696e76616c6964000000000000000000006020830152565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff409116019060ff82116118d757565b908160051b91808304602014901517156118d757565b9160ff83169060c082101580611ce1575b15611c7c575050611c7790611c7160ff611c6b602095611bf8565b16611c29565b90612295565b015190565b91509160808310611c8f57505050600090565b81519081841015611cae575050602091611ca891611477565b51015190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff1660045260245260446000fd5b5060fe8210611c50565b91611d0a91926020611cfd82516117db565b5160f81c91015190611c3f565b90808211611d16575090565b907f7181c5d10000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b60ff60019116019060ff82116118d757565b9091600060ff611d6984516117db565b5160f81c1690611d80836020860193845190611c3f565b15611ded5750611da561193461190e611dad9551611d9f600760ff1690565b90611818565b905190611c3f565b905b808211611dba575090565b7f7181c5d10000000000000000000000000000000000000000000000000000000060005260049190915260245260446000fd5b7fff00000000000000000000000000000000000000000000000000000000000000611da591611e25611e2f965160ff60081690611818565b9050511660f81c90565b90611daf565b3d15611e60573d90611e4682610377565b91611e5460405193846102e7565b82523d6000602084013e565b606090565b6000606060068351511061201657611e8361193461190e85516117cb565b9360ff611e9a61194f836020880198895190611c3f565b1694600486146120075760078614611ff857611ee7611ece611ece84611ec661193461190e8b516117eb565b855190611c3f565b73ffffffffffffffffffffffffffffffffffffffff1690565b94611f36611f2f611f0a85611f0261193461190e87516117f8565b865190611c3f565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b848361255f565b9660018103611f6057505050505050816000929160208493519201905af4611f5c611e35565b9091565b80611f835750505050505081600092918360208194519301915af1611f5c611e35565b60028103611fa857505050505050816000929160208493519201905afa611f5c611e35565b60039098969497959814611fbe575b5050505050565b600096508695975090611da561193461190e611fdb959451611808565b602083519301915af1611fec611e35565b90913880808080611fb7565b5092915050611f5c92506123ec565b5092915050611f5c925061230f565b7f9f3b23380000000000000000000000000000000000000000000000000000000060005260046000fd5b919091805180516003101561148b576023015160f81c60ff612069856020850193845190611c3f565b16915180516004101561148b5761208b9160248692015160f81c905190611c3f565b15159160ff820361209d575b50505090565b90919293849360009580519460ff8116958610156121a8575b505015612191576020825110612169576020820151906120ec6120e76120e16020858701015161226c565b60051c90565b611c29565b946120fe6120f98761227a565b61222d565b94875b61210a8861227a565b8110156121425760018161212d61190e61212761210a958a612288565b8a611818565b8b1a612139828b611818565b53019050612101565b509350945094505061215f916121588285611477565b5282611477565b505b388080612097565b6004857f455639ff000000000000000000000000000000000000000000000000000000008152fd5b9350906121a2916121588285611477565b50612161565b6121c4919794959293965061194f6121bf91611d47565b6128f1565b93835b87518110156121fa57806121dd6001928a611477565b516121e88289611477565b526121f38188611477565b50016121c7565b5092955092939038806120b6565b6040805190919061221983826102e7565b60208152918290601f190190369060200137565b9061223782610377565b61224460405191826102e7565b828152601f196122548294610377565b0190602036910137565b60060190816006116118d757565b90601f82018092116118d757565b60200190816020116118d757565b919082018092116118d757565b61229d612208565b9160005b602081106122af5750505090565b808201908183116118d757600191845181106122cd575b50016122a1565b7fff000000000000000000000000000000000000000000000000000000000000006122fa60009287611818565b5116901a6123088287611818565b53386122c6565b9190603f6123497fff0000000000000000000000000000000000000000000000000000000000000061234186516117db565b511660f81c90565b16908051808310156123b857506123966123666020938493611477565b5194611c7160ff8461238c61238761193461190e8751611d9f600760ff1690565b611bf8565b9301519216611c29565b0151926123aa6123a4612208565b94611c29565b010151602083015260019190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff831660045260245260446000fd5b90603f6123ff61193461190e85516117db565b16908051808310156123b8575060209161241891611477565b510151602061247361243961238761193461190e8751611d9f600760ff1690565b611c7160ff61246a61238761193461190e8861245f818d0198611c71888b519216611c29565b01519a516008611d9f565b92519216611c29565b0151911c1690612481612208565b91602083015260019190565b602081016020825282518091526040820191602060408360051b8301019401926000915b8383106124c057505050505090565b90919293946020806124fc837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08660019603018752895161021d565b970193019301919392906124b1565b90601f1982019182116118d757565b610319909291926020604051948261253b87945180928580880191016101fa565b830161254f825180938580850191016101fa565b010103601f1981018452836102e7565b600693909260005b845180518710156128e55761258761194f61193461190e8a60ff95611818565b1461259f576125976001916118aa565b950194612567565b9193909294505b81156128a7576125f46126026125be84979697611c29565b94604051928391602083017fffffffff000000000000000000000000000000000000000000000000000000006004921681520190565b03601f1981018352826102e7565b916060936000915b80831061261f57505050506103e5925061251a565b61263661193461190e8698959651611d9f8861225e565b9660ff8816600060fe82036127a35750506040519861266a8a61265c846020830161248d565b03601f1981018c528b6102e7565b6126776120f98b5161250b565b9760205b8b518110156126b2578061269461190e6001938f611818565b6126ab6126a08361250b565b9160001a918d611818565b530161267b565b50959492939199509795600196875b15612750575090876126fd60019493604051906126f8826126ea87602083019190602083019252565b03601f1981018452836102e7565b61251a565b971561272557612710906127189361251a565b975190612288565b925b01919096959661260a565b61273e61274a936127386127449361293a565b9061251a565b986129c5565b90612288565b9261271a565b909194965060208893985103612773575060019161276d9161251a565b9461271a565b7f6f3c00d50000000000000000000000000000000000000000000000000000000060005260ff1660045260246000fd5b60c0829b9a989997969495939b10158061289d575b156127e65750506127dc6127cb87611bf8565b611c7160ff60208901519216611c29565b96600096876126c1565b6080821061284a575050603f86168951808210156128155750612809908a611477565b519660009660016126c1565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff90911660045260245260446000fd5b8a518083101561286b575050612860908a611477565b5196600096876126c1565b7fe64f383100000000000000000000000000000000000000000000000000000000825260ff8916600452602452604490fd5b5060fe82106127b8565b50506040517fffffffff0000000000000000000000000000000000000000000000000000000090911660208201529091506103e581602481016125f4565b509193909294506125a6565b906128fb8261035f565b61290860405191826102e7565b828152601f19612918829461035f565b019060005b82811061292957505050565b80606060208093850101520161291d565b908151601f81018091116118d7576120f961295a61295f9260051c611c29565b61227a565b8251602082015260005b83518110156129c057807fff000000000000000000000000000000000000000000000000000000000000006129a060019387611818565b51166129b96129ae8361227a565b9160001a9185611818565b5301612969565b509150565b51601f81018091116118d757601f198160051c911690808204602014901517156118d757602001806020116118d7579056fea164736f6c634300081e000a", + BytecodeHash: "0x39e5c0c601d19ddec71cb69988ef491e7c93c84aca797b1c718a0a3cb52141ed", + DeployedBytecode: "0x60806040526004361015610015575b3661179357005b60003560e01c806306fdde03146101f557806312715922146101f057806325692962146101eb5780632cfa37cb146101e65780632e66fb7b146101e157806334bb3700146101dc578063453004ed146101d75780634e9e7760146101d25780634f1ef286146101cd57806352d1902d146101c857806354d1f13d146101c357806354fd4d50146101be578063598993a0146101b9578063649f069b146101b45780636899a9d6146101af578063715018a6146101aa5780637976a334146101a55780637be6a1b5146101a05780638da5cb5b1461019b57806395d89b4114610196578063a2bff4cf14610191578063c2fb26a61461018c578063c4d66de814610187578063ccc2040114610182578063dfe86ac51461017d578063f04e283e14610178578063f2fde38b14610173578063f72060d51461016e578063fd5508e7146101695763fee81cf40361000e57611156565b611138565b611121565b6110e5565b611095565b611072565b610ee5565b610e67565b610de9565b610d7b565b610d1c565b610ce4565b610ca9565b610c6e565b610c03565b610b8a565b610b77565b610b38565b610b1c565b610ad4565b610a72565b6108d8565b610892565b61084d565b610727565b610706565b6106bb565b61063c565b61061c565b610242565b60005b83811061020d5750506000910152565b81810151838201526020016101fd565b90601f19601f60209361023b815180928187528780880191016101fa565b0116010190565b346102765760006003193601126102765761027261025e611189565b60405191829160208352602083019061021d565b0390f35b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176102c657604052565b61027b565b6080810190811067ffffffffffffffff8211176102c657604052565b90601f601f19910116810190811067ffffffffffffffff8211176102c657604052565b6040519061031960a0836102e7565b565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361027657565b359073ffffffffffffffffffffffffffffffffffffffff8216820361027657565b67ffffffffffffffff81116102c65760051b60200190565b67ffffffffffffffff81116102c657601f01601f191660200190565b92919261039f82610377565b916103ad60405193846102e7565b829481845281830111610276578281602093846000960137010152565b9080601f83011215610276578160206103e593359101610393565b90565b91906040838203126102765760405190610401826102aa565b8193803567ffffffffffffffff811161027657826104209183016103ca565b835260208101359167ffffffffffffffff83116102765760209261044492016103ca565b910152565b9291906104558161035f565b9361046360405195866102e7565b602085838152019160051b8101918383116102765781905b838210610489575050505050565b813567ffffffffffffffff8111610276576020916104aa87849387016103e8565b81520191019061047b565b9080601f83011215610276578160206103e593359101610449565b9291906104dc8161035f565b936104ea60405195866102e7565b602085838152019160051b8101918383116102765781905b838210610510575050505050565b813567ffffffffffffffff81116102765760209161053187849387016103ca565b815201910190610502565b9080601f83011215610276578160206103e5933591016104d0565b91909160a0818403126102765761056c61030a565b926105768261033e565b8452602082013567ffffffffffffffff811161027657816105989184016104b5565b6020850152604082013567ffffffffffffffff811161027657816105bd91840161053c565b604085015260608201356060850152608082013567ffffffffffffffff8111610276576105ea92016103ca565b6080830152565b6020600319820112610276576004359067ffffffffffffffff8211610276576103e591600401610557565b3461027657602061063461062f366105f1565b6111c4565b604051908152f35b60006003193601126102765763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b6020600319820112610276576004359067ffffffffffffffff8211610276576003198260a0920301126102765760040190565b34610276576106d16106cc36610688565b6112be565b005b6020600319820112610276576004359067ffffffffffffffff821161027657600319826040920301126102765760040190565b346102765760206106346107223661071d366106d3565b6107ea565b611559565b346102765760206003193601126102765760043567ffffffffffffffff8111610276576080600319823603011261027657604051610764816102cb565b816004013567ffffffffffffffff81116102765761078890600436918501016103ca565b815260248201359067ffffffffffffffff8211610276576107d06064610272946107bb6107da95600436918401016103ca565b6020850152604481013560408501520161033e565b6060820152611300565b6040519081529081906020820190565b91906040838203126102765760405190610803826102aa565b8193803567ffffffffffffffff81116102765782610420918301610557565b6020600319820112610276576004359067ffffffffffffffff8211610276576103e5916004016107ea565b3461027657602061087461086036610822565b8261086b8251611387565b91015190611a22565b73ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346102765760206106346108a5366105f1565b611387565b9181601f840112156102765782359167ffffffffffffffff8311610276576020838186019501011161027657565b6040600319360112610276576108ec61031b565b60243567ffffffffffffffff81116102765761090c9036906004016108aa565b90917f00000000000000000000000000000000000000000000000000000000000000003014610a645761093e33611b37565b15610a145773ffffffffffffffffffffffffffffffffffffffff163d6000526352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602060016004601d855afa5103610a0657807fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a281817f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc556109e757005b816000926040519485378338925af4156109fd57005b3d6000823e3d90fd5b6355299b496001526004601dfd5b610a60610a1f611650565b6040519182917fb921d87d00000000000000000000000000000000000000000000000000000000835260ff600484015260406024840152604483019061021d565b0390fd5b639f03a0266000526004601cfd5b3461027657600060031936011261027657307f000000000000000000000000000000000000000000000000000000000000000003610a645760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b60006003193601126102765763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b346102765760006003193601126102765761027261025e61140d565b346102765760406003193601126102765760043567ffffffffffffffff811161027657610634610b6e602092369060040161053c565b602435906114c5565b3461027657602061063461072236610822565b346102765760206003193601126102765760043567ffffffffffffffff811161027657610bbb9036906004016108aa565b810190600090602081840312610bff5780359167ffffffffffffffff8311610bfc576106d1610bec85858501610557565b60406020820151910151906118dc565b80fd5b5080fd5b600060031936011261027657610c17611ac8565b6000638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a37f8000000000000000000000000000000000000000000000000000000000000000638b78c6d81955005b346102765760206003193601126102765760043567ffffffffffffffff811161027657610634610ca460209236906004016104b5565b6115b4565b346102765760206003193601126102765760043567ffffffffffffffff811161027657610634610cdf60209236906004016103e8565b6115f7565b34610276576000600319360112610276576020638b78c6d8195473ffffffffffffffffffffffffffffffffffffffff60405191168152f35b34610276576000600319360112610276576102726040805190610d3f81836102e7565b600582527f504c55475300000000000000000000000000000000000000000000000000000060208301525191829160208352602083019061021d565b610d8436610688565b3068929eee149b4bd212685414610ddb573068929eee149b4bd2126855610daa33611b37565b15610a1457610dc890610dc061062f3683610557565b6001556112be565b60006001553868929eee149b4bd2126855005b63ab143c066000526004601cfd5b3461027657600060031936011261027657610e026116a3565b60405180916020825273ffffffffffffffffffffffffffffffffffffffff6060610e51610e3b84516080602088015260a087019061021d565b6020850151601f1987830301604088015261021d565b9260408101518286015201511660808301520390f35b3461027657602060031936011261027657610e8061031b565b638b78c6d81954610ed75773ffffffffffffffffffffffffffffffffffffffff16801560ff1b8117638b78c6d8195560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3005b630dc149f06000526004601cfd5b610eee366106d3565b3068929eee149b4bd212685414610ddb573068929eee149b4bd2126855610f41610f246108a536610f1f8580611732565b610557565b610f3b610f346020850185611b6c565b3691610393565b90611a22565b610f9f610f8e610f88610f61610f578680611732565b6080810190611b6c565b50357fffffffffffffffffffffffff00000000000000000000000000000000000000001690565b60a01c90565b6bffffffffffffffffffffffff1690565b6001610fc3826001908060081c600052600060205260ff60406000205491161c1690565b15151461106757610fed908060081c6000526000602052600160ff604060002092161b8154179055565b73ffffffffffffffffffffffffffffffffffffffff80638b78c6d81954921691160361102a5761101c90611765565b3868929eee149b4bd2126855005b610a606110356116f7565b6040519182917fb921d87d0000000000000000000000000000000000000000000000000000000083526004830161168b565b610a60611035611bbd565b346102765760006003193601126102765760206106346110906116a3565b611300565b6020600319360112610276576110a961031b565b6110b1611ac8565b63389a75e1600c52806000526020600c2090815442116110d75760006106d19255611ae5565b636f5e88186000526004601cfd5b6020600319360112610276576110f961031b565b611101611ac8565b8060601b15611113576106d190611ae5565b637448fbae6000526004601cfd5b346102765760206106346108a536610f1f36610688565b34610276576000600319360112610276576020600154604051908152f35b346102765760206003193601126102765761116f61031b565b63389a75e1600c52600052602080600c2054604051908152f35b604051906111986040836102e7565b600b82527f506c756720536f636b65740000000000000000000000000000000000000000006020830152565b73ffffffffffffffffffffffffffffffffffffffff815116906111ea60208201516115b4565b90604081015190608061120360608301938451906114c5565b925191015160208151910120916040519360208501957f5e753b799be998b96f5989c4585d8cf47a8e46682fc527f23d88e0d6a09048cf875260408601526060850152608084015260a083015260c082015260c0815261126460e0826102e7565b51902090565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610276570180359067ffffffffffffffff821161027657602001918160051b3603831361027657565b6112fd906112f76112e56112ef6112d8602085018561126a565b929094604081019061126a565b9490923691610449565b9236916104d0565b906118dc565b50565b805160208151910120906020810151602081519101209073ffffffffffffffffffffffffffffffffffffffff6060604083015192015116906040519260208401947f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f865260408501526060840152608083015260a082015260a0815261126460c0826102e7565b6113c86113c2611395611189565b61139d61140d565b604051916113aa836102cb565b82526020820152466040820152306060820152611300565b916111c4565b6040519060208201927f1901000000000000000000000000000000000000000000000000000000000000845260228301526042820152604281526112646062826102e7565b6040519061141c6040836102e7565b600582527f302e302e310000000000000000000000000000000000000000000000000000006020830152565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b805182101561148b5760209160051b010190565b611448565b6020806103199294939460405195816114b288935180928680870191016101fa565b82019083820152030180855201836102e7565b6000929160605b825185101561154b5760019082861c821682036115295761152090600060206114f860405191826102e7565b527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090611490565b945b01936114cc565b611545906115378786611477565b516020815191012090611490565b94611522565b805160209091012093505050565b602061156582516111c4565b910151602081519101206040519060208201927feb79b94df94230950addab3b4e85da31ca18ec7ce505cc225a455e8e31773a1d845260408301526060820152606081526112646080826102e7565b60009060605b81518310156115ea576115e26001916115dc6115d68686611477565b516115f7565b90611490565b9201916115ba565b9150506020815191012090565b60208151818151910120910151602081519101206040519060208201927f4a5c402b142b42f7a370c0499cf5ed6a0a39a73d479eb129087b5b45e608ccc6845260408301526060820152606081526112646080826102e7565b6040519061165f6040836102e7565b601782527f506c7567436f72653a73656e6465722d696e76616c69640000000000000000006020830152565b9060406103e59260ff8152816020820152019061021d565b600060606040516116b3816102cb565b81815281602082015282604082015201526116cc611189565b6116d461140d565b604051916116e1836102cb565b8252602082015246604082015230606082015290565b604051906117066040836102e7565b601a82527f506c7567436f72653a7369676e61747572652d696e76616c69640000000000006020830152565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6181360301821215610276570190565b6106cc61178c9161178361062f61177c8380611732565b3690610557565b60015580611732565b6000600155565b60003560e01c63bc197c81811463f23a6e6182141763150b7a028214176117c257633c10b94e6000526004601cfd5b6020526020603cf35b80516002101561148b5760220190565b80516006101561148b5760260190565b80511561148b5760200190565b80516001101561148b5760210190565b80516005101561148b5760250190565b90815181101561148b570160200190565b604051906118386040836102e7565b601482527f506c7567436f72653a706c75672d6661696c65640000000000000000000000006020830152565b6040906103e593928152816020820152019061021d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146118d75760010190565b61187b565b906000905b8251821015611a1c5761195561194f61194f8361193a61193461190e611907898b611477565b51516117cb565b517fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b6020611946888a611477565b51015190611c3f565b60ff1690565b60058114611a03576006146119e557611978816119728486611477565b51611e65565b9190156119a65761199d91611997916119918587611477565b51612040565b916118aa565b925b92906118e1565b826119af611829565b90610a606040519283927fb921d87d00000000000000000000000000000000000000000000000000000000845260048401611864565b90816119f46119fd9285611477565b51845190611d59565b9261199f565b509081611a136119fd9285611477565b51845190611ceb565b91505090565b91909160405192805180604014611a8c57604114611a4d57505050505b638baa579f6000526004601cfd5b602091606082015160001a835260408201516060525b60005201516040526020600160806000825afa519160006060526040523d610319575050611a3f565b506020917f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040830151601b8160ff1c01855216606052611a63565b638b78c6d819543303611ad757565b6382b429006000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff1680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3801560ff1b17638b78c6d81955565b73ffffffffffffffffffffffffffffffffffffffff80638b78c6d81954169116908114908115611b65575090565b9050301490565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610276570180359067ffffffffffffffff82116102765760200191813603831361027657565b60405190611bcc6040836102e7565b601682527f506c7567436f72653a6e6f6e63652d696e76616c6964000000000000000000006020830152565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff409116019060ff82116118d757565b908160051b91808304602014901517156118d757565b9160ff83169060c082101580611ce1575b15611c7c575050611c7790611c7160ff611c6b602095611bf8565b16611c29565b90612295565b015190565b91509160808310611c8f57505050600090565b81519081841015611cae575050602091611ca891611477565b51015190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff1660045260245260446000fd5b5060fe8210611c50565b91611d0a91926020611cfd82516117db565b5160f81c91015190611c3f565b90808211611d16575090565b907f7181c5d10000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b60ff60019116019060ff82116118d757565b9091600060ff611d6984516117db565b5160f81c1690611d80836020860193845190611c3f565b15611ded5750611da561193461190e611dad9551611d9f600760ff1690565b90611818565b905190611c3f565b905b808211611dba575090565b7f7181c5d10000000000000000000000000000000000000000000000000000000060005260049190915260245260446000fd5b7fff00000000000000000000000000000000000000000000000000000000000000611da591611e25611e2f965160ff60081690611818565b9050511660f81c90565b90611daf565b3d15611e60573d90611e4682610377565b91611e5460405193846102e7565b82523d6000602084013e565b606090565b6000606060068351511061201657611e8361193461190e85516117cb565b9360ff611e9a61194f836020880198895190611c3f565b1694600486146120075760078614611ff857611ee7611ece611ece84611ec661193461190e8b516117eb565b855190611c3f565b73ffffffffffffffffffffffffffffffffffffffff1690565b94611f36611f2f611f0a85611f0261193461190e87516117f8565b865190611c3f565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b848361255f565b9660018103611f6057505050505050816000929160208493519201905af4611f5c611e35565b9091565b80611f835750505050505081600092918360208194519301915af1611f5c611e35565b60028103611fa857505050505050816000929160208493519201905afa611f5c611e35565b60039098969497959814611fbe575b5050505050565b600096508695975090611da561193461190e611fdb959451611808565b602083519301915af1611fec611e35565b90913880808080611fb7565b5092915050611f5c92506123ec565b5092915050611f5c925061230f565b7f9f3b23380000000000000000000000000000000000000000000000000000000060005260046000fd5b919091805180516003101561148b576023015160f81c60ff612069856020850193845190611c3f565b16915180516004101561148b5761208b9160248692015160f81c905190611c3f565b15159160ff820361209d575b50505090565b90919293849360009580519460ff8116958610156121a8575b505015612191576020825110612169576020820151906120ec6120e76120e16020858701015161226c565b60051c90565b611c29565b946120fe6120f98761227a565b61222d565b94875b61210a8861227a565b8110156121425760018161212d61190e61212761210a958a612288565b8a611818565b8b1a612139828b611818565b53019050612101565b509350945094505061215f916121588285611477565b5282611477565b505b388080612097565b6004857f455639ff000000000000000000000000000000000000000000000000000000008152fd5b9350906121a2916121588285611477565b50612161565b6121c4919794959293965061194f6121bf91611d47565b6128f1565b93835b87518110156121fa57806121dd6001928a611477565b516121e88289611477565b526121f38188611477565b50016121c7565b5092955092939038806120b6565b6040805190919061221983826102e7565b60208152918290601f190190369060200137565b9061223782610377565b61224460405191826102e7565b828152601f196122548294610377565b0190602036910137565b60060190816006116118d757565b90601f82018092116118d757565b60200190816020116118d757565b919082018092116118d757565b61229d612208565b9160005b602081106122af5750505090565b808201908183116118d757600191845181106122cd575b50016122a1565b7fff000000000000000000000000000000000000000000000000000000000000006122fa60009287611818565b5116901a6123088287611818565b53386122c6565b9190603f6123497fff0000000000000000000000000000000000000000000000000000000000000061234186516117db565b511660f81c90565b16908051808310156123b857506123966123666020938493611477565b5194611c7160ff8461238c61238761193461190e8751611d9f600760ff1690565b611bf8565b9301519216611c29565b0151926123aa6123a4612208565b94611c29565b010151602083015260019190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff831660045260245260446000fd5b90603f6123ff61193461190e85516117db565b16908051808310156123b8575060209161241891611477565b510151602061247361243961238761193461190e8751611d9f600760ff1690565b611c7160ff61246a61238761193461190e8861245f818d0198611c71888b519216611c29565b01519a516008611d9f565b92519216611c29565b0151911c1690612481612208565b91602083015260019190565b602081016020825282518091526040820191602060408360051b8301019401926000915b8383106124c057505050505090565b90919293946020806124fc837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08660019603018752895161021d565b970193019301919392906124b1565b90601f1982019182116118d757565b610319909291926020604051948261253b87945180928580880191016101fa565b830161254f825180938580850191016101fa565b010103601f1981018452836102e7565b600693909260005b845180518710156128e55761258761194f61193461190e8a60ff95611818565b1461259f576125976001916118aa565b950194612567565b9193909294505b81156128a7576125f46126026125be84979697611c29565b94604051928391602083017fffffffff000000000000000000000000000000000000000000000000000000006004921681520190565b03601f1981018352826102e7565b916060936000915b80831061261f57505050506103e5925061251a565b61263661193461190e8698959651611d9f8861225e565b9660ff8816600060fe82036127a35750506040519861266a8a61265c846020830161248d565b03601f1981018c528b6102e7565b6126776120f98b5161250b565b9760205b8b518110156126b2578061269461190e6001938f611818565b6126ab6126a08361250b565b9160001a918d611818565b530161267b565b50959492939199509795600196875b15612750575090876126fd60019493604051906126f8826126ea87602083019190602083019252565b03601f1981018452836102e7565b61251a565b971561272557612710906127189361251a565b975190612288565b925b01919096959661260a565b61273e61274a936127386127449361293a565b9061251a565b986129c5565b90612288565b9261271a565b909194965060208893985103612773575060019161276d9161251a565b9461271a565b7f6f3c00d50000000000000000000000000000000000000000000000000000000060005260ff1660045260246000fd5b60c0829b9a989997969495939b10158061289d575b156127e65750506127dc6127cb87611bf8565b611c7160ff60208901519216611c29565b96600096876126c1565b6080821061284a575050603f86168951808210156128155750612809908a611477565b519660009660016126c1565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff90911660045260245260446000fd5b8a518083101561286b575050612860908a611477565b5196600096876126c1565b7fe64f383100000000000000000000000000000000000000000000000000000000825260ff8916600452602452604490fd5b5060fe82106127b8565b50506040517fffffffff0000000000000000000000000000000000000000000000000000000090911660208201529091506103e581602481016125f4565b509193909294506125a6565b906128fb8261035f565b61290860405191826102e7565b828152601f19612918829461035f565b019060005b82811061292957505050565b80606060208093850101520161291d565b908151601f81018091116118d7576120f961295a61295f9260051c611c29565b61227a565b8251602082015260005b83518110156129c057807fff000000000000000000000000000000000000000000000000000000000000006129a060019387611818565b51166129b96129ae8361227a565b9160001a9185611818565b5301612969565b509150565b51601f81018091116118d757601f198160051c911690808204602014901517156118d757602001806020116118d7579056fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x010e3ef0588d9427cf3d83376ab720af71058360f1a8b26bd9f8cb78167a93fa", + ABI: `[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"fallback","stateMutability":"payable"},{"type":"receive","stateMutability":"payable"},{"type":"function","name":"cancelOwnershipHandover","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"completeOwnershipHandover","inputs":[{"name":"pendingOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"containerize","inputs":[{"name":"$plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"containerize","inputs":[{"name":"$data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"digest","inputs":[{"name":"$plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$digest","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"domain","inputs":[],"outputs":[{"name":"$domain","type":"tuple","internalType":"struct PlugTypesLib.EIP712Domain","components":[{"name":"name","type":"string","internalType":"string"},{"name":"version","type":"string","internalType":"string"},{"name":"chainId","type":"uint256","internalType":"uint256"},{"name":"verifyingContract","type":"address","internalType":"address"}]}],"stateMutability":"view"},{"type":"function","name":"domainHash","inputs":[],"outputs":[{"name":"$domainHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"getBytesArrayHash","inputs":[{"name":"$input","type":"bytes[]","internalType":"bytes[]"},{"name":"$mask","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"getEIP712DomainHash","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.EIP712Domain","components":[{"name":"name","type":"string","internalType":"string"},{"name":"version","type":"string","internalType":"string"},{"name":"chainId","type":"uint256","internalType":"uint256"},{"name":"verifyingContract","type":"address","internalType":"address"}]}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"getLivePlugsHash","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.LivePlugs","components":[{"name":"plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"getLivePlugsSigner","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.LivePlugs","components":[{"name":"plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$signer","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getPlugArrayHash","inputs":[{"name":"$input","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"getPlugHash","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.Plug","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"getPlugsDigest","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$digest","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"getPlugsHash","inputs":[{"name":"$input","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$typeHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"hash","inputs":[{"name":"$livePlugs","type":"tuple","internalType":"struct PlugTypesLib.LivePlugs","components":[{"name":"plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"$livePlugsHash","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"initialize","inputs":[{"name":"$owner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"$name","type":"string","internalType":"string"}],"stateMutability":"pure"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"result","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"ownershipHandoverExpiresAt","inputs":[{"name":"pendingOwner","type":"address","internalType":"address"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"plug","inputs":[{"name":"$plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"plug","inputs":[{"name":"$livePlugs","type":"tuple","internalType":"struct PlugTypesLib.LivePlugs","components":[{"name":"plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"plugsHash","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"requestOwnershipHandover","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"$symbol","type":"string","internalType":"string"}],"stateMutability":"pure"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"version","inputs":[],"outputs":[{"name":"$version","type":"string","internalType":"string"}],"stateMutability":"pure"},{"type":"event","name":"OwnershipHandoverCanceled","inputs":[{"name":"pendingOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"OwnershipHandoverRequested","inputs":[{"name":"pendingOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"oldOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AlreadyInitialized","inputs":[]},{"type":"error","name":"FnSelectorNotRecognized","inputs":[]},{"type":"error","name":"InvalidIndicesLength","inputs":[]},{"type":"error","name":"InvalidJumpDestination","inputs":[{"name":"destination","type":"uint256","internalType":"uint256"},{"name":"plugsLength","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"InvalidReturnData","inputs":[]},{"type":"error","name":"InvalidStateIndex","inputs":[{"name":"index","type":"uint8","internalType":"uint8"},{"name":"stateLength","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"NewOwnerIsZeroAddress","inputs":[]},{"type":"error","name":"NoHandoverRequest","inputs":[]},{"type":"error","name":"PlugFailed","inputs":[{"name":"$index","type":"uint256","internalType":"uint256"},{"name":"$reason","type":"string","internalType":"string"}]},{"type":"error","name":"Reentrancy","inputs":[]},{"type":"error","name":"StaticValueNotBytes32","inputs":[{"name":"index","type":"uint8","internalType":"uint8"}]},{"type":"error","name":"Unauthorized","inputs":[]},{"type":"error","name":"UnauthorizedCallContext","inputs":[]},{"type":"error","name":"UpgradeFailed","inputs":[]}]`, + } + Container = Contract{ + Address: ethcommon.HexToAddress("0x000000005FA363Bd7F5cf6fd59688bc334da4be4"), + Bytecode: "0x6080806040523460155761162b908161001b8239f35b600080fdfe60806040526004361015610015575b366104fc57005b60003560e01c80632cfa37cb1461003557636899a9d60361000e576100ec565b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75760043567ffffffffffffffff81116100e757806004019060a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126100e7576100df60446100d76100cd6100e5956100c36024870182610226565b9490960190610226565b9490923691610379565b923691610436565b906106d5565b005b600080fd5b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75760043567ffffffffffffffff81116100e757366023820112156100e75780600401359067ffffffffffffffff82116100e75736602483830101116100e757602401908101906020818303126100e75780359067ffffffffffffffff82116100e7570160a0818303126100e75761018f6102ed565b610198826104a2565b8152602082013567ffffffffffffffff81116100e757836101ba9184016104c3565b9060208101918252604083013567ffffffffffffffff81116100e757846101e29185016104e1565b92604082019384526060810135606083015260808101359067ffffffffffffffff82116100e7576100e59560809261021a9201610332565b910152519051906106d5565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156100e7570180359067ffffffffffffffff82116100e757602001918160051b360383136100e757565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176102c557604052565b61027a565b90601f601f19910116810190811067ffffffffffffffff8211176102c557604052565b604051906102fc60a0836102ca565b565b67ffffffffffffffff81116102c55760051b60200190565b67ffffffffffffffff81116102c557601f01601f191660200190565b81601f820112156100e75780359061034982610316565b9261035760405194856102ca565b828452602083830101116100e757816000926020809301838601378301015290565b92919092610386846102fe565b9361039460405195866102ca565b602085828152019060051b8201918383116100e75780915b8383106103ba575050505050565b823567ffffffffffffffff81116100e75782016040818703126100e757604051916103e4836102a9565b813567ffffffffffffffff81116100e75787610401918401610332565b835260208201359267ffffffffffffffff84116100e75761042788602095869501610332565b838201528152019201916103ac565b929190610442816102fe565b9361045060405195866102ca565b602085838152019160051b8101918383116100e75781905b838210610476575050505050565b813567ffffffffffffffff81116100e7576020916104978784938701610332565b815201910190610468565b359073ffffffffffffffffffffffffffffffffffffffff821682036100e757565b9080601f830112156100e7578160206104de93359101610379565b90565b9080601f830112156100e7578160206104de93359101610436565b60003560e01c63bc197c81811463f23a6e6182141763150b7a0282141761052b57633c10b94e6000526004601cfd5b6020526020603cf35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b80518210156105775760209160051b010190565b610534565b8051600210156105775760220190565b8051600610156105775760260190565b8051156105775760200190565b8051600110156105775760210190565b8051600510156105775760250190565b908151811015610577570160200190565b604051906105e96040836102ca565b601482527f506c7567436f72653a706c75672d6661696c65640000000000000000000000006020830152565b60005b8381106106285750506000910152565b8181015183820152602001610618565b90601f19601f60209361065681518092818752878088019101610615565b0116010190565b6040906104de939281528160208201520190610638565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106d05760010190565b610674565b906000905b82518210156108195761074e6107486107488361073361072d610707610700898b610563565b515161057c565b517fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b602061073f888a610563565b51015190610866565b60ff1690565b60058114610800576006146107e2576107718161076b8486610563565b51610a8c565b91901561079f57610796916107909161078a8587610563565b51610c67565b916106a3565b925b92906106da565b826107a86105da565b906107de6040519283927fb921d87d0000000000000000000000000000000000000000000000000000000084526004840161065d565b0390fd5b90816107f16107fa9285610563565b51845190610980565b92610798565b5090816108106107fa9285610563565b51845190610912565b91505090565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff409116019060ff82116106d057565b908160051b91808304602014901517156106d057565b9160ff83169060c082101580610908575b156108a357505061089e9061089860ff61089260209561081f565b16610850565b90610ebc565b015190565b915091608083106108b657505050600090565b815190818410156108d55750506020916108cf91610563565b51015190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff1660045260245260446000fd5b5060fe8210610877565b9161093191926020610924825161058c565b5160f81c91015190610866565b9080821161093d575090565b907f7181c5d10000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b60ff60019116019060ff82116106d057565b9091600060ff610990845161058c565b5160f81c16906109a7836020860193845190610866565b15610a1457506109cc61072d6107076109d495516109c6600760ff1690565b906105c9565b905190610866565b905b8082116109e1575090565b7f7181c5d10000000000000000000000000000000000000000000000000000000060005260049190915260245260446000fd5b7fff000000000000000000000000000000000000000000000000000000000000006109cc91610a4c610a56965160ff600816906105c9565b9050511660f81c90565b906109d6565b3d15610a87573d90610a6d82610316565b91610a7b60405193846102ca565b82523d6000602084013e565b606090565b60006060600683515110610c3d57610aaa61072d610707855161057c565b9360ff610ac1610748836020880198895190610866565b169460048614610c2e5760078614610c1f57610b0e610af5610af584610aed61072d6107078b5161059c565b855190610866565b73ffffffffffffffffffffffffffffffffffffffff1690565b94610b5d610b56610b3185610b2961072d61070787516105a9565b865190610866565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b8483611186565b9660018103610b8757505050505050816000929160208493519201905af4610b83610a5c565b9091565b80610baa5750505050505081600092918360208194519301915af1610b83610a5c565b60028103610bcf57505050505050816000929160208493519201905afa610b83610a5c565b60039098969497959814610be5575b5050505050565b6000965086959750906109cc61072d610707610c029594516105b9565b602083519301915af1610c13610a5c565b90913880808080610bde565b5092915050610b839250611013565b5092915050610b839250610f36565b7f9f3b23380000000000000000000000000000000000000000000000000000000060005260046000fd5b9190918051805160031015610577576023015160f81c60ff610c90856020850193845190610866565b16915180516004101561057757610cb29160248692015160f81c905190610866565b15159160ff8203610cc4575b50505090565b90919293849360009580519460ff811695861015610dcf575b505015610db8576020825110610d9057602082015190610d13610d0e610d0860208587010151610e93565b60051c90565b610850565b94610d25610d2087610ea1565b610e54565b94875b610d3188610ea1565b811015610d6957600181610d54610707610d4e610d31958a610eaf565b8a6105c9565b8b1a610d60828b6105c9565b53019050610d28565b5093509450945050610d8691610d7f8285610563565b5282610563565b505b388080610cbe565b6004857f455639ff000000000000000000000000000000000000000000000000000000008152fd5b935090610dc991610d7f8285610563565b50610d88565b610deb9197949592939650610748610de69161096e565b611518565b93835b8751811015610e215780610e046001928a610563565b51610e0f8289610563565b52610e1a8188610563565b5001610dee565b509295509293903880610cdd565b60408051909190610e4083826102ca565b60208152918290601f190190369060200137565b90610e5e82610316565b610e6b60405191826102ca565b828152601f19610e7b8294610316565b0190602036910137565b60060190816006116106d057565b90601f82018092116106d057565b60200190816020116106d057565b919082018092116106d057565b610ec4610e2f565b9160005b60208110610ed65750505090565b808201908183116106d05760019184518110610ef4575b5001610ec8565b7fff00000000000000000000000000000000000000000000000000000000000000610f21600092876105c9565b5116901a610f2f82876105c9565b5338610eed565b9190603f610f707fff00000000000000000000000000000000000000000000000000000000000000610f68865161058c565b511660f81c90565b1690805180831015610fdf5750610fbd610f8d6020938493610563565b519461089860ff84610fb3610fae61072d61070787516109c6600760ff1690565b61081f565b9301519216610850565b015192610fd1610fcb610e2f565b94610850565b010151602083015260019190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff831660045260245260446000fd5b90603f61102661072d610707855161058c565b1690805180831015610fdf575060209161103f91610563565b510151602061109a611060610fae61072d61070787516109c6600760ff1690565b61089860ff611091610fae61072d61070788611086818d0198610898888b519216610850565b01519a5160086109c6565b92519216610850565b0151911c16906110a8610e2f565b91602083015260019190565b602081016020825282518091526040820191602060408360051b8301019401926000915b8383106110e757505050505090565b9091929394602080611123837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528951610638565b970193019301919392906110d8565b90601f1982019182116106d057565b6102fc90929192602060405194826111628794518092858088019101610615565b830161117682518093858085019101610615565b010103601f1981018452836102ca565b600693909260005b8451805187101561150c576111ae61074861072d6107078a60ff956105c9565b146111c6576111be6001916106a3565b95019461118e565b9193909294505b81156114ce5761121b6112296111e584979697610850565b94604051928391602083017fffffffff000000000000000000000000000000000000000000000000000000006004921681520190565b03601f1981018352826102ca565b916060936000915b80831061124657505050506104de9250611141565b61125d61072d61070786989596516109c688610e85565b9660ff8816600060fe82036113ca575050604051986112918a61128384602083016110b4565b03601f1981018c528b6102ca565b61129e610d208b51611132565b9760205b8b518110156112d957806112bb6107076001938f6105c9565b6112d26112c783611132565b9160001a918d6105c9565b53016112a2565b50959492939199509795600196875b1561137757509087611324600194936040519061131f8261131187602083019190602083019252565b03601f1981018452836102ca565b611141565b971561134c576113379061133f93611141565b975190610eaf565b925b019190969596611231565b6113656113719361135f61136b93611561565b90611141565b986115ec565b90610eaf565b92611341565b90919496506020889398510361139a575060019161139491611141565b94611341565b7f6f3c00d50000000000000000000000000000000000000000000000000000000060005260ff1660045260246000fd5b60c0829b9a989997969495939b1015806114c4575b1561140d5750506114036113f28761081f565b61089860ff60208901519216610850565b96600096876112e8565b60808210611471575050603f861689518082101561143c5750611430908a610563565b519660009660016112e8565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff90911660045260245260446000fd5b8a5180831015611492575050611487908a610563565b5196600096876112e8565b7fe64f383100000000000000000000000000000000000000000000000000000000825260ff8916600452602452604490fd5b5060fe82106113df565b50506040517fffffffff0000000000000000000000000000000000000000000000000000000090911660208201529091506104de816024810161121b565b509193909294506111cd565b90611522826102fe565b61152f60405191826102ca565b828152601f1961153f82946102fe565b019060005b82811061155057505050565b806060602080938501015201611544565b908151601f81018091116106d057610d206115816115869260051c610850565b610ea1565b8251602082015260005b83518110156115e757807fff000000000000000000000000000000000000000000000000000000000000006115c7600193876105c9565b51166115e06115d583610ea1565b9160001a91856105c9565b5301611590565b509150565b51601f81018091116106d057601f198160051c911690808204602014901517156106d057602001806020116106d0579056fea164736f6c634300081e000a", + BytecodeHash: "0xc8ccad62769221d20a21d8890da880d1a0691f40fab97330668908c54ddf48e0", + DeployedBytecode: "0x60806040526004361015610015575b366104fc57005b60003560e01c80632cfa37cb1461003557636899a9d60361000e576100ec565b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75760043567ffffffffffffffff81116100e757806004019060a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126100e7576100df60446100d76100cd6100e5956100c36024870182610226565b9490960190610226565b9490923691610379565b923691610436565b906106d5565b005b600080fd5b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75760043567ffffffffffffffff81116100e757366023820112156100e75780600401359067ffffffffffffffff82116100e75736602483830101116100e757602401908101906020818303126100e75780359067ffffffffffffffff82116100e7570160a0818303126100e75761018f6102ed565b610198826104a2565b8152602082013567ffffffffffffffff81116100e757836101ba9184016104c3565b9060208101918252604083013567ffffffffffffffff81116100e757846101e29185016104e1565b92604082019384526060810135606083015260808101359067ffffffffffffffff82116100e7576100e59560809261021a9201610332565b910152519051906106d5565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156100e7570180359067ffffffffffffffff82116100e757602001918160051b360383136100e757565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176102c557604052565b61027a565b90601f601f19910116810190811067ffffffffffffffff8211176102c557604052565b604051906102fc60a0836102ca565b565b67ffffffffffffffff81116102c55760051b60200190565b67ffffffffffffffff81116102c557601f01601f191660200190565b81601f820112156100e75780359061034982610316565b9261035760405194856102ca565b828452602083830101116100e757816000926020809301838601378301015290565b92919092610386846102fe565b9361039460405195866102ca565b602085828152019060051b8201918383116100e75780915b8383106103ba575050505050565b823567ffffffffffffffff81116100e75782016040818703126100e757604051916103e4836102a9565b813567ffffffffffffffff81116100e75787610401918401610332565b835260208201359267ffffffffffffffff84116100e75761042788602095869501610332565b838201528152019201916103ac565b929190610442816102fe565b9361045060405195866102ca565b602085838152019160051b8101918383116100e75781905b838210610476575050505050565b813567ffffffffffffffff81116100e7576020916104978784938701610332565b815201910190610468565b359073ffffffffffffffffffffffffffffffffffffffff821682036100e757565b9080601f830112156100e7578160206104de93359101610379565b90565b9080601f830112156100e7578160206104de93359101610436565b60003560e01c63bc197c81811463f23a6e6182141763150b7a0282141761052b57633c10b94e6000526004601cfd5b6020526020603cf35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b80518210156105775760209160051b010190565b610534565b8051600210156105775760220190565b8051600610156105775760260190565b8051156105775760200190565b8051600110156105775760210190565b8051600510156105775760250190565b908151811015610577570160200190565b604051906105e96040836102ca565b601482527f506c7567436f72653a706c75672d6661696c65640000000000000000000000006020830152565b60005b8381106106285750506000910152565b8181015183820152602001610618565b90601f19601f60209361065681518092818752878088019101610615565b0116010190565b6040906104de939281528160208201520190610638565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106d05760010190565b610674565b906000905b82518210156108195761074e6107486107488361073361072d610707610700898b610563565b515161057c565b517fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b602061073f888a610563565b51015190610866565b60ff1690565b60058114610800576006146107e2576107718161076b8486610563565b51610a8c565b91901561079f57610796916107909161078a8587610563565b51610c67565b916106a3565b925b92906106da565b826107a86105da565b906107de6040519283927fb921d87d0000000000000000000000000000000000000000000000000000000084526004840161065d565b0390fd5b90816107f16107fa9285610563565b51845190610980565b92610798565b5090816108106107fa9285610563565b51845190610912565b91505090565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff409116019060ff82116106d057565b908160051b91808304602014901517156106d057565b9160ff83169060c082101580610908575b156108a357505061089e9061089860ff61089260209561081f565b16610850565b90610ebc565b015190565b915091608083106108b657505050600090565b815190818410156108d55750506020916108cf91610563565b51015190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff1660045260245260446000fd5b5060fe8210610877565b9161093191926020610924825161058c565b5160f81c91015190610866565b9080821161093d575090565b907f7181c5d10000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b60ff60019116019060ff82116106d057565b9091600060ff610990845161058c565b5160f81c16906109a7836020860193845190610866565b15610a1457506109cc61072d6107076109d495516109c6600760ff1690565b906105c9565b905190610866565b905b8082116109e1575090565b7f7181c5d10000000000000000000000000000000000000000000000000000000060005260049190915260245260446000fd5b7fff000000000000000000000000000000000000000000000000000000000000006109cc91610a4c610a56965160ff600816906105c9565b9050511660f81c90565b906109d6565b3d15610a87573d90610a6d82610316565b91610a7b60405193846102ca565b82523d6000602084013e565b606090565b60006060600683515110610c3d57610aaa61072d610707855161057c565b9360ff610ac1610748836020880198895190610866565b169460048614610c2e5760078614610c1f57610b0e610af5610af584610aed61072d6107078b5161059c565b855190610866565b73ffffffffffffffffffffffffffffffffffffffff1690565b94610b5d610b56610b3185610b2961072d61070787516105a9565b865190610866565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b8483611186565b9660018103610b8757505050505050816000929160208493519201905af4610b83610a5c565b9091565b80610baa5750505050505081600092918360208194519301915af1610b83610a5c565b60028103610bcf57505050505050816000929160208493519201905afa610b83610a5c565b60039098969497959814610be5575b5050505050565b6000965086959750906109cc61072d610707610c029594516105b9565b602083519301915af1610c13610a5c565b90913880808080610bde565b5092915050610b839250611013565b5092915050610b839250610f36565b7f9f3b23380000000000000000000000000000000000000000000000000000000060005260046000fd5b9190918051805160031015610577576023015160f81c60ff610c90856020850193845190610866565b16915180516004101561057757610cb29160248692015160f81c905190610866565b15159160ff8203610cc4575b50505090565b90919293849360009580519460ff811695861015610dcf575b505015610db8576020825110610d9057602082015190610d13610d0e610d0860208587010151610e93565b60051c90565b610850565b94610d25610d2087610ea1565b610e54565b94875b610d3188610ea1565b811015610d6957600181610d54610707610d4e610d31958a610eaf565b8a6105c9565b8b1a610d60828b6105c9565b53019050610d28565b5093509450945050610d8691610d7f8285610563565b5282610563565b505b388080610cbe565b6004857f455639ff000000000000000000000000000000000000000000000000000000008152fd5b935090610dc991610d7f8285610563565b50610d88565b610deb9197949592939650610748610de69161096e565b611518565b93835b8751811015610e215780610e046001928a610563565b51610e0f8289610563565b52610e1a8188610563565b5001610dee565b509295509293903880610cdd565b60408051909190610e4083826102ca565b60208152918290601f190190369060200137565b90610e5e82610316565b610e6b60405191826102ca565b828152601f19610e7b8294610316565b0190602036910137565b60060190816006116106d057565b90601f82018092116106d057565b60200190816020116106d057565b919082018092116106d057565b610ec4610e2f565b9160005b60208110610ed65750505090565b808201908183116106d05760019184518110610ef4575b5001610ec8565b7fff00000000000000000000000000000000000000000000000000000000000000610f21600092876105c9565b5116901a610f2f82876105c9565b5338610eed565b9190603f610f707fff00000000000000000000000000000000000000000000000000000000000000610f68865161058c565b511660f81c90565b1690805180831015610fdf5750610fbd610f8d6020938493610563565b519461089860ff84610fb3610fae61072d61070787516109c6600760ff1690565b61081f565b9301519216610850565b015192610fd1610fcb610e2f565b94610850565b010151602083015260019190565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff831660045260245260446000fd5b90603f61102661072d610707855161058c565b1690805180831015610fdf575060209161103f91610563565b510151602061109a611060610fae61072d61070787516109c6600760ff1690565b61089860ff611091610fae61072d61070788611086818d0198610898888b519216610850565b01519a5160086109c6565b92519216610850565b0151911c16906110a8610e2f565b91602083015260019190565b602081016020825282518091526040820191602060408360051b8301019401926000915b8383106110e757505050505090565b9091929394602080611123837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528951610638565b970193019301919392906110d8565b90601f1982019182116106d057565b6102fc90929192602060405194826111628794518092858088019101610615565b830161117682518093858085019101610615565b010103601f1981018452836102ca565b600693909260005b8451805187101561150c576111ae61074861072d6107078a60ff956105c9565b146111c6576111be6001916106a3565b95019461118e565b9193909294505b81156114ce5761121b6112296111e584979697610850565b94604051928391602083017fffffffff000000000000000000000000000000000000000000000000000000006004921681520190565b03601f1981018352826102ca565b916060936000915b80831061124657505050506104de9250611141565b61125d61072d61070786989596516109c688610e85565b9660ff8816600060fe82036113ca575050604051986112918a61128384602083016110b4565b03601f1981018c528b6102ca565b61129e610d208b51611132565b9760205b8b518110156112d957806112bb6107076001938f6105c9565b6112d26112c783611132565b9160001a918d6105c9565b53016112a2565b50959492939199509795600196875b1561137757509087611324600194936040519061131f8261131187602083019190602083019252565b03601f1981018452836102ca565b611141565b971561134c576113379061133f93611141565b975190610eaf565b925b019190969596611231565b6113656113719361135f61136b93611561565b90611141565b986115ec565b90610eaf565b92611341565b90919496506020889398510361139a575060019161139491611141565b94611341565b7f6f3c00d50000000000000000000000000000000000000000000000000000000060005260ff1660045260246000fd5b60c0829b9a989997969495939b1015806114c4575b1561140d5750506114036113f28761081f565b61089860ff60208901519216610850565b96600096876112e8565b60808210611471575050603f861689518082101561143c5750611430908a610563565b519660009660016112e8565b7fe64f38310000000000000000000000000000000000000000000000000000000060005260ff90911660045260245260446000fd5b8a5180831015611492575050611487908a610563565b5196600096876112e8565b7fe64f383100000000000000000000000000000000000000000000000000000000825260ff8916600452602452604490fd5b5060fe82106113df565b50506040517fffffffff0000000000000000000000000000000000000000000000000000000090911660208201529091506104de816024810161121b565b509193909294506111cd565b90611522826102fe565b61152f60405191826102ca565b828152601f1961153f82946102fe565b019060005b82811061155057505050565b806060602080938501015201611544565b908151601f81018091116106d057610d206115816115869260051c610850565b610ea1565b8251602082015260005b83518110156115e757807fff000000000000000000000000000000000000000000000000000000000000006115c7600193876105c9565b51166115e06115d583610ea1565b9160001a91856105c9565b5301611590565b509150565b51601f81018091116106d057601f198160051c911690808204602014901517156106d057602001806020116106d0579056fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x05de22ebaf7ccc5d4d21e18134ba5a07ffcb66eb321253422de3f1202b3dfa4c", + ABI: `[{"type":"fallback","stateMutability":"payable"},{"type":"receive","stateMutability":"payable"},{"type":"function","name":"containerize","inputs":[{"name":"$plugs","type":"tuple","internalType":"struct PlugTypesLib.Plugs","components":[{"name":"socket","type":"address","internalType":"address"},{"name":"plugs","type":"tuple[]","internalType":"struct PlugTypesLib.Plug[]","components":[{"name":"indices","type":"bytes","internalType":"bytes"},{"name":"literals","type":"bytes","internalType":"bytes"}]},{"name":"state","type":"bytes[]","internalType":"bytes[]"},{"name":"mask","type":"uint256","internalType":"uint256"},{"name":"salt","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"containerize","inputs":[{"name":"$data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"error","name":"FnSelectorNotRecognized","inputs":[]},{"type":"error","name":"InvalidIndicesLength","inputs":[]},{"type":"error","name":"InvalidJumpDestination","inputs":[{"name":"destination","type":"uint256","internalType":"uint256"},{"name":"plugsLength","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"InvalidReturnData","inputs":[]},{"type":"error","name":"InvalidStateIndex","inputs":[{"name":"index","type":"uint8","internalType":"uint8"},{"name":"stateLength","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"PlugFailed","inputs":[{"name":"$index","type":"uint256","internalType":"uint256"},{"name":"$reason","type":"string","internalType":"string"}]},{"type":"error","name":"StaticValueNotBytes32","inputs":[{"name":"index","type":"uint8","internalType":"uint8"}]}]`, + } + Assert = Contract{ + Address: ethcommon.HexToAddress("0x00000000ff1E7d22a6e604192bfc379c9cCB6914"), + Bytecode: "0x60808060405234601e576001600055607c60015560e090816100248239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c63ce31646b14602657600080fd5b3460ce5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011260ce5760043580151580910360ce576024359081151580920360ce5703607257005b807f08c379a0000000000000000000000000000000000000000000000000000000006064925260206004820152601a60248201527f506c75674173736572743a636f6e646974696f6e2d66616c73650000000000006044820152fd5b600080fdfea164736f6c634300081e000a", + BytecodeHash: "0x7b522d4dab720646e9de8760953f5be441f59eb12b758ad1ef62248ff5e2d55f", + DeployedBytecode: "0x6080806040526004361015601257600080fd5b60003560e01c63ce31646b14602657600080fd5b3460ce5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011260ce5760043580151580910360ce576024359081151580920360ce5703607257005b807f08c379a0000000000000000000000000000000000000000000000000000000006064925260206004820152601a60248201527f506c75674173736572743a636f6e646974696f6e2d66616c73650000000000006044820152fd5b600080fdfea164736f6c634300081e000a", + DeployedBytecodeHash: "0x8369182b21aa58b993bb35184bfff8c888afa63acf516add419a536f47a8883b", + ABI: `[{"type":"function","name":"assertEqual","inputs":[{"name":"value","type":"bool","internalType":"bool"},{"name":"condition","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"pure"}]`, + } + Boolean = Contract{ + Address: ethcommon.HexToAddress("0x000000007d215C10C545aDEEd2abDe29c2f176a9"), + Bytecode: "0x60808060405234601557610339908161001b8239f35b600080fdfe608080604052600436101561001357600080fd5b60003560e01c90816309aa2a75146102e1575080631c8d23cd146102bd5780631d235a521461028a5780633182925b146102595780633ced224a146102405780633eaea839146102095780635a1389d7146101d65780635f3e9a58146101bc57806375bafee5146101805780637de3e9c11461016657806395f4ac381461012b578063af4256ba146100f0578063dcf6a592146100d75763e9970b6c146100b957600080fd5b346100d25760206100c9366102f8565b60405191108152f35b600080fd5b346100d25760206100e7366102f8565b60405191118152f35b346100d25760406003193601126100d257602061010b61030e565b61011361031d565b81610123575b5060405190158152f35b905082610119565b346100d25760606003193601126100d25760206004356024358110159081610159575b506040519015158152f35b905060443510158261014e565b346100d2576020610176366102f8565b6040519114158152f35b346100d25760406003193601126100d257602061019b61030e565b6101a361031d565b81156101b457506040519015158152f35b90508261014e565b346100d25760206101cc366102f8565b6040519111158152f35b346100d25760406003193601126100d25760206101f161030e565b6101f961031d565b8115610123575060405190158152f35b346100d25760406003193601126100d257602061022461030e565b61022c61031d565b90159081156101b457506040519015158152f35b346100d2576020610250366102f8565b60405191148152f35b346100d25760406003193601126100d257602061027461030e565b61027c61031d565b151560405191151514158152f35b346100d25760406003193601126100d25760206102a561030e565b6102ad61031d565b816101b457506040519015158152f35b346100d25760206003193601126100d25760206102d861030e565b15604051908152f35b346100d2576020906102f2366102f8565b11158152f35b60031960409101126100d2576004359060243590565b6004359081151582036100d257565b6024359081151582036100d25756fea164736f6c634300081e000a", + BytecodeHash: "0xdcf3e93fb21070b0959958de7f44632778db633b8ff4198f66c89a4d62f9a62f", + DeployedBytecode: "0x608080604052600436101561001357600080fd5b60003560e01c90816309aa2a75146102e1575080631c8d23cd146102bd5780631d235a521461028a5780633182925b146102595780633ced224a146102405780633eaea839146102095780635a1389d7146101d65780635f3e9a58146101bc57806375bafee5146101805780637de3e9c11461016657806395f4ac381461012b578063af4256ba146100f0578063dcf6a592146100d75763e9970b6c146100b957600080fd5b346100d25760206100c9366102f8565b60405191108152f35b600080fd5b346100d25760206100e7366102f8565b60405191118152f35b346100d25760406003193601126100d257602061010b61030e565b61011361031d565b81610123575b5060405190158152f35b905082610119565b346100d25760606003193601126100d25760206004356024358110159081610159575b506040519015158152f35b905060443510158261014e565b346100d2576020610176366102f8565b6040519114158152f35b346100d25760406003193601126100d257602061019b61030e565b6101a361031d565b81156101b457506040519015158152f35b90508261014e565b346100d25760206101cc366102f8565b6040519111158152f35b346100d25760406003193601126100d25760206101f161030e565b6101f961031d565b8115610123575060405190158152f35b346100d25760406003193601126100d257602061022461030e565b61022c61031d565b90159081156101b457506040519015158152f35b346100d2576020610250366102f8565b60405191148152f35b346100d25760406003193601126100d257602061027461030e565b61027c61031d565b151560405191151514158152f35b346100d25760406003193601126100d25760206102a561030e565b6102ad61031d565b816101b457506040519015158152f35b346100d25760206003193601126100d25760206102d861030e565b15604051908152f35b346100d2576020906102f2366102f8565b11158152f35b60031960409101126100d2576004359060243590565b6004359081151582036100d257565b6024359081151582036100d25756fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x4c330a6c664fda6f466ee41129283d9821f294da2c8280cdc538fddbd75c195d", + ABI: `[{"type":"function","name":"isAnd","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isBetween","inputs":[{"name":"value","type":"uint256","internalType":"uint256"},{"name":"min","type":"uint256","internalType":"uint256"},{"name":"max","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isEqual","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isGreaterThan","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isGreaterThanOrEqual","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isImplies","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isLessThan","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isLessThanOrEqual","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isNand","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isNor","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isNot","inputs":[{"name":"a","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isNotEqual","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isOr","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"isXor","inputs":[{"name":"a","type":"bool","internalType":"bool"},{"name":"b","type":"bool","internalType":"bool"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"pure"}]`, + } + Coercion = Contract{ + Address: ethcommon.HexToAddress("0x0000000016A10AD5Db4E12A62740f6E6D6024486"), + Bytecode: "0x60808060405234601557610b52908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c80630cc4681e146101885780631cf887fc14610183578063220096421461017e5780632665fad0146101795780634c0999c7146101745780635192f3c01461016f578063612eb5cc1461015b578063809fdd331461016a578063886d3db9146101335780639374068f146101655780639c6f59be14610160578063a3586a4c1461015b578063bfe370d914610156578063c819325514610151578063cf65b4d31461014c578063d6bd32aa14610147578063dd2a031614610142578063dfbe873b1461013d578063dfef6beb14610138578063e60b142414610133578063f136dc021461012e578063f73ecfde14610129578063fbe706fd146101295763fdcf791b1461012457600080fd5b610a8c565b610a1c565b6109b4565b6104dc565b610939565b6108b5565b61084d565b6107e5565b61077d565b610712565b61066f565b61044b565b610555565b6104ec565b610465565b610422565b610371565b610302565b6102d6565b61022a565b6101c2565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60209101126101bd5760043590565b600080fd5b346101bd576101d03661018d565b60ff81169081036101e657602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a75696e74382d6f766572666c6f7700000000006044820152fd5b346101bd576102383661018d565b6bffffffffffffffffffffffff811690810361025957602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7439362d6f766572666c6f77000000006044820152fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60209101126101bd5760043580151581036101bd5790565b346101bd576102e43661029d565b156102f957602060015b60ff60405191168152f35b602060006102ee565b346101bd576103103661018d565b67ffffffffffffffff811690810361032d57602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7436342d6f766572666c6f77000000006044820152fd5b346101bd5761037f3661018d565b604061038a816105ec565b9160208352602083017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0830136823752805180926020825280519081602084015260005b82811061040b5750506000828201840152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168101030190f35b6020828201810151878301870152869450016103ce565b346101bd576104303661029d565b1561044257602060015b604051908152f35b6020600061043a565b346101bd57602061045b3661018d565b1515604051908152f35b346101bd576104733661018d565b6fffffffffffffffffffffffffffffffff811690810361049857602090604051908152f35b606460405162461bcd60e51b815260206004820152601d60248201527f506c7567436f657263696f6e3a75696e743132382d6f766572666c6f770000006044820152fd5b346101bd57602061043a3661018d565b346101bd576104fa3661018d565b61ffff811690810361051157602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7431362d6f766572666c6f77000000006044820152fd5b346101bd576105633661018d565b8060030b90810361057957602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7433322d6f766572666c6f7700000000006044820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051930116820182811067ffffffffffffffff82111761063057604052565b6105bd565b67ffffffffffffffff811161063057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b346101bd5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bd5760043567ffffffffffffffff81116101bd57366023820112156101bd578060040135906106d26106cd83610635565b6105ec565b82815236602484840101116101bd57600060208461070e9560246106fe96018386013783010152610af1565b6040519081529081906020820190565b0390f35b346101bd576107203661018d565b63ffffffff811690810361073957602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7433322d6f766572666c6f77000000006044820152fd5b346101bd5761078b3661018d565b8060010b9081036107a157602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7431362d6f766572666c6f7700000000006044820152fd5b346101bd576107f33661018d565b8060070b90810361080957602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7436342d6f766572666c6f7700000000006044820152fd5b346101bd5761085b3661018d565b80600f0b90810361087157602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a696e743132382d6f766572666c6f77000000006044820152fd5b346101bd576108c33661018d565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116108f557602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a696e743235362d6f766572666c6f77000000006044820152fd5b346101bd576109473661018d565b73ffffffffffffffffffffffffffffffffffffffff811690810361097057602090604051908152f35b606460405162461bcd60e51b815260206004820152601d60248201527f506c7567436f657263696f6e3a75696e743136302d6f766572666c6f770000006044820152fd5b346101bd576109c23661018d565b8060000b9081036109d857602090604051908152f35b606460405162461bcd60e51b815260206004820152601a60248201527f506c7567436f657263696f6e3a696e74382d6f766572666c6f770000000000006044820152fd5b346101bd5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bd5760043573ffffffffffffffffffffffffffffffffffffffff811681036101bd5760209073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346101bd57610a9a3661018d565b60008112610aad57602090604051908152f35b606460405162461bcd60e51b815260206004820152601a60248201527f506c7567436f657263696f6e3a6e656761746976652d75696e740000000000006044820152fd5b6020815111610b01576020015190565b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a62797465732d746f6f2d6c6f6e6700000000006044820152fdfea164736f6c634300081e000a", + BytecodeHash: "0x0ea9b25d537846cab3eac882df79a5f5dd087252446e1b959898ba39aaa02bf4", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c80630cc4681e146101885780631cf887fc14610183578063220096421461017e5780632665fad0146101795780634c0999c7146101745780635192f3c01461016f578063612eb5cc1461015b578063809fdd331461016a578063886d3db9146101335780639374068f146101655780639c6f59be14610160578063a3586a4c1461015b578063bfe370d914610156578063c819325514610151578063cf65b4d31461014c578063d6bd32aa14610147578063dd2a031614610142578063dfbe873b1461013d578063dfef6beb14610138578063e60b142414610133578063f136dc021461012e578063f73ecfde14610129578063fbe706fd146101295763fdcf791b1461012457600080fd5b610a8c565b610a1c565b6109b4565b6104dc565b610939565b6108b5565b61084d565b6107e5565b61077d565b610712565b61066f565b61044b565b610555565b6104ec565b610465565b610422565b610371565b610302565b6102d6565b61022a565b6101c2565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60209101126101bd5760043590565b600080fd5b346101bd576101d03661018d565b60ff81169081036101e657602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a75696e74382d6f766572666c6f7700000000006044820152fd5b346101bd576102383661018d565b6bffffffffffffffffffffffff811690810361025957602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7439362d6f766572666c6f77000000006044820152fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60209101126101bd5760043580151581036101bd5790565b346101bd576102e43661029d565b156102f957602060015b60ff60405191168152f35b602060006102ee565b346101bd576103103661018d565b67ffffffffffffffff811690810361032d57602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7436342d6f766572666c6f77000000006044820152fd5b346101bd5761037f3661018d565b604061038a816105ec565b9160208352602083017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0830136823752805180926020825280519081602084015260005b82811061040b5750506000828201840152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168101030190f35b6020828201810151878301870152869450016103ce565b346101bd576104303661029d565b1561044257602060015b604051908152f35b6020600061043a565b346101bd57602061045b3661018d565b1515604051908152f35b346101bd576104733661018d565b6fffffffffffffffffffffffffffffffff811690810361049857602090604051908152f35b606460405162461bcd60e51b815260206004820152601d60248201527f506c7567436f657263696f6e3a75696e743132382d6f766572666c6f770000006044820152fd5b346101bd57602061043a3661018d565b346101bd576104fa3661018d565b61ffff811690810361051157602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7431362d6f766572666c6f77000000006044820152fd5b346101bd576105633661018d565b8060030b90810361057957602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7433322d6f766572666c6f7700000000006044820152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051930116820182811067ffffffffffffffff82111761063057604052565b6105bd565b67ffffffffffffffff811161063057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b346101bd5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bd5760043567ffffffffffffffff81116101bd57366023820112156101bd578060040135906106d26106cd83610635565b6105ec565b82815236602484840101116101bd57600060208461070e9560246106fe96018386013783010152610af1565b6040519081529081906020820190565b0390f35b346101bd576107203661018d565b63ffffffff811690810361073957602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a75696e7433322d6f766572666c6f77000000006044820152fd5b346101bd5761078b3661018d565b8060010b9081036107a157602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7431362d6f766572666c6f7700000000006044820152fd5b346101bd576107f33661018d565b8060070b90810361080957602090604051908152f35b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a696e7436342d6f766572666c6f7700000000006044820152fd5b346101bd5761085b3661018d565b80600f0b90810361087157602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a696e743132382d6f766572666c6f77000000006044820152fd5b346101bd576108c33661018d565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116108f557602090604051908152f35b606460405162461bcd60e51b815260206004820152601c60248201527f506c7567436f657263696f6e3a696e743235362d6f766572666c6f77000000006044820152fd5b346101bd576109473661018d565b73ffffffffffffffffffffffffffffffffffffffff811690810361097057602090604051908152f35b606460405162461bcd60e51b815260206004820152601d60248201527f506c7567436f657263696f6e3a75696e743136302d6f766572666c6f770000006044820152fd5b346101bd576109c23661018d565b8060000b9081036109d857602090604051908152f35b606460405162461bcd60e51b815260206004820152601a60248201527f506c7567436f657263696f6e3a696e74382d6f766572666c6f770000000000006044820152fd5b346101bd5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bd5760043573ffffffffffffffffffffffffffffffffffffffff811681036101bd5760209073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346101bd57610a9a3661018d565b60008112610aad57602090604051908152f35b606460405162461bcd60e51b815260206004820152601a60248201527f506c7567436f657263696f6e3a6e656761746976652d75696e740000000000006044820152fd5b6020815111610b01576020015190565b606460405162461bcd60e51b815260206004820152601b60248201527f506c7567436f657263696f6e3a62797465732d746f6f2d6c6f6e6700000000006044820152fdfea164736f6c634300081e000a", + DeployedBytecodeHash: "0x6377b10a46c327f7b99bcfe1534f4aa881b9aa5bcdf5e328203636bf7f04adb3", + ABI: `[{"type":"function","name":"addressToUint160","inputs":[{"name":"y","type":"address","internalType":"address"}],"outputs":[{"name":"z","type":"uint160","internalType":"uint160"}],"stateMutability":"pure"},{"type":"function","name":"boolToInt","inputs":[{"name":"y","type":"bool","internalType":"bool"}],"outputs":[{"name":"z","type":"int256","internalType":"int256"}],"stateMutability":"pure"},{"type":"function","name":"boolToUint","inputs":[{"name":"y","type":"bool","internalType":"bool"}],"outputs":[{"name":"z","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"bytes32ToBytes","inputs":[{"name":"y","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"z","type":"bytes","internalType":"bytes"}],"stateMutability":"pure"},{"type":"function","name":"bytes32ToUint","inputs":[{"name":"y","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"z","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"bytesToBytes32","inputs":[{"name":"y","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"z","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"intToBool","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"toInt128","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"int128","internalType":"int128"}],"stateMutability":"pure"},{"type":"function","name":"toInt16","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"int16","internalType":"int16"}],"stateMutability":"pure"},{"type":"function","name":"toInt256","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"int256","internalType":"int256"}],"stateMutability":"pure"},{"type":"function","name":"toInt32","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"int32","internalType":"int32"}],"stateMutability":"pure"},{"type":"function","name":"toInt64","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"int64","internalType":"int64"}],"stateMutability":"pure"},{"type":"function","name":"toInt8","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"int8","internalType":"int8"}],"stateMutability":"pure"},{"type":"function","name":"toUint128","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint128","internalType":"uint128"}],"stateMutability":"pure"},{"type":"function","name":"toUint16","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint16","internalType":"uint16"}],"stateMutability":"pure"},{"type":"function","name":"toUint160","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint160","internalType":"uint160"}],"stateMutability":"pure"},{"type":"function","name":"toUint256","inputs":[{"name":"y","type":"int256","internalType":"int256"}],"outputs":[{"name":"z","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"toUint32","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint32","internalType":"uint32"}],"stateMutability":"pure"},{"type":"function","name":"toUint64","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint64","internalType":"uint64"}],"stateMutability":"pure"},{"type":"function","name":"toUint8","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint8","internalType":"uint8"}],"stateMutability":"pure"},{"type":"function","name":"toUint96","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"uint96","internalType":"uint96"}],"stateMutability":"pure"},{"type":"function","name":"uint160ToAddress","inputs":[{"name":"y","type":"uint160","internalType":"uint160"}],"outputs":[{"name":"z","type":"address","internalType":"address"}],"stateMutability":"pure"},{"type":"function","name":"uintToBool","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"uintToBytes32","inputs":[{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"z","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"}]`, + } + Database = Contract{ + Address: ethcommon.HexToAddress("0x00000000ACf6D984AcF18ffe9aDCC58e242F2bDd"), + Bytecode: "0x608080604052346015576103f1908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c806333cd33bd146100875780635628f232146100825780637133e9c71461007d57806379e77db0146100785780637d34a97614610073578063de38bc3a1461006e5763e8aa8e9d1461006957600080fd5b6102e7565b61027f565b610232565b6101db565b61014c565b610105565b346100dd5760406003193601126100dd576100a06100e2565b73ffffffffffffffffffffffffffffffffffffffff6024359116600052600160205260406000209060005260205260406000205460805260206080f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036100dd57565b346100dd5760206003193601126100dd5773ffffffffffffffffffffffffffffffffffffffff6101336100e2565b1660005260006020526020604060002054604051908152f35b346100dd5760606003193601126100dd576101656100e2565b73ffffffffffffffffffffffffffffffffffffffff16600090815260026020908152604080832060243584528252808320604435845282529182902054915191825290f35b9181601f840112156100dd5782359167ffffffffffffffff83116100dd576020808501948460051b0101116100dd57565b346100dd5760406003193601126100dd576101f46100e2565b60243567ffffffffffffffff81116100dd5760209161021a6102299236906004016101aa565b9161022483610359565b6103a0565b54604051908152f35b346100dd5760606003193601126100dd5761024b6100e2565b60243567ffffffffffffffff81116100dd5761026e61027c9136906004016101aa565b906044359361022483610359565b55005b346100dd5760806003193601126100dd576102986100e2565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602090815260408083206024358452825280832060443584528252808320606435845282529182902054915191825290f35b346100dd5760a06003193601126100dd576103006100e2565b73ffffffffffffffffffffffffffffffffffffffff166000908152600460209081526040808320602435845282528083206044358452825280832060643584528252808320608435845282529182902054915191825290f35b801561039a5780600114610394578060021461038e57806003146103885760041461038357600080fd5b600490565b50600390565b50600290565b50600190565b50600090565b92906040519081526020810193845260408120936000935b8385106103c757505050505090565b600585901b810135835294815260408220946001909401936103b856fea164736f6c634300081e000a", + BytecodeHash: "0xb8161293041f80e64e881f828c9be3bcbd6b9a8fdf5e481c63bda288b0a271f2", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c806333cd33bd146100875780635628f232146100825780637133e9c71461007d57806379e77db0146100785780637d34a97614610073578063de38bc3a1461006e5763e8aa8e9d1461006957600080fd5b6102e7565b61027f565b610232565b6101db565b61014c565b610105565b346100dd5760406003193601126100dd576100a06100e2565b73ffffffffffffffffffffffffffffffffffffffff6024359116600052600160205260406000209060005260205260406000205460805260206080f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036100dd57565b346100dd5760206003193601126100dd5773ffffffffffffffffffffffffffffffffffffffff6101336100e2565b1660005260006020526020604060002054604051908152f35b346100dd5760606003193601126100dd576101656100e2565b73ffffffffffffffffffffffffffffffffffffffff16600090815260026020908152604080832060243584528252808320604435845282529182902054915191825290f35b9181601f840112156100dd5782359167ffffffffffffffff83116100dd576020808501948460051b0101116100dd57565b346100dd5760406003193601126100dd576101f46100e2565b60243567ffffffffffffffff81116100dd5760209161021a6102299236906004016101aa565b9161022483610359565b6103a0565b54604051908152f35b346100dd5760606003193601126100dd5761024b6100e2565b60243567ffffffffffffffff81116100dd5761026e61027c9136906004016101aa565b906044359361022483610359565b55005b346100dd5760806003193601126100dd576102986100e2565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602090815260408083206024358452825280832060443584528252808320606435845282529182902054915191825290f35b346100dd5760a06003193601126100dd576103006100e2565b73ffffffffffffffffffffffffffffffffffffffff166000908152600460209081526040808320602435845282528083206044358452825280832060643584528252808320608435845282529182902054915191825290f35b801561039a5780600114610394578060021461038e57806003146103885760041461038357600080fd5b600490565b50600390565b50600290565b50600190565b50600090565b92906040519081526020810193845260408120936000935b8385106103c757505050505090565b600585901b810135835294815260408220946001909401936103b856fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x8a068988b70a582d2e39ee31d14dd2e2d1266fa562fcb8fd779c6b7d6352cfa7", + ABI: `[{"type":"function","name":"get","inputs":[{"name":"$address","type":"address","internalType":"address"},{"name":"$path","type":"bytes32[]","internalType":"bytes32[]"}],"outputs":[{"name":"$out","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"set","inputs":[{"name":"$address","type":"address","internalType":"address"},{"name":"$path","type":"bytes32[]","internalType":"bytes32[]"},{"name":"$value","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"v","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"vv","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"vvv","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"vvvv","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"vvvvv","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"}]`, + } + Evm = Contract{ + Address: ethcommon.HexToAddress("0x00000000d89DF93e89AD0cD3309E26C5701c0923"), + Bytecode: "0x60808060405234601557610543908161001b8239f35b600080fdfe6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c8063095bcdb614610360578063162790551461033a578063188ec3561461031f5780632cc8377d146103045780633408e470146102e957806342cbb15c146102ce578063455259cb146102b3578063538684901461029757806370a082311461027357806381ea44081461024f5780639663f88f14610208578063a9059cbb146101c8578063ab470f05146101ad5763ee82ac5e0361000e57346101a85760206003193601126101a85760043543811080610146575b156100e85760209060405190408152f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f506c756745564d3a626c6f636b2d686173682d756e617661696c61626c6500006044820152fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff004301438111610179578110156100d7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b346101a85760006003193601126101a8576020604051338152f35b60406003193601126101a85760006101de6104ae565b81806024356101ef814710156104d1565b38935af1156101fa57005b63b12d13eb6000526004601cfd5b346101a85760006003193601126101a8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43014381116101795760209060405190408152f35b346101a85760206003193601126101a857602061026a6104ae565b3f604051908152f35b346101a85760206003193601126101a857602061028e6104ae565b31604051908152f35b346101a85760006003193601126101a85760205a604051908152f35b346101a85760006003193601126101a85760206040513a8152f35b346101a85760006003193601126101a8576020604051438152f35b346101a85760006003193601126101a8576020604051468152f35b346101a85760006003193601126101a8576020604051458152f35b346101a85760006003193601126101a8576020604051428152f35b346101a85760206003193601126101a85760206103556104ae565b3b1515604051908152f35b60606003193601126101a8576103746104ae565b60243590610384824710156104d1565b8147106101fa5760003881808585604435f11561048b575b505060405160209080820167ffffffffffffffff81118282101761045c57604052600081527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08201368383013760405180926001825260408183015282519283604084015260005b848110610445575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000606080968601015201168101030190f35b818101830151868201606001528593508201610404565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601691600b916000526073825360ff602053f0156104aa57808061039c565b3838fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101a857565b156104d857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f506c756745564d3a696e73756666696369656e742d62616c616e6365000000006044820152fdfea164736f6c634300081e000a", + BytecodeHash: "0x66ef8816f09163ec04736dd70f99fba2815580a38b0cb20352c9b9cbf067a2f8", + DeployedBytecode: "0x6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c8063095bcdb614610360578063162790551461033a578063188ec3561461031f5780632cc8377d146103045780633408e470146102e957806342cbb15c146102ce578063455259cb146102b3578063538684901461029757806370a082311461027357806381ea44081461024f5780639663f88f14610208578063a9059cbb146101c8578063ab470f05146101ad5763ee82ac5e0361000e57346101a85760206003193601126101a85760043543811080610146575b156100e85760209060405190408152f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f506c756745564d3a626c6f636b2d686173682d756e617661696c61626c6500006044820152fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff004301438111610179578110156100d7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b346101a85760006003193601126101a8576020604051338152f35b60406003193601126101a85760006101de6104ae565b81806024356101ef814710156104d1565b38935af1156101fa57005b63b12d13eb6000526004601cfd5b346101a85760006003193601126101a8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43014381116101795760209060405190408152f35b346101a85760206003193601126101a857602061026a6104ae565b3f604051908152f35b346101a85760206003193601126101a857602061028e6104ae565b31604051908152f35b346101a85760006003193601126101a85760205a604051908152f35b346101a85760006003193601126101a85760206040513a8152f35b346101a85760006003193601126101a8576020604051438152f35b346101a85760006003193601126101a8576020604051468152f35b346101a85760006003193601126101a8576020604051458152f35b346101a85760006003193601126101a8576020604051428152f35b346101a85760206003193601126101a85760206103556104ae565b3b1515604051908152f35b60606003193601126101a8576103746104ae565b60243590610384824710156104d1565b8147106101fa5760003881808585604435f11561048b575b505060405160209080820167ffffffffffffffff81118282101761045c57604052600081527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08201368383013760405180926001825260408183015282519283604084015260005b848110610445575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000606080968601015201168101030190f35b818101830151868201606001528593508201610404565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601691600b916000526073825360ff602053f0156104aa57808061039c565b3838fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101a857565b156104d857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f506c756745564d3a696e73756666696369656e742d62616c616e6365000000006044820152fdfea164736f6c634300081e000a", + DeployedBytecodeHash: "0x224c8cda50679dc4560e53768a08e3d26fe438edbf8d9c6d5b33823c763f4f5d", + ABI: `[{"type":"receive","stateMutability":"payable"},{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getBlockGasLimit","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getBlockHash","inputs":[],"outputs":[{"name":"result","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"getBlockHash","inputs":[{"name":"blockNumber","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"getBlockNumber","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getCaller","inputs":[],"outputs":[{"name":"result","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getChainId","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getCodeHash","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"result","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"getGasPrice","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getRemainingGas","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTimestamp","inputs":[],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"isContract","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"result","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"recipient","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"gasLimit","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"success","type":"bool","internalType":"bool"},{"name":"returnData","type":"bytes","internalType":"bytes"}],"stateMutability":"payable"},{"type":"function","name":"transfer","inputs":[{"name":"recipient","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"payable"}]`, + } + Math = Contract{ + Address: ethcommon.HexToAddress("0x0000000091C3cB31715865006288BF7C0BD10a8e"), + Bytecode: "0x6080806040523460155761033e908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c8063165c4a16146102895780633ef5e445146102675780636d5433e614610246578063771602f7146101f55780637ae2b5c7146101cd578063baaf073d1461014e578063c04f01fc1461012d5763f88e9fbf1461007457600080fd5b3461012857610082366102a2565b9080156100ca57811561009b5760209160405191048152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f506c75674d6174683a6469766964652d62792d7a65726f0000000000000000006044820152fd5b600080fd5b34610128576020610146610140366102a2565b906102e9565b604051908152f35b346101285761015c366102a2565b90811561016f5760209160405191068152f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f506c75674d6174683a6d6f64756c6f2d62792d7a65726f0000000000000000006044820152fd5b346101285760206101dd366102a2565b808210156101ee5750604051908152f35b9050610146565b3461012857610203366102a2565b810180911161021757602090604051908152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b34610128576020610256366102a2565b808211156101ee5750604051908152f35b3461012857610275366102a2565b810390811161021757602090604051908152f35b3461012857602061014661029c366102a2565b906102d6565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6040910112610128576004359060243590565b8181029291811591840414171561021757565b90801561032a57811561032357906001916000915b81831061030b5750505090565b90919261031a826001926102d6565b930191906102fe565b5050600090565b505060019056fea164736f6c634300081e000a", + BytecodeHash: "0x349c749bb961a7fd729cb443479f12b0a091dff3222b35290059e2d51d88422c", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c8063165c4a16146102895780633ef5e445146102675780636d5433e614610246578063771602f7146101f55780637ae2b5c7146101cd578063baaf073d1461014e578063c04f01fc1461012d5763f88e9fbf1461007457600080fd5b3461012857610082366102a2565b9080156100ca57811561009b5760209160405191048152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f506c75674d6174683a6469766964652d62792d7a65726f0000000000000000006044820152fd5b600080fd5b34610128576020610146610140366102a2565b906102e9565b604051908152f35b346101285761015c366102a2565b90811561016f5760209160405191068152f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f506c75674d6174683a6d6f64756c6f2d62792d7a65726f0000000000000000006044820152fd5b346101285760206101dd366102a2565b808210156101ee5750604051908152f35b9050610146565b3461012857610203366102a2565b810180911161021757602090604051908152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b34610128576020610256366102a2565b808211156101ee5750604051908152f35b3461012857610275366102a2565b810390811161021757602090604051908152f35b3461012857602061014661029c366102a2565b906102d6565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6040910112610128576004359060243590565b8181029291811591840414171561021757565b90801561032a57811561032357906001916000915b81831061030b5750505090565b90919261031a826001926102d6565b930191906102fe565b5050600090565b505060019056fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x1982ae43dfccf681102ded5a7f2ff69e657e8496dfbb434f25ef03e984eed53d", + ABI: `[{"type":"function","name":"add","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"divide","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"max","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"min","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"modulo","inputs":[{"name":"a","type":"uint256","internalType":"uint256"},{"name":"b","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"multiply","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"power","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"subtract","inputs":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"result","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"}]`, + } + Time = Contract{ + Address: ethcommon.HexToAddress("0x00000000BfF8407FF7e9D93456f48C4DE8939c05"), + Bytecode: "0x60808060405234601557610eaa908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c8063097ff95e1461063d57806319956d2f1461061e5780633187e5a01461059957806345999fa01461057a5780636c9230db1461055f5780638583c7cc146102035780639650fa09146101d2578063b633620c14610149578063c002d1d2146100c25763fe33d6561461008a57600080fd5b346100bd5760406003193601126100bd5760243560078110156100bd576100b56020916004356106ef565b604051908152f35b600080fd5b346100bd5760206003193601126100bd57600435428111156100eb576100b5602091429061069d565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f54696d657374616d7020697320696e20746865207061737400000000000000006044820152fd5b346100bd5760206003193601126100bd576201518060043504620151808102908082046201518014901517156101a357620151808101908181116101a3576201517f81019182116101a35760409182519182526020820152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b346100bd5760206003193601126100bd5760606101f06004356109d1565b9060405192835260208301526040820152f35b346100bd5760606003193601126100bd576024356007811015806100bd5760443590600435821561050157610237836106aa565b937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051960116850185811067ffffffffffffffff8211176104d25760409493929452828552610289836106aa565b937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020870195013686376000928215915b8585106103075786886040519182916020830190602084525180915260408301919060005b8181106102ee575050500390f35b82518452859450602093840193909201916001016102e0565b816104a357821561036d57610e1081018091116101a357935b875181101561033e578085602060019360051b8b01015201936102bb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600091506001840361038d576201518081018091116101a357935b610320565b600284036103a85762093a8081018091116101a35793610320565b60009150600384036103c7576212750081018091116101a35793610320565b60048403610410576103db6103f6916109d1565b506103e682916106c2565b91600c83116103fc575b50610c96565b93610320565b91505061040a6001916106c2565b8a6103f0565b600091506005840361047a57610425906109d1565b5081600382018083116101a3578092600c8211610449575b50506103f69250610c96565b6002019250821190506101a357600c900690600182018092116101a3576104736103f692916106c2565b8a8061043d565b9360068403610388579361048d906109d1565b5050600181018091116101a3576103f690610b7f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f496e76616c696420636f756e74000000000000000000000000000000000000006044820152fd5b346100bd5760006003193601126100bd576020604051428152f35b346100bd5760206003193601126100bd57602060405160043542108152f35b346100bd5760206003193601126100bd576004354281116105c0576100b56020914261069d565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f54696d657374616d7020697320696e20746865206675747572650000000000006044820152fd5b346100bd5760206003193601126100bd57602060405160043542118152f35b346100bd5760406003193601126100bd57602061065b6004356109d1565b6106696024939293356109d1565b93919091149384610693575b505082610689575b50506040519015158152f35b149050828061067d565b1492508480610675565b919082039182116101a357565b67ffffffffffffffff81116104d25760051b60200190565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146101a35760010190565b9060078110156104a3578061071c5750610e109004610e10810290808204610e1014901517156101a35790565b600060018203610748575050620151809004620151808102908082046201518014901517156101a35790565b50600281036107b3575061075b81610d2a565b600781036107a85750620151806000915b0490620151808202918083046201518014901517156101a357620151808102908082046201518014901517156101a3576107a59161069d565b90565b62015180909161076c565b60006003820361085d5750506107c881610d2a565b600781036108525750620151806000915b0490620151808202918083046201518014901517156101a357620151808102908082046201518014901517156101a3576108129161069d565b6007620151808204046001166108255790565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6c58081019081116101a35790565b6201518090916107d9565b506004810361087a57506108736107a5916109d1565b5090610c96565b60006005820361095857505061088f906109d1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81018181116101a3576000821561092b5750048060021b90808204600414901517156101a357600181018091116101a35760021c600c810290808204600c14901517156101a3577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81019081116101a3576107a591610c96565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526012600452fd5b506006146109be5760646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f496e76616c696420696e74657276616c000000000000000000000000000000006044820152fd5b6109ca6107a5916109d1565b5050610b7f565b62015180900462010bd981019062010bd98212600082129080158216911516176101a357622649650190600062253d8c831291129080158216911516176101a3578060021b6004810582036101a35762023ab19005908162023ab1029062023ab1820583036101a357600382019160006003841291129080158216911516176101a3576004610a61920590610e84565b906001820160018112600084129080158216911516176101a35780610fa00290610fa08205036101a35762164b09610aa79105926004610aa085610da7565b0590610e84565b91601f8301926000601f851291129080158216911516176101a357826050026050810584036101a35761098f9005928361098f029061098f820585036101a3576050610af4920590610e84565b92600b810590600281019060006002831291129080158216911516176101a35781600c02600c810583036101a357610b2b91610e84565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf81019081136001166101a357806064029060648205036101a357610b7992610b7491610d8b565b610d8b565b91909192565b6107b281106100bd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82b6610bb282610d4d565b917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff391610bf8906004610bf1610bec600c87058098610d8b565b610da7565b0590610d8b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90600184136001166101a357610c4b610c5191600c610bf1610c46600196610c408b610dba565b90610e84565b610dd0565b92610d6c565b92136001166101a3576004610aa06064610c71610c7c96610c7796610d8b565b05610de3565b610e54565b620151808102908082046201518014901517156101a35790565b6107b281106100bd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82b6906004610aa06064610c71610c7c96600c610bf1610d24610d1e610cfb610c779b8a610bf1610bec610cf18f610d4d565b89610bf18c610df4565b84610bf1610c46610d0b89610e24565b610c4084610d188c610df4565b05610dba565b98610d6c565b92610df4565b620151809004600381018091116101a35760079006600181018091116101a35790565b906112c082019160006112c0841291129080158216911516176101a357565b906113248201916000611324841291129080158216911516176101a357565b919091600083820193841291129080158216911516176101a357565b90816105b502916105b58305036101a357565b90600c820291808305600c14901517156101a357565b908161016f029161016f8305036101a357565b90816003029160038305036101a357565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff282019182136001166101a357565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe82019182136001166101a357565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdac27482019182136001166101a357565b818103929160001380158285131691841216176101a35756fea164736f6c634300081e000a", + BytecodeHash: "0x75af953e8e4d461e0c617ee6f0a0b1543ffd0b45d44097462c36beded2e2881e", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c8063097ff95e1461063d57806319956d2f1461061e5780633187e5a01461059957806345999fa01461057a5780636c9230db1461055f5780638583c7cc146102035780639650fa09146101d2578063b633620c14610149578063c002d1d2146100c25763fe33d6561461008a57600080fd5b346100bd5760406003193601126100bd5760243560078110156100bd576100b56020916004356106ef565b604051908152f35b600080fd5b346100bd5760206003193601126100bd57600435428111156100eb576100b5602091429061069d565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f54696d657374616d7020697320696e20746865207061737400000000000000006044820152fd5b346100bd5760206003193601126100bd576201518060043504620151808102908082046201518014901517156101a357620151808101908181116101a3576201517f81019182116101a35760409182519182526020820152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b346100bd5760206003193601126100bd5760606101f06004356109d1565b9060405192835260208301526040820152f35b346100bd5760606003193601126100bd576024356007811015806100bd5760443590600435821561050157610237836106aa565b937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051960116850185811067ffffffffffffffff8211176104d25760409493929452828552610289836106aa565b937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020870195013686376000928215915b8585106103075786886040519182916020830190602084525180915260408301919060005b8181106102ee575050500390f35b82518452859450602093840193909201916001016102e0565b816104a357821561036d57610e1081018091116101a357935b875181101561033e578085602060019360051b8b01015201936102bb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600091506001840361038d576201518081018091116101a357935b610320565b600284036103a85762093a8081018091116101a35793610320565b60009150600384036103c7576212750081018091116101a35793610320565b60048403610410576103db6103f6916109d1565b506103e682916106c2565b91600c83116103fc575b50610c96565b93610320565b91505061040a6001916106c2565b8a6103f0565b600091506005840361047a57610425906109d1565b5081600382018083116101a3578092600c8211610449575b50506103f69250610c96565b6002019250821190506101a357600c900690600182018092116101a3576104736103f692916106c2565b8a8061043d565b9360068403610388579361048d906109d1565b5050600181018091116101a3576103f690610b7f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f496e76616c696420636f756e74000000000000000000000000000000000000006044820152fd5b346100bd5760006003193601126100bd576020604051428152f35b346100bd5760206003193601126100bd57602060405160043542108152f35b346100bd5760206003193601126100bd576004354281116105c0576100b56020914261069d565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f54696d657374616d7020697320696e20746865206675747572650000000000006044820152fd5b346100bd5760206003193601126100bd57602060405160043542118152f35b346100bd5760406003193601126100bd57602061065b6004356109d1565b6106696024939293356109d1565b93919091149384610693575b505082610689575b50506040519015158152f35b149050828061067d565b1492508480610675565b919082039182116101a357565b67ffffffffffffffff81116104d25760051b60200190565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146101a35760010190565b9060078110156104a3578061071c5750610e109004610e10810290808204610e1014901517156101a35790565b600060018203610748575050620151809004620151808102908082046201518014901517156101a35790565b50600281036107b3575061075b81610d2a565b600781036107a85750620151806000915b0490620151808202918083046201518014901517156101a357620151808102908082046201518014901517156101a3576107a59161069d565b90565b62015180909161076c565b60006003820361085d5750506107c881610d2a565b600781036108525750620151806000915b0490620151808202918083046201518014901517156101a357620151808102908082046201518014901517156101a3576108129161069d565b6007620151808204046001166108255790565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6c58081019081116101a35790565b6201518090916107d9565b506004810361087a57506108736107a5916109d1565b5090610c96565b60006005820361095857505061088f906109d1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81018181116101a3576000821561092b5750048060021b90808204600414901517156101a357600181018091116101a35760021c600c810290808204600c14901517156101a3577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81019081116101a3576107a591610c96565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526012600452fd5b506006146109be5760646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f496e76616c696420696e74657276616c000000000000000000000000000000006044820152fd5b6109ca6107a5916109d1565b5050610b7f565b62015180900462010bd981019062010bd98212600082129080158216911516176101a357622649650190600062253d8c831291129080158216911516176101a3578060021b6004810582036101a35762023ab19005908162023ab1029062023ab1820583036101a357600382019160006003841291129080158216911516176101a3576004610a61920590610e84565b906001820160018112600084129080158216911516176101a35780610fa00290610fa08205036101a35762164b09610aa79105926004610aa085610da7565b0590610e84565b91601f8301926000601f851291129080158216911516176101a357826050026050810584036101a35761098f9005928361098f029061098f820585036101a3576050610af4920590610e84565b92600b810590600281019060006002831291129080158216911516176101a35781600c02600c810583036101a357610b2b91610e84565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf81019081136001166101a357806064029060648205036101a357610b7992610b7491610d8b565b610d8b565b91909192565b6107b281106100bd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82b6610bb282610d4d565b917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff391610bf8906004610bf1610bec600c87058098610d8b565b610da7565b0590610d8b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90600184136001166101a357610c4b610c5191600c610bf1610c46600196610c408b610dba565b90610e84565b610dd0565b92610d6c565b92136001166101a3576004610aa06064610c71610c7c96610c7796610d8b565b05610de3565b610e54565b620151808102908082046201518014901517156101a35790565b6107b281106100bd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82b6906004610aa06064610c71610c7c96600c610bf1610d24610d1e610cfb610c779b8a610bf1610bec610cf18f610d4d565b89610bf18c610df4565b84610bf1610c46610d0b89610e24565b610c4084610d188c610df4565b05610dba565b98610d6c565b92610df4565b620151809004600381018091116101a35760079006600181018091116101a35790565b906112c082019160006112c0841291129080158216911516176101a357565b906113248201916000611324841291129080158216911516176101a357565b919091600083820193841291129080158216911516176101a357565b90816105b502916105b58305036101a357565b90600c820291808305600c14901517156101a357565b908161016f029161016f8305036101a357565b90816003029160038305036101a357565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff282019182136001166101a357565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe82019182136001166101a357565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdac27482019182136001166101a357565b818103929160001380158285131691841216176101a35756fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x5f0bfb05afe19310d8a26b04e5d0657139d44e6ba065e290aa942ff8052691a7", + ABI: `[{"type":"function","name":"getCurrentTimestamp","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getDateParts","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"$year","type":"uint256","internalType":"uint256"},{"name":"$month","type":"uint256","internalType":"uint256"},{"name":"$day","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"getOccurence","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"},{"name":"$interval","type":"uint8","internalType":"enum PlugTime.Interval"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"getOccurences","inputs":[{"name":"$occurence","type":"uint256","internalType":"uint256"},{"name":"$interval","type":"uint8","internalType":"enum PlugTime.Interval"},{"name":"$count","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"$occurrences","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"pure"},{"type":"function","name":"getTimestamp","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"$start","type":"uint256","internalType":"uint256"},{"name":"$end","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"isAfter","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"isBefore","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"isDate","inputs":[{"name":"$timestamp1","type":"uint256","internalType":"uint256"},{"name":"$timestamp2","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"function","name":"timeSince","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"timeUntil","inputs":[{"name":"$timestamp","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"}]`, + } + Echo = Contract{ + Address: ethcommon.HexToAddress("0x000000007BF6e3a91E1f01A5E19416165e0DB4cD"), + Bytecode: "0x6080806040523460155761028f908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c80639a34832d14610103578063b5531d21146100c85763d72a26651461003d57600080fd5b346100c35760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760243567ffffffffffffffff81116100c35761008c903690600401610165565b7f3752a357a949d58944fee07631779eb98f8c1ba788096770cc9cbc014e2375b8604051806100be600435948261021a565b0390a2005b600080fd5b346100c35760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760206040516004358152f35b346100c35760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760043567ffffffffffffffff81116100c357610155610161913690600401610165565b6040519182918261021a565b0390f35b81601f820112156100c35780359067ffffffffffffffff82116101eb57604051927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81601f8601160116840184811067ffffffffffffffff8211176101eb57604052828452602083830101116100c357816000926020809301838601378301015290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b9190916020815282519283602083015260005b84811061026c5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b806020809284010151604082860101520161022d56fea164736f6c634300081e000a", + BytecodeHash: "0x93be343b964a44026e871548ff814e25875adb511cdd16986ac0649304619af1", + DeployedBytecode: "0x6080604052600436101561001257600080fd5b60003560e01c80639a34832d14610103578063b5531d21146100c85763d72a26651461003d57600080fd5b346100c35760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760243567ffffffffffffffff81116100c35761008c903690600401610165565b7f3752a357a949d58944fee07631779eb98f8c1ba788096770cc9cbc014e2375b8604051806100be600435948261021a565b0390a2005b600080fd5b346100c35760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760206040516004358152f35b346100c35760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c35760043567ffffffffffffffff81116100c357610155610161913690600401610165565b6040519182918261021a565b0390f35b81601f820112156100c35780359067ffffffffffffffff82116101eb57604051927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81601f8601160116840184811067ffffffffffffffff8211176101eb57604052828452602083830101116100c357816000926020809301838601378301015290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b9190916020815282519283602083015260005b84811061026c5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b806020809284010151604082860101520161022d56fea164736f6c634300081e000a", + DeployedBytecodeHash: "0x74d963013924f70ce5024adb9c16a183b8568b72362044d55d5f512f819c8c48", + ABI: `[{"type":"function","name":"echo","inputs":[{"name":"value","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"result","type":"bytes32","internalType":"bytes32"}],"stateMutability":"pure"},{"type":"function","name":"echoBytes","inputs":[{"name":"value","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"result","type":"bytes","internalType":"bytes"}],"stateMutability":"pure"},{"type":"function","name":"log","inputs":[{"name":"topic","type":"bytes32","internalType":"bytes32"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"Echo","inputs":[{"name":"topic","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"data","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}]`, + } +) diff --git a/packages/api/contracts/type_hashes.go b/packages/api/contracts/type_hashes.go new file mode 100644 index 000000000..573ca6bc8 --- /dev/null +++ b/packages/api/contracts/type_hashes.go @@ -0,0 +1,8 @@ +package contracts + +var ( + PlugTypeHash = "0x4a5c402b142b42f7a370c0499cf5ed6a0a39a73d479eb129087b5b45e608ccc6" + PlugsTypeHash = "0x5e753b799be998b96f5989c4585d8cf47a8e46682fc527f23d88e0d6a09048cf" + LivePlugsTypeHash = "0xeb79b94df94230950addab3b4e85da31ca18ec7ce505cc225a455e8e31773a1d" + Eip712DomainTypeHash = "0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f" +) diff --git a/packages/api/domain.go b/packages/api/domain.go new file mode 100644 index 000000000..28268a83c --- /dev/null +++ b/packages/api/domain.go @@ -0,0 +1,57 @@ +package api + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/terminally-online/plug/packages/api/contracts" +) + +var DomainTypeHash = common.HexToHash(contracts.Eip712DomainTypeHash) + +// Domain represents the EIP-712 domain separator. +type Domain struct { + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address +} + +// NewDomain creates a Domain with the given parameters. +func NewDomain(name, version string, chainId *big.Int, verifyingContract common.Address) Domain { + return Domain{ + Name: name, + Version: version, + ChainId: chainId, + VerifyingContract: verifyingContract, + } +} + +// SocketDomain creates a default domain for a PlugSocket contract. +func SocketDomain(socket common.Address, chainId *big.Int) Domain { + return Domain{ + Name: "PlugSocket", + Version: "1.0.0", + ChainId: chainId, + VerifyingContract: socket, + } +} + +// Hash computes the EIP-712 domain separator hash. +func (d Domain) Hash() common.Hash { + nameHash := crypto.Keccak256Hash([]byte(d.Name)) + versionHash := crypto.Keccak256Hash([]byte(d.Version)) + + data := make([]byte, 32*5) + copy(data[0:32], DomainTypeHash[:]) + copy(data[32:64], nameHash[:]) + copy(data[64:96], versionHash[:]) + + chainIdBytes := d.ChainId.Bytes() + copy(data[128-len(chainIdBytes):128], chainIdBytes) + + copy(data[140:160], d.VerifyingContract[:]) + + return crypto.Keccak256Hash(data) +} diff --git a/packages/api/domain_test.go b/packages/api/domain_test.go new file mode 100644 index 000000000..1d75fb093 --- /dev/null +++ b/packages/api/domain_test.go @@ -0,0 +1,73 @@ +package api + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestNewDomain(t *testing.T) { + socket := common.HexToAddress("0x1234567890123456789012345678901234567890") + chainId := big.NewInt(1) + + domain := NewDomain("TestName", "1.0.0", chainId, socket) + + if domain.Name != "TestName" { + t.Errorf("Name = %s, want TestName", domain.Name) + } + if domain.Version != "1.0.0" { + t.Errorf("Version = %s, want 1.0.0", domain.Version) + } + if domain.ChainId.Cmp(chainId) != 0 { + t.Errorf("ChainId = %s, want %s", domain.ChainId.String(), chainId.String()) + } + if domain.VerifyingContract != socket { + t.Errorf("VerifyingContract = %s, want %s", domain.VerifyingContract.Hex(), socket.Hex()) + } +} + +func TestSocketDomain(t *testing.T) { + socket := common.HexToAddress("0x1234567890123456789012345678901234567890") + chainId := big.NewInt(1) + + domain := SocketDomain(socket, chainId) + + if domain.Name != "PlugSocket" { + t.Errorf("Name = %s, want PlugSocket", domain.Name) + } + if domain.Version != "1.0.0" { + t.Errorf("Version = %s, want 1.0.0", domain.Version) + } + if domain.VerifyingContract != socket { + t.Errorf("VerifyingContract = %s, want %s", domain.VerifyingContract.Hex(), socket.Hex()) + } +} + +func TestDomain_Hash(t *testing.T) { + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + hash := domain.Hash() + + if hash == (common.Hash{}) { + t.Error("Domain.Hash() should not be zero") + } +} + +func TestDomain_Hash_Deterministic(t *testing.T) { + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + hash1 := domain.Hash() + hash2 := domain.Hash() + + if hash1 != hash2 { + t.Errorf("Hash is not deterministic: %s != %s", hash1.Hex(), hash2.Hex()) + } +} + +func TestDomainTypeHash(t *testing.T) { + expected := common.HexToHash("0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f") + if DomainTypeHash != expected { + t.Errorf("DomainTypeHash mismatch:\n got: %s\n expected: %s", DomainTypeHash.Hex(), expected.Hex()) + } +} diff --git a/packages/api/encode.go b/packages/api/encode.go new file mode 100644 index 000000000..8b7ae0f94 --- /dev/null +++ b/packages/api/encode.go @@ -0,0 +1,158 @@ +package api + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// EncodeLiteral encodes a value as a 32-byte left-padded literal +func EncodeLiteral(value any) ([]byte, error) { + result := make([]byte, 32) + + switch v := value.(type) { + case common.Address: + copy(result[12:], v.Bytes()) + case *common.Address: + if v != nil { + copy(result[12:], v.Bytes()) + } + case *big.Int: + if v != nil { + bytes := v.Bytes() + if len(bytes) > 32 { + return nil, fmt.Errorf("big.Int too large: %d bytes", len(bytes)) + } + copy(result[32-len(bytes):], bytes) + } + case big.Int: + bytes := v.Bytes() + if len(bytes) > 32 { + return nil, fmt.Errorf("big.Int too large: %d bytes", len(bytes)) + } + copy(result[32-len(bytes):], bytes) + case uint64: + b := new(big.Int).SetUint64(v) + bytes := b.Bytes() + copy(result[32-len(bytes):], bytes) + case uint32: + b := new(big.Int).SetUint64(uint64(v)) + bytes := b.Bytes() + copy(result[32-len(bytes):], bytes) + case uint16: + result[30] = byte(v >> 8) + result[31] = byte(v) + case uint8: + result[31] = v + case int64: + b := big.NewInt(v) + if v < 0 { + // Two's complement for negative numbers + b.Add(b, new(big.Int).Lsh(big.NewInt(1), 256)) + } + bytes := b.Bytes() + copy(result[32-len(bytes):], bytes) + case int: + b := big.NewInt(int64(v)) + if v < 0 { + b.Add(b, new(big.Int).Lsh(big.NewInt(1), 256)) + } + bytes := b.Bytes() + copy(result[32-len(bytes):], bytes) + case bool: + if v { + result[31] = 1 + } + case []byte: + if len(v) > 32 { + return nil, fmt.Errorf("byte slice too large: %d bytes", len(v)) + } + copy(result[32-len(v):], v) + case [4]byte: + copy(result[28:], v[:]) + case [20]byte: + copy(result[12:], v[:]) + case [32]byte: + copy(result, v[:]) + case string: + // Treat string as bytes, left-pad if <= 32 + if len(v) > 32 { + return nil, fmt.Errorf("string too large: %d bytes", len(v)) + } + copy(result[32-len(v):], []byte(v)) + default: + return nil, fmt.Errorf("unsupported type: %T", value) + } + + return result, nil +} + +// MustEncodeLiteral is like EncodeLiteral but panics on error +func MustEncodeLiteral(value any) []byte { + result, err := EncodeLiteral(value) + if err != nil { + panic(err) + } + return result +} + +// EncodeLiterals encodes multiple values as consecutive 32-byte slots +func EncodeLiterals(values ...any) ([]byte, error) { + result := make([]byte, 0, len(values)*32) + for i, v := range values { + encoded, err := EncodeLiteral(v) + if err != nil { + return nil, fmt.Errorf("failed to encode value at index %d: %w", i, err) + } + result = append(result, encoded...) + } + return result, nil +} + +// MustEncodeLiterals is like EncodeLiterals but panics on error +func MustEncodeLiterals(values ...any) []byte { + result, err := EncodeLiterals(values...) + if err != nil { + panic(err) + } + return result +} + +// EncodeSelector computes the 4-byte function selector from a signature. +// The signature should be in the format "functionName(type1,type2,...)" +// e.g., "transfer(address,uint256)" +func EncodeSelector(signature string) [4]byte { + hash := crypto.Keccak256([]byte(signature)) + var selector [4]byte + copy(selector[:], hash[:4]) + return selector +} + +// SelectorFromBytes converts a byte slice to a selector +func SelectorFromBytes(b []byte) [4]byte { + var selector [4]byte + if len(b) >= 4 { + copy(selector[:], b[:4]) + } + return selector +} + +// EncodeDynamicBytes encodes variable-length bytes with length prefix and padding +// This is used when pre-populating state with dynamic data +func EncodeDynamicBytes(data []byte) []byte { + length := len(data) + paddedLen := ((length + 31) / 32) * 32 + + result := make([]byte, 32+paddedLen) + + // Length prefix (32 bytes) + lenBytes := big.NewInt(int64(length)).Bytes() + copy(result[32-len(lenBytes):32], lenBytes) + + // Padded data + copy(result[32:], data) + + return result +} diff --git a/packages/api/encode_test.go b/packages/api/encode_test.go new file mode 100644 index 000000000..87f8f7aee --- /dev/null +++ b/packages/api/encode_test.go @@ -0,0 +1,254 @@ +package api + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestEncodeLiteral(t *testing.T) { + tests := []struct { + name string + value any + expected string + }{ + { + name: "uint8", + value: uint8(42), + expected: "000000000000000000000000000000000000000000000000000000000000002a", + }, + { + name: "uint16", + value: uint16(1000), + expected: "00000000000000000000000000000000000000000000000000000000000003e8", + }, + { + name: "uint32", + value: uint32(100000), + expected: "00000000000000000000000000000000000000000000000000000000000186a0", + }, + { + name: "uint64", + value: uint64(1000000), + expected: "00000000000000000000000000000000000000000000000000000000000f4240", + }, + { + name: "bool_true", + value: true, + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }, + { + name: "bool_false", + value: false, + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, + { + name: "address", + value: common.HexToAddress("0xdead000000000000000000000000000000000000"), + expected: "000000000000000000000000dead000000000000000000000000000000000000", + }, + { + name: "big.Int", + value: big.NewInt(256), + expected: "0000000000000000000000000000000000000000000000000000000000000100", + }, + { + name: "big.Int_large", + value: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), + expected: "0000000000000000000000000000000100000000000000000000000000000000", + }, + { + name: "negative_int64", + value: int64(-1), + expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + { + name: "bytes4", + value: [4]byte{0xde, 0xad, 0xbe, 0xef}, + expected: "00000000000000000000000000000000000000000000000000000000deadbeef", + }, + { + name: "bytes32", + value: [32]byte{0x01, 0x02, 0x03}, + expected: "0102030000000000000000000000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := EncodeLiteral(tt.value) + if err != nil { + t.Fatalf("EncodeLiteral failed: %v", err) + } + + got := hex.EncodeToString(result) + if got != tt.expected { + t.Errorf("EncodeLiteral(%v) = %s, want %s", tt.value, got, tt.expected) + } + }) + } +} + +func TestEncodeLiteral_Error(t *testing.T) { + tests := []struct { + name string + value any + }{ + { + name: "byte_slice_too_large", + value: make([]byte, 33), + }, + { + name: "string_too_large", + value: string(make([]byte, 33)), + }, + { + name: "unsupported_type", + value: struct{}{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := EncodeLiteral(tt.value) + if err == nil { + t.Error("expected error, got nil") + } + }) + } +} + +func TestEncodeLiterals(t *testing.T) { + result, err := EncodeLiterals(uint64(100), uint64(50)) + if err != nil { + t.Fatalf("EncodeLiterals failed: %v", err) + } + + if len(result) != 64 { + t.Errorf("EncodeLiterals length = %d, want 64", len(result)) + } + + expectedHex := "00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000032" + got := hex.EncodeToString(result) + if got != expectedHex { + t.Errorf("EncodeLiterals mismatch:\n got: %s\n want: %s", got, expectedHex) + } +} + +func TestMustEncodeLiteral(t *testing.T) { + result := MustEncodeLiteral(uint64(42)) + if len(result) != 32 { + t.Errorf("MustEncodeLiteral length = %d, want 32", len(result)) + } +} + +func TestMustEncodeLiteral_Panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("MustEncodeLiteral should panic on invalid input") + } + }() + MustEncodeLiteral(struct{}{}) +} + +func TestEncodeDynamicBytes(t *testing.T) { + data := []byte{0x01, 0x02, 0x03} + result := EncodeDynamicBytes(data) + + // Should be: 32 bytes length + 32 bytes padded data + if len(result) != 64 { + t.Errorf("EncodeDynamicBytes length = %d, want 64", len(result)) + } + + // Check length prefix + lengthBytes := result[0:32] + expectedLength := make([]byte, 32) + expectedLength[31] = 3 + if hex.EncodeToString(lengthBytes) != hex.EncodeToString(expectedLength) { + t.Errorf("length prefix mismatch: got %x, want %x", lengthBytes, expectedLength) + } + + // Check data + if result[32] != 0x01 || result[33] != 0x02 || result[34] != 0x03 { + t.Error("data bytes mismatch") + } +} + +func TestEncodeSelector(t *testing.T) { + tests := []struct { + name string + signature string + expected string + }{ + { + name: "transfer", + signature: "transfer(address,uint256)", + expected: "a9059cbb", + }, + { + name: "approve", + signature: "approve(address,uint256)", + expected: "095ea7b3", + }, + { + name: "balanceOf", + signature: "balanceOf(address)", + expected: "70a08231", + }, + { + name: "transferFrom", + signature: "transferFrom(address,address,uint256)", + expected: "23b872dd", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + selector := EncodeSelector(tt.signature) + got := hex.EncodeToString(selector[:]) + if got != tt.expected { + t.Errorf("EncodeSelector(%q) = %s, want %s", tt.signature, got, tt.expected) + } + }) + } +} + +func TestSelectorFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + expected [4]byte + }{ + { + name: "exact_4_bytes", + input: []byte{0xa9, 0x05, 0x9c, 0xbb}, + expected: [4]byte{0xa9, 0x05, 0x9c, 0xbb}, + }, + { + name: "more_than_4_bytes", + input: []byte{0xa9, 0x05, 0x9c, 0xbb, 0xde, 0xad}, + expected: [4]byte{0xa9, 0x05, 0x9c, 0xbb}, + }, + { + name: "less_than_4_bytes", + input: []byte{0xa9, 0x05}, + expected: [4]byte{0x00, 0x00, 0x00, 0x00}, + }, + { + name: "empty", + input: []byte{}, + expected: [4]byte{0x00, 0x00, 0x00, 0x00}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SelectorFromBytes(tt.input) + if got != tt.expected { + t.Errorf("SelectorFromBytes(%x) = %x, want %x", tt.input, got, tt.expected) + } + }) + } +} diff --git a/packages/api/go.mod b/packages/api/go.mod new file mode 100644 index 000000000..c16bf4f06 --- /dev/null +++ b/packages/api/go.mod @@ -0,0 +1,12 @@ +module github.com/terminally-online/plug/packages/api + +go 1.24.0 + +require github.com/ethereum/go-ethereum v1.15.11 + +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/sys v0.38.0 // indirect +) diff --git a/packages/api/go.sum b/packages/api/go.sum new file mode 100644 index 000000000..34436f0bf --- /dev/null +++ b/packages/api/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/domDngBU= +github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packages/api/live_plugs.go b/packages/api/live_plugs.go new file mode 100644 index 000000000..fd61ba978 --- /dev/null +++ b/packages/api/live_plugs.go @@ -0,0 +1,58 @@ +package api + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/terminally-online/plug/packages/api/contracts" +) + +var LivePlugsTypeHash = common.HexToHash(contracts.LivePlugsTypeHash) + +// LivePlugs is a signed bundle ready for on-chain execution. +type LivePlugs struct { + Plugs Plugs + Signature []byte +} + +// NewLivePlugs creates a LivePlugs with the given plugs and signature. +func NewLivePlugs(plugs Plugs, signature []byte) LivePlugs { + return LivePlugs{ + Plugs: plugs, + Signature: signature, + } +} + +// Hash computes the EIP-712 struct hash for this LivePlugs. +func (lp LivePlugs) Hash() common.Hash { + data := make([]byte, 32*3) + + copy(data[0:32], LivePlugsTypeHash[:]) + + plugsHash := lp.Plugs.Hash() + copy(data[32:64], plugsHash[:]) + + signatureHash := crypto.Keccak256Hash(lp.Signature) + copy(data[64:96], signatureHash[:]) + + return crypto.Keccak256Hash(data) +} + +// RecoverSigner recovers the signer address from the signature. +func (lp LivePlugs) RecoverSigner(domain Domain) (common.Address, error) { + digest := lp.Plugs.Digest(domain) + + sig := make([]byte, 65) + copy(sig, lp.Signature) + if sig[64] >= 27 { + sig[64] -= 27 + } + + pubKey, err := crypto.SigToPub(digest[:], sig) + if err != nil { + return common.Address{}, fmt.Errorf("failed to recover public key: %w", err) + } + + return crypto.PubkeyToAddress(*pubKey), nil +} diff --git a/packages/api/live_plugs_test.go b/packages/api/live_plugs_test.go new file mode 100644 index 000000000..6161a57aa --- /dev/null +++ b/packages/api/live_plugs_test.go @@ -0,0 +1,73 @@ +package api + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestNewLivePlugs(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + signature := []byte{0x01, 0x02, 0x03} + + livePlugs := NewLivePlugs(*plugs, signature) + + if livePlugs.Plugs.Socket != testSocketAddress { + t.Errorf("Socket = %s, want %s", livePlugs.Plugs.Socket.Hex(), testSocketAddress.Hex()) + } + if len(livePlugs.Signature) != 3 { + t.Errorf("Signature length = %d, want 3", len(livePlugs.Signature)) + } +} + +func TestLivePlugs_Hash(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + signature := make([]byte, 65) + + livePlugs := NewLivePlugs(*plugs, signature) + hash := livePlugs.Hash() + + if hash == (common.Hash{}) { + t.Error("LivePlugs.Hash() should not be zero") + } +} + +func TestLivePlugs_RecoverSigner(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + expectedSigner := crypto.PubkeyToAddress(privateKey.PublicKey) + + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(10))). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug) + + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + livePlugs, err := plugs.SignToLivePlugs(domain, privateKey) + if err != nil { + t.Fatalf("SignToLivePlugs failed: %v", err) + } + + recoveredSigner, err := livePlugs.RecoverSigner(domain) + if err != nil { + t.Fatalf("RecoverSigner failed: %v", err) + } + + if recoveredSigner != expectedSigner { + t.Errorf("Recovered signer = %s, want %s", recoveredSigner.Hex(), expectedSigner.Hex()) + } +} + +func TestLivePlugsTypeHash(t *testing.T) { + expected := common.HexToHash("0xeb79b94df94230950addab3b4e85da31ca18ec7ce505cc225a455e8e31773a1d") + if LivePlugsTypeHash != expected { + t.Errorf("LivePlugsTypeHash mismatch:\n got: %s\n expected: %s", LivePlugsTypeHash.Hex(), expected.Hex()) + } +} diff --git a/packages/api/plug.go b/packages/api/plug.go new file mode 100644 index 000000000..4c97aeedc --- /dev/null +++ b/packages/api/plug.go @@ -0,0 +1,398 @@ +package api + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/terminally-online/plug/packages/api/contracts" +) + +var PlugTypeHash = common.HexToHash(contracts.PlugTypeHash) + +// Envelope field positions in the indices array +const ( + EnvTo = 0 // Target address + EnvSelector = 1 // Function selector (bytes4) + EnvMethod = 2 // Call method (CALL, STATIC_CALL, etc.) + EnvIndex = 3 // Output state slot index + EnvDynamic = 4 // Whether output is dynamic length + EnvValue = 5 // ETH value to send + EnvParamStart = 6 // Start of calldata param indices +) + +// ParamType indicates the source of a function parameter. +type ParamType int + +const ( + ParamStatic ParamType = iota // 32-byte value from state slot + ParamDynamic // Variable-length value from state slot + ParamLiteral // 32-byte literal value +) + +// ParamSource describes where a single function argument comes from. +type ParamSource struct { + Type ParamType + Slot uint8 + Literal []byte +} + +// EnvSource tracks the source of an envelope field (literal vs state). +type EnvSource struct { + FromState bool + StateSlot uint8 +} + +// Plug represents a single command in a Coil execution sequence. +// The indices array encodes both envelope fields (positions 0-5) and calldata +// params (positions 6+). Each index byte specifies where to get the value: +// - 0x00-0x7F: Static state reference (32 bytes from state[idx]) +// - 0x80-0xBF: Dynamic state reference (variable length from state[idx & 0x3F]) +// - 0xC0-0xFD: Literal reference (32 bytes from literals[(idx - 0xC0) * 32]) +// - 0xFF: End of args (terminates calldata params section) +type Plug struct { + Indices []byte + Literals []byte + + // Builder fields for envelope (not part of the on-chain struct) + to common.Address + toSource EnvSource + + selector [4]byte + selectorSource EnvSource + + method uint8 + methodSource EnvSource + + index uint8 + indexSource EnvSource + + dynamic bool + dynamicSource EnvSource + + value *big.Int + valueSource EnvSource + + params []ParamSource +} + +// NewPlug creates a Plug with the required fields and sensible defaults. +func NewPlug(to common.Address, selector [4]byte) *Plug { + return &Plug{ + to: to, + selector: selector, + method: CALL, + index: IDX_END_OF_ARGS, + dynamic: false, + value: big.NewInt(0), + Indices: []byte{}, + Literals: []byte{}, + params: []ParamSource{}, + } +} + +// NewJumpPlug creates an unconditional jump plug that jumps to the specified destination. +// The destination is a plug index in the execution sequence. +func NewJumpPlug(dest uint8) *Plug { + p := &Plug{ + to: common.Address{}, + selector: [4]byte{}, + method: JUMP, + index: IDX_END_OF_ARGS, + dynamic: false, + value: big.NewInt(0), + Indices: []byte{}, + Literals: []byte{}, + params: []ParamSource{}, + } + + destBytes := make([]byte, 32) + destBytes[31] = dest + p.params = append(p.params, ParamSource{ + Type: ParamLiteral, + Literal: destBytes, + }) + + return p +} + +// NewJumpIfPlug creates a conditional jump plug. If the condition (from conditionSlot) +// is true (non-zero), execution jumps to trueDest. Otherwise, execution jumps to falseDest. +// The condition must be a state slot containing a boolean result from a previous plug. +func NewJumpIfPlug(conditionSlot uint8, trueDest uint8, falseDest uint8) *Plug { + p := &Plug{ + to: common.Address{}, + selector: [4]byte{}, + method: JUMP_IF, + index: IDX_END_OF_ARGS, + dynamic: false, + value: big.NewInt(0), + Indices: []byte{}, + Literals: []byte{}, + params: []ParamSource{}, + } + + p.params = append(p.params, ParamSource{ + Type: ParamStatic, + Slot: conditionSlot, + }) + + trueDestBytes := make([]byte, 32) + trueDestBytes[31] = trueDest + p.params = append(p.params, ParamSource{ + Type: ParamLiteral, + Literal: trueDestBytes, + }) + + falseDestBytes := make([]byte, 32) + falseDestBytes[31] = falseDest + p.params = append(p.params, ParamSource{ + Type: ParamLiteral, + Literal: falseDestBytes, + }) + + return p +} + +// WithMethod sets the call method. +func (p *Plug) WithMethod(method uint8) *Plug { + p.method = method + p.methodSource = EnvSource{FromState: false} + return p +} + +// WithMethodFromState sets the call method to be read from a state slot. +func (p *Plug) WithMethodFromState(slot uint8) *Plug { + p.methodSource = EnvSource{FromState: true, StateSlot: slot} + return p +} + +// WithOutput specifies that this plug should write its return value to a state slot. +func (p *Plug) WithOutput(index uint8, dynamic bool) *Plug { + p.index = index + p.dynamic = dynamic + return p +} + +// WithOutputFromState specifies output index and dynamic flag from state slots. +func (p *Plug) WithOutputFromState(indexSlot uint8, dynamicSlot uint8) *Plug { + p.indexSource = EnvSource{FromState: true, StateSlot: indexSlot} + p.dynamicSource = EnvSource{FromState: true, StateSlot: dynamicSlot} + return p +} + +// WithValue sets the ETH value to send (automatically sets method to VALUE_CALL). +func (p *Plug) WithValue(value *big.Int) *Plug { + p.value = value + p.valueSource = EnvSource{FromState: false} + if p.method == CALL && !p.methodSource.FromState { + p.method = VALUE_CALL + } + return p +} + +// WithTarget sets the target address for this plug (overrides constructor value). +func (p *Plug) WithTarget(to common.Address) *Plug { + p.to = to + p.toSource = EnvSource{FromState: false} + return p +} + +// WithTargetFromState sets the target address to be read from a state slot. +func (p *Plug) WithTargetFromState(slot uint8) *Plug { + p.toSource = EnvSource{FromState: true, StateSlot: slot} + return p +} + +// WithSelector sets the function selector (overrides constructor value). +func (p *Plug) WithSelector(selector [4]byte) *Plug { + p.selector = selector + p.selectorSource = EnvSource{FromState: false} + return p +} + +// WithSelectorFromState sets the function selector to be read from a state slot. +func (p *Plug) WithSelectorFromState(slot uint8) *Plug { + p.selectorSource = EnvSource{FromState: true, StateSlot: slot} + return p +} + +// WithValueFromState sets the ETH value to be read from a state slot. +func (p *Plug) WithValueFromState(slot uint8) *Plug { + p.valueSource = EnvSource{FromState: true, StateSlot: slot} + if p.method == CALL && !p.methodSource.FromState { + p.method = VALUE_CALL + } + return p +} + +// AddStaticState adds a parameter that reads a 32-byte value from state[slot]. +func (p *Plug) AddStaticState(slot uint8) *Plug { + p.params = append(p.params, ParamSource{ + Type: ParamStatic, + Slot: slot, + }) + return p +} + +// AddDynamicState adds a parameter that reads variable-length data from state[slot]. +func (p *Plug) AddDynamicState(slot uint8) *Plug { + p.params = append(p.params, ParamSource{ + Type: ParamDynamic, + Slot: slot, + }) + return p +} + +// AddLiteral adds a parameter with a known 32-byte value. +func (p *Plug) AddLiteral(value []byte) *Plug { + padded := make([]byte, 32) + if len(value) <= 32 { + copy(padded[32-len(value):], value) + } else { + copy(padded, value[:32]) + } + p.params = append(p.params, ParamSource{ + Type: ParamLiteral, + Literal: padded, + }) + return p +} + +// AddParam adds a pre-built ParamSource. +func (p *Plug) AddParam(param ParamSource) *Plug { + p.params = append(p.params, param) + return p +} + +// Build computes the final Indices and Literals from the accumulated params. +// The indices array layout is: +// +// [0] = to (address) +// [1] = selector (bytes4) +// [2] = method (uint8) +// [3] = index (uint8) +// [4] = dynamic (bool) +// [5] = value (uint256) +// [6+] = calldata params (terminated by 0xFF) +func (p *Plug) Build() *Plug { + literals := make([]byte, 0) + litSlot := uint8(0) + indices := make([]byte, 6) + + // [0] to - can be literal or state reference + if p.toSource.FromState { + indices[EnvTo] = StaticStateIndex(p.toSource.StateSlot) + } else { + toBytes := make([]byte, 32) + copy(toBytes[12:], p.to.Bytes()) + literals = append(literals, toBytes...) + indices[EnvTo] = LiteralIndex(litSlot) + litSlot++ + } + + // [1] selector - can be literal or state reference + if p.selectorSource.FromState { + indices[EnvSelector] = StaticStateIndex(p.selectorSource.StateSlot) + } else { + selectorBytes := make([]byte, 32) + copy(selectorBytes[:4], p.selector[:]) + literals = append(literals, selectorBytes...) + indices[EnvSelector] = LiteralIndex(litSlot) + litSlot++ + } + + // [2] method - can be literal or state reference + if p.methodSource.FromState { + indices[EnvMethod] = StaticStateIndex(p.methodSource.StateSlot) + } else { + methodBytes := make([]byte, 32) + methodBytes[31] = p.method + literals = append(literals, methodBytes...) + indices[EnvMethod] = LiteralIndex(litSlot) + litSlot++ + } + + // [3] index - can be literal or state reference + if p.indexSource.FromState { + indices[EnvIndex] = StaticStateIndex(p.indexSource.StateSlot) + } else { + indexBytes := make([]byte, 32) + indexBytes[31] = p.index + literals = append(literals, indexBytes...) + indices[EnvIndex] = LiteralIndex(litSlot) + litSlot++ + } + + // [4] dynamic - can be literal or state reference + if p.dynamicSource.FromState { + indices[EnvDynamic] = StaticStateIndex(p.dynamicSource.StateSlot) + } else { + dynamicBytes := make([]byte, 32) + if p.dynamic { + dynamicBytes[31] = 1 + } + literals = append(literals, dynamicBytes...) + indices[EnvDynamic] = LiteralIndex(litSlot) + litSlot++ + } + + // [5] value - can be literal or state reference + if p.valueSource.FromState { + indices[EnvValue] = StaticStateIndex(p.valueSource.StateSlot) + } else { + valueBytes := make([]byte, 32) + if p.value != nil && p.value.Sign() > 0 { + vBytes := p.value.Bytes() + copy(valueBytes[32-len(vBytes):], vBytes) + } + literals = append(literals, valueBytes...) + indices[EnvValue] = LiteralIndex(litSlot) + litSlot++ + } + + // Add calldata params + for _, param := range p.params { + switch param.Type { + case ParamStatic: + indices = append(indices, StaticStateIndex(param.Slot)) + case ParamDynamic: + indices = append(indices, DynamicStateIndex(param.Slot)) + case ParamLiteral: + indices = append(indices, LiteralIndex(litSlot)) + literals = append(literals, param.Literal...) + litSlot++ + } + } + indices = append(indices, IDX_END_OF_ARGS) + + p.Indices = indices + p.Literals = literals + return p +} + +// GetMethod returns the call method for this plug. +func (p *Plug) GetMethod() uint8 { + return p.method +} + +// IsStaticCall returns true if this plug is a read-only static call. +func (p *Plug) IsStaticCall() bool { + return p.method == STATIC_CALL +} + +// Hash computes the EIP-712 struct hash for this Plug. +// The hash is computed as: keccak256(PLUG_TYPEHASH, keccak256(indices), keccak256(literals)) +func (p Plug) Hash() common.Hash { + data := make([]byte, 32*3) + + copy(data[0:32], PlugTypeHash[:]) + + indicesHash := crypto.Keccak256Hash(p.Indices) + copy(data[32:64], indicesHash[:]) + + literalsHash := crypto.Keccak256Hash(p.Literals) + copy(data[64:96], literalsHash[:]) + + return crypto.Keccak256Hash(data) +} diff --git a/packages/api/plug_test.go b/packages/api/plug_test.go new file mode 100644 index 000000000..90ee37479 --- /dev/null +++ b/packages/api/plug_test.go @@ -0,0 +1,354 @@ +package api + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + testEchoContract = common.HexToAddress("0x1234567890123456789012345678901234567890") + testEchoSelector = [4]byte{0x34, 0xd9, 0x70, 0x14} // echo(bytes32) + testEchoBSelector = [4]byte{0x78, 0x4e, 0xf3, 0xb0} // echoBytes(bytes) +) + +func TestNewPlug(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector) + + if plug.to != testEchoContract { + t.Errorf("to = %s, want %s", plug.to.Hex(), testEchoContract.Hex()) + } + if plug.selector != testEchoSelector { + t.Errorf("selector = %x, want %x", plug.selector, testEchoSelector) + } + if plug.method != CALL { + t.Errorf("method = %d, want %d", plug.method, CALL) + } +} + +func TestPlug_AllLiterals(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + WithOutput(0, false). + Build() + + if plug.index != 0 { + t.Errorf("index = %d, want 0", plug.index) + } + if plug.dynamic { + t.Error("dynamic should be false") + } + + expectedIndicesLen := 6 + 2 + 1 // 6 envelope + 2 params + end marker + if len(plug.Indices) != expectedIndicesLen { + t.Errorf("Indices length = %d, want %d", len(plug.Indices), expectedIndicesLen) + } + + for i := 0; i < 6; i++ { + if plug.Indices[i] != LiteralIndex(uint8(i)) { + t.Errorf("Indices[%d] = 0x%02x, want 0x%02x", i, plug.Indices[i], LiteralIndex(uint8(i))) + } + } + + if plug.Indices[6] != LiteralIndex(6) { + t.Errorf("Indices[6] = 0x%02x, want 0x%02x", plug.Indices[6], LiteralIndex(6)) + } + if plug.Indices[7] != LiteralIndex(7) { + t.Errorf("Indices[7] = 0x%02x, want 0x%02x", plug.Indices[7], LiteralIndex(7)) + } + if plug.Indices[8] != IDX_END_OF_ARGS { + t.Errorf("Indices[8] = 0x%02x, want 0x%02x", plug.Indices[8], IDX_END_OF_ARGS) + } + + expectedLiteralsLen := 6*32 + 2*32 // 6 envelope fields + 2 params + if len(plug.Literals) != expectedLiteralsLen { + t.Errorf("Literals length = %d, want %d", len(plug.Literals), expectedLiteralsLen) + } +} + +func TestPlug_StateReference(t *testing.T) { + multiplySelector := [4]byte{0x16, 0x5c, 0x4a, 0x16} + + plug := NewPlug(testEchoContract, multiplySelector). + AddStaticState(0). + AddLiteral(MustEncodeLiteral(uint64(2))). + WithOutput(1, false). + Build() + + expectedIndicesLen := 6 + 2 + 1 // 6 envelope + 2 params + end marker + if len(plug.Indices) != expectedIndicesLen { + t.Errorf("Indices length = %d, want %d", len(plug.Indices), expectedIndicesLen) + } + + if plug.Indices[6] != StaticStateIndex(0) { + t.Errorf("Indices[6] = 0x%02x, want 0x%02x", plug.Indices[6], StaticStateIndex(0)) + } + if plug.Indices[7] != LiteralIndex(6) { + t.Errorf("Indices[7] = 0x%02x, want 0x%02x", plug.Indices[7], LiteralIndex(6)) + } + + expectedLiteralsLen := 6*32 + 1*32 // 6 envelope fields + 1 literal param + if len(plug.Literals) != expectedLiteralsLen { + t.Errorf("Literals length = %d, want %d", len(plug.Literals), expectedLiteralsLen) + } +} + +func TestPlug_DynamicState(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddDynamicState(0). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + if plug.Indices[6] != DynamicStateIndex(0) { + t.Errorf("Indices[6] = 0x%02x, want 0x%02x", plug.Indices[6], DynamicStateIndex(0)) + } +} + +func TestPlug_ValueCall(t *testing.T) { + value := big.NewInt(1e18) + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + WithValue(value). + Build() + + if plug.method != VALUE_CALL { + t.Errorf("method = %d, want %d (VALUE_CALL)", plug.method, VALUE_CALL) + } + if plug.value.Cmp(value) != 0 { + t.Errorf("value = %s, want %s", plug.value.String(), value.String()) + } + + valueSlot := plug.Literals[5*32 : 6*32] + expectedValue := make([]byte, 32) + vBytes := value.Bytes() + copy(expectedValue[32-len(vBytes):], vBytes) + for i := range valueSlot { + if valueSlot[i] != expectedValue[i] { + t.Errorf("value literal mismatch at byte %d: got 0x%02x, want 0x%02x", i, valueSlot[i], expectedValue[i]) + break + } + } +} + +func TestPlug_Methods(t *testing.T) { + tests := []struct { + name string + method uint8 + expected uint8 + }{ + {"CALL", CALL, CALL}, + {"DELEGATE_CALL", DELEGATE_CALL, DELEGATE_CALL}, + {"STATIC_CALL", STATIC_CALL, STATIC_CALL}, + {"VALUE_CALL", VALUE_CALL, VALUE_CALL}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + WithMethod(tt.method). + Build() + + if plug.method != tt.expected { + t.Errorf("method = %d, want %d", plug.method, tt.expected) + } + + methodSlot := plug.Literals[2*32 : 3*32] + if methodSlot[31] != tt.expected { + t.Errorf("method literal = %d, want %d", methodSlot[31], tt.expected) + } + }) + } +} + +func TestPlug_AddParam(t *testing.T) { + param := ParamSource{ + Type: ParamLiteral, + Literal: MustEncodeLiteral(uint64(42)), + } + + plug := NewPlug(testEchoContract, testEchoSelector). + AddParam(param). + Build() + + expectedIndicesLen := 6 + 1 + 1 // 6 envelope + 1 param + end marker + if len(plug.Indices) != expectedIndicesLen { + t.Errorf("Indices length = %d, want %d", len(plug.Indices), expectedIndicesLen) + } + if plug.Indices[6] != LiteralIndex(6) { + t.Errorf("Indices[6] = 0x%02x, want 0x%02x", plug.Indices[6], LiteralIndex(6)) + } +} + +func TestPlug_Hash(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + WithOutput(0, false). + Build() + + hash1 := plug.Hash() + hash2 := plug.Hash() + + if hash1 != hash2 { + t.Errorf("Hash is not deterministic: %s != %s", hash1.Hex(), hash2.Hex()) + } + + if hash1 == (common.Hash{}) { + t.Error("Hash should not be zero") + } +} + +func TestPlug_Hash_Deterministic(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + Build() + + hash1 := plug.Hash() + hash2 := plug.Hash() + + if hash1 != hash2 { + t.Errorf("Hash is not deterministic: %s != %s", hash1.Hex(), hash2.Hex()) + } +} + +func TestPlugTypeHash(t *testing.T) { + expected := common.HexToHash("0x4a5c402b142b42f7a370c0499cf5ed6a0a39a73d479eb129087b5b45e608ccc6") + if PlugTypeHash != expected { + t.Errorf("PlugTypeHash mismatch:\n got: %s\n expected: %s", PlugTypeHash.Hex(), expected.Hex()) + } +} + +func TestPlug_EnvelopeFields(t *testing.T) { + to := common.HexToAddress("0xDEADBEEF") + selector := [4]byte{0x12, 0x34, 0x56, 0x78} + value := big.NewInt(1e18) + + plug := NewPlug(to, selector). + WithOutput(5, true). + WithValue(value). + Build() + + toSlot := plug.Literals[0:32] + expectedTo := make([]byte, 32) + copy(expectedTo[12:], to.Bytes()) + for i := range toSlot { + if toSlot[i] != expectedTo[i] { + t.Errorf("to literal mismatch at byte %d", i) + break + } + } + + selectorSlot := plug.Literals[32:64] + if selectorSlot[0] != selector[0] || selectorSlot[1] != selector[1] || + selectorSlot[2] != selector[2] || selectorSlot[3] != selector[3] { + t.Errorf("selector mismatch: got %x, want %x", selectorSlot[:4], selector) + } + + methodSlot := plug.Literals[2*32 : 3*32] + if methodSlot[31] != VALUE_CALL { + t.Errorf("method should be VALUE_CALL (%d), got %d", VALUE_CALL, methodSlot[31]) + } + + indexSlot := plug.Literals[3*32 : 4*32] + if indexSlot[31] != 5 { + t.Errorf("index should be 5, got %d", indexSlot[31]) + } + + dynamicSlot := plug.Literals[4*32 : 5*32] + if dynamicSlot[31] != 1 { + t.Errorf("dynamic should be 1 (true), got %d", dynamicSlot[31]) + } +} + +func TestPlug_Jump(t *testing.T) { + plug := NewPlug(common.Address{}, [4]byte{}). + WithMethod(JUMP). + AddLiteral(MustEncodeLiteral(uint8(5))). + Build() + + if plug.method != JUMP { + t.Errorf("method = %d, want %d (JUMP)", plug.method, JUMP) + } + + expectedIndicesLen := 6 + 1 + 1 // 6 envelope + 1 destination param + end marker + if len(plug.Indices) != expectedIndicesLen { + t.Errorf("Indices length = %d, want %d", len(plug.Indices), expectedIndicesLen) + } + + methodSlot := plug.Literals[2*32 : 3*32] + if methodSlot[31] != JUMP { + t.Errorf("method literal = %d, want %d (JUMP)", methodSlot[31], JUMP) + } +} + +func TestPlug_JumpFromState(t *testing.T) { + plug := NewPlug(common.Address{}, [4]byte{}). + WithMethod(JUMP). + AddStaticState(2). + Build() + + if plug.method != JUMP { + t.Errorf("method = %d, want %d (JUMP)", plug.method, JUMP) + } + + if plug.Indices[6] != StaticStateIndex(2) { + t.Errorf("Indices[6] = 0x%02x, want 0x%02x (state slot 2)", plug.Indices[6], StaticStateIndex(2)) + } +} + +func TestPlug_JumpIf(t *testing.T) { + plug := NewPlug(common.Address{}, [4]byte{}). + WithMethod(JUMP_IF). + AddStaticState(0). + AddLiteral(MustEncodeLiteral(uint8(3))). + AddLiteral(MustEncodeLiteral(uint8(5))). + Build() + + if plug.method != JUMP_IF { + t.Errorf("method = %d, want %d (JUMP_IF)", plug.method, JUMP_IF) + } + + expectedIndicesLen := 6 + 3 + 1 // 6 envelope + 3 params (condition, trueDest, falseDest) + end marker + if len(plug.Indices) != expectedIndicesLen { + t.Errorf("Indices length = %d, want %d", len(plug.Indices), expectedIndicesLen) + } + + if plug.Indices[6] != StaticStateIndex(0) { + t.Errorf("Indices[6] (condition) = 0x%02x, want 0x%02x", plug.Indices[6], StaticStateIndex(0)) + } + + methodSlot := plug.Literals[2*32 : 3*32] + if methodSlot[31] != JUMP_IF { + t.Errorf("method literal = %d, want %d (JUMP_IF)", methodSlot[31], JUMP_IF) + } +} + +func TestPlug_Jump_Hash(t *testing.T) { + plug := NewPlug(common.Address{}, [4]byte{}). + WithMethod(JUMP). + AddLiteral(MustEncodeLiteral(uint8(5))). + Build() + + hash1 := plug.Hash() + hash2 := plug.Hash() + + if hash1 != hash2 { + t.Errorf("Hash is not deterministic: %s != %s", hash1.Hex(), hash2.Hex()) + } + if hash1 == (common.Hash{}) { + t.Error("Hash should not be zero") + } +} + +func BenchmarkPlug_Build(b *testing.B) { + for i := 0; i < b.N; i++ { + NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + WithOutput(0, false). + Build() + } +} diff --git a/packages/api/plugs.go b/packages/api/plugs.go new file mode 100644 index 000000000..a1cc78cb0 --- /dev/null +++ b/packages/api/plugs.go @@ -0,0 +1,460 @@ +package api + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/terminally-online/plug/packages/api/contracts" +) + +var PlugsTypeHash = common.HexToHash(contracts.PlugsTypeHash) + +// StackFrame tracks an unclosed control flow structure (if, else, or loop). +type StackFrame struct { + Type string + PatchPlug int + PatchLit int + Start int + Breaks []int +} + +// Plugs represents a bundle of commands to execute in sequence. +type Plugs struct { + Socket common.Address + Plugs []Plug + State [][]byte + Mask *big.Int + Salt []byte + + stack []StackFrame +} + +// NewPlugs creates a Plugs bundle with the required fields and sensible defaults. +func NewPlugs(socket common.Address) *Plugs { + return &Plugs{ + Socket: socket, + Plugs: []Plug{}, + State: [][]byte{}, + Mask: big.NewInt(0), + Salt: []byte{}, + } +} + +// AddPlug adds a plug to the bundle. +func (p *Plugs) AddPlug(plug Plug) *Plugs { + p.Plugs = append(p.Plugs, plug) + return p +} + +// WithState sets the initial state array. +func (p *Plugs) WithState(state [][]byte) *Plugs { + p.State = state + return p +} + +// WithStateSlots initializes state with the given number of empty slots. +func (p *Plugs) WithStateSlots(count int) *Plugs { + p.State = make([][]byte, count) + return p +} + +// SetStateSlot sets a specific state slot value. +func (p *Plugs) SetStateSlot(index int, value []byte) *Plugs { + if index >= len(p.State) { + newState := make([][]byte, index+1) + copy(newState, p.State) + p.State = newState + } + p.State[index] = value + return p +} + +// NextStateSlot returns the next available state slot index based on the maximum +// output index used by existing plugs. This allows actions to allocate temporary +// state slots without manual tracking. +func (p *Plugs) NextStateSlot() uint8 { + maxSlot := len(p.State) + for _, plug := range p.Plugs { + if plug.index != IDX_END_OF_ARGS && int(plug.index) >= maxSlot { + maxSlot = int(plug.index) + 1 + } + } + return uint8(maxSlot) +} + +// WithMask sets the solver slot mask. Bits that are set indicate state slots +// that can be filled by a solver at execution time. +func (p *Plugs) WithMask(mask *big.Int) *Plugs { + p.Mask = mask + return p +} + +// SetMaskBit sets a specific bit in the mask, marking that state slot as solver-fillable. +func (p *Plugs) SetMaskBit(index uint) *Plugs { + if p.Mask == nil { + p.Mask = big.NewInt(0) + } + p.Mask.SetBit(p.Mask, int(index), 1) + return p +} + +// WithSalt sets the replay protection salt. +func (p *Plugs) WithSalt(salt []byte) *Plugs { + p.Salt = salt + return p +} + +// AddIf adds a conditional branch. If the boolean at conditionSlot is true, execution +// continues to the next plug. If false, execution jumps to the matching else or end. +// The condition must be a state slot containing a boolean result from a previous plug. +func (p *Plugs) AddIf(conditionSlot uint8) *Plugs { + currentIdx := len(p.Plugs) + trueDest := uint8(currentIdx + 1) + + plug := NewJumpIfPlug(conditionSlot, trueDest, 0) + plug.Build() + + p.stack = append(p.stack, StackFrame{ + Type: "if", + PatchPlug: currentIdx, + PatchLit: 7, + }) + + p.Plugs = append(p.Plugs, *plug) + return p +} + +// AddElse closes the if-body and starts the else-body. When the if-condition was true +// and the if-body completes, execution jumps past the else-body. When the if-condition +// was false, execution continues into the else-body. +func (p *Plugs) AddElse() error { + if len(p.stack) == 0 { + return fmt.Errorf("else without matching if") + } + + block := p.stack[len(p.stack)-1] + if block.Type != "if" { + return fmt.Errorf("else must follow if, found %s", block.Type) + } + + p.stack = p.stack[:len(p.stack)-1] + + currentIdx := len(p.Plugs) + patchPlugLiteral(&p.Plugs[block.PatchPlug], block.PatchLit, uint8(currentIdx+1)) + + jumpPlug := NewJumpPlug(0) + jumpPlug.Build() + + p.stack = append(p.stack, StackFrame{ + Type: "else", + PatchPlug: currentIdx, + PatchLit: 6, + }) + + p.Plugs = append(p.Plugs, *jumpPlug) + return nil +} + +// AddEndIf closes the current if or else block. +func (p *Plugs) AddEndIf() error { + if len(p.stack) == 0 { + return fmt.Errorf("end without matching if or else") + } + + block := p.stack[len(p.stack)-1] + if block.Type != "if" && block.Type != "else" { + return fmt.Errorf("end must close if or else, found %s", block.Type) + } + + p.stack = p.stack[:len(p.stack)-1] + + currentIdx := len(p.Plugs) + patchPlugLiteral(&p.Plugs[block.PatchPlug], block.PatchLit, uint8(currentIdx)) + + return nil +} + +// AddLoop marks the current position as the start of a loop. This is where continue +// and the end-of-iteration jump will return to. Use AddLoopCondition to add an exit +// condition, or create an infinite loop that exits only via break. +func (p *Plugs) AddLoop() *Plugs { + p.stack = append(p.stack, StackFrame{ + Type: "loop", + PatchPlug: -1, + Start: len(p.Plugs), + Breaks: []int{}, + }) + return p +} + +// AddLoopCondition adds a conditional exit to the current loop. If the condition +// (from conditionSlot) is false, execution jumps past the loop. If true, execution +// continues into the loop body. Only one condition per loop is supported. +func (p *Plugs) AddLoopCondition(conditionSlot uint8) error { + loopIdx := p.findInnermostLoop() + if loopIdx < 0 { + return fmt.Errorf("loop condition without matching loop") + } + + loop := &p.stack[loopIdx] + if loop.PatchPlug != -1 { + return fmt.Errorf("loop already has a condition") + } + + currentIdx := len(p.Plugs) + trueDest := uint8(currentIdx + 1) + + plug := NewJumpIfPlug(conditionSlot, trueDest, 0) + plug.Build() + + loop.PatchPlug = currentIdx + loop.PatchLit = 7 + + p.Plugs = append(p.Plugs, *plug) + return nil +} + +// AddContinue jumps back to the start of the innermost loop, skipping the rest +// of the current iteration's body. +func (p *Plugs) AddContinue() error { + loopIdx := p.findInnermostLoop() + if loopIdx < 0 { + return fmt.Errorf("continue outside of loop") + } + + loop := p.stack[loopIdx] + + plug := NewJumpPlug(uint8(loop.Start)) + plug.Build() + + p.Plugs = append(p.Plugs, *plug) + return nil +} + +// AddBreak exits the innermost loop. The jump destination is patched when +// AddEndLoop is called. +func (p *Plugs) AddBreak() error { + loopIdx := p.findInnermostLoop() + if loopIdx < 0 { + return fmt.Errorf("break outside of loop") + } + + loop := &p.stack[loopIdx] + currentIdx := len(p.Plugs) + + plug := NewJumpPlug(0) + plug.Build() + + loop.Breaks = append(loop.Breaks, currentIdx) + + p.Plugs = append(p.Plugs, *plug) + return nil +} + +// AddEnd closes the topmost control flow block (if, else, or loop). +// This provides a unified way to end any block without needing to track the type. +func (p *Plugs) AddEnd() error { + if len(p.stack) == 0 { + return fmt.Errorf("no open block to close") + } + + block := p.stack[len(p.stack)-1] + switch block.Type { + case "if", "else": + return p.AddEndIf() + case "loop": + return p.AddEndLoop() + default: + return fmt.Errorf("unknown block type: %s", block.Type) + } +} + +// AddEndLoop closes the current loop. It adds an unconditional jump back to the +// loop start and patches all exit destinations (condition false path, break statements). +func (p *Plugs) AddEndLoop() error { + loopIdx := p.findInnermostLoop() + if loopIdx < 0 { + return fmt.Errorf("end loop without matching loop start") + } + + loop := p.stack[loopIdx] + p.stack = append(p.stack[:loopIdx], p.stack[loopIdx+1:]...) + + plug := NewJumpPlug(uint8(loop.Start)) + plug.Build() + p.Plugs = append(p.Plugs, *plug) + + exitIndex := uint8(len(p.Plugs)) + + if loop.PatchPlug >= 0 { + patchPlugLiteral(&p.Plugs[loop.PatchPlug], loop.PatchLit, exitIndex) + } + + for _, breakIdx := range loop.Breaks { + patchPlugLiteral(&p.Plugs[breakIdx], 6, exitIndex) + } + + return nil +} + +// LoopCount returns the number of open loops. +func (p *Plugs) LoopCount() int { + count := 0 + for _, block := range p.stack { + if block.Type == "loop" { + count++ + } + } + return count +} + +// ValidateControlFlow returns an error if there are unclosed control flow blocks or loops. +func (p *Plugs) ValidateControlFlow() error { + if len(p.stack) > 0 { + types := make([]string, len(p.stack)) + for i, b := range p.stack { + types[i] = b.Type + } + return fmt.Errorf("unclosed control flow blocks: %v", types) + } + return nil +} + +// OpenBlockCount returns the number of unclosed control flow blocks (if/else only). +func (p *Plugs) OpenBlockCount() int { + count := 0 + for _, block := range p.stack { + if block.Type == "if" || block.Type == "else" { + count++ + } + } + return count +} + +// findInnermostLoop returns the index of the innermost loop in the control stack, +// or -1 if there is no open loop. +func (p *Plugs) findInnermostLoop() int { + for i := len(p.stack) - 1; i >= 0; i-- { + if p.stack[i].Type == "loop" { + return i + } + } + return -1 +} + +// patchPlugLiteral updates a specific literal slot in a built plug's Literals array. +func patchPlugLiteral(plug *Plug, literalIdx int, value uint8) { + offset := literalIdx*32 + 31 + if offset < len(plug.Literals) { + plug.Literals[offset] = value + } +} + +// Hash computes the EIP-712 struct hash for this Plugs. +func (p Plugs) Hash() common.Hash { + data := make([]byte, 32*6) + + copy(data[0:32], PlugsTypeHash[:]) + copy(data[44:64], p.Socket[:]) + + plugsArrayHash := hashPlugArray(p.Plugs) + copy(data[64:96], plugsArrayHash[:]) + + stateArrayHash := hashBytesArray(p.State, p.Mask) + copy(data[96:128], stateArrayHash[:]) + + maskBytes := make([]byte, 32) + if p.Mask != nil { + p.Mask.FillBytes(maskBytes) + } + copy(data[128:160], maskBytes) + + saltHash := crypto.Keccak256Hash(p.Salt) + copy(data[160:192], saltHash[:]) + + return crypto.Keccak256Hash(data) +} + +// Digest computes the EIP-712 digest for signing this Plugs. +func (p Plugs) Digest(domain Domain) common.Hash { + domainHash := domain.Hash() + structHash := p.Hash() + + data := make([]byte, 2+32+32) + data[0] = 0x19 + data[1] = 0x01 + copy(data[2:34], domainHash[:]) + copy(data[34:66], structHash[:]) + + return crypto.Keccak256Hash(data) +} + +// Sign signs this Plugs with the given private key and returns the signature. +func (p Plugs) Sign(domain Domain, privateKey *ecdsa.PrivateKey) ([]byte, error) { + digest := p.Digest(domain) + + signature, err := crypto.Sign(digest[:], privateKey) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } + + if signature[64] < 27 { + signature[64] += 27 + } + + return signature, nil +} + +// SignToLivePlugs signs this Plugs and returns a LivePlugs. +func (p Plugs) SignToLivePlugs(domain Domain, privateKey *ecdsa.PrivateKey) (LivePlugs, error) { + signature, err := p.Sign(domain, privateKey) + if err != nil { + return LivePlugs{}, err + } + + return LivePlugs{ + Plugs: p, + Signature: signature, + }, nil +} + +// hashPlugArray hashes an array of plugs. +func hashPlugArray(plugs []Plug) common.Hash { + if len(plugs) == 0 { + return crypto.Keccak256Hash(nil) + } + + data := make([]byte, len(plugs)*32) + for i, plug := range plugs { + hash := plug.Hash() + copy(data[i*32:(i+1)*32], hash[:]) + } + + return crypto.Keccak256Hash(data) +} + +// hashBytesArray hashes a bytes array, excluding slots marked in the mask. +// Masked slots (where bit i is set) are replaced with the hash of empty bytes. +func hashBytesArray(arr [][]byte, mask *big.Int) common.Hash { + if len(arr) == 0 { + return crypto.Keccak256Hash(nil) + } + + emptyHash := crypto.Keccak256Hash([]byte{}) + data := make([]byte, len(arr)*32) + + for i, b := range arr { + var hash common.Hash + if mask != nil && mask.Bit(i) == 1 { + hash = emptyHash + } else { + hash = crypto.Keccak256Hash(b) + } + copy(data[i*32:(i+1)*32], hash[:]) + } + + return crypto.Keccak256Hash(data) +} diff --git a/packages/api/plugs_test.go b/packages/api/plugs_test.go new file mode 100644 index 000000000..c43c9a410 --- /dev/null +++ b/packages/api/plugs_test.go @@ -0,0 +1,868 @@ +package api + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var testSocketAddress = common.HexToAddress("0xSocketSocketSocketSocketSocketSocket00") + +func TestNewPlugs(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + if plugs.Socket != testSocketAddress { + t.Errorf("Socket = %s, want %s", plugs.Socket.Hex(), testSocketAddress.Hex()) + } + if len(plugs.Plugs) != 0 { + t.Errorf("Plugs length = %d, want 0", len(plugs.Plugs)) + } + if len(plugs.State) != 0 { + t.Errorf("State length = %d, want 0", len(plugs.State)) + } + if plugs.Mask.Cmp(big.NewInt(0)) != 0 { + t.Errorf("Mask = %s, want 0", plugs.Mask.String()) + } + if len(plugs.Salt) != 0 { + t.Errorf("Salt length = %d, want 0", len(plugs.Salt)) + } +} + +func TestPlugs_AddPlug(t *testing.T) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug) + + if len(plugs.Plugs) != 1 { + t.Errorf("Plugs length = %d, want 1", len(plugs.Plugs)) + } +} + +func TestPlugs_WithStateSlots(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + WithStateSlots(3) + + if len(plugs.State) != 3 { + t.Errorf("State length = %d, want 3", len(plugs.State)) + } +} + +func TestPlugs_SetStateSlot(t *testing.T) { + value := MustEncodeLiteral(uint64(42)) + + plugs := NewPlugs(testSocketAddress). + SetStateSlot(0, value) + + if len(plugs.State) != 1 { + t.Errorf("State length = %d, want 1", len(plugs.State)) + } + if len(plugs.State[0]) != 32 { + t.Errorf("State[0] length = %d, want 32", len(plugs.State[0])) + } +} + +func TestPlugs_SetStateSlot_Expand(t *testing.T) { + value := MustEncodeLiteral(uint64(42)) + + plugs := NewPlugs(testSocketAddress). + SetStateSlot(5, value) + + if len(plugs.State) != 6 { + t.Errorf("State length = %d, want 6", len(plugs.State)) + } +} + +func TestPlugs_WithMask(t *testing.T) { + mask := big.NewInt(7) // bits 0, 1, 2 set + + plugs := NewPlugs(testSocketAddress). + WithMask(mask) + + if plugs.Mask.Cmp(mask) != 0 { + t.Errorf("Mask = %s, want %s", plugs.Mask.String(), mask.String()) + } +} + +func TestPlugs_SetMaskBit(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + SetMaskBit(0). + SetMaskBit(2). + SetMaskBit(5) + + // Expected: bits 0, 2, 5 set = 1 + 4 + 32 = 37 + expected := big.NewInt(37) + if plugs.Mask.Cmp(expected) != 0 { + t.Errorf("Mask = %s, want %s", plugs.Mask.String(), expected.String()) + } +} + +func TestPlugs_WithSalt(t *testing.T) { + salt := []byte{0xde, 0xad, 0xbe, 0xef} + + plugs := NewPlugs(testSocketAddress). + WithSalt(salt) + + if len(plugs.Salt) != 4 { + t.Errorf("Salt length = %d, want 4", len(plugs.Salt)) + } +} + +func TestPlugs_WithState(t *testing.T) { + state := [][]byte{ + MustEncodeLiteral(uint64(100)), + MustEncodeLiteral(uint64(200)), + } + + plugs := NewPlugs(testSocketAddress). + WithState(state) + + if len(plugs.State) != 2 { + t.Errorf("State length = %d, want 2", len(plugs.State)) + } +} + +func TestPlugs_ChainedPlugs(t *testing.T) { + multiplySelector := [4]byte{0x16, 0x5c, 0x4a, 0x16} + + plug0 := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(10))). + AddLiteral(MustEncodeLiteral(uint64(5))). + WithOutput(0, false). + Build() + + plug1 := NewPlug(testEchoContract, multiplySelector). + AddStaticState(0). + AddLiteral(MustEncodeLiteral(uint64(2))). + WithOutput(1, false). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug0). + AddPlug(*plug1). + WithStateSlots(2) + + if len(plugs.Plugs) != 2 { + t.Errorf("Plugs count = %d, want 2", len(plugs.Plugs)) + } + if len(plugs.State) != 2 { + t.Errorf("State slots = %d, want 2", len(plugs.State)) + } + + if plugs.Plugs[0].Indices[EnvParamStart] != LiteralIndex(6) || + plugs.Plugs[0].Indices[EnvParamStart+1] != LiteralIndex(7) { + t.Errorf("Plug 0 param indices wrong: %x", plugs.Plugs[0].Indices) + } + + if plugs.Plugs[1].Indices[EnvParamStart] != StaticStateIndex(0) || + plugs.Plugs[1].Indices[EnvParamStart+1] != LiteralIndex(6) { + t.Errorf("Plug 1 param indices wrong: %x", plugs.Plugs[1].Indices) + } +} + +func TestPlugs_Hash(t *testing.T) { + plug := NewPlug( + common.HexToAddress("0x1234567890123456789012345678901234567890"), + [4]byte{0xa5, 0xf3, 0xc2, 0x3b}, + ). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + WithOutput(0, false). + Build() + + socket := common.HexToAddress("0x536F636b6574536F636B6574536f636b65743030") + + plugs := Plugs{ + Socket: socket, + Plugs: []Plug{*plug}, + State: [][]byte{nil}, + Mask: big.NewInt(0), + Salt: []byte{}, + } + + hash1 := plugs.Hash() + hash2 := plugs.Hash() + + if hash1 != hash2 { + t.Errorf("Hash is not deterministic: %s != %s", hash1.Hex(), hash2.Hex()) + } + + if hash1 == (common.Hash{}) { + t.Error("Hash should not be zero") + } +} + +func TestPlugs_Digest(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + digest := plugs.Digest(domain) + + if digest == (common.Hash{}) { + t.Error("Digest should not be zero") + } +} + +func TestPlugs_Sign(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + + plugs := NewPlugs(testSocketAddress) + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + signature, err := plugs.Sign(domain, privateKey) + if err != nil { + t.Fatalf("Sign failed: %v", err) + } + + if len(signature) != 65 { + t.Errorf("Signature length = %d, want 65", len(signature)) + } + + if signature[64] < 27 { + t.Errorf("V value = %d, should be >= 27", signature[64]) + } +} + +func TestPlugs_SignToLivePlugs(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(10))). + AddLiteral(MustEncodeLiteral(uint64(5))). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug) + + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + livePlugs, err := plugs.SignToLivePlugs(domain, privateKey) + if err != nil { + t.Fatalf("SignToLivePlugs failed: %v", err) + } + + if len(livePlugs.Signature) != 65 { + t.Errorf("Signature length = %d, want 65", len(livePlugs.Signature)) + } +} + +func TestPlugsTypeHash(t *testing.T) { + expected := common.HexToHash("0x5e753b799be998b96f5989c4585d8cf47a8e46682fc527f23d88e0d6a09048cf") + if PlugsTypeHash != expected { + t.Errorf("PlugsTypeHash mismatch:\n got: %s\n expected: %s", PlugsTypeHash.Hex(), expected.Hex()) + } +} + +func TestPlugs_AddIf(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + AddIf(0) + + if len(plugs.Plugs) != 1 { + t.Errorf("Plugs length = %d, want 1", len(plugs.Plugs)) + } + + if plugs.OpenBlockCount() != 1 { + t.Errorf("OpenBlockCount = %d, want 1", plugs.OpenBlockCount()) + } + + if plugs.Plugs[0].method != JUMP_IF { + t.Errorf("method = %d, want %d (JUMP_IF)", plugs.Plugs[0].method, JUMP_IF) + } +} + +func TestPlugs_AddIfElseEnd(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + ifBodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(10))). + Build() + + elseBodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(20))). + Build() + + plugs.AddIf(0) + plugs.AddPlug(*ifBodyPlug) + + if err := plugs.AddElse(); err != nil { + t.Fatalf("AddElse failed: %v", err) + } + + plugs.AddPlug(*elseBodyPlug) + + if err := plugs.AddEnd(); err != nil { + t.Fatalf("AddEnd failed: %v", err) + } + + if len(plugs.Plugs) != 4 { + t.Errorf("Plugs length = %d, want 4", len(plugs.Plugs)) + } + + if plugs.OpenBlockCount() != 0 { + t.Errorf("OpenBlockCount = %d, want 0", plugs.OpenBlockCount()) + } + + if err := plugs.ValidateControlFlow(); err != nil { + t.Errorf("ValidateControlFlow failed: %v", err) + } + + jumpIfPlug := plugs.Plugs[0] + falseDest := jumpIfPlug.Literals[7*32+31] + if falseDest != 3 { + t.Errorf("JUMP_IF false dest = %d, want 3 (start of else body)", falseDest) + } + + jumpPlug := plugs.Plugs[2] + jumpDest := jumpPlug.Literals[6*32+31] + if jumpDest != 4 { + t.Errorf("JUMP dest = %d, want 4 (after else body)", jumpDest) + } +} + +func TestPlugs_AddIfEnd_NoElse(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + ifBodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(10))). + Build() + + plugs.AddIf(0) + plugs.AddPlug(*ifBodyPlug) + + if err := plugs.AddEnd(); err != nil { + t.Fatalf("AddEnd failed: %v", err) + } + + if len(plugs.Plugs) != 2 { + t.Errorf("Plugs length = %d, want 2", len(plugs.Plugs)) + } + + if plugs.OpenBlockCount() != 0 { + t.Errorf("OpenBlockCount = %d, want 0", plugs.OpenBlockCount()) + } + + jumpIfPlug := plugs.Plugs[0] + falseDest := jumpIfPlug.Literals[7*32+31] + if falseDest != 2 { + t.Errorf("JUMP_IF false dest = %d, want 2 (after if body)", falseDest) + } +} + +func TestPlugs_AddIfNested(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + innerBodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + Build() + + outerBodyAfterInner := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(200))). + Build() + + plugs.AddIf(0) + plugs.AddIf(1) + plugs.AddPlug(*innerBodyPlug) + + if err := plugs.AddEnd(); err != nil { + t.Fatalf("Inner AddEnd failed: %v", err) + } + + plugs.AddPlug(*outerBodyAfterInner) + + if err := plugs.AddEnd(); err != nil { + t.Fatalf("Outer AddEnd failed: %v", err) + } + + if plugs.OpenBlockCount() != 0 { + t.Errorf("OpenBlockCount = %d, want 0", plugs.OpenBlockCount()) + } + + if err := plugs.ValidateControlFlow(); err != nil { + t.Errorf("ValidateControlFlow failed: %v", err) + } + + outerJumpIf := plugs.Plugs[0] + outerFalseDest := outerJumpIf.Literals[7*32+31] + if outerFalseDest != 4 { + t.Errorf("Outer JUMP_IF false dest = %d, want 4", outerFalseDest) + } + + innerJumpIf := plugs.Plugs[1] + innerFalseDest := innerJumpIf.Literals[7*32+31] + if innerFalseDest != 3 { + t.Errorf("Inner JUMP_IF false dest = %d, want 3", innerFalseDest) + } +} + +func TestPlugs_AddElse_Error_NoIf(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddElse() + if err == nil { + t.Error("AddElse should fail without matching if") + } +} + +func TestPlugs_AddEnd_Error_NoBlock(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddEnd() + if err == nil { + t.Error("AddEnd should fail without open block") + } +} + +func TestPlugs_ValidateControlFlow_Unclosed(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + AddIf(0) + + err := plugs.ValidateControlFlow() + if err == nil { + t.Error("ValidateControlFlow should fail with unclosed if") + } +} + +func TestNewJumpPlug(t *testing.T) { + plug := NewJumpPlug(5).Build() + + if plug.method != JUMP { + t.Errorf("method = %d, want %d (JUMP)", plug.method, JUMP) + } + + destSlot := plug.Literals[6*32 : 7*32] + if destSlot[31] != 5 { + t.Errorf("dest = %d, want 5", destSlot[31]) + } +} + +func TestNewJumpIfPlug(t *testing.T) { + plug := NewJumpIfPlug(0, 2, 5).Build() + + if plug.method != JUMP_IF { + t.Errorf("method = %d, want %d (JUMP_IF)", plug.method, JUMP_IF) + } + + if plug.Indices[6] != StaticStateIndex(0) { + t.Errorf("condition index = 0x%02x, want 0x%02x", plug.Indices[6], StaticStateIndex(0)) + } + + trueDestSlot := plug.Literals[6*32 : 7*32] + if trueDestSlot[31] != 2 { + t.Errorf("trueDest = %d, want 2", trueDestSlot[31]) + } + + falseDestSlot := plug.Literals[7*32 : 8*32] + if falseDestSlot[31] != 5 { + t.Errorf("falseDest = %d, want 5", falseDestSlot[31]) + } +} + +func TestPlugs_AddLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + AddLoop() + + if plugs.LoopCount() != 1 { + t.Errorf("LoopCount = %d, want 1", plugs.LoopCount()) + } + + if len(plugs.Plugs) != 0 { + t.Errorf("Plugs length = %d, want 0 (AddLoop doesn't add a plug)", len(plugs.Plugs)) + } +} + +func TestPlugs_SimpleLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + bodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + plugs.AddLoop() + + if err := plugs.AddLoopCondition(0); err != nil { + t.Fatalf("AddLoopCondition failed: %v", err) + } + + plugs.AddPlug(*bodyPlug) + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("AddEndLoop failed: %v", err) + } + + if len(plugs.Plugs) != 3 { + t.Errorf("Plugs length = %d, want 3", len(plugs.Plugs)) + } + + if plugs.LoopCount() != 0 { + t.Errorf("LoopCount = %d, want 0", plugs.LoopCount()) + } + + if err := plugs.ValidateControlFlow(); err != nil { + t.Errorf("ValidateControlFlow failed: %v", err) + } + + conditionPlug := plugs.Plugs[0] + falseDest := conditionPlug.Literals[7*32+31] + if falseDest != 3 { + t.Errorf("condition false dest = %d, want 3 (after loop)", falseDest) + } + + endLoopPlug := plugs.Plugs[2] + jumpDest := endLoopPlug.Literals[6*32+31] + if jumpDest != 0 { + t.Errorf("end loop jump dest = %d, want 0 (loop start)", jumpDest) + } +} + +func TestPlugs_LoopWithBreak(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + bodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + plugs.AddLoop() + + if err := plugs.AddLoopCondition(0); err != nil { + t.Fatalf("AddLoopCondition failed: %v", err) + } + + plugs.AddPlug(*bodyPlug) + + if err := plugs.AddBreak(); err != nil { + t.Fatalf("AddBreak failed: %v", err) + } + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("AddEndLoop failed: %v", err) + } + + if len(plugs.Plugs) != 4 { + t.Errorf("Plugs length = %d, want 4", len(plugs.Plugs)) + } + + breakPlug := plugs.Plugs[2] + breakDest := breakPlug.Literals[6*32+31] + if breakDest != 4 { + t.Errorf("break dest = %d, want 4 (after loop)", breakDest) + } + + conditionPlug := plugs.Plugs[0] + falseDest := conditionPlug.Literals[7*32+31] + if falseDest != 4 { + t.Errorf("condition false dest = %d, want 4 (after loop)", falseDest) + } +} + +func TestPlugs_LoopWithContinue(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + bodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + plugs.AddLoop() + + if err := plugs.AddLoopCondition(0); err != nil { + t.Fatalf("AddLoopCondition failed: %v", err) + } + + plugs.AddPlug(*bodyPlug) + + if err := plugs.AddContinue(); err != nil { + t.Fatalf("AddContinue failed: %v", err) + } + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("AddEndLoop failed: %v", err) + } + + continuePlug := plugs.Plugs[2] + continueDest := continuePlug.Literals[6*32+31] + if continueDest != 0 { + t.Errorf("continue dest = %d, want 0 (loop start)", continueDest) + } +} + +func TestPlugs_InfiniteLoopWithBreak(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + bodyPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + plugs.AddLoop() + plugs.AddPlug(*bodyPlug) + + if err := plugs.AddBreak(); err != nil { + t.Fatalf("AddBreak failed: %v", err) + } + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("AddEndLoop failed: %v", err) + } + + if len(plugs.Plugs) != 3 { + t.Errorf("Plugs length = %d, want 3", len(plugs.Plugs)) + } + + breakPlug := plugs.Plugs[1] + breakDest := breakPlug.Literals[6*32+31] + if breakDest != 3 { + t.Errorf("break dest = %d, want 3 (after loop)", breakDest) + } + + endLoopPlug := plugs.Plugs[2] + jumpDest := endLoopPlug.Literals[6*32+31] + if jumpDest != 0 { + t.Errorf("end loop jump dest = %d, want 0 (loop start)", jumpDest) + } +} + +func TestPlugs_NestedLoops(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + innerBody := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + Build() + + plugs.AddLoop() + if err := plugs.AddLoopCondition(0); err != nil { + t.Fatalf("outer AddLoopCondition failed: %v", err) + } + + plugs.AddLoop() + if err := plugs.AddLoopCondition(1); err != nil { + t.Fatalf("inner AddLoopCondition failed: %v", err) + } + + plugs.AddPlug(*innerBody) + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("inner AddEndLoop failed: %v", err) + } + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("outer AddEndLoop failed: %v", err) + } + + if plugs.LoopCount() != 0 { + t.Errorf("LoopCount = %d, want 0", plugs.LoopCount()) + } + + outerCond := plugs.Plugs[0] + outerFalseDest := outerCond.Literals[7*32+31] + if outerFalseDest != 5 { + t.Errorf("outer condition false dest = %d, want 5", outerFalseDest) + } + + innerCond := plugs.Plugs[1] + innerFalseDest := innerCond.Literals[7*32+31] + if innerFalseDest != 4 { + t.Errorf("inner condition false dest = %d, want 4", innerFalseDest) + } + + innerEnd := plugs.Plugs[3] + innerEndDest := innerEnd.Literals[6*32+31] + if innerEndDest != 1 { + t.Errorf("inner end jump dest = %d, want 1 (inner loop start)", innerEndDest) + } + + outerEnd := plugs.Plugs[4] + outerEndDest := outerEnd.Literals[6*32+31] + if outerEndDest != 0 { + t.Errorf("outer end jump dest = %d, want 0 (outer loop start)", outerEndDest) + } +} + +func TestPlugs_AddLoopCondition_Error_NoLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddLoopCondition(0) + if err == nil { + t.Error("AddLoopCondition should fail without loop") + } +} + +func TestPlugs_AddContinue_Error_NoLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddContinue() + if err == nil { + t.Error("AddContinue should fail without loop") + } +} + +func TestPlugs_AddBreak_Error_NoLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddBreak() + if err == nil { + t.Error("AddBreak should fail without loop") + } +} + +func TestPlugs_AddEndLoop_Error_NoLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + err := plugs.AddEndLoop() + if err == nil { + t.Error("AddEndLoop should fail without loop") + } +} + +func TestPlugs_ValidateControlFlow_UnclosedLoop(t *testing.T) { + plugs := NewPlugs(testSocketAddress). + AddLoop() + + err := plugs.ValidateControlFlow() + if err == nil { + t.Error("ValidateControlFlow should fail with unclosed loop") + } +} + +func TestPlugs_ConditionalVariable(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + conditionPlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(1))). + AddLiteral(MustEncodeLiteral(uint64(0))). + WithOutput(0, false). + Build() + plugs.AddPlug(*conditionPlug) + + plugs.AddIf(0) + + trueValuePlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(0))). + WithOutput(1, false). + Build() + plugs.AddPlug(*trueValuePlug) + + if err := plugs.AddElse(); err != nil { + t.Fatalf("AddElse failed: %v", err) + } + + falseValuePlug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(200))). + AddLiteral(MustEncodeLiteral(uint64(0))). + WithOutput(1, false). + Build() + plugs.AddPlug(*falseValuePlug) + + if err := plugs.AddEnd(); err != nil { + t.Fatalf("AddEnd failed: %v", err) + } + + useVariablePlug := NewPlug(testEchoContract, testEchoSelector). + AddStaticState(1). + AddLiteral(MustEncodeLiteral(uint64(1))). + WithOutput(2, false). + Build() + plugs.AddPlug(*useVariablePlug) + + if len(plugs.Plugs) != 6 { + t.Errorf("Plugs length = %d, want 6", len(plugs.Plugs)) + } + + finalPlug := plugs.Plugs[5] + if finalPlug.Indices[EnvParamStart] != StaticStateIndex(1) { + t.Errorf("final plug first param = %x, want StaticStateIndex(1)=%x", + finalPlug.Indices[EnvParamStart], StaticStateIndex(1)) + } + + if err := plugs.ValidateControlFlow(); err != nil { + t.Errorf("ValidateControlFlow failed: %v", err) + } +} + +func TestPlugs_LoopWithAccumulator(t *testing.T) { + plugs := NewPlugs(testSocketAddress) + + plugs.AddLoop() + + if err := plugs.AddLoopCondition(0); err != nil { + t.Fatalf("AddLoopCondition failed: %v", err) + } + + accumulatorPlug := NewPlug(testEchoContract, testEchoSelector). + AddStaticState(1). + AddLiteral(MustEncodeLiteral(uint64(1))). + WithOutput(1, false). + Build() + plugs.AddPlug(*accumulatorPlug) + + if err := plugs.AddEndLoop(); err != nil { + t.Fatalf("AddEndLoop failed: %v", err) + } + + useFinalPlug := NewPlug(testEchoContract, testEchoSelector). + AddStaticState(1). + AddLiteral(MustEncodeLiteral(uint64(0))). + WithOutput(2, false). + Build() + plugs.AddPlug(*useFinalPlug) + + if len(plugs.Plugs) != 4 { + t.Errorf("Plugs length = %d, want 4", len(plugs.Plugs)) + } + + accPlug := plugs.Plugs[1] + if accPlug.Indices[EnvParamStart] != StaticStateIndex(1) { + t.Errorf("accumulator reads from wrong slot") + } + + outputIdx := accPlug.Literals[3*32+31] + if outputIdx != 1 { + t.Errorf("accumulator writes to slot %d, want 1", outputIdx) + } + + if err := plugs.ValidateControlFlow(); err != nil { + t.Errorf("ValidateControlFlow failed: %v", err) + } +} + +func BenchmarkPlugs_Hash(b *testing.B) { + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + plugs.Hash() + } +} + +func BenchmarkPlugs_Sign(b *testing.B) { + privateKey, _ := crypto.GenerateKey() + + plug := NewPlug(testEchoContract, testEchoSelector). + AddLiteral(MustEncodeLiteral(uint64(100))). + AddLiteral(MustEncodeLiteral(uint64(50))). + Build() + + plugs := NewPlugs(testSocketAddress). + AddPlug(*plug) + + domain := SocketDomain(testSocketAddress, big.NewInt(1)) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + plugs.Sign(domain, privateKey) + } +} diff --git a/packages/app/.env.encrypted b/packages/app/.env.encrypted deleted file mode 100644 index af2b110ec..000000000 --- a/packages/app/.env.encrypted +++ /dev/null @@ -1 +0,0 @@ -d7653af2ffae8f594fa13cfa0f24d165:d4d4754226f2ceef349507f10bd7239c2032382ad39a7798943723d0ff1575de1996515de47010fc291444e7f14e35f18c49723500377db9e423bdc9ea33e1f5618e2377e593798fca590cca235ee45bac1c5256ff8b31d07306d276be79f814b07be57e901f8c49bb9d0e3a2c65f7a3d9eb949fda9fd9676638b8c2342783301b010e522e258a344e80aafad2b24d430a847653cebc851409dcce1b5c26229aeb1a4a81f7a53979b987a42d10b0b300b7d06c1d73f12230ef4f80287adf7ee74a929093bb86fc646aaa31637c948414a159aa9a1797ebac65b8a8589e532c6e70d00063fa0fa2296351f3902a0973a921ae771743b6b9d399fc929d2c955bc65dfeefe1c9dc4046dca472390802f4e48116d3d348ad2c5dc1efa9d77abfd432e73be6c718d76304413cfceef59a9c04578cef3d60b4b229dfdfc4aa5f982cbf6cf2f7acb91d8019f8eb5baf0fc3775f5ad8b93453fcbc754c239690fe864601e2672a395155dd4b39a351737fe84d4d17b610f775ddd85c2bc3edc245b1bcb1cc91ffd4797e8868a742bb3db6f6001ebf7708b308728ebc5143b0e5d50660ba6f0249052757a768e2a6f149431a71342245b5497d487ed689a3bb5ce58e32df379cf1935f37047d3614d9c71d381697e5b54db392b3e6ee028ae0de64a32ab99d4d81f93e0764b792476345339ae23ae7507d6a0edfd75be7cd2876e0f251b005666c8d0a30f234ea79696f465b71c14ab65e40f4c700a4b1888f76c6b5242706ce44fa49d2d5388ca5021d982bc0e92a05edd0d90a5f0834b6970fbae02ba703e56d195ed425d3eae2463694770f85df57d81f7bd86278235d2423ce5414cd9d9a0b8cd094a18bdcd9c893594b268f27a5986e5c4234b34640821accd0ab6eac35908e55fdd92220100e57463111865d39bc9f3d2bf198dbf004d788cc7c090dc4b998027dc1b14624d42e87cb89aa0a01adb4bf71bf014fc5df88e9c857332136a236cf4e17ff50ab8adc90084ed5f8c86dac124251591759a0b7094d191104d9b0fa3bc2f7d9b97c8ca5dd3b1114a38a87321876ce484f10220d761ba5620eb362475d64c1bb0eaaf51fcd6b83ec194ade06d2326d7dc56ba0234f52a3bb4097d3b8da8e1a35eba422eb030ce08cb25bac6463b1d65730f706b2ee544d184b930051746e5f24cc8f8665889715f3cb3e7b074a92d6c77956040a4032ad9a944e5db44610558cc0cb6d7f2973980ed05fa4c6ab12b8b0d6600313ee10291937ba965575ae973c4587f4f6f59bf78fcd311fd2e14a37d1a5f6fc0c6ba2995f4bb5a521380247740d15fa03257dd7b3432428e0b19e73dfbc591fe59ed1c07b532a9af70e011107077cc530ff90742e \ No newline at end of file diff --git a/packages/app/.env.example b/packages/app/.env.example deleted file mode 100644 index 521e3e3f8..000000000 --- a/packages/app/.env.example +++ /dev/null @@ -1 +0,0 @@ -ENCRYPTION_KEY= diff --git a/packages/app/LICENSE b/packages/app/LICENSE index efaab3926..edd7c4ed9 100644 --- a/packages/app/LICENSE +++ b/packages/app/LICENSE @@ -1,7 +1,7 @@ BUSINESS SOURCE LICENSE VERSION 1.1 -Copyright (C) 2023 nftchance. Use of this software (@nftchance/plug-app) is +Copyright (C) 2024 Terminally Online. Use of this software (@terminally-online/plug-app) is governed by the Business Source License included in the LICENSE file and at www.onplug.io/license/busl. @@ -52,7 +52,7 @@ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. -nftchance hereby grants you permission to use this License's text to license +Terminally Online hereby grants you permission to use this License's text to license your works, and to refer to it using the trademark "Business Source License", as long as you comply with the Covenants of Licensor below. @@ -60,7 +60,7 @@ as long as you comply with the Covenants of Licensor below. In consideration of the right to use this License's text and the "Business Source License" name and trademark, Licensor covenants to -nftchance, and to all other recipients of the licensed work to +Terminally Online, and to all other recipients of the licensed work to be provided by Licensor: 1. To specify as the Change License the GPL Version 2.0 or any later version, diff --git a/packages/app/components/app/columns/authenticate/column.tsx b/packages/app/components/app/columns/authenticate/column.tsx index 17a33eecf..0dd82d09c 100644 --- a/packages/app/components/app/columns/authenticate/column.tsx +++ b/packages/app/components/app/columns/authenticate/column.tsx @@ -6,28 +6,26 @@ import { useAtomValue } from "jotai" import { Callout } from "@/components/app/utils/callout" import { Button } from "@/components/shared/buttons/button" -import { - formatAddress, -} from "@/lib" -import { useAccount } from "@/lib/hooks/account/useAccount" +import { formatAddress } from "@/lib/functions/format" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" import { useAuthenticate } from "@/lib/hooks/account/useAuthenticate" import { authenticationAtom } from "@/state/authentication" -import { columnByIndexAtom, useColumnActions } from "@/state/columns" +import { useColumn, useColumnActions } from "@/state/columns" import { ConnectorList } from "@/components/app/columns/authenticate/connector/list" -export const ColumnAuthenticate: FC<{ index: number }> = ({ index }) => { +export const ColumnAuthenticate: FC<{ id: string }> = ({ id }) => { const { data: session } = useSession() - const account = useAccount() + const account = useAuthentication() const { authenticate, failureReason, isLoading } = useAuthenticate() - const { navigate } = useColumnActions() + const { navigate } = useColumnActions(id) - const column = useAtomValue(columnByIndexAtom(index)) + const column = useColumn(id) const authentication = useAtomValue(authenticationAtom) const handleAuthenticate = () => { authenticate(undefined, { - onSuccess: () => navigate({ index, from: column?.from }), + onSuccess: () => navigate({ from: column?.from }), onError: error => console.error(error) }) } @@ -71,7 +69,7 @@ export const ColumnAuthenticate: FC<{ index: number }> = ({ index }) => { )} - {account.address === undefined && } + {account.address === undefined && } ) } diff --git a/packages/app/components/app/columns/authenticate/connector/item.tsx b/packages/app/components/app/columns/authenticate/connector/item.tsx index 1ba88a3ed..f5a9272f7 100644 --- a/packages/app/components/app/columns/authenticate/connector/item.tsx +++ b/packages/app/components/app/columns/authenticate/connector/item.tsx @@ -8,16 +8,18 @@ import { useSetAtom } from "jotai" import { ConnectorImage } from "@/components/app/columns/authenticate/connector/image" import { Accordion } from "@/components/shared/utils/accordion" -import { cn, CONNECTOR_ICON_OVERRIDE_MAP, greenGradientStyle, recentConnectorIdAtom, useRecentConnectorId } from "@/lib" +import { greenGradientStyle } from "@/lib/constants" +import { CONNECTOR_ICON_OVERRIDE_MAP, recentConnectorIdAtom, useRecentConnectorId } from "@/lib/constants/wallet" +import { cn } from "@/lib/utils/style" import { useAuthenticate } from "@/lib/hooks/account/useAuthenticate" import { useColumnActions } from "@/state/columns" -type Props = { connector: wagmiConnector; index: number; from?: string } -export const ConnectorItem: FC = ({ connector, index, from }) => { +type Props = { connector: wagmiConnector; id: string; from?: string } +export const ConnectorItem: FC = ({ connector, id, from }) => { const connection = useConnect() const { authenticate } = useAuthenticate() - const { navigate } = useColumnActions() + const { navigate } = useColumnActions(id) const updateRecentConnectorId = useSetAtom(recentConnectorIdAtom) @@ -42,7 +44,7 @@ export const ConnectorItem: FC = ({ connector, index, from }) => { { onSuccess: data => { updateRecentConnectorId(connector.id) - authenticate({ address: data.accounts[0] }, { onSuccess: () => navigate({ index, from }) }) + authenticate({ address: data.accounts[0] }, { onSuccess: () => navigate({ from }) }) } } ) diff --git a/packages/app/components/app/columns/authenticate/connector/list.tsx b/packages/app/components/app/columns/authenticate/connector/list.tsx index e54cf158e..2a9b02e23 100644 --- a/packages/app/components/app/columns/authenticate/connector/list.tsx +++ b/packages/app/components/app/columns/authenticate/connector/list.tsx @@ -6,7 +6,7 @@ import { useOrderedConnections } from "@/lib/hooks/account/useConnections" import { ConnectorItem } from "@/components/app/columns/authenticate/connector/item"; import { ConnectorQrCode } from "@/components/app/columns/authenticate/connector/qr-code"; -export const ConnectorList: FC<{ index: number; from?: string }> = ({ index, from }) => { +export const ConnectorList: FC<{ id: string; from?: string }> = ({ id, from }) => { const connectors = useOrderedConnections(true) return ( @@ -17,7 +17,7 @@ export const ConnectorList: FC<{ index: number; from?: string }> = ({ index, fro {connectors.map(connector => ( - + ))} diff --git a/packages/app/components/app/columns/authenticate/connector/qr-code.tsx b/packages/app/components/app/columns/authenticate/connector/qr-code.tsx index 32655d431..ea51310ef 100644 --- a/packages/app/components/app/columns/authenticate/connector/qr-code.tsx +++ b/packages/app/components/app/columns/authenticate/connector/qr-code.tsx @@ -8,9 +8,7 @@ import { Loader2 } from "lucide-react" import { useAtomValue } from "jotai" import { Image } from "@/components/app/utils/image" -import { - cn, -} from "@/lib" +import { cn } from "@/lib/utils/style" import { walletConnectURIMatrixAtom } from "@/state/authentication" const QR_CODE_SIZE = 200 diff --git a/packages/app/components/app/columns/column-row.tsx b/packages/app/components/app/columns/column-row.tsx index af7ad54fa..544539edb 100644 --- a/packages/app/components/app/columns/column-row.tsx +++ b/packages/app/components/app/columns/column-row.tsx @@ -8,63 +8,58 @@ import { DragDropContext, Droppable, DropResult } from "@hello-pangea/dnd" import { ConsoleColumn } from "@/components/app/columns/column" import { ColumnAdd } from "@/components/app/columns/utils/column-add" -import { moveColumnAtom, primaryColumnsAtom } from "@/state/columns" +import { moveColumnAtom, primaryColumnIdsAtom } from "@/state/columns" -import { ConsoleSidebarPane } from "../sidebar" +import { routes } from "@/lib/constants/routes" + +import { ConsoleSidebar, ConsoleSidebarPane } from "../sidebar" export const ConsoleColumnRow = () => { - const columns = useAtomValue(primaryColumnsAtom) + const columnIds = useAtomValue(primaryColumnIdsAtom) - const [, move] = useAtom(moveColumnAtom) + const [, move] = useAtom(moveColumnAtom) - const onDragEnd = (result: DropResult) => { - if (!columns || !result.destination) return + const onDragEnd = (result: DropResult) => { + if (!result.destination) return - move({ - from: result.source.index, - to: result.destination.index - }) - } + move({ + from: result.source.index, + to: result.destination.index + }) + } - return ( -
- + return ( +
+
+ +
+ - - - {provided => ( -
- {columns - .filter(column => column?.index >= 0) - .sort((a, b) => a.index - b.index) - .map(column => ( - - ))} - {provided.placeholder} -
- )} -
-
+ + + {provided => ( +
+ {columnIds.map((id, dragIndex) => ( + + ))} + {provided.placeholder} +
+ )} +
+
- + -
- - Terms of Service - - - - Privacy Policy - - -

A โšซ Terminally Online project.

-
-
- ) +
+ + Terms of Service + + +

A โšซ Terminally Online project.

+
+
+ ) } diff --git a/packages/app/components/app/columns/column.tsx b/packages/app/components/app/columns/column.tsx index 450e9aa87..c50c15da7 100644 --- a/packages/app/components/app/columns/column.tsx +++ b/packages/app/components/app/columns/column.tsx @@ -1,6 +1,6 @@ import React, { FC, memo, useEffect, useRef, useState } from "react" -import { Check, ChevronLeft, GitFork, Plus, Share, X } from "lucide-react" +import { Check, EyeClosed, GitFork, Plus, Share, X } from "lucide-react" import { useAtom } from "jotai" @@ -14,273 +14,257 @@ import { PlugsDiscover } from "@/components/app/plugs/discover" import { PlugsMine } from "@/components/app/plugs/mine" import { Plug } from "@/components/app/plugs/plug" import { SocketActivity } from "@/components/app/sockets/activity/activity-list" -import { SocketCollectionList } from "@/components/app/sockets/collectibles/collection-list" -import { SocketPositionList } from "@/components/app/sockets/position/position-list" -import { SocketTokenList } from "@/components/app/sockets/tokens/token-list" import { Button } from "@/components/shared/buttons/button" -import { cardColors, cn, formatTitle } from "@/lib" -import { useSocket } from "@/state/authentication" -import { columnByIndexAtom, COLUMNS, useColumnActions } from "@/state/columns" +import { cardColors } from "@/lib/constants/colors" +import { formatTitle } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" +import { COLUMNS, useColumn, useColumnActions } from "@/state/columns" import { plugByIdAtom, usePlugActions } from "@/state/plugs" import { SparklingText } from "../utils/sparkling-text" +import { ColumnAssets } from "./utils/column-assets" +import { AddressFrame } from "../frames/address" -const MIN_COLUMN_WIDTH = 420 -const MAX_COLUMN_WIDTH = 680 +const STEP = (COLUMNS.MAX_WIDTH - COLUMNS.MIN_WIDTH) / 4 +const getBoundedWidth = (width: number) => { + const bounded = Math.min(Math.max(width, COLUMNS.MIN_WIDTH), COLUMNS.MAX_WIDTH) + return COLUMNS.MIN_WIDTH + Math.round((bounded - COLUMNS.MIN_WIDTH) / STEP) * STEP +} -const getBoundedWidth = (width: number) => Math.min(Math.max(width, MIN_COLUMN_WIDTH), MAX_COLUMN_WIDTH) +const ColumnContent = memo<{ id: string; columnKey: string; item?: string; from?: string }>( + ({ id, columnKey, item, from }) => { + if (columnKey === COLUMNS.KEYS.ADD) return + if (columnKey === COLUMNS.KEYS.ASSETS) return + if (columnKey === COLUMNS.KEYS.SETTINGS) return + if (columnKey === COLUMNS.KEYS.APPLICATION) return + if (columnKey === COLUMNS.KEYS.DISCOVER) return + if (columnKey === COLUMNS.KEYS.MY_PLUGS) return + if (columnKey === COLUMNS.KEYS.PLUG && item) return + if (columnKey === COLUMNS.KEYS.ACTIVITY) return + return null + } +) +ColumnContent.displayName = "ColumnContent" -export const ConsoleColumn: FC<{ - index: number -}> = memo(({ index }) => { - const resizeRef = useRef(null) +export const ConsoleColumn: FC<{ id: string, dragIndex: number }> = memo(({ id, dragIndex }) => { + const resizeRef = useRef(null) + const columnRef = useRef(null) - const { socket } = useSocket() + const column = useColumn(id) + const { remove, resize } = useColumnActions(id) - const [column] = useAtom(columnByIndexAtom(index)) - const { remove, resize, navigate } = useColumnActions(index) + const [plug] = useAtom(plugByIdAtom(column?.item ?? "__non-existant__")) + const { fork } = usePlugActions(id) - const [plug] = useAtom(plugByIdAtom(column?.item ?? "__non-existant__")) - const { fork } = usePlugActions() + const [copied, setCopied] = useState(false) + const widthRef = useRef(column?.width ?? COLUMNS.DEFAULT_WIDTH) - const [width, setWidth] = useState(column?.width ?? 0) - const [isResizing, setIsResizing] = useState(false) - const [copied, setCopied] = useState(false) + const handleResizeStart = (e: React.MouseEvent) => { + e.preventDefault() - useEffect(() => { - if (!column) return + const handleMouseMove = (e: MouseEvent) => { + if (!resizeRef.current || !columnRef.current) return + const newWidth = getBoundedWidth(e.clientX - resizeRef.current.getBoundingClientRect().left) + if (newWidth !== widthRef.current) { + widthRef.current = newWidth + columnRef.current.style.width = `${newWidth}px` + } + } - const handleMouseMove = (e: MouseEvent) => { - if (!resizeRef.current || !isResizing) return + const handleMouseUp = () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mouseup", handleMouseUp) + resize(widthRef.current) + } - setWidth(getBoundedWidth(e.clientX - resizeRef.current.getBoundingClientRect().left)) - } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mouseup", handleMouseUp) + } - const handleMouseUp = () => { - setIsResizing(false) + if (!column) return null - resize({ - index: column.index, - width - }) - } + return ( +
+ + {(provided, snapshot) => ( +
{ + provided.innerRef(el) + columnRef.current = el + }} + className="relative flex h-full w-full flex-row rounded-lg" + {...provided.draggableProps} + style={{ + ...provided.draggableProps.style, + width: `${column.width}px` + }} + > +
+
+
+ {plug ? ( +
+ ) : ( + (OPTIONS.find(option => option.label === column.key)?.icon ?? ( + + )) + )} - if (isResizing) { - window.addEventListener("mousemove", handleMouseMove) - window.addEventListener("mouseup", handleMouseUp) - } +
+ (plug.createdAt ?? 0) && + plug.renamedAt !== plug.createdAt + )} + sparkleKey={new Date(plug?.renamedAt ?? "")?.getTime()} + color={cardColors[plug?.color ?? "yellow"]} + item={column.item ?? ""} + > + {formatTitle( + plug && + column.key === COLUMNS.KEYS.PLUG && + column.item !== undefined + ? plug.name + : (column.key?.replace("_", " ").toLowerCase() ?? "ERROR") + )} + +
- return () => { - window.removeEventListener("mousemove", handleMouseMove) - window.removeEventListener("mouseup", handleMouseUp) - } - }, [resize, column, width, isResizing]) + {column.key === COLUMNS.KEYS.ASSETS ? ( + + ) : column.key === COLUMNS.KEYS.PLUG && plug && ( +
+ {plug.actions.length !== 0 && <> + - if (!column) return null + + } - return ( -
- - {(provided, snapshot) => ( -
-
-
-
- {column.key !== COLUMNS.KEYS.ADD && ( - - )} + +
+ )} +
+ } + nextPadded={false} + nextOnClick={ + column.key !== COLUMNS.KEYS.PLUG ? () => remove() : undefined + } + nextLabel={ + + } + /> +
- {plug ? ( -
- ) : ( - (OPTIONS.find(option => option.label === column.key)?.icon ?? ( - - )) - )} +
+ +
-
- (plug.createdAt ?? 0) && - plug.renamedAt !== plug.createdAt - )} - sparkleKey={new Date(plug?.renamedAt ?? "")?.getTime()} - color={cardColors[plug?.color ?? "yellow"]} - item={column.item ?? ""} - > - {formatTitle( - plug && - column.key === COLUMNS.KEYS.PLUG && - column.item !== undefined - ? plug.name - : (column.key?.replace("_", " ").toLowerCase() ?? "ERROR") - )} - -
+ +
- {plug && ( -
- {plug.actions.length !== 0 && <> - - - - } - - -
- )} -
- } - nextPadded={false} - nextOnClick={ - column.key !== COLUMNS.KEYS.PLUG ? () => remove(column.index) : undefined - } - nextLabel={ - - } - /> -
- -
- {column.key === COLUMNS.KEYS.ADD ? ( - - ) : column.key === COLUMNS.KEYS.DISCOVER ? ( - - ) : column.key === COLUMNS.KEYS.MY_PLUGS ? ( - - ) : column.key === COLUMNS.KEYS.PLUG && column.item ? ( - - ) : column.key === COLUMNS.KEYS.ACTIVITY ? ( - - ) : column.key === COLUMNS.KEYS.TOKENS ? ( - - ) : column.key === COLUMNS.KEYS.COLLECTIBLES ? ( - - ) : column.key === COLUMNS.KEYS.POSITIONS ? ( - - ) : column.key === COLUMNS.KEYS.SETTINGS ? ( - - ) : column.key === COLUMNS.KEYS.APPLICATION ? ( - - ) : ( - - )} -
-
- -
-
-
{ - e.preventDefault() - setIsResizing(true) - }} - /> -
-
- ) - } - -
- ) +
+
+
+
+
+ ) + } + +
+ ) }) ConsoleColumn.displayName = "ConsoleColumn" diff --git a/packages/app/components/app/columns/settings/column.tsx b/packages/app/components/app/columns/settings/column.tsx index 5bf7a26c6..f2bda1d48 100644 --- a/packages/app/components/app/columns/settings/column.tsx +++ b/packages/app/components/app/columns/settings/column.tsx @@ -1,212 +1,202 @@ import { FC, HTMLAttributes, useState } from "react" import { - BookUser, - Cable, - CalendarCheck, - CalendarClock, - Computer, - Glasses, - Globe, - Handshake, - Hash, - MessageCircleIcon, - Puzzle, - Sigma, - User, - Waypoints + BookUser, + Cable, + CalendarCheck, + CalendarClock, + Computer, + Glasses, + Globe, + Waypoints } from "lucide-react" import plugCore from "@terminallyonline/plug-core/package.json" -import { ChainId, formatAddress, getChainName } from "@/lib" import app from "@/package.json" -import { useSocket } from "@/state/authentication" -import { Flag, useFlags } from "@/state/flags" -import { useAccount } from "@/lib/hooks/account/useAccount" -import { ChainImage } from "../../sockets/chains/chain.image" -import { connectedChains } from "@/contexts" -import { SocketDeployFrame } from "../../frames/socket/deploy/frame" -import { ColumnSettingsDeploymentItem } from "./deployment/item" + +/* import { connectedChains } from "@/contexts" */ +import { ChainId } from "@/lib/constants/chains" +import { getChainName } from "@/lib/functions/blockchain" +import { formatAddress } from "@/lib/functions/format" import { useResponse } from "@/lib/hooks/useResponse" import { api } from "@/server/client" +import { useUser } from "@/state/authentication" +import { Flag, useFlags } from "@/state/flags" -export const ColumnSettings: FC & { index: number }> = ({ index, ...props }) => { - const { getFlag } = useFlags() - - const { user, chainId } = useAccount() - const { socket } = useSocket() - - const [killed, setKilled] = useState(false) - - useResponse(() => api.solver.killer.killed.useQuery(undefined), { - onSuccess: data => setKilled(data.killed) - }) - - if (!socket) return null - - return ( - <> -
-
-

Session

-
-
- {user &&

- - Id{" "} - navigator.clipboard.writeText(user.id ?? "")} - > - {formatAddress(user.id)} - -

} - {chainId &&

- - Chain{" "} - - - {getChainName(chainId as ChainId)} ({chainId}) - -

} - -
-

Socket

-
-
-

- - Address{" "} - navigator.clipboard.writeText(socket?.socketAddress ?? "")} - > - {formatAddress(socket?.socketAddress)} - -

-

- - Nonce{" "} - 1738 -

-

- - Implementation{" "} - navigator.clipboard.writeText(socket?.deploymentImplementation ?? "")} - > - {socket?.deploymentImplementation ? formatAddress(socket?.deploymentImplementation) : "None"} - -

- -
-

Status

-
-
-

- Solver - - {killed ? "Halted" : "Operational"} - -

- - {connectedChains.map((chain, chainIndex) => ( - - ))} - -
-

Account

-
-
-

- - Created - - {new Date(socket?.createdAt).toLocaleDateString()} - -

-

- - Updated{" "} - - {new Date(socket?.updatedAt).toLocaleDateString()} - -

- -
-

Identity

-
-
-

- - ENS{" "} - - {socket?.identity?.ens?.name || "None"} - -

-

- - Farcaster Id{" "} - - {socket?.identity?.farcasterId || "None"} - -

-

- - Referred By{" "} - - {formatAddress(socket?.identity?.referrerId ?? "") || "None"} - -

- -
-

Flags

-
-
-

- - Show Application{" "} - - {getFlag(Flag.SHOW_PWA) ? "Yes" : "No"} - -

- -
-

Versions

-
-
-

- - App{" "} - v{app.version} -

-

- - Cord{" "} - v9.1.4 -

-

- - Core{" "} - v{plugCore.version} -

-
- - {connectedChains.map((chain, chainIndex) => ( - - ))} - - ) +/* import { SocketDeployFrame } from "../../frames/socket/deploy/frame" */ +import { ChainImage } from "../../sockets/chains/chain.image" +/* import { ColumnSettingsDeploymentItem } from "./deployment/item" */ +import { useAccount } from "wagmi" +import { useAddress } from "@terminallyonline/plug-sdk" + +// TODO: I have temporarily hidden the inclusion of chain specific stuff because it is stored in Gusher now. The +// nuance of if we are showing this data will be re-approached later since this is a polish task. At the time +// of writing this comment I do not think there is neccessarily a reason anyone actually needs this. +// +// NOTE: At the same time as writing the comment above, I commented out the ability to use the Socket Deployment +// frames at the bottom of this components. We will want to bring this one back because it makes development +// far more approachable even though most other app developers will not need/want it. + +export const ColumnSettings: FC & { id: string }> = ({ id, ...props }) => { + const { getFlag } = useFlags() + + const { chainId } = useAccount() + const user = useUser() + + const [killed, setKilled] = useState(false) + + /* useResponse(() => api.solver.killer.killed.useQuery(undefined), { */ + /* onSuccess: data => setKilled(data.killed) */ + /* }) */ + + if (!user) return null + + return ( + <> +
+
+

Session

+
+
+ + {user && ( +

+ + Address{" "} + navigator.clipboard.writeText(user.address ?? "")} + > + {formatAddress(user.address)} + +

+ )} + + {chainId && ( +

+ + Chain{" "} + + + {getChainName(chainId as ChainId)} ({chainId}) + +

+ )} + + {/*
*/} + {/*

Socket

*/} + {/*
*/} + {/*
*/} + {/*

*/} + {/* */} + {/* Address{" "} */} + {/* navigator.clipboard.writeText(user?.socketAddress ?? "")} */} + {/* > */} + {/* {formatAddress(socket?.socketAddress)} */} + {/* */} + {/*

*/} + {/*

*/} + {/* */} + {/* Nonce{" "} */} + {/* 1738 */} + {/*

*/} + {/*

*/} + {/* */} + {/* Implementation{" "} */} + {/* navigator.clipboard.writeText(socket?.deploymentImplementation ?? "")} */} + {/* > */} + {/* {socket?.deploymentImplementation ? formatAddress(socket?.deploymentImplementation) : "None"} */} + {/* */} + {/*

*/} + +
+

Status

+
+
+ +

+ Solver + + {killed ? "Halted" : "Operational"} + +

+ + {/* {connectedChains.map((chain, chainIndex) => ( */} + {/* */} + {/* ))} */} + +
+

Account

+
+
+ +

+ + Created + + {new Date(user?.createdAt).toLocaleDateString()} + +

+ +

+ + Updated{" "} + + {new Date(user?.updatedAt).toLocaleDateString()} + +

+ +
+

Flags

+
+
+ +

+ + Show Application{" "} + + {getFlag(Flag.SHOW_PWA) ? "Yes" : "No"} + +

+ +
+

Versions

+
+
+ +

+ + App{" "} + v{app.version} +

+ +

+ + Cord{" "} + v9.1.4 +

+ +

+ + Core{" "} + v{plugCore.version} +

+
+ + {/* {connectedChains.map((chain, chainIndex) => ( */} + {/* */} + {/* ))} */} + + ) } diff --git a/packages/app/components/app/columns/settings/deployment/item.tsx b/packages/app/components/app/columns/settings/deployment/item.tsx index b7c2831f1..d8a739adc 100644 --- a/packages/app/components/app/columns/settings/deployment/item.tsx +++ b/packages/app/components/app/columns/settings/deployment/item.tsx @@ -1,17 +1,19 @@ import { ChainImage } from "@/components/app/sockets/chains/chain.image" -import { ChainId, cn, getChainName } from "@/lib" +import { ChainId } from "@/lib/constants/chains" +import { getChainName } from "@/lib/functions/blockchain" +import { cn } from "@/lib/utils/style" import { useColumnActions } from "@/state/columns" import { FC } from "react" import { useBytecode } from "wagmi" type ColumnSettingsDeploymentItemProps = { - index: number + id: string chainId: ChainId factory: string | null address: string } -export const ColumnSettingsDeploymentItem: FC = ({ index, chainId, factory, address }) => { - const { frame } = useColumnActions(index) +export const ColumnSettingsDeploymentItem: FC = ({ id, chainId, factory, address }) => { + const { frame } = useColumnActions(id) const { data: factoryBytecode } = useBytecode({ chainId, diff --git a/packages/app/components/app/columns/utils/column-add.tsx b/packages/app/components/app/columns/utils/column-add.tsx index 871489951..625b7ecf8 100644 --- a/packages/app/components/app/columns/utils/column-add.tsx +++ b/packages/app/components/app/columns/utils/column-add.tsx @@ -1,201 +1,191 @@ import { type JSX, memo, useEffect, useMemo, useRef, useState } from "react" -import { Activity, Cable, Cog, Coins, Globe, ImageIcon, LockIcon, PiggyBank, Plug, Plus, Star, X } from "lucide-react" +import { Activity, Cable, Globe, PiggyBank, Plug, Plus, Star, X } from "lucide-react" import { useAtomValue } from "jotai" import { Button } from "@/components/shared/buttons/button" import { Accordion } from "@/components/shared/utils/accordion" -import { cn, formatTitle } from "@/lib" -import { useSocket } from "@/state/authentication" -import { COLUMNS, primaryColumnsAtom, useColumnActions } from "@/state/columns" +import { formatTitle } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" +import { COLUMNS, primaryColumnIdsAtom, useColumnActions, useColumnIndex } from "@/state/columns" import { Flag, useFlags } from "@/state/flags" import { usePlugActions } from "@/state/plugs" type Options = Array<{ - label: keyof (typeof COLUMNS)["KEYS"] - description: string - icon: JSX.Element + label: keyof (typeof COLUMNS)["KEYS"] + description: string + icon: JSX.Element }> export const ANONYMOUS_OPTIONS: Options = [ - { - label: "DISCOVER", - description: "Discover curated and community Plugs.", - icon: - }, - { - label: "MY_PLUGS", - description: "Create, edit, and run your Plugs.", - icon: - } + { + label: "DISCOVER", + description: "Discover curated and community Plugs.", + icon: + }, + { + label: "MY_PLUGS", + description: "Create, edit, and run your Plugs.", + icon: + } ] export const OPTIONS: Options = [ - ...ANONYMOUS_OPTIONS, - { - label: "ACTIVITY", - description: "View the simulations and runs of your Plugs.", - icon: - }, - { - label: "TOKENS", - description: "View your tokens and manage them.", - icon: - }, - { - label: "COLLECTIBLES", - description: "View your collectibles and manage them.", - icon: - }, - { - label: "POSITIONS", - description: "View your positions and manage them.", - icon: - } -] as const - -export const ColumnAdd = memo(({ index }: { index: number }) => { - const resizeRef = useRef(null) - - const { getFlag } = useFlags() - - const columns = useAtomValue(primaryColumnsAtom) - const { add, navigate } = useColumnActions(index) - - const { socket } = useSocket() - const { add: addPlug } = usePlugActions() - - const [width, setWidth] = useState(COLUMNS.DEFAULT_WIDTH) - const [isResizing, setIsResizing] = useState(false) - - useEffect(() => { - const getBoundedWidth = (width: number) => Math.min(Math.max(width, 380), 620) - - const handleMouseMove = (e: MouseEvent) => { - if (!resizeRef.current || !isResizing) return - - setWidth(getBoundedWidth(e.clientX - resizeRef.current.getBoundingClientRect().left)) - } - - const handleMouseUp = () => { - setIsResizing(false) - } - - if (isResizing) { - window.addEventListener("mousemove", handleMouseMove) - window.addEventListener("mouseup", handleMouseUp) - } - - return () => { - window.removeEventListener("mousemove", handleMouseMove) - window.removeEventListener("mouseup", handleMouseUp) - } - }, [isResizing]) - - const flagOptions = useMemo(() => { - const options: Options = [] - - if (getFlag(Flag.SHOW_PWA)) - options.push({ - label: "APPLICATION", - description: "Install Plug as an app on your device.", - icon: - }) - - return options - }, [getFlag]) - - - const options = useMemo(() => { - const base = [ - ...flagOptions, - ...OPTIONS, - ] - - return base - }, []) - - const isBody = index != columns.length - 2 - - return ( - <> -
column.index >= 0) ? "" : "ml-2" - )} - style={!isBody ? { width, minWidth: width } : {}} - > - {!isBody && ( -
-
- -

Add

- -
-
- )} - -
-
- addPlug(isBody ? { index } : undefined)}> -
-
- -
- -
-

Create Plug

-

Start creating a new Plug from scratch.

-
-
-
- - {options.map(option => ( - - isBody ? navigate({ index, key: option.label }) : add({ key: option.label }) - } - > -
-
- {option.icon} -
- -
-

{formatTitle(option?.label?.replace("_", " ").toLowerCase() ?? "")}

-

{option.description}

-
-
-
- ))} -
-
-
- -
-
-
{ - e.preventDefault() - setIsResizing(true) - }} - /> -
- - ) + { + label: "ASSETS", + description: "View and manage your assets.", + icon: + }, + ...ANONYMOUS_OPTIONS, + { + label: "ACTIVITY", + description: "View the activity of your account.", + icon: + }, +] + +export const ColumnAdd = memo(({ id }: { id: string }) => { + const resizeRef = useRef(null) + + const { getFlag } = useFlags() + + const columnIds = useAtomValue(primaryColumnIdsAtom) + const index = useColumnIndex(id) + const { add, navigate } = useColumnActions(id) + + const { add: addPlug } = usePlugActions() + + const [width, setWidth] = useState(COLUMNS.DEFAULT_WIDTH) + const [isResizing, setIsResizing] = useState(false) + + useEffect(() => { + const getBoundedWidth = (width: number) => Math.min(Math.max(width, 380), 620) + + const handleMouseMove = (e: MouseEvent) => { + if (!resizeRef.current || !isResizing) return + + setWidth(getBoundedWidth(e.clientX - resizeRef.current.getBoundingClientRect().left)) + } + + const handleMouseUp = () => { + setIsResizing(false) + } + + if (isResizing) { + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mouseup", handleMouseUp) + } + + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mouseup", handleMouseUp) + } + }, [isResizing]) + + const flagOptions = useMemo(() => { + const options: Options = [] + + if (getFlag(Flag.SHOW_PWA)) + options.push({ + label: "APPLICATION", + description: "Install Plug as an app on your device.", + icon: + }) + + return options + }, [getFlag]) + + + const options = useMemo(() => { + const base = [ + ...flagOptions, + ...OPTIONS, + ] + + return base + }, [flagOptions]) + + const isBody = index >= 0 && index !== columnIds.length - 1 + + return ( + <> +
0 ? "" : "ml-2" + )} + style={!isBody ? { width, minWidth: width } : {}} + > + {!isBody && ( +
+
+ +

Add

+ +
+
+ )} + +
+
+ addPlug(isBody && index !== undefined ? { index } : undefined)}> +
+
+ +
+ +
+

Create Plug

+

Start creating a new Plug from scratch.

+
+
+
+ + {options.map(option => ( + + isBody ? navigate({ key: option.label }) : add({ key: option.label }) + } + > +
+
+ {option.icon} +
+ +
+

{formatTitle(option?.label?.replace("_", " ").toLowerCase() ?? "")}

+

{option.description}

+
+
+
+ ))} +
+
+
+ +
+
+
{ + e.preventDefault() + setIsResizing(true) + }} + /> +
+ + ) }) ColumnAdd.displayName = "ColumnAdd" diff --git a/packages/app/components/app/columns/utils/column-application.tsx b/packages/app/components/app/columns/utils/column-application.tsx index 52080a308..3be32c2d2 100755 --- a/packages/app/components/app/columns/utils/column-application.tsx +++ b/packages/app/components/app/columns/utils/column-application.tsx @@ -6,33 +6,32 @@ import { motion } from "framer-motion" import { Button } from "@/components/shared/buttons/button" import { useBeforeInstall } from "@/contexts" -import { cn, GTM_EVENTS, useAnalytics } from "@/lib" +import { analytics } from "@/lib/constants/analytics" +import { cn } from "@/lib/utils/style" import { useColumnActions } from "@/state/columns" import { Flag } from "@/state/flags" import { useFlags } from "@/state/flags" -export const ColumnApplication: FC & { index: number }> = ({ - index, +export const ColumnApplication: FC & { id: string }> = ({ + id, className, ...props }) => { const { data: session } = useSession() const { prompt, isNativePromptAvailable, instructions } = useBeforeInstall() const { handleFlag } = useFlags() - const { remove } = useColumnActions() - - const handlePWAInstall = useAnalytics(GTM_EVENTS.CTA_CLICKED, session?.user?.id, true, "/") + const { remove } = useColumnActions(id) const [currentStep, setCurrentStep] = useState(0) const handleClose = useCallback( (added: boolean = false) => { - if (added) handlePWAInstall() + if (added) analytics.app.pwa() handleFlag(Flag.SHOW_PWA, false) - remove(index) + remove() }, - [index, remove, handleFlag, handlePWAInstall] + [remove, handleFlag] ) if (instructions.length === 0) return null diff --git a/packages/app/components/app/columns/utils/column-assets.tsx b/packages/app/components/app/columns/utils/column-assets.tsx new file mode 100644 index 000000000..e4170f938 --- /dev/null +++ b/packages/app/components/app/columns/utils/column-assets.tsx @@ -0,0 +1,88 @@ +import { useMemo, useState } from "react" + +import { usePositions } from "@terminallyonline/plug-sdk" + +import { Callout } from "@/components/app/utils/callout" +import { Animate } from "../../utils/animate" +import { SocketTokenItem } from "../../sockets/tokens/token-item" + +import { useDebounce } from "@/lib/hooks/useDebounce" +import { cn } from "@/lib/utils/style" +import { PLACEHOLDER_TOKENS } from "@/lib/constants/placeholder/tokens" + +import { useUser } from "@/state/authentication" +import { Search } from "../../inputs/search" +import { SearchIcon } from "lucide-react" +import { Header } from "@/components/landing/header" + + +type ColumnAssetsProps = { id: string, address?: string } + +type ColumnAssetsType = React.FC> + +export const ColumnAssets: ColumnAssetsType = ({ id, address, className, ...props }) => { + const { address: userAddress, isAnonymous, isAuthenticated } = useUser() + + const [search, debouncedSearch, handleSearch] = useDebounce("") + + const { data: positions, isLoading } = usePositions(address || userAddress, { + enabled: !!(address || userAddress) && isAuthenticated, + search: debouncedSearch ? { all: debouncedSearch } : undefined + }) + + const tokens = useMemo(() => { + if (positions.length === 0) return [Array(5).fill(undefined), []] + + if (!positions || isAnonymous) return [PLACEHOLDER_TOKENS, []] + + return positions.reduce( + ([fungibles, nfts], pos) => { + pos.standard === "erc:20" ? fungibles.push(pos) : nfts.push(pos) + return [fungibles, nfts] + }, + [[], []] as Array + ) + }, [isAnonymous, positions]) + + return ( +
+ } + placeholder="Search assets" + search={search} + handleSearch={handleSearch} + clear={true} + isLoading={isLoading} + className="px-4" + /> + + {tokens[0].length > 0 && + {tokens[0].map((token, tokenIndex) => ( + + + + ))} + } + + {tokens.length > 1 && tokens[1].length > 0 && (<> +
+ +
+ + {tokens[1].map((token, tokenIndex) => ( + + + + ))} + +
+ )} + + + +
+ ) +} diff --git a/packages/app/components/app/columns/utils/column-stats.tsx b/packages/app/components/app/columns/utils/column-stats.tsx index ba0bdb8ac..439c4dafc 100644 --- a/packages/app/components/app/columns/utils/column-stats.tsx +++ b/packages/app/components/app/columns/utils/column-stats.tsx @@ -1,232 +1,228 @@ -import { useSession } from "next-auth/react" import React, { FC, useState } from "react" -import { Clipboard } from "lucide-react" - -import { Search } from "@/components/app/inputs/search" -import { Button } from "@/components/shared/buttons/button" import { Counter } from "@/components/shared/utils/counter" -import { cn } from "@/lib" +import { cn } from "@/lib/utils/style" import { api } from "@/server/client" -import { useSocket } from "@/state/authentication" const COLORS = ["#F3B08A", "#F3EF8A", "#9F8AF3", "#8AF3E6"] const ProfileStat: FC<{ - index: number - isActive: boolean - stats: Array - max: number - onHover: (index: number | undefined) => void + index: number + isActive: boolean + stats: Array + max: number + onHover: (index: number | undefined) => void }> = ({ index, isActive, stats, max, onHover }) => { - const [hovering, setHovering] = useState(undefined) - - const total = stats.reduce((a, b) => (a ?? 0) + (b ?? 0), 0) ?? 0 - - return ( -
onHover(index)} - onMouseLeave={() => onHover(undefined)} - > -
-

- -

-
- {stats.map((stat, i) => ( - <> -
{ - setHovering(i) - }} - onMouseLeave={() => { - setHovering(undefined) - }} - /> -
- - ))} -
-
- ) + const [hovering, setHovering] = useState(undefined) + + const total = stats.reduce((a, b) => (a ?? 0) + (b ?? 0), 0) ?? 0 + + return ( +
onHover(index)} + onMouseLeave={() => onHover(undefined)} + > +
+

+ +

+
+ {stats.map((stat, i) => ( + <> +
{ + setHovering(i) + }} + onMouseLeave={() => { + setHovering(undefined) + }} + /> +
+ + ))} +
+
+ ) } -const ProfileStats = () => { - const [hoveredPeriod, setHoveredPeriod] = useState(undefined) - const [toggledStats, setToggledStats] = useState([false, false, false, false]) - - const { data: statsData } = api.socket.stats.get.useQuery(undefined) - - const stats: number[][] = - statsData?.periods.map((_, index) => [ - statsData.counts.plugs[index] ?? 0, - statsData.counts.forks[index] ?? 0, - statsData.counts.views[index] ?? 0, - statsData.counts.referrals[index] ?? 0 - ]) ?? Array(4).fill([0, 0, 0, 0]) - - const max = Math.max(...stats.map(period => period.reduce((sum: number, value: number) => sum + (value ?? 0), 0))) - const currentStats = hoveredPeriod !== undefined ? stats[hoveredPeriod] : stats[stats.length - 1] - - const startDate = statsData?.periods[0]?.weekStart - ? new Date(statsData.periods[0].weekStart).toLocaleDateString("en-US", { - month: "2-digit", - year: "2-digit" - }) - : "09/24" - const endDate = statsData?.periods[statsData.periods.length - 1]?.weekEnd - ? new Date(statsData.periods[statsData.periods.length - 1].weekEnd).toLocaleDateString("en-US", { - month: "2-digit", - year: "2-digit" - }) - : "10/13" - - const handleToggle = (statIndex: number) => { - setToggledStats(prev => [...prev.slice(0, statIndex), !prev[statIndex], ...prev.slice(statIndex + 1)]) - } - - return ( -
-
-
-
handleToggle(0)} - > -

- -

-

- - Plugs -

-
-
handleToggle(1)} - > -

- -

-

- - Forks -

-
-
-
-
handleToggle(2)} - > -

- -

-

- - Views -

-
-
handleToggle(3)} - > -

- -

-

- - Referrals -

-
-
-
-
-
- {Array.from({ length: stats.length }).map((_, i) => ( - (toggledStats[j] ? 0 : stat))} - max={max} - onHover={setHoveredPeriod} - /> - ))} -
-
-

{startDate}

-

{endDate}

-
-
-
- ) +export const ProfileStats = () => { + const [hoveredPeriod, setHoveredPeriod] = useState(undefined) + const [toggledStats, setToggledStats] = useState([false, false, false, false]) + + const { data: statsData } = api.user.stats.useQuery(undefined) + + const stats: number[][] = + statsData?.periods.map((_, index) => [ + statsData.counts.plugs[index] ?? 0, + statsData.counts.forks[index] ?? 0, + statsData.counts.views[index] ?? 0, + statsData.counts.referrals[index] ?? 0 + ]) ?? Array(4).fill([0, 0, 0, 0]) + + const max = Math.max(...stats.map(period => period.reduce((sum: number, value: number) => sum + (value ?? 0), 0))) + const currentStats = hoveredPeriod !== undefined ? stats[hoveredPeriod] : stats[stats.length - 1] + + const startDate = statsData?.periods[0]?.weekStart + ? new Date(statsData.periods[0].weekStart).toLocaleDateString("en-US", { + month: "2-digit", + year: "2-digit" + }) + : "09/24" + const endDate = statsData?.periods[statsData.periods.length - 1]?.weekEnd + ? new Date(statsData.periods[statsData.periods.length - 1].weekEnd).toLocaleDateString("en-US", { + month: "2-digit", + year: "2-digit" + }) + : "10/13" + + const handleToggle = (statIndex: number) => { + setToggledStats(prev => [...prev.slice(0, statIndex), !prev[statIndex], ...prev.slice(statIndex + 1)]) + } + + return ( +
+
+
+
handleToggle(0)} + > +

+ +

+

+ + Plugs +

+
+
handleToggle(1)} + > +

+ +

+

+ + Forks +

+
+
+
+
handleToggle(2)} + > +

+ +

+

+ + Views +

+
+
handleToggle(3)} + > +

+ +

+

+ + Referrals +

+
+
+
+
+
+ {Array.from({ length: stats.length }).map((_, i) => ( + (toggledStats[j] ? 0 : stat))} + max={max} + onHover={setHoveredPeriod} + /> + ))} +
+
+

{startDate}

+

{endDate}

+
+
+
+ ) } -export const ColumnStats: FC<{ index: number }> = () => { - const { data: session } = useSession() - const { socket } = useSocket() - const [copied, setCopied] = useState(false) - - if (!socket || !session?.user.id) return null - - const handleCopy = async () => { - try { - await navigator.clipboard.writeText(window.location.href) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } catch (err) { - console.error("Failed to copy:", err) - } - } - - return ( -
- - -
- } - placeholder="Copy Referral Link" - search={socket?.identity?.referralCode ?? ""} - handleSearch={() => { }} - /> - -
-
- ) +// TODO: I commented out the referral link copy functionality for now since it's not implemented yet +// // on the database side. I am going to use this opportunity in a few days to think about +// // it and make sure that things are implemented the way we want. + +export const ColumnStats: FC<{ id: string }> = () => { + /* const user = useUser() */ + + /* const [copied, setCopied] = useState(false) */ + + /* const handleCopy = async () => { */ + /* try { */ + /* await navigator.clipboard.writeText(window.location.href) */ + /* setCopied(true) */ + /* setTimeout(() => setCopied(false), 2000) */ + /* } catch (err) { */ + /* console.error("Failed to copy:", err) */ + /* } */ + /* } */ + + return ( +
+ + + {/*
*/} + {/* } */} + {/* placeholder="Copy Referral Link" */} + {/* search={socket?.identity?.referralCode ?? ""} */} + {/* handleSearch={() => { }} */} + {/* /> */} + {/* */} + {/*
*/} +
+ ) } diff --git a/packages/app/components/app/columns/utils/column-wallet.tsx b/packages/app/components/app/columns/utils/column-wallet.tsx index 4442397da..56586b2b7 100644 --- a/packages/app/components/app/columns/utils/column-wallet.tsx +++ b/packages/app/components/app/columns/utils/column-wallet.tsx @@ -1,16 +1,17 @@ -import { useSession } from "next-auth/react" -import { FC } from "react" +import { ProfileStats } from "./column-stats" -import { SocketAssets } from "@/components/app/sockets/assets" -import { useSocket } from "@/state/authentication" +type ColumnWalletProps = { id: string } +type ColumnWalletType = React.FC> -export const ColumnWallet: FC<{ index: number }> = () => { - const { data: session } = useSession() - const { socket } = useSocket() +export const ColumnWallet: ColumnWalletType = () => { + return ( +
+
+ {/* TODO: Put something here -- Current idea is their founding ticket. */} +
+
- return ( -
- -
- ) + +
+ ) } diff --git a/packages/app/components/app/frames/account.tsx b/packages/app/components/app/frames/account.tsx index 9316ff3c4..4a87cdd49 100644 --- a/packages/app/components/app/frames/account.tsx +++ b/packages/app/components/app/frames/account.tsx @@ -3,82 +3,83 @@ import { signOut } from "next-auth/react" import { useDisconnect } from "wagmi" import BlockiesSvg from "blockies-react-svg" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { StatCard } from "@/components/app/cards/stat" import { Frame } from "@/components/app/frames/base" import { Image } from "@/components/app/utils/image" import { Button } from "@/components/shared/buttons/button" import { Counter } from "@/components/shared/utils/counter" -import { useSocket } from "@/state/authentication" -import { columnByIndexAtom, COLUMNS, isFrameAtom } from "@/state/columns" +/* import { useSocket } from "@/state/authentication" */ +import { COLUMNS, isFrameAtom, useColumn } from "@/state/columns" -// NOTE: This is only accessible on the mobile view so the index will always be -1. +// NOTE: This is only accessible on the mobile view so the id will always be COLUMNS.IDS.MOBILE. export const AccountFrame = () => { - const { socket, avatar } = useSocket() - const { disconnect } = useDisconnect({ - mutation: { - onSuccess: () => signOut({ callbackUrl: "/" }) - } - }) - - const [column] = useAtom(columnByIndexAtom(COLUMNS.MOBILE_INDEX)) - const isFrame = useAtomValue(isFrameAtom)(column, "account") - - if (!socket) return null - - return ( - - ) : ( - - ) - } - label="Wallet" - visible={isFrame} - > -
-
- - - - ETH - -

Fees Earned

-
- - - -

Points Earned

-
-
- -

- Plugs Used - -

-

- Runs - -

-

- Users Onboarded - -

-
- -
- - -
- - ) + return (<>) + /* const { socket, avatar } = useSocket() */ + /* const { disconnect } = useDisconnect({ */ + /* mutation: { */ + /* onSuccess: () => signOut({ callbackUrl: "/" }) */ + /* } */ + /* }) */ + /**/ + /* const column = useColumn(COLUMNS.IDS.MOBILE) */ + /* const isFrame = useAtomValue(isFrameAtom)(column, "account") */ + /**/ + /* if (!socket) return null */ + /**/ + /* return ( */ + /* */ + /* ) : ( */ + /* */ + /* ) */ + /* } */ + /* label="Wallet" */ + /* visible={isFrame} */ + /* > */ + /*
*/ + /*
*/ + /* */ + /* */ + /* */ + /* ETH */ + /* */ + /*

Fees Earned

*/ + /*
*/ + /**/ + /* */ + /* */ + /*

Points Earned

*/ + /*
*/ + /*
*/ + /**/ + /*

*/ + /* Plugs Used */ + /* */ + /*

*/ + /*

*/ + /* Runs */ + /* */ + /*

*/ + /*

*/ + /* Users Onboarded */ + /* */ + /*

*/ + /*
*/ + /**/ + /*
*/ + /* */ + /* */ + /*
*/ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/activity/execution/frame.tsx b/packages/app/components/app/frames/activity/execution/frame.tsx index 3725f1544..75ed4d8f0 100644 --- a/packages/app/components/app/frames/activity/execution/frame.tsx +++ b/packages/app/components/app/frames/activity/execution/frame.tsx @@ -12,244 +12,251 @@ import { Accordion } from "@/components/shared/utils/accordion" import { Counter } from "@/components/shared/utils/counter" import { DateSince } from "@/components/shared/utils/date-since" import { TimeUntil } from "@/components/shared/utils/time-until" -import { useActivities } from "@/contexts" -import { cardColors, ChainId, cn, formatFrequency, formatTitle, getChainName } from "@/lib" +/* import { useActivities } from "@/contexts" */ +import { ChainId } from "@/lib/constants/chains" +import { cardColors } from "@/lib/constants/colors" +import { getChainName } from "@/lib/functions/blockchain" +import { formatFrequency, formatTitle } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, COLUMNS, isFrameAtom, useColumnActions } from "@/state/columns" +import { COLUMNS, isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { ChainImage } from "../../../sockets/chains/chain.image" const ITEMS_PER_PAGE = 10 export const ExecutionFrame: FC<{ - index: number - icon: JSX.Element - activity: RouterOutputs["plugs"]["activity"]["get"][number] -}> = ({ index, icon, activity }) => { - const loadMoreRef = useRef(null) - - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${activity?.id}-activity` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, navigate } = useColumnActions(index, frameKey) - - const { handle: activityHandle } = useActivities() - const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE) - - const actions = activity?.inputs ?? [] - - const { visibleRuns, totalRuns, hasMore } = useMemo( - () => ({ - visibleRuns: activity?.runs?.slice(0, visibleCount) ?? [], - totalRuns: activity?.runs?.length ?? 0, - hasMore: visibleCount < (activity?.runs?.length ?? 0) - }), - [activity?.runs, visibleCount] - ) - - useEffect(() => { - if (!isFrame || !activity) return - setVisibleCount(ITEMS_PER_PAGE) - - const observer = new IntersectionObserver( - entries => { - if (entries[0].isIntersecting && hasMore) { - setVisibleCount(prev => Math.min(prev + ITEMS_PER_PAGE, totalRuns)) - } - }, - { threshold: 0.1 } - ) - - if (loadMoreRef.current) { - observer.observe(loadMoreRef.current) - } - - return () => observer.disconnect() - }, [isFrame, activity, hasMore, totalRuns]) - - if (!activity) return null - - return ( - <> - -
- {activity.inputs.map(input => formatTitle(input.action)).join(", ")} - - } - visible={isFrame} - hasOverlay={true} - > -
- - -
- - {activity.status !== "completed" && ( - - )} -
- -
-

Details

-
-
- -
-

- - - Status - {" "} - {formatTitle(activity.status)} -

- -

- - Chain - - - {getChainName(activity.chainId as ChainId)} - -

- -
-

Schedule

-
-
-

- - - Frequency - {" "} - {formatFrequency(activity.frequency)} -

-

- - - Start At - {" "} - -

- {activity.endAt && ( -

- - - Stop At - {" "} - -

- )} -
- - {(activity.status !== "paused" || activity.runs.length > 0) && ( -
-

Runs

-
-
- )} - -
- {activity.status !== "paused" && activity.nextSimulationAt && ( - -
- -
-
-

- Run{" "} - - (#{activity.runs.length + 1}) - -

- -
-
-

Upcoming

-

- -

-
-
-
-
- )} - - {visibleRuns.map((run, index) => ( - frame(`${run.id}-simulation`)}> -
- -
-
-

- Run{" "} - - (#{activity.runs.length - index}) - -

- -
-
-

{formatTitle(run.status)}

-

- -

-
-
-
-
- ))} - - {hasMore &&
} -
-
- - - ) + id: string + icon: JSX.Element + /* activity: RouterOutputs["plugs"]["activity"]["get"][number] */ +}> = ({ id, icon }) => { + return (<>) + /* const loadMoreRef = useRef(null) */ + /**/ + /* const [column] = useAtom(columnByIndexAtom(index)) */ + /* const frameKey = `${activity?.id}-activity` */ + /* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ + /* const { frame, navigate } = useColumnActions(index, frameKey) */ + /**/ + /* const { handle: activityHandle } = useActivities() */ + /* const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE) */ + /**/ + /* const actions = activity?.inputs ?? [] */ + /**/ + /* const { visibleRuns, totalRuns, hasMore } = useMemo( */ + /* () => ({ */ + /* visibleRuns: activity?.runs?.slice(0, visibleCount) ?? [], */ + /* totalRuns: activity?.runs?.length ?? 0, */ + /* hasMore: visibleCount < (activity?.runs?.length ?? 0) */ + /* }), */ + /* [activity?.runs, visibleCount] */ + /* ) */ + /**/ + /* useEffect(() => { */ + /* if (!isFrame || !activity) return */ + /* setVisibleCount(ITEMS_PER_PAGE) */ + /**/ + /* const observer = new IntersectionObserver( */ + /* entries => { */ + /* if (entries[0].isIntersecting && hasMore) { */ + /* setVisibleCount(prev => Math.min(prev + ITEMS_PER_PAGE, totalRuns)) */ + /* } */ + /* }, */ + /* { threshold: 0.1 } */ + /* ) */ + /**/ + /* if (loadMoreRef.current) { */ + /* observer.observe(loadMoreRef.current) */ + /* } */ + /**/ + /* return () => observer.disconnect() */ + /* }, [isFrame, activity, hasMore, totalRuns]) */ + /**/ + /* if (!activity) return null */ + /**/ + /* return ( */ + /* <> */ + /* */ + /*
*/ + /* {activity.inputs.map(input => formatTitle(input.action)).join(", ")} */ + /* */ + /* } */ + /* visible={isFrame} */ + /* hasOverlay={true} */ + /* > */ + /*
*/ + /* */ + /**/ + /*
*/ + /* */ + /* {activity.status !== "completed" && ( */ + /* */ + /* )} */ + /*
*/ + /**/ + /*
*/ + /*

Details

*/ + /*
*/ + /*
*/ + /**/ + /*
*/ + /*

*/ + /* */ + /* */ + /* Status */ + /* {" "} */ + /* {formatTitle(activity.status)} */ + /*

*/ + /**/ + /*

*/ + /* */ + /* Chain */ + /* */ + /* */ + /* {getChainName(activity.chainId as ChainId)} */ + /* */ + /*

*/ + /**/ + /*
*/ + /*

Schedule

*/ + /*
*/ + /*
*/ + /*

*/ + /* */ + /* */ + /* Frequency */ + /* {" "} */ + /* {formatFrequency(activity.frequency)} */ + /*

*/ + /*

*/ + /* */ + /* */ + /* Start At */ + /* {" "} */ + /* */ + /*

*/ + /* {activity.endAt && ( */ + /*

*/ + /* */ + /* */ + /* Stop At */ + /* {" "} */ + /* */ + /*

*/ + /* )} */ + /*
*/ + /**/ + /* {(activity.status !== "paused" || activity.runs.length > 0) && ( */ + /*
*/ + /*

Runs

*/ + /*
*/ + /*
*/ + /* )} */ + /**/ + /*
*/ + /* {activity.status !== "paused" && activity.nextSimulationAt && ( */ + /* */ + /*
*/ + /* */ + /*
*/ + /*
*/ + /*

*/ + /* Run{" "} */ + /* */ + /* (#{activity.runs.length + 1}) */ + /* */ + /*

*/ + /* */ + /*
*/ + /*
*/ + /*

Upcoming

*/ + /*

*/ + /* */ + /*

*/ + /*
*/ + /*
*/ + /*
*/ + /*
*/ + /* )} */ + /**/ + /* {visibleRuns.map((run, index) => ( */ + /* frame(`${run.id}-simulation`)}> */ + /*
*/ + /* */ + /*
*/ + /*
*/ + /*

*/ + /* Run{" "} */ + /* */ + /* (#{activity.runs.length - index}) */ + /* */ + /*

*/ + /* */ + /*
*/ + /*
*/ + /*

{formatTitle(run.status)}

*/ + /*

*/ + /* */ + /*

*/ + /*
*/ + /*
*/ + /*
*/ + /*
*/ + /* ))} */ + /**/ + /* {hasMore &&
} */ + /*
*/ + /*
*/ + /* */ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/activity/simulation/frame.tsx b/packages/app/components/app/frames/activity/simulation/frame.tsx index 7eaf526e9..259291c38 100644 --- a/packages/app/components/app/frames/activity/simulation/frame.tsx +++ b/packages/app/components/app/frames/activity/simulation/frame.tsx @@ -1,125 +1,132 @@ import { FC, useCallback } from "react" -import { Bell, CircleDollarSign, Clock10, MessageCircleQuestionIcon, Share } from "lucide-react" - -import { Frame } from "@/components/app/frames/base" -import { ActionPreview } from "@/components/app/plugs/actions/action-preview" -import { ActivityIcon } from "@/components/app/sockets/activity/activity-item" -import { Button } from "@/components/shared/buttons/button" -import { Counter } from "@/components/shared/utils/counter" -import { DateSince } from "@/components/shared/utils/date-since" -import { formatTitle } from "@/lib" +/* import { Bell, CircleDollarSign, Clock10, MessageCircleQuestionIcon, Share } from "lucide-react" */ +/**/ +/* import { Frame } from "@/components/app/frames/base" */ +/* import { ActionPreview } from "@/components/app/plugs/actions/action-preview" */ +/* import { ActivityIcon } from "@/components/app/sockets/activity/activity-item" */ +/* import { Button } from "@/components/shared/buttons/button" */ +/* import { Counter } from "@/components/shared/utils/counter" */ +/* import { DateSince } from "@/components/shared/utils/date-since" */ +/* import { formatTitle } from "@/lib" */ import { RouterOutputs } from "@/server/client" -import { useActions } from "@/state/actions" -import { useColumnActions } from "@/state/columns" +/* import { useActions } from "@/state/actions" */ +/* import { useColumnActions } from "@/state/columns" */ export const SimulationFrame: FC<{ index: number - activity: RouterOutputs["plugs"]["activity"]["get"][number] | undefined + activity: RouterOutputs["plugs"]["activity"]["get"] | undefined simulationId: string | undefined }> = ({ index, activity, simulationId }) => { - const [solverActions] = useActions() - - const { frame } = useColumnActions(index, `${activity?.id}-activity`) - - const simulation = activity?.runs.find(sim => sim.id === simulationId) - const actions = activity?.inputs - - const handleShare = useCallback(() => { - if (!activity || !activity.plug) return - - try { - const cleanedActions = activity.inputs.map(action => ({ - protocol: action.protocol?.toLowerCase?.(), - sentence: solverActions[action.protocol]["schema"][action.action].sentence - })) - const params = new URLSearchParams({ - name: activity.plug?.name.slice(0, 100), // Reasonable name length - protocols: cleanedActions.map(a => a.protocol).join(","), - sentences: cleanedActions.map(a => a.sentence).join(",") - }) - const url = `/api/canvas/opengraph?${params.toString()}` - - console.log("Debug - OpenGraph URL:", url) - window.open(url, "_blank") - } catch (e) { - console.error("Share generation failed:", e, { - plugId: activity.plug?.id, - actions: activity.inputs - }) - } - }, [activity, solverActions]) - - if (!activity || !simulation) return null - - return ( - } - label={`Simulation ${formatTitle(simulation.status)}`} - visible={simulation !== undefined} - handleBack={() => frame()} - hasOverlay={true} - > - - - {simulation.error && ( -

- Warning: {simulation.error} -

- )} - - {activity.plug && simulation.status === "success" ? ( - - ) : simulation.status !== "success" && ( - - )} - -
-

Details

-
-
- -

- - Status{" "} - {formatTitle(simulation.status)} -

- -

- - Simulated{" "} - - - -

- - {simulation.gasEstimate && ( -

- - Fee{" "} - - - -

- )} - - ) + return (<>) + + /* const [solverActions] = useActions() */ + /**/ + /* const { frame } = useColumnActions(index, `${activity?.id}-activity`) */ + /**/ + /* const simulation = activity?.runs.find(sim => sim.id === simulationId) */ + /* const actions = activity?.inputs */ + /**/ + /* const handleShare = useCallback(() => { */ + /* if (!activity || !activity.plug) return */ + /**/ + /* try { */ + /* const cleanedActions = activity.inputs.map(action => ({ */ + /* protocol: action.protocol?.toLowerCase?.(), */ + /* sentence: solverActions[action.protocol]["schema"][action.action].sentence */ + /* })) */ + /* const params = new URLSearchParams({ */ + /* name: activity.plug?.name.slice(0, 100), // Reasonable name length */ + /* protocols: cleanedActions.map(a => a.protocol).join(","), */ + /* sentences: cleanedActions.map(a => a.sentence).join(",") */ + /* }) */ + /* const url = `/api/canvas/opengraph?${params.toString()}` */ + /**/ + /* console.log("Debug - OpenGraph URL:", url) */ + /* window.open(url, "_blank") */ + /* } catch (e) { */ + /* console.error("Share generation failed:", e, { */ + /* plugId: activity.plug?.id, */ + /* actions: activity.inputs */ + /* }) */ + /* } */ + /* }, [activity, solverActions]) */ + /**/ + /* if (!activity || !simulation) return null */ + /**/ + /* return ( */ + /* } */ + /* label={`Simulation ${formatTitle(simulation.status)}`} */ + /* visible={simulation !== undefined} */ + /* handleBack={() => frame()} */ + /* hasOverlay={true} */ + /* > */ + /* */ + /**/ + /* {simulation.error && ( */ + /*

*/ + /* Warning: {simulation.error} */ + /*

*/ + /* )} */ + /**/ + /* {activity.plug && simulation.status === "success" ? ( */ + /* */ + /* ) : ( */ + /* simulation.status !== "success" && ( */ + /* */ + /* ) */ + /* )} */ + /**/ + /*
*/ + /*

Details

*/ + /*
*/ + /*
*/ + /**/ + /*

*/ + /* */ + /* Status{" "} */ + /* {formatTitle(simulation.status)} */ + /*

*/ + /**/ + /*

*/ + /* */ + /* Simulated{" "} */ + /* */ + /* */ + /* */ + /*

*/ + /**/ + /* {simulation.gasEstimate && ( */ + /*

*/ + /* */ + /* Fee{" "} */ + /* */ + /* */ + /* */ + /*

*/ + /* )} */ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/address.tsx b/packages/app/components/app/frames/address.tsx new file mode 100644 index 000000000..209829271 --- /dev/null +++ b/packages/app/components/app/frames/address.tsx @@ -0,0 +1,57 @@ +import { FC, memo } from "react" + +import { useColumn, useColumnActions } from "@/state/columns" +import { usePositions } from "@terminallyonline/plug-sdk" + +import { RelationshipFrame } from "./relationship" +import { RelationshipsFrame } from "./relationships" +import { TokenFrame } from "./token" +import { useUser } from "@/state/authentication" + +type AddressFrameProps = { id: string } + +export const AddressFrame: FC = memo(({ id }) => { + const { address: userAddress, isAuthenticated } = useUser() + + const column = useColumn(id) + + const { frame } = useColumnActions(id) + + const [namespace, chainId, contractAddress, tokenId] = column?.frame?.split(":") || [] + + const frameKey = `token:${chainId}:${contractAddress}` + const { data: positions } = usePositions(userAddress, { + enabled: !!(userAddress && isAuthenticated && namespace === "token" && chainId && contractAddress), + filter: { + chain_id: [Number(chainId)], + address: [contractAddress], + token_id: tokenId ? [tokenId] : undefined + }, + }) + + const token = positions[0] + + if (namespace !== "token" || !token) return null + + return ( + <> + {token.tokens && token.tokens.length > 0 && /^-?\d+$/.test(tokenId) ? ( + + ) : token.tokens && token.tokens.length > 0 ? : } + + ) +}) + +AddressFrame.displayName = "AddressFrame" diff --git a/packages/app/components/app/frames/assets/collectible/frame.tsx b/packages/app/components/app/frames/assets/collectible/frame.tsx deleted file mode 100644 index 693718d29..000000000 --- a/packages/app/components/app/frames/assets/collectible/frame.tsx +++ /dev/null @@ -1,398 +0,0 @@ -import { FC, memo, useEffect, useState } from "react" -import ReactMarkdown from "react-markdown" - -import { getAddress } from "viem" - -import { - BookDashed, - BookText, - ChevronDown, - Globe, - Hash, - Instagram, - MapIcon, - MessageCircle, - Send, - Ship, - Twitter, - Waypoints -} from "lucide-react" - -import { useAtom, useAtomValue } from "jotai" - -import { TransferFrame } from "@/components/app/frames/assets/transfer/frame" -import { Frame } from "@/components/app/frames/base" -import { ChainImage } from "@/components/app/sockets/chains/chain.image" -import { CollectibleImage } from "@/components/app/sockets/collectibles/collectible-image" -import { - cn, - formatAddress, - formatTitle, - formatTokenStandard, - getBlockExplorerAddress, - getChainId, - getTextColor -} from "@/lib" -import { api, RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" - -type Traits = Array<{ trait_type: string; value: string }> - -export const CollectibleFrame: FC<{ - index: number - collection: NonNullable[number] - collectible: NonNullable[number]["collectibles"][number] -}> = memo(({ index, collection, collectible }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${collection.address}-${collection.chain}-${collectible?.tokenId}` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, transfer } = useColumnActions(index, frameKey) - - const { data: metadata } = api.socket.balances.metadata.useQuery( - { - type: "ERC721", - address: collectible.collectionAddress, - chain: collectible.collectionChain, - tokenId: collectible.tokenId - }, - { enabled: isFrame } - ) - - const [expanded, setExpanded] = useState(false) - - const textColor = getTextColor(metadata?.color ?? "#ffffff") - - useEffect(() => { - if (isFrame === false) setExpanded(false) - }, [isFrame]) - - return ( - <> - -
-
-
- } - label={collection.name} - visible={isFrame} - hasChildrenPadding={false} - hasOverlay - > -
- - -

{collectible?.name}

- - {collection.description && ( - <> -
-

{children}

, - a: ({ node, children, ...props }) => ( - { - e.preventDefault() - e.stopPropagation() - if (props.href) { - window.open(props.href, "_blank", "noopener,noreferrer") - } - }} - > - {children} - - ) - }} - > - {expanded - ? `${collection.description.trim()}` - : `${collection.description.trim().slice(0, 120)}...`} -
-
- - {collection.description.trim().length > 120 && ( - <> - - - )} - - )} -
- -
- -
- - {metadata?.traits && (metadata?.traits?.length ?? 0) > 0 && ( -
- {(metadata.traits as Traits).map((trait, index) => ( -
-

- {formatTitle(trait.trait_type)} -

-

- {trait.value} -

-
- ))} -
- )} - -
-
-
-

Details

-
-
- -
-

- - Address - {formatAddress(getAddress(collectible.collectionAddress))} -

-

- - Identifier - {collectible.tokenId.length > 11 - ? formatAddress(collectible.tokenId) - : collectible.tokenId} -

- {collectible.interface === "ERC1155" && ( -

- - Balance - {collectible.amount} -

- )} -

- - Chain - - - {formatTitle(collection.chain)} - -

-

- - Token Standard - {formatTokenStandard(collectible.interface)} -

-
- -
-

Links

-
-
- -
- - - Explorer - - - {collection.projectUrl && ( - - - Website - - )} - - {collection.discordUrl && ( - - - Discord - - )} - - {collection.twitterUsername && ( - - - Twitter - - )} - - {collection.telegramUrl && ( - - - Telegram - - )} - - {collection.instagramUsername && ( - - - Instagram - - )} - - {collection.openseaUrl && ( - - - Opensea - - )} - - {collection.wikiUrl && ( - - - Wiki - - )} -
-
-
- - - - - ) -}) - -CollectibleFrame.displayName = "CollectibleFrame" diff --git a/packages/app/components/app/frames/assets/position/frame.tsx b/packages/app/components/app/frames/assets/position/frame.tsx index 8012c321b..fd77466aa 100644 --- a/packages/app/components/app/frames/assets/position/frame.tsx +++ b/packages/app/components/app/frames/assets/position/frame.tsx @@ -1,205 +1,142 @@ import React, { FC, useMemo, useState } from "react" -import { ExternalLink, EyeOff } from "lucide-react" +import { ExternalLink } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" -import { SocketTokenPriceChart } from "@/components/app/sockets/tokens/token-chart" import { TokenImage } from "@/components/app/sockets/tokens/token-image" -import { Image } from "@/components/app/utils/image" +import { Accordion } from "@/components/shared/utils/accordion" import { Counter } from "@/components/shared/utils/counter" -import { chains, cn, formatTitle, getChainId, getTextColor } from "@/lib" +import { getTextColor } from "@/lib/functions/color" +import { getZerionTokenIconUrl } from "@/lib/functions/zerion" +import { cn } from "@/lib/utils/style" import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" +import { COLUMNS, isFrameAtom, useColumn } from "@/state/columns" export const PositionFrame: FC<{ - index: number - protocol: RouterOutputs["socket"]["balances"]["positions"]["protocols"][number] -}> = ({ index, protocol }) => { - const { positions } = protocol - - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${protocol.name}-position` + id: string + protocols?: NonNullable["data"] +}> = ({ id, protocols }) => { + const protocol = protocols?.at(0) + const column = useColumn(id) + const frameKey = `${protocol?.relationships.dapp?.data.id ?? ""}-position` const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const [color, setColor] = useState("") - const [colors, setColors] = useState>({}) - const [excludedKeys, setExcludedKeys] = useState>([]) + const [color, setColor] = useState("#ffffff") + const textColor = getTextColor(color) - const textColor = getTextColor(color ?? "#ffffff") + const positions = protocols const groupedPositions = useMemo(() => { - const grouped: Record< - string, - Array - > = {} - + if (!positions) return {} + const grouped: Record = {} positions.forEach(position => { - if (grouped[position.type] === undefined) { - grouped[position.type] = [] - } - - grouped[position.type].push(position) + const type = position.attributes?.position_type || "unknown" + if (!grouped[type]) grouped[type] = [] + grouped[type].push(position) }) - return grouped }, [positions]) - const keys = useMemo(() => { - return positions - .filter(position => Boolean(position.fungible)) - .map(position => { - const contract = - position.fungible.implementations.find(implementation => implementation.chain === position.chain) - ?.contract ?? "" - - return { - chain: position.chain, - contract - } - }) - .filter(position => position.contract !== "") - .map(position => `${position.chain}:${position.contract}`) - .filter(key => !excludedKeys.includes(key)) - }, [positions, excludedKeys]) - return ( } - label={protocol.name} + label={protocol?.attributes?.application_metadata?.name ?? ""} + icon={ + + } visible={isFrame} hasChildrenPadding={false} hasOverlay > - - {keys.length > 0 && ( - - )} - -
0 && "pt-4")}> - - - Manage - - - {Object.keys(groupedPositions).map(type => ( +
+
+ {id === COLUMNS.IDS.SIDEBAR && ( + + + Manage + + )} + + {Object.entries(groupedPositions).map(([type, group]) => (
-

{formatTitle(type)}

-
-

Value

+

{type.charAt(0).toUpperCase() + type.slice(1)}

+
- -
- {groupedPositions[type].map((position, positionIndex) => { - if (!position.fungible) return null - - const implementation = position.fungible.implementations.find( - implementation => implementation.chain === position.chain - ) - const key = `${position.chain}:${implementation?.contract}` - +
+ {group.map((position, idx) => { + const change = position.attributes?.changes?.percent_1d return ( -
-
- - -
-
-

{position.fungible.name}

-
-

$

- +
+
+
+

+ {position.attributes.fungible_info.name} +

+
+ $ +
- -
- {position.chain} -
- +
+
+
+ +

+ {position.attributes.fungible_info.symbol?.toUpperCase()} +

+
0 - ? "text-plug-green" - : "text-red-500" + : change >= 0 + ? "text-chart-green" + : "text-plug-red" )} > <> - {position.change !== undefined ? ( + {typeof change === "number" ? ( <> - -

%

+ % ) : ( "-" @@ -209,14 +146,14 @@ export const PositionFrame: FC<{
-
+ ) })}
))}
- +
) } diff --git a/packages/app/components/app/frames/assets/swap/amount/frame.tsx b/packages/app/components/app/frames/assets/swap/amount/frame.tsx index 6d954dc26..e212b5b2f 100644 --- a/packages/app/components/app/frames/assets/swap/amount/frame.tsx +++ b/packages/app/components/app/frames/assets/swap/amount/frame.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo, useState } from "react" import { formatUnits, getAddress } from "viem" +import { useSendTransaction } from "wagmi" import { ArrowRight, Bell, CheckCircle, CircleDollarSign, Loader, TriangleRight, Waypoints } from "lucide-react" @@ -8,405 +9,441 @@ import { useAtom, useAtomValue } from "jotai" import { SwapAmountInput } from "@/components/app/frames/assets/swap/amount/input" import { Frame } from "@/components/app/frames/base" +import { ChainImage } from "@/components/app/sockets/chains/chain.image" import { TokenImage } from "@/components/app/sockets/tokens/token-image" import { Counter } from "@/components/shared/utils/counter" -import { cn, getChainId, getChainName, getTextColor, NATIVE_TOKEN_ADDRESS, useDebounceInline } from "@/lib" -import { api, RouterOutputs } from "@/server/client" -import { useSocket } from "@/state/authentication" -import { columnByIndexAtom, COLUMNS, isFrameAtom, useColumnActions } from "@/state/columns" - -import { useSendTransaction } from "wagmi" -import { ChainImage } from "@/components/app/sockets/chains/chain.image" -import { ScrollingError } from "../../scrolling-error" +import { NATIVE_TOKEN_ADDRESS } from "@/lib/constants" +import { getChainId, getChainName } from "@/lib/functions/blockchain" +import { getTextColor } from "@/lib/functions/color" +import { getZerionTokenIconUrl, ZerionFungible, ZerionPosition } from "@/lib/functions/zerion" +import { useStateDebounce } from "@/lib/hooks/useDebounce" +import { cn } from "@/lib/utils/style" import { useAllowance } from "@/lib/hooks/chain/useApproval" +import { api } from "@/server/client" +import { useUser } from "@/state/authentication" +import { COLUMNS, isFrameAtom, useColumn, useColumnActions } from "@/state/columns" -type Token = - | NonNullable["tokens"][number] - | NonNullable[number] +import { ScrollingError } from "../../scrolling-error" type SwapAmountFrameProps = { - index: number - tokenIn: Token - tokenOut: Token + id: string + tokenIn: ZerionFungible + tokenOut: ZerionPosition } -export const SwapAmountFrame = ({ index, tokenIn, tokenOut }: SwapAmountFrameProps) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${tokenOut.symbol}-${tokenIn.symbol}-swap-amount` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, navigate } = useColumnActions(index, frameKey) - - const { socket } = useSocket() - const { error, sendTransaction, isPending } = useSendTransaction() - - const { tokenOutImplementation, tokenInImplementation } = useMemo(() => { - const tokenOutImplementation = tokenOut.implementations.find(implementation => implementation.chain === "base") - const tokenInImplementation = tokenIn.implementations.find(implementation => implementation?.chain === "base") - - return { tokenOutImplementation, tokenInImplementation } - }, [tokenIn, tokenOut]) - - const [step, setStep] = useState(0) - const [tokenOutColor, setTokenOutColor] = useState("#000000") - const [tokenInColor, setTokenInColor] = useState("#000000") - const [amounts, setAmounts] = useState({ - [tokenOut.symbol]: { - precise: ((tokenOutImplementation?.balance ?? 0) / 2).toString(), - percentage: (tokenOutImplementation?.balance ?? 0) / 2 === 0 ? 0 : 50 - }, - [tokenIn.symbol]: { - precise: "0", - percentage: 0 - } - }) - - const isSufficientBalance = - tokenOutImplementation && - (tokenOutImplementation?.balance ?? 0) > 0 && - (tokenOutImplementation?.balance ?? 0) >= Number(amounts[tokenOut.symbol].precise) - - const isEOA = column && column.index === COLUMNS.SIDEBAR_INDEX - const from = socket ? (column && column.index === COLUMNS.SIDEBAR_INDEX ? socket.id : socket.socketAddress) : "" - const request = useDebounceInline<{ - chainId: number, - from: string | `0x${string}` - inputs: Array<{ - protocol: string, - action: string, - [key: string]: string - }>, options?: { isEOA?: boolean, simulate?: boolean } - }>({ - chainId: getChainId(tokenOutImplementation?.chain ?? "base"), - from, - inputs: [ - { - protocol: "plug", - action: "swap", - amount: amounts[tokenOut.symbol].precise, - token: `${getAddress(tokenOutImplementation?.contract ?? NATIVE_TOKEN_ADDRESS)}:${tokenOutImplementation?.decimals ?? 18}:${20}`, - tokenIn: `${getAddress(tokenInImplementation?.contract ?? NATIVE_TOKEN_ADDRESS)}:${tokenInImplementation?.decimals ?? 18}:${20}` - } - ], - options: { - isEOA: isEOA, - simulate: true - } - }) - - const { data: intent, error: intentError, isLoading } = api.solver.actions.intent.useQuery(request, { - enabled: - isFrame && - !!tokenInImplementation && - !!tokenOutImplementation && - amounts[tokenOut.symbol].precise !== "0" && - isSufficientBalance && - !!socket, - }) - - const isReady = - amounts[tokenOut.symbol].precise !== "0" && !intentError && !isLoading && isSufficientBalance && !isPending - const meta = intent ? intent.transactions[intent.transactions.length - 1].meta : null - - const { approval } = useAllowance({ - token: getAddress(tokenOutImplementation?.contract ?? NATIVE_TOKEN_ADDRESS), - owner: from, - spender: meta?.settlementAddress - }) - - const toggleSavedMutation = api.plugs.activity.toggleSaved.useMutation() - const handleTransactionOffchain = useCallback(async () => { - if (!intent) return - - if (step === 0) toggleSavedMutation.mutateAsync({ id: intent.intentId }) +const getFungibleImplementation = ( + implementations: ZerionPosition["attributes"]["fungible_info"]["implementations"], + chainName: string +) => { + const implementation = implementations.find(implementation => implementation.chain_id === chainName) + const address = getAddress(implementation?.address ?? NATIVE_TOKEN_ADDRESS) + const decimals = implementation?.decimals ?? 18 + const lookup = `${address}:${decimals}:${20}` - const handleTransactionRedirect = () => { - navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) - frame(`${intent.intentId}-activity`) - } - - if (step === intent.transactions.length - 1) handleTransactionRedirect() - else setStep(prev => prev + 1) - }, [intent, step, toggleSavedMutation, navigate, frame, index]) - - - const isApproved = approval > BigInt(meta?.sellTokens[getAddress(tokenOutImplementation?.contract ?? "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE")]?.amount ?? "0") - const handleTransactionOnchain = useCallback(async () => { - if (!column || !intent) return - - const transactionAtStep = isApproved && intent.transactions.length > 1 ? 1 : step - - if (column.index === COLUMNS.SIDEBAR_INDEX) - sendTransaction({ - to: intent.transactions[transactionAtStep].to, - data: intent.transactions[transactionAtStep].data, - value: intent.transactions[transactionAtStep].value - }, { - onSuccess: handleTransactionOffchain - }) - }, [column, intent, step, isApproved, sendTransaction, handleTransactionOffchain]) - - return ( - -
- -
-
- -
-
- } - label={ - - {tokenOut.symbol} - - {tokenIn.symbol} - - } - visible={isFrame} - handleBack={() => frame(`${tokenOut.symbol}-swap-token`)} - hasChildrenPadding={false} - hasOverlay - > -
-
- - setAmounts(prev => { - if (typeof amount === "function") { - return { - ...prev, - [tokenOut.symbol]: amount(prev[tokenOut.symbol]) - } - } - return { - ...prev, - [tokenOut.symbol]: amount - } - }) - } - /> - - - setAmounts(prev => { - if (typeof amount === "function") { - return { - ...prev, - [tokenIn.symbol]: amount(prev[tokenIn.symbol]) - } - } - return { - ...prev, - [tokenIn.symbol]: amount - } - }) - } - /> -
-
- -
-
-

Details

-
-
- -

- - - Slippage - {" "} - - - = 0 - ? "" - : "text-red-500" - )} - > - {meta?.slippage > 0 && "+"} - % - - - -

- -

- - - Price Impact - {" "} - - - = 0 - ? "" - : "text-red-500" - )} - > - {meta?.priceImpact > 0 && "+"} - % - - - -

- - {tokenOutImplementation?.chain && ( -

- - - Chain - {" "} - - - {getChainName(getChainId(tokenOutImplementation.chain))} - -

- )} - - -

- - - Approval - {" "} - - {tokenOutImplementation?.contract === "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" || isApproved ? "Sufficient" : "Insufficient"} - -

- -

- - - Fee - {" "} - - - ETH - - - Free - - -

-
- - -
- - - -
- - ) + return { ...implementation, address, decimals, lookup } +} +export const SwapAmountFrame = ({ id, tokenIn, tokenOut }: SwapAmountFrameProps) => { + return (<>) } +/**/ +/* export const SwapAmountFrame = ({ index, tokenIn, tokenOut }: SwapAmountFrameProps) => { */ +/* const implementations = useMemo( */ +/* () => ({ */ +/* tokenOut: getFungibleImplementation(tokenOut.attributes.fungible_info.implementations, "base"), */ +/* tokenIn: getFungibleImplementation(tokenIn.attributes.implementations, "base") */ +/* }), */ +/* [tokenIn, tokenOut] */ +/* ) */ +/**/ +/* const [column] = useAtom(columnByIndexAtom(index)) */ +/* const frameKey = `${tokenOut.attributes.fungible_info.symbol}-${tokenIn.attributes.symbol}-swap-amount` */ +/* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ +/* const { frame, navigate } = useColumnActions(index, frameKey) */ +/**/ +/* const { address } = useUser() */ +/* const { error, sendTransaction, isPending } = useSendTransaction() */ +/**/ +/* const balance = implementations.tokenOut?.balance ?? 0 */ +/**/ +/* const [amounts, setAmounts] = useState({ */ +/* [tokenOut.attributes.fungible_info.symbol]: { */ +/* precise: (balance / 2).toString(), */ +/* percentage: balance / 2 === 0 ? 0 : 50 */ +/* }, */ +/* [tokenIn.attributes.symbol]: { */ +/* precise: "0", */ +/* percentage: 0 */ +/* } */ +/* }) */ +/* const [step, setStep] = useState(0) */ +/* const [tokenOutColor, setTokenOutColor] = useState("#000000") */ +/* const [tokenInColor, setTokenInColor] = useState("#000000") */ +/**/ +/* const isSufficientBalance = */ +/* balance > 0 && balance >= Number(amounts[tokenOut.attributes.fungible_info.symbol].precise) */ +/**/ +/* const isEOA = column && column.index === COLUMNS.SIDEBAR_INDEX */ +/* const from = address || "" */ +/**/ +/* const amount = useStateDebounce(amounts[tokenOut.attributes.fungible_info.symbol].precise) */ +/**/ +/* const { */ +/* data: intent, */ +/* error: intentError, */ +/* isLoading, */ +/* isFetching */ +/* } = api.solver.actions.intent.useQuery( */ +/* { */ +/* chainId: getChainId(implementations.tokenOut?.chain_id ?? "base"), */ +/* from, */ +/* inputs: [ */ +/* { */ +/* protocol: "plug", */ +/* action: "swap", */ +/* amount, */ +/* token: implementations.tokenOut.lookup, */ +/* tokenIn: implementations.tokenIn.lookup */ +/* } */ +/* ], */ +/* options: { */ +/* isEOA: isEOA, */ +/* simulate: true */ +/* } */ +/* }, */ +/* { */ +/* enabled: */ +/* isFrame && */ +/* !!address && */ +/* !!implementations.tokenIn && */ +/* !!implementations.tokenOut && */ +/* amounts[tokenOut.attributes.fungible_info.symbol].precise !== "0" && */ +/* isSufficientBalance, */ +/* placeholderData: prev => prev */ +/* } */ +/* ) */ +/**/ +/* const isReady = */ +/* amounts[tokenOut.attributes.fungible_info.symbol].precise !== "0" && */ +/* !intentError && */ +/* !isLoading && */ +/* !isFetching && */ +/* isSufficientBalance && */ +/* !isPending */ +/* const meta = intent ? intent.transactions[intent.transactions.length - 1].meta : null */ +/**/ +/* const { approval } = useAllowance({ */ +/* token: implementations.tokenOut.address, */ +/* owner: from, */ +/* spender: meta?.settlementAddress */ +/* }) */ +/**/ +/* const toggleSavedMutation = api.plugs.activity.toggleSaved.useMutation() */ +/* const handleTransactionOffchain = useCallback(async () => { */ +/* if (!intent) return */ +/**/ +/* if (step === 0) toggleSavedMutation.mutateAsync({ id: intent.intentId }) */ +/**/ +/* const handleTransactionRedirect = () => { */ +/* navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) */ +/* frame(`${intent.intentId}-activity`) */ +/* } */ +/**/ +/* if (step === intent.transactions.length - 1) handleTransactionRedirect() */ +/* else setStep(prev => prev + 1) */ +/* }, [intent, step, toggleSavedMutation, navigate, frame, index]) */ +/**/ +/* const isApproved = approval > BigInt(meta?.sellTokens[implementations.tokenOut?.address]?.amount ?? "0") */ +/**/ +/* const handleTransactionOnchain = useCallback(async () => { */ +/* if (!column || !intent) return */ +/**/ +/* const transactionAtStep = isApproved && intent.transactions.length > 1 ? 1 : step */ +/**/ +/* if (column.index === COLUMNS.SIDEBAR_INDEX) */ +/* sendTransaction( */ +/* { */ +/* to: intent.transactions[transactionAtStep].to, */ +/* data: intent.transactions[transactionAtStep].data, */ +/* value: intent.transactions[transactionAtStep].value */ +/* }, */ +/* { */ +/* onSuccess: handleTransactionOffchain */ +/* } */ +/* ) */ +/* }, [column, intent, step, isApproved, sendTransaction, handleTransactionOffchain]) */ +/**/ +/* return ( */ +/* */ +/*
*/ +/* */ +/*
*/ +/*
*/ +/* */ +/*
*/ +/*
*/ +/* } */ +/* label={ */ +/* */ +/* {tokenOut.attributes.fungible_info.symbol} */ +/* */ +/* {tokenIn.attributes.symbol} */ +/* */ +/* } */ +/* visible={isFrame} */ +/* handleBack={() => frame(`${tokenOut.attributes.fungible_info.symbol}-swap-token`)} */ +/* hasChildrenPadding={false} */ +/* hasOverlay */ +/* > */ +/*
*/ +/* */ +/* setAmounts(prev => { */ +/* if (typeof amount === "function") { */ +/* return { */ +/* ...prev, */ +/* [tokenOut.attributes.fungible_info.symbol]: amount( */ +/* prev[tokenOut.attributes.fungible_info.symbol] */ +/* ) */ +/* } */ +/* } */ +/* return { */ +/* ...prev, */ +/* [tokenOut.attributes.fungible_info.symbol]: amount */ +/* } */ +/* }) */ +/* } */ +/* /> */ +/**/ +/* */ +/* setAmounts(prev => { */ +/* if (typeof amount === "function") { */ +/* return { */ +/* ...prev, */ +/* [tokenIn.attributes.symbol]: amount(prev[tokenIn.attributes.symbol]) */ +/* } */ +/* } */ +/* return { */ +/* ...prev, */ +/* [tokenIn.attributes.symbol]: amount */ +/* } */ +/* }) */ +/* } */ +/* /> */ +/*
*/ +/**/ +/*
*/ +/*
*/ +/*

Details

*/ +/*
*/ +/*
*/ +/**/ +/*

*/ +/* */ +/* */ +/* Slippage */ +/* {" "} */ +/* */ +/* */ +/* = 0 */ +/* ? "text-chart-green" */ +/* : "text-plug-red" */ +/* )} */ +/* > */ +/* {meta?.slippage > 0 && "+"} */ +/* % */ +/* */ +/* */ +/* */ +/*

*/ +/**/ +/*

*/ +/* */ +/* */ +/* Price Impact */ +/* {" "} */ +/* */ +/* */ +/* = 0 */ +/* ? "text-chart-green" */ +/* : "text-plug-red" */ +/* )} */ +/* > */ +/* {meta?.priceImpact > 0 && "+"} */ +/* % */ +/* */ +/* */ +/* */ +/*

*/ +/**/ +/* {implementations.tokenOut?.chain_id && ( */ +/*

*/ +/* */ +/* */ +/* Chain */ +/* {" "} */ +/* */ +/* */ +/* {getChainName(getChainId(implementations.tokenOut.chain_id))} */ +/* */ +/*

*/ +/* )} */ +/**/ +/*

*/ +/* */ +/* */ +/* Approval */ +/* {" "} */ +/* */ +/* {implementations.tokenOut?.address === NATIVE_TOKEN_ADDRESS || isApproved */ +/* ? "Sufficient" */ +/* : "Insufficient"} */ +/* */ +/*

*/ +/**/ +/*

*/ +/* */ +/* */ +/* Fee */ +/* {" "} */ +/* */ +/* */ +/* ETH */ +/* */ +/* Free */ +/* */ +/*

*/ +/*
*/ +/**/ +/*
*/ +/* */ +/**/ +/* */ +/*
*/ +/* */ +/* ) */ +/* } */ diff --git a/packages/app/components/app/frames/assets/swap/amount/input.tsx b/packages/app/components/app/frames/assets/swap/amount/input.tsx index bfd019161..e44b69c80 100644 --- a/packages/app/components/app/frames/assets/swap/amount/input.tsx +++ b/packages/app/components/app/frames/assets/swap/amount/input.tsx @@ -1,18 +1,16 @@ -import { FC, useCallback, useRef, useState } from "react" +import { FC, useCallback, useMemo, useRef, useState } from "react" +import { ChainImage } from "@/components/app/sockets/chains/chain.image" import { TokenImage } from "@/components/app/sockets/tokens/token-image" import { Counter } from "@/components/shared/utils/counter" -import { getChainId } from "@/lib" -import { RouterOutputs } from "@/server/client" -import { ChainImage } from "@/components/app/sockets/chains/chain.image" - -type Token = - | RouterOutputs["socket"]["balances"]["positions"]["tokens"][number] - | RouterOutputs["solver"]["tokens"]["get"][number] +import { NATIVE_TOKEN_ADDRESS } from "@/lib/constants" +import { getChainId } from "@/lib/functions/blockchain" +import { getZerionTokenIconUrl, ZerionPosition } from "@/lib/functions/zerion" +import { getAddress } from "viem" type SwapAmountInputProps = { index: number - token: Token + token: ZerionPosition color: string amounts: { precise: string @@ -25,12 +23,25 @@ type SwapAmountInputProps = { ) => void } +const getFungibleImplementation = (implementations: ZerionPosition["attributes"]["fungible_info"]["implementations"], chainName: string) => { + const implementation = implementations.find( + implementation => implementation.chain_id === chainName + ) + const address = getAddress(implementation?.address ?? NATIVE_TOKEN_ADDRESS) + const decimals = implementation?.decimals ?? 18 + const lookup = `${address}:${decimals}:${20}` + + return { ...implementation, address, decimals, lookup } +} + export const SwapAmountInput: FC = ({ token, color, amounts, setAmounts }) => { const containerRef = useRef(null) const inputRef = useRef(null) const [isPrecise, setIsPrecise] = useState(false) + const implementation = useMemo(() => getFungibleImplementation(token.attributes.fungible_info.implementations, "base"), [token]) + const handleDragStart = useCallback( (e: React.MouseEvent) => { e.preventDefault() @@ -46,8 +57,8 @@ export const SwapAmountInput: FC = ({ token, color, amount const x = e.clientX - rect.left const percentage = Math.min(Math.max((x / rect.width) * 100, 0), 100) - // @ts-ignore - const newAmount = ((token?.balance ?? token.implementations[0]?.balance ?? 0) * percentage) / 100 + // @ts-ignore + const newAmount = ((token?.balance ?? implementation?.balance ?? 0) * percentage) / 100 const formattedAmount = newAmount.toFixed(20).replace(/\.?0+$/, "") const [integerPart, decimalPart] = formattedAmount.split(".") @@ -81,7 +92,7 @@ export const SwapAmountInput: FC = ({ token, color, amount document.addEventListener("mousemove", handleDrag) document.addEventListener("mouseup", handleDragEnd) }, - [token, setAmounts] + [token, implementation, setAmounts] ) const handleAmountChange = (value: string) => { @@ -92,7 +103,7 @@ export const SwapAmountInput: FC = ({ token, color, amount setAmounts({ ...amounts, precise: "0" }) } else if (!isNaN(parsedValue)) { // @ts-ignore - const maxBalance = token?.balance ?? token?.implementations[0]?.balance ?? 0 + const maxBalance = token?.balance ?? implementation?.balance ?? 0 const clampedValue = Math.min(Math.max(parsedValue, 0), maxBalance) const percentage = (clampedValue / maxBalance) * 100 @@ -117,18 +128,18 @@ export const SwapAmountInput: FC = ({ token, color, amount
-

{token.symbol}

+

{token.attributes.fungible_info.symbol}

- +

%

@@ -154,9 +165,11 @@ export const SwapAmountInput: FC = ({ token, color, amount > @@ -172,7 +185,9 @@ export const SwapAmountInput: FC = ({ token, color, amount

$ ["tokens"][number] - | NonNullable[number] +import { ZerionFungible, ZerionPosition } from "@/lib/functions/zerion" type SwapFrameProps = { - index: number - tokenOut: Token + id: string + tokenOut: ZerionPosition color: string textColor: string } -export const SwapFrame: FC = ({ index, tokenOut }) => { - const [tokenIn, setTokenIn] = useState(undefined) +export const SwapFrame: FC = ({ id, tokenOut }) => { + const [tokenIn, setTokenIn] = useState() return ( <> - + - {tokenIn && } + {tokenIn && } ) } diff --git a/packages/app/components/app/frames/assets/swap/token/frame.tsx b/packages/app/components/app/frames/assets/swap/token/frame.tsx index b5268ace6..b8822ea27 100644 --- a/packages/app/components/app/frames/assets/swap/token/frame.tsx +++ b/packages/app/components/app/frames/assets/swap/token/frame.tsx @@ -1,6 +1,6 @@ import { FC } from "react" -import { BadgeCheck, SearchIcon } from "lucide-react" +import { SearchIcon } from "lucide-react" import { useAtom, useAtomValue } from "jotai" @@ -8,100 +8,151 @@ import { Frame } from "@/components/app/frames/base" import { Search } from "@/components/app/inputs/search" import { TokenImage } from "@/components/app/sockets/tokens/token-image" import { Accordion } from "@/components/shared/utils/accordion" -import { getChainId, greenGradientStyle, useDebounce } from "@/lib" -import { api, RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" - -type Token = - | NonNullable["tokens"][number] - | NonNullable[number] +import { Counter } from "@/components/shared/utils/counter" +import { getZerionTokenIconUrl, ZerionFungible, ZerionPosition } from "@/lib/functions/zerion" +import { useDebounce } from "@/lib/hooks/useDebounce" +import { cn } from "@/lib/utils/style" +import { api } from "@/server/client" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" type SwapTokenFrameProps = { - index: number - tokenOut: Token - handleTokenIn: (token: Token) => void + id: string + tokenOut: ZerionPosition + handleTokenIn: (token: ZerionFungible) => void } -export const SwapTokenFrame: FC = ({ index, tokenOut, handleTokenIn }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${tokenOut.symbol}-swap-token` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame } = useColumnActions(index, frameKey) - - const [search, debouncedSearch, handleSearch] = useDebounce("") - - const { data: tokens } = api.solver.tokens.get.useQuery(debouncedSearch, { - placeholderData: prev => prev - }) - - return ( - - -

- } - label={`Swap ${tokenOut.symbol}`} - visible={isFrame} - handleBack={() => frame(`${tokenOut.symbol}-token`)} - hasChildrenPadding={false} - hasOverlay - > -
- } - placeholder="Search tokens" - search={search} - handleSearch={handleSearch} - clear - /> - - {tokens - ?.filter(token => token?.symbol !== tokenOut?.symbol) - .map((token, tokenIndex) => ( - { - handleTokenIn(token) - frame(`${tokenOut.symbol}-${token.symbol}-swap-amount`) - }} - > -
- {token.implementations && token.implementations.length > 0 && ( - - )} +export const SwapTokenFrame: FC = ({ id, tokenOut, handleTokenIn }) => { + return (<>) -
-
-

{token.name}

-

{token.symbol}

-
+ // TODO: This has all been commented out because in the previous architecture we utilized + // Zerion responses here. Now that our API is functional we no longer need this + // and can deliver things in a direct experience very nicely :). - {token.flags.verified && ( -

- Verified -

- )} -
-
-
- ))} -
- - ) + /**/ + /* const [column] = useAtom(columnByIndexAtom(index)) */ + /* const frameKey = `${tokenOut.attributes.fungible_info.symbol}-swap-token` */ + /* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ + /* const { frame } = useColumnActions(index, frameKey) */ + /**/ + /* const [search, debouncedSearch, handleSearch] = useDebounce("", 100) */ + /**/ + /* const { data } = api.service.zerion.fungibles.list.useQuery( */ + /* { */ + /* query: { */ + /* filter: { */ + /* implementationChainId: "base", */ + /* searchQuery: debouncedSearch || undefined */ + /* } */ + /* } */ + /* }, */ + /* { placeholderData: prev => prev } */ + /* ) */ + /* const tokens = data?.data || [] */ + /**/ + /* return ( */ + /* */ + /* */ + /*
*/ + /* } */ + /* label={`Swap ${tokenOut.attributes.fungible_info.symbol}`} */ + /* visible={isFrame} */ + /* handleBack={() => frame(`${tokenOut.attributes.fungible_info.symbol}-token`)} */ + /* hasChildrenPadding={false} */ + /* hasOverlay */ + /* > */ + /*
*/ + /* } */ + /* placeholder="Search tokens" */ + /* search={search} */ + /* handleSearch={handleSearch} */ + /* clear */ + /* /> */ + /**/ + /* {tokens.map((token, tokenIndex) => { */ + /* if (tokenOut.relationships.fungible.data.id === token.id) return null */ + /**/ + /* return ( */ + /* { */ + /* handleTokenIn(token) */ + /* frame( */ + /* `${tokenOut.attributes.fungible_info.symbol}-${token.attributes.symbol}-swap-amount` */ + /* ) */ + /* }} */ + /* > */ + /*
*/ + /* {token.attributes.implementations && token.attributes.implementations.length > 0 && ( */ + /* */ + /* )} */ + /**/ + /*
*/ + /*
*/ + /*

{token.attributes.name}

*/ + /*
*/ + /* $ */ + /* */ + /*
*/ + /*
*/ + /**/ + /*
*/ + /*
*/ + /*

*/ + /* {token.attributes.symbol?.toUpperCase()} */ + /*

*/ + /*
*/ + /**/ + /*
= 0 */ + /* ? "text-chart-green" */ + /* : "text-plug-red" */ + /* : "opacity-60" */ + /* )} */ + /* > */ + /* <> */ + /* {token.attributes?.market_data?.changes?.percent_1d ? ( */ + /* <> */ + /* */ + /* % */ + /* */ + /* ) : ( */ + /* "-" */ + /* )} */ + /* */ + /*
*/ + /*
*/ + /*
*/ + /*
*/ + /*
*/ + /* ) */ + /* })} */ + /*
*/ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/assets/token/frame.tsx b/packages/app/components/app/frames/assets/token/frame.tsx deleted file mode 100644 index 1241c008e..000000000 --- a/packages/app/components/app/frames/assets/token/frame.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { FC, useMemo, useState } from "react" - -import { ArrowDownFromLine, ArrowRightLeft, MapIcon, Send } from "lucide-react" - -import { useAtom, useAtomValue } from "jotai" - -import { Frame } from "@/components/app/frames/base" -import { SocketTokenPriceChart } from "@/components/app/sockets/tokens/token-chart" -import { TokenImage } from "@/components/app/sockets/tokens/token-image" -import { Image } from "@/components/app/utils/image" -import { Counter } from "@/components/shared/utils/counter" -import { chains, cn, formatTitle, getBlockExplorerAddress, getChainId } from "@/lib" -import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, COLUMNS, isFrameAtom, useColumnActions } from "@/state/columns" - -import { ChainImage } from "../../../sockets/chains/chain.image" - -export const TokenFrame: FC<{ - index: number - token?: NonNullable["tokens"][number] - color: string - textColor: string -}> = ({ index, token, color, textColor }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${token?.symbol}-token` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, transfer } = useColumnActions(index) - - const [header, setHeader] = useState<{ - title?: string - change?: Array - }>({ - title: undefined, - change: undefined - }) - const [tooltipData, setTooltipData] = useState< - | { - timestamp: string - price: number - start: Array - } - | undefined - >() - - const change = useMemo(() => { - if (!token) return undefined - - if (header.change && !tooltipData) return header.change[0] - - if (tooltipData) { - const start = tooltipData.start[0] - const percentageChange = (tooltipData.price - start) / start - return percentageChange * 100 - } - - return token.change - }, [header, tooltipData, token]) - - const formatTimestamp = (timestamp: number) => { - if (isNaN(timestamp)) return - - const milliseconds = timestamp.toString().length === 10 ? timestamp * 1000 : timestamp - const date = new Date(milliseconds) - - const formatter = new Intl.DateTimeFormat("en-US", { - year: "numeric", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - hour12: true - }) - - return formatter.format(date) - } - - if (token === undefined) return null - - return ( - - -
- } - label={token.name} - visible={isFrame} - hasChildrenPadding={false} - hasOverlay - > -
-
-
- {tooltipData || token.price ? ( - <> -

$

- - - ) : ( - <>- - )} -
-

- {`$${token.symbol}`} -

-
-
= 0 ? "text-plug-green" : "text-red-500" - )} - > -
- -

%

-
-

- {tooltipData ? formatTimestamp(Number(tooltipData.timestamp)) : (header.title ?? "Today")} -

-
-
- - - -
- {token.implementations.some(implementation => implementation.balance) && ( - - )} - - -
- -
-
-

Balance

-
-

Value

-
- -
-
- - -

{token.symbol}

-
-
-

$

- -
-
-
- -
-

Distribution

-
-
- -
- {token.implementations.map((implementation, index) => ( -
- - -

{formatTitle(implementation.chain ?? "Unknown")}

- -
- -
- -
- % -
-
- ))} -
- -
-

Links

-
-
- -
- {token.implementations - // @ts-ignore - .map((implementation, index) => ( - - ))} -
- - ) -} diff --git a/packages/app/components/app/frames/assets/transfer/amount/frame.tsx b/packages/app/components/app/frames/assets/transfer/amount/frame.tsx index a31abb491..11e82dca2 100644 --- a/packages/app/components/app/frames/assets/transfer/amount/frame.tsx +++ b/packages/app/components/app/frames/assets/transfer/amount/frame.tsx @@ -1,6 +1,6 @@ -import { FC, useCallback } from "react" +import { useCallback } from "react" -import { getAddress, isAddress } from "viem" +import { isAddress } from "viem" import { base } from "viem/chains" import { useSendTransaction } from "wagmi" @@ -11,182 +11,192 @@ import { TransferRecipient } from "@/components/app/frames/assets/transfer/recip import { Frame } from "@/components/app/frames/base" import { TokenImage } from "@/components/app/sockets/tokens/token-image" import { ChainSpecificButton } from "@/components/shared/buttons/authenticate" -import { cn, getChainId, NATIVE_TOKEN_ADDRESS, useDebounceInline } from "@/lib" -import { useAccount } from "@/lib/hooks/account/useAccount" -import { api, RouterOutputs } from "@/server/client" -import { useSocket } from "@/state/authentication" -import { columnByIndexAtom, COLUMNS, isFrameAtom, useColumnActions } from "@/state/columns" +import { NATIVE_TOKEN_ADDRESS } from "@/lib/constants" +import { getZerionTokenIconUrl, ZerionPosition } from "@/lib/functions/zerion" +import { useDebounceInline } from "@/lib/hooks/useDebounce" +import { cn } from "@/lib/utils/style" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" +import { api } from "@/server/client" +import { useUser } from "@/state/authentication" +import { COLUMNS, isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { ScrollingError } from "../../scrolling-error" type TransferAmountFrameProps = { - index: number - token: NonNullable["tokens"][number] - color: string - textColor: string + id: string + token: ZerionPosition + color: string + textColor: string } -export const TransferAmountFrame: FC = ({ index, token, color, textColor }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${token?.symbol}-transfer-${index === COLUMNS.SIDEBAR_INDEX ? "deposit" : "amount"}` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, navigate } = useColumnActions(index, frameKey) - - const { isAuthenticated } = useAccount() - const { socket } = useSocket() - const { error, sendTransaction, isPending } = useSendTransaction() - - const isReady = token && column && parseFloat(column?.transfer?.precise ?? "0") > 0 && !isPending - const from = socket - ? index === COLUMNS.SIDEBAR_INDEX - ? getAddress(socket.id) - : getAddress(socket.socketAddress) - : "" - const recipient = - column && socket - ? index === COLUMNS.SIDEBAR_INDEX - ? getAddress(socket.socketAddress) - : column.transfer?.recipient && isAddress(column.transfer?.recipient) - ? getAddress(column.transfer?.recipient) - : "" - : "" - - const chain = "base" - const chainId = getChainId(chain) - const implementation = token?.implementations.find(implementation => implementation.chain === chain) - const request = useDebounceInline({ - chainId, - from, - inputs: [ - { - protocol: "plug", - action: "transfer", - amount: `${column?.transfer?.precise ?? 0}`, - token: `${implementation?.contract ?? NATIVE_TOKEN_ADDRESS}:${implementation?.decimals ?? 18}:20`, - recipient - } - ], - options: { - isEOA: column && column.index === COLUMNS.SIDEBAR_INDEX, - simulate: true - } - }) - const { data: intent } = api.solver.actions.intent.useQuery(request, { - enabled: isFrame && isReady && !!column && !!socket && !!implementation - }) - - const toggleSavedMutation = api.plugs.activity.toggleSaved.useMutation() - const handleTransactionOffchain = useCallback(() => { - if (!intent) return - - toggleSavedMutation.mutate( - { id: intent.intentId }, - { - onSuccess: () => { - navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) - frame(`${intent.intentId}-activity`) - } - } - ) - }, [intent, frame, index, navigate, toggleSavedMutation]) - - const handleTransactionOnchain = useCallback(() => { - if (!column || !intent || !isReady || isPending) return - - if (column.index === COLUMNS.SIDEBAR_INDEX) - sendTransaction( - { - to: intent.transactions[0].to, - data: intent.transactions[0].data, - value: intent.transactions[0].value - }, - { - onSuccess: handleTransactionOffchain - } - ) - else handleTransactionOffchain() - }, [column, intent, handleTransactionOffchain, isPending, isReady, sendTransaction]) - - const isDisabled = (intent && isPending) || isReady === false - - if (!token || !column) return null - - return ( - - -
- } - label={`${index === COLUMNS.SIDEBAR_INDEX ? "Deposit" : "Transfer"}`} - visible={isFrame} - handleBack={() => - frame( - index !== COLUMNS.SIDEBAR_INDEX ? `${token.symbol}-transfer-recipient` : `${token.symbol}-token` - ) - } - hasChildrenPadding={false} - hasOverlay - > -
- {index !== COLUMNS.SIDEBAR_INDEX && ( -
- frame(`${token.symbol}-transfer-recipient`)} - /> -
- )} - -
- {token.implementations.map((implementation, implementationIndex) => ( - - ))} -
- -
- - - - {!isAuthenticated - ? "Connect Wallet" - : isPending - ? "Transferring..." - : isReady - ? index === COLUMNS.SIDEBAR_INDEX - ? "Deposit" - : "Send" - : "Enter Amount"} - -
-
- - ) +export const TransferAmountFrame: React.FC = ({ id, token, color, textColor }) => { + return (<>) } + +/* export const TransferAmountFrame: React.FC = ({ index, token, color, textColor }) => { */ +/* const [column] = useAtom(columnByIndexAtom(index)) */ +/* const frameKey = `${token.attributes.fungible_info.symbol}-transfer-${index === COLUMNS.SIDEBAR_INDEX ? "deposit" : "amount"}` */ +/* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ +/* const { frame, navigate } = useColumnActions(index, frameKey) */ +/**/ +/* const { isAuthenticated } = useAccount() */ +/* const { address } = useUser() */ +/* const { error, sendTransaction, isPending } = useSendTransaction() */ +/**/ +/* const isReady = token && column && parseFloat(column?.transfer?.precise ?? "0") > 0 && !isPending */ +/* const from = address ?? "" */ +/**/ + // TODO: Commented this out because I would still like to find a way to avoid having to store more account + // information in an application-side database because that means something in the architecture is wrong. + /* const recipient = */ + /* column && socket */ + /* ? index === COLUMNS.SIDEBAR_INDEX */ + /* ? getAddress(socket.socketAddress) */ + /* : column.transfer?.recipient && isAddress(column.transfer?.recipient) */ + /* ? getAddress(column.transfer?.recipient) */ + /* : "" */ + /* : "" */ + /* const recipient = "" */ +/**/ +/* const implementation = token.attributes.fungible_info.implementations.find( */ +/* implementation => implementation.chain_id === "base" */ +/* ) */ +/* const request = useDebounceInline({ */ +/* chainId: base.id, */ +/* from, */ +/* inputs: [ */ +/* { */ +/* protocol: "plug", */ +/* action: "transfer", */ +/* amount: `${column?.transfer?.precise ?? 0}`, */ +/* token: `${implementation?.address ?? NATIVE_TOKEN_ADDRESS}:${implementation?.decimals ?? 18}:20`, */ +/* recipient */ +/* } */ +/* ], */ +/* options: { */ +/* isEOA: column && column.index === COLUMNS.SIDEBAR_INDEX, */ +/* simulate: true */ +/* } */ +/* }) */ +/* const { data: intent } = api.solver.actions.intent.useQuery(request, { */ +/* enabled: isFrame && isReady && !!column && !!implementation && isAddress(from) */ +/* }) */ +/**/ +/* const toggleSavedMutation = api.plugs.activity.toggleSaved.useMutation() */ +/* const handleTransactionOffchain = useCallback(() => { */ +/* if (!intent) return */ +/**/ +/* toggleSavedMutation.mutate( */ +/* { id: intent.intentId }, */ +/* { */ +/* onSuccess: () => { */ +/* navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) */ +/* frame(`${intent.intentId}-activity`) */ +/* } */ +/* } */ +/* ) */ +/* }, [intent, frame, index, navigate, toggleSavedMutation]) */ +/**/ +/* const handleTransactionOnchain = useCallback(() => { */ +/* if (!column || !intent || !isReady || isPending) return */ +/**/ +/* if (column.index === COLUMNS.SIDEBAR_INDEX) */ +/* sendTransaction( */ +/* { */ +/* to: intent.transactions[0].to, */ +/* data: intent.transactions[0].data, */ +/* value: intent.transactions[0].value */ +/* }, */ +/* { */ +/* onSuccess: handleTransactionOffchain */ +/* } */ +/* ) */ +/* else handleTransactionOffchain() */ +/* }, [column, intent, handleTransactionOffchain, isPending, isReady, sendTransaction]) */ +/**/ +/* const isDisabled = (intent && isPending) || isReady === false */ +/**/ +/* if (!token || !column) return null */ +/**/ +/* return ( */ +/* */ +/* */ +/*
*/ +/* } */ +/* label={`${index === COLUMNS.SIDEBAR_INDEX ? "Deposit" : "Transfer"}`} */ +/* visible={isFrame} */ +/* handleBack={() => */ +/* frame( */ +/* index !== COLUMNS.SIDEBAR_INDEX */ +/* ? `${token.attributes.fungible_info.symbol}-transfer-recipient` */ +/* : `${token.attributes.fungible_info.symbol}-token` */ +/* ) */ +/* } */ +/* hasChildrenPadding={false} */ +/* hasOverlay */ +/* > */ +/*
*/ +/* {index !== COLUMNS.SIDEBAR_INDEX && ( */ +/*
*/ +/* frame(`${token.attributes.fungible_info.symbol}-transfer-recipient`)} */ +/* /> */ +/*
*/ +/* )} */ +/**/ +/*
*/ +/* {token.attributes.fungible_info.implementations */ +/* // TODO: (#797) Implement support based on `connectedChains` */ +/* .filter(implementation => implementation.chain_id === "base") */ +/* .filter(implementation => implementation.balance && implementation.balance > 0) */ +/* .map((implementation, implementationIndex) => ( */ +/* */ +/* ))} */ +/*
*/ +/**/ +/*
*/ +/* */ +/**/ +/* */ +/* {!isAuthenticated */ +/* ? "Connect Wallet" */ +/* : isPending */ +/* ? "Transferring..." */ +/* : isReady */ +/* ? index === COLUMNS.SIDEBAR_INDEX */ +/* ? "Deposit" */ +/* : "Send" */ +/* : "Enter Amount"} */ +/* */ +/*
*/ +/*
*/ +/* */ +/* ) */ +/* } */ diff --git a/packages/app/components/app/frames/assets/transfer/amount/implementation.tsx b/packages/app/components/app/frames/assets/transfer/amount/implementation.tsx index 7fcbe6731..26e5d834a 100644 --- a/packages/app/components/app/frames/assets/transfer/amount/implementation.tsx +++ b/packages/app/components/app/frames/assets/transfer/amount/implementation.tsx @@ -1,30 +1,25 @@ import { FC, useCallback, useRef, useState } from "react" -import { useAtom } from "jotai" - import { TokenImage } from "@/components/app/sockets/tokens/token-image" import { Counter } from "@/components/shared/utils/counter" -import { formatTitle, getChainId } from "@/lib" -import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, useColumnActions } from "@/state/columns" +import { getChainId } from "@/lib/functions/blockchain" +import { formatTitle } from "@/lib/functions/format" +import { getZerionTokenIconUrl, ZerionPosition } from "@/lib/functions/zerion" +import { useColumn, useColumnActions } from "@/state/columns" import { ChainImage } from "../../../../sockets/chains/chain.image" -type Implementation = NonNullable< - RouterOutputs["socket"]["balances"]["positions"] ->["tokens"][number]["implementations"][number] - export const TransferTokenImplementation: FC<{ - implementation: Implementation - token: NonNullable["tokens"][number] - index: number + token: ZerionPosition + implementation: ZerionPosition["attributes"]["fungible_info"]["implementations"][number] + id: string color: string -}> = ({ implementation, token, index, color }) => { +}> = ({ implementation, token, id, color }) => { const containerRef = useRef(null) const inputRef = useRef(null) - const [column] = useAtom(columnByIndexAtom(index)) - const { transfer } = useColumnActions(index) + const column = useColumn(id) + const { transfer } = useColumnActions(id) const [isPrecise, setIsPrecise] = useState(false) @@ -48,7 +43,7 @@ export const TransferTokenImplementation: FC<{ percentage })) - const newAmount = (implementation.balance * percentage) / 100 + const newAmount = (implementation?.balance ?? 0 * percentage) / 100 const formattedAmount = newAmount.toFixed(20).replace(/\.?0+$/, "") const [integerPart, decimalPart] = formattedAmount.split(".") let finalAmount = integerPart @@ -64,7 +59,7 @@ export const TransferTokenImplementation: FC<{ transfer(prev => ({ ...prev, - precise: percentage >= 99.5 ? implementation.balance.toString() : finalAmount + precise: percentage >= 99.5 ? (implementation?.balance ?? 0).toString() : finalAmount })) setIsPrecise(true) @@ -92,7 +87,7 @@ export const TransferTokenImplementation: FC<{ if (numericValue === "") { transfer(prev => ({ ...prev, precise: "0" })) } else if (!isNaN(parsedValue)) { - const maxBalance = implementation.balance + const maxBalance = implementation?.balance ?? 0 const clampedValue = Math.min(Math.max(parsedValue, 0), maxBalance) const percentage = (clampedValue / maxBalance) * 100 @@ -107,8 +102,8 @@ export const TransferTokenImplementation: FC<{ return (
setIsPrecise(true)} onMouseLeave={() => setIsPrecise(false)} @@ -116,18 +111,15 @@ export const TransferTokenImplementation: FC<{
-

{formatTitle(token.symbol)}

+

{token.attributes.fungible_info.symbol}

- +

%

@@ -135,7 +127,6 @@ export const TransferTokenImplementation: FC<{
-
{isPrecise && ( @@ -154,9 +145,11 @@ export const TransferTokenImplementation: FC<{ > @@ -173,8 +166,8 @@ export const TransferTokenImplementation: FC<{ $ diff --git a/packages/app/components/app/frames/assets/transfer/frame.tsx b/packages/app/components/app/frames/assets/transfer/frame.tsx index 807718e01..8e8b7db9c 100644 --- a/packages/app/components/app/frames/assets/transfer/frame.tsx +++ b/packages/app/components/app/frames/assets/transfer/frame.tsx @@ -3,41 +3,29 @@ import { FC } from "react" import { TransferAmountFrame } from "@/components/app/frames/assets/transfer/amount/frame" import { TransferNFTFrame } from "@/components/app/frames/assets/transfer/nft/frame" import { TransferRecipientFrame } from "@/components/app/frames/assets/transfer/recipient/frame" -import { TransferSuccessFrame } from "@/components/app/frames/assets/transfer/success/frame" +import { ZerionPosition } from "@/lib/functions/zerion" import { RouterOutputs } from "@/server/client" type TransferFrameProps = { - index: number - token?: NonNullable["tokens"][number] - collectible?: NonNullable[number]["collectibles"][number] - collection?: NonNullable[number] + id: string + token?: ZerionPosition + collectible?: NonNullable + included?: NonNullable[number] color: string textColor: string } -export const TransferFrame: FC = ({ index, token, collectible, collection, ...colors }) => { - const isNFT = Boolean(collectible && collection) - - return ( - <> - - - {token && ( - <> - - - - )} - - {isNFT && collectible && collection && ( - - )} - - ) -} +export const TransferFrame: FC = ({ id, token, collectible, included, ...colors }) => ( + <> + + {token && } + {collectible && included && ( + + )} + +) diff --git a/packages/app/components/app/frames/assets/transfer/nft/frame.tsx b/packages/app/components/app/frames/assets/transfer/nft/frame.tsx index 929ea507a..0cb14382d 100644 --- a/packages/app/components/app/frames/assets/transfer/nft/frame.tsx +++ b/packages/app/components/app/frames/assets/transfer/nft/frame.tsx @@ -1,317 +1,242 @@ -import { FC, useCallback, useMemo, useRef, useState } from "react" +import { useCallback, useMemo } from "react" import { useAtom, useAtomValue } from "jotai" import { TransferRecipient } from "@/components/app/frames/assets/transfer/recipient/recipient" import { Frame } from "@/components/app/frames/base" import { CollectibleImage } from "@/components/app/sockets/collectibles/collectible-image" -import { Image } from "@/components/app/utils/image" import { Accordion } from "@/components/shared/utils/accordion" -import { Counter } from "@/components/shared/utils/counter" -import { chains, cn, formatTitle } from "@/lib" -import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" - -export const TransferNFTFrame: FC<{ - index: number - collectible: NonNullable[number]["collectibles"][number] - collection: NonNullable[number] - color: string - textColor: string - isERC1155: boolean -}> = ({ index, collectible, collection, color, textColor, isERC1155 }) => { - const containerRef = useRef(null) - const inputRef = useRef(null) - - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${collection.address}-${collection.chain}-${collectible.tokenId}-transfer-amount` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, transfer } = useColumnActions(index, frameKey) - - const [isPrecise, setIsPrecise] = useState(false) - - const maxAmount = parseInt(collectible.amount) - - const handleDragStart = useCallback( - (e: React.MouseEvent) => { - e.preventDefault() - - const activeElement = document.activeElement as HTMLElement - if (activeElement && activeElement.tagName === "INPUT") { - activeElement.blur() - } - - const handleDrag = (e: MouseEvent) => { - if (containerRef.current) { - const rect = containerRef.current.getBoundingClientRect() - const x = e.clientX - rect.left - const rawPercentage = x / rect.width - const nearestWholeNumber = Math.round(rawPercentage * maxAmount) - const snappedPercentage = (nearestWholeNumber / maxAmount) * 100 - - transfer(prev => ({ - ...prev, - percentage: snappedPercentage, - precise: nearestWholeNumber.toString() - })) - } - } - - const handleDragEnd = () => { - document.removeEventListener("mousemove", handleDrag) - document.removeEventListener("mouseup", handleDragEnd) - if (inputRef.current) { - inputRef.current.focus() - } - } - - document.addEventListener("mousemove", handleDrag) - document.addEventListener("mouseup", handleDragEnd) - }, - [transfer, maxAmount, containerRef] - ) - - const handleAmountChange = (value: string) => { - const numericValue = value.replace(/[^0-9]/g, "") - - if (numericValue === "") { - transfer(prev => ({ - ...prev, - precise: "0", - percentage: 0 - })) - } else { - const parsedValue = parseInt(numericValue) - const maxAmount = parseInt(collectible.amount) - const clampedValue = Math.min(Math.max(0, parsedValue), maxAmount) - const percentage = (clampedValue / maxAmount) * 100 - - transfer(prev => ({ - ...prev, - precise: clampedValue.toString(), - percentage - })) - } - } - - const handleMaxClick = useCallback(() => { - if (isERC1155) { - transfer(prev => ({ - ...prev, - percentage: 100, - precise: maxAmount.toString() - })) - } - }, [isERC1155, maxAmount, transfer]) - - // Computed values - const isReady = useMemo(() => { - if (!isERC1155) return true - const numAmount = parseInt(column?.transfer?.precise ?? "0") - return !isNaN(numAmount) && numAmount > 0 && numAmount <= maxAmount - }, [column?.transfer?.precise, isERC1155, maxAmount]) - - return ( - -
-
-
- } - label="Transfer" - visible={isFrame} - handleBack={() => - frame(`${collection.address}-${collection.chain}-${collectible.tokenId}-transfer-recipient`) - } - hasChildrenPadding={false} - hasOverlay - > -
-
-
- - frame(`${collection.address}-${collection.chain}-${collectible.tokenId}-recipient`) - } - /> -
- - {!isERC1155 ? ( -
- -
-
- -
-
-
-

- {formatTitle(collectible.name || collection.name)} -

-
-
-

- #{collectible.tokenId} -

-
-
-
-
-
- ) : ( -
-
setIsPrecise(true)} - onMouseLeave={() => setIsPrecise(false)} - > -
-
-
- -
- -
-

{`${collectible.name}`}

-

- % -

-
-
- -
-
- {isPrecise && ( - handleAmountChange(e.target.value)} - className="sr-only pointer-events-none absolute inset-0" - autoFocus - /> - )} - -

- - - {isPrecise && ( -

- )} -

-
-
-
- -
-
-
-
-
-
-
- )} -
- -
-

- {"ethereum"} - $0.50 - ~11 secs -

- - {isERC1155 && ( -

- Max -

- )} -
+import { getChainId } from "@/lib/functions/blockchain" +import { formatTitle } from "@/lib/functions/format" +import { useStateDebounce } from "@/lib/hooks/useDebounce" +import { cn } from "@/lib/utils/style" +import { api, RouterOutputs } from "@/server/client" +import { COLUMNS, isFrameAtom, useColumn, useColumnActions } from "@/state/columns" +import { useUser } from "@/state/authentication" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" +import { useSendTransaction } from "wagmi" +import { TransferSFTAmount } from "../sft/amount" +import { ScrollingError } from "../../scrolling-error" +import { ChainSpecificButton } from "@/components/shared/buttons/authenticate" +import { base } from "viem/chains" + +type TransferNFTFrameProps = { + id: string + collectible?: NonNullable + included?: NonNullable[number] + color: string + textColor: string +} -
- -
-
- - ) +export const TransferNFTFrame: React.FC = ({ id, collectible, included, color, textColor }) => { + return (<>) } +/**/ +/* export const TransferNFTFrame: React.FC = ({ index, collectible, included, color, textColor }) => { */ +/* const [column] = useAtom(columnByIndexAtom(index)) */ +/* const frameKey = `collectible___${collectible?.id}___transfer-amount` */ +/* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ +/* const { frame, navigate } = useColumnActions(index, frameKey) */ +/**/ +/* const { isAuthenticated } = useAccount() */ +/* const { address } = useUser() */ +/* const { error, sendTransaction, isPending } = useSendTransaction() */ +/**/ +/* const chainId = getChainId("base") */ +/* const from = address ?? "" */ +/**/ +/* const balance = 1 */ +/* const token = `${collectible?.attributes?.contract_address}:${collectible?.attributes?.token_id}:${collectible?.attributes?.interface === "ERC1155" ? 1155 : 721}` */ +/* const amount = useStateDebounce(`${parseInt(column?.transfer?.precise ?? "0")}`) */ +/* // TODO: Commented this out because I would still like to find a way to avoid having to store more account */ + /* const recipient = */ + /* column && socket */ + /* ? index === COLUMNS.SIDEBAR_INDEX */ + /* ? getAddress(socket.socketAddress) */ + /* : column.transfer?.recipient && isAddress(column.transfer?.recipient) */ + /* ? getAddress(column.transfer?.recipient) */ + /* : "" */ + /* : "" */ +/* const recipient = "" */ +/**/ +/* const isEOA = column && column.index === COLUMNS.SIDEBAR_INDEX */ +/* const options = { isEOA, simulate: true } */ +/**/ +/* const isReady = useMemo(() => { */ +/* const amount = parseInt(column?.transfer?.precise ?? "0") */ +/**/ +/* if (isNaN(amount)) return false */ +/**/ +/* return amount > 0 && amount <= balance */ +/* }, [column?.transfer?.precise, balance]) */ +/* const enabled = !!column && isAuthenticated && isReady && isFrame */ +/**/ +/* const { data: intent } = api.solver.actions.intent.useQuery({ */ +/* chainId, */ +/* from, */ +/* inputs: [{ */ +/* protocol: "plug", */ +/* action: "transfer", */ +/* token, */ +/* amount, */ +/* recipient */ +/* }], */ +/* options */ +/* }, { */ +/* enabled, */ +/* }) */ +/**/ +/* const toggleSavedMutation = api.plugs.activity.toggleSaved.useMutation() */ +/* const handleTransactionOffchain = useCallback(() => { */ +/* if (!intent) return */ +/**/ +/* toggleSavedMutation.mutate( */ +/* { id: intent.intentId }, */ +/* { */ +/* onSuccess: () => { */ +/* navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) */ +/* frame(`${intent.intentId}-activity`) */ +/* } */ +/* } */ +/* ) */ +/* }, [intent, frame, index, navigate, toggleSavedMutation]) */ +/* const handleTransactionOnchain = useCallback(() => { */ +/* if (!column || !intent || !isReady || isPending) return */ +/**/ +/* if (column.index === COLUMNS.SIDEBAR_INDEX) */ +/* sendTransaction( */ +/* { */ +/* to: intent.transactions[0].to, */ +/* data: intent.transactions[0].data, */ +/* value: intent.transactions[0].value */ +/* }, */ +/* { */ +/* onSuccess: handleTransactionOffchain */ +/* } */ +/* ) */ +/* else handleTransactionOffchain() */ +/* }, [column, intent, handleTransactionOffchain, isPending, isReady, sendTransaction]) */ +/**/ +/* const isDisabled = (intent && isPending) || isReady === false */ +/**/ +/* return ( */ +/* */ +/*
*/ +/*
*/ +/*
*/ +/* } */ +/* label="Transfer" */ +/* visible={isFrame} */ +/* handleBack={() => */ +/* frame(index !== COLUMNS.SIDEBAR_INDEX */ +/* ? `collectible___${collectible?.id}___transfer-recipient` */ +/* : `collectible___${collectible?.id}` */ +/* ) */ +/* } */ +/* hasChildrenPadding={false} */ +/* hasOverlay */ +/* > */ +/*
*/ +/*
*/ +/* {index !== COLUMNS.SIDEBAR_INDEX && ( */ +/*
*/ +/* frame(`collectible___${collectible?.id}___transfer-recipient`)} */ +/* /> */ +/*
*/ +/* )} */ +/**/ +/* {collectible?.attributes?.interface !== "ERC1155" ? ( */ +/*
*/ +/* */ +/*
*/ +/*
*/ +/* */ +/*
*/ +/*
*/ +/*
*/ +/*

*/ +/* {formatTitle(collectible?.attributes?.metadata?.name || included?.attributes?.metadata?.name || "")} */ +/*

*/ +/*
*/ +/*
*/ +/*

*/ +/* #{collectible?.attributes?.token_id} */ +/*

*/ +/*
*/ +/*
*/ +/*
*/ +/*
*/ +/*
*/ +/* ) : ( */ +/* */ +/* )} */ +/*
*/ +/**/ +/*
*/ +/* */ +/**/ +/* */ +/* {!isAuthenticated */ +/* ? "Connect Wallet" */ +/* : isPending */ +/* ? "Transferring..." */ +/* : isReady */ +/* ? index === COLUMNS.SIDEBAR_INDEX */ +/* ? "Deposit" */ +/* : "Send" */ +/* : "Enter Amount"} */ +/* */ +/*
*/ +/*
*/ +/* */ +/* ) */ +/* } */ diff --git a/packages/app/components/app/frames/assets/transfer/recipient/frame.tsx b/packages/app/components/app/frames/assets/transfer/recipient/frame.tsx index dc76d0c45..61ba3eb34 100644 --- a/packages/app/components/app/frames/assets/transfer/recipient/frame.tsx +++ b/packages/app/components/app/frames/assets/transfer/recipient/frame.tsx @@ -4,38 +4,34 @@ import { isAddress } from "viem" import { SearchIcon } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { TransferRecipient } from "@/components/app/frames/assets/transfer/recipient/recipient" import { Frame } from "@/components/app/frames/base" import { Search } from "@/components/app/inputs/search" import { TokenImage } from "@/components/app/sockets/tokens/token-image" -import { getChainId } from "@/lib" -import { useAccount } from "@/lib/hooks/account/useAccount" +import { getZerionTokenIconUrl, ZerionPosition } from "@/lib/functions/zerion" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { useRecipients } from "@/state/recipients" -type TokenType = NonNullable["tokens"][number] -type CollectibleType = NonNullable[number]["collectibles"][number] -type CollectionType = NonNullable[number] - type TransferRecipientFrameProps = { - index: number - token?: TokenType - collectible?: CollectibleType - collection?: CollectionType + id: string + token?: ZerionPosition + collectible?: NonNullable + included?: NonNullable[number] } -export const TransferRecipientFrame: FC = ({ index, token, collectible, collection }) => { - const account = useAccount() +export const TransferRecipientFrame: FC = ({ id, token, collectible, included }) => { + const account = useAuthentication() - const [column] = useAtom(columnByIndexAtom(index)) + const column = useColumn(id) const frameKey = token - ? `${token.symbol}-transfer-recipient` - : `${collection?.address}-${collection?.chain}-${collectible?.tokenId}-transfer-recipient` + ? `${token.attributes.fungible_info.symbol}-transfer-recipient` + : `collectible___${collectible?.id}___transfer-recipient` const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, transfer } = useColumnActions(index, frameKey) + const { frame, transfer } = useColumnActions(id, frameKey) const formatRecipientInput = (input: string): string => { if (!input) return "" @@ -54,19 +50,12 @@ export const TransferRecipientFrame: FC = ({ index, const { recipients, handleRecent } = useRecipients(formattedRecipient) const icon = token ? ( - + ) : (
) @@ -81,18 +70,18 @@ export const TransferRecipientFrame: FC = ({ index, if (address !== "") { if (token) { - frame(`${token.symbol}-transfer-amount`) - } else if (collectible && collection) { - frame(`${collection.address}-${collection.chain}-${collectible.tokenId}-transfer-amount`) + frame(`${token.attributes.fungible_info.symbol}-transfer-amount`) + } else if (collectible) { + frame(`collectible___${collectible?.id}___transfer-amount`) } } } const handleBack = () => { if (token) { - frame(`${token.symbol}-token`) - } else if (collectible && collection) { - frame(`${collection.address}-${collection.chain}-${collectible.tokenId}`) + frame(`${token.attributes.fungible_info.symbol}-token`) + } else if (collectible) { + frame(`collectible___${collectible?.id}`) } } @@ -100,7 +89,7 @@ export const TransferRecipientFrame: FC = ({ index, return ( {icon}
} label="Transfer Recipient" diff --git a/packages/app/components/app/frames/assets/transfer/recipient/recipient.tsx b/packages/app/components/app/frames/assets/transfer/recipient/recipient.tsx index 0f912d492..dae4d7953 100644 --- a/packages/app/components/app/frames/assets/transfer/recipient/recipient.tsx +++ b/packages/app/components/app/frames/assets/transfer/recipient/recipient.tsx @@ -6,10 +6,11 @@ import { mainnet } from "viem/chains" import { normalize } from "viem/ens" import { useEnsAddress, useEnsAvatar, useEnsName } from "wagmi" -import { Avatar } from "@/components/app/sockets/profile" +import { Avatar } from "@/components/app/sockets/profile/avatar" import { Accordion } from "@/components/shared/utils/accordion" -import { formatAddress, greenGradientStyle } from "@/lib" -import { useAccount } from "@/lib/hooks/account/useAccount" +import { greenGradientStyle } from "@/lib/constants" +import { formatAddress } from "@/lib/functions/format" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" export const TransferRecipient: FC< HTMLAttributes & { @@ -18,7 +19,7 @@ export const TransferRecipient: FC< handleSelect: (address: string) => void } > = ({ isRecent = false, address = "", handleSelect, className }) => { - const account = useAccount() + const account = useAuthentication() const { data: ensAddress } = useEnsAddress({ name: normalize(address), diff --git a/packages/app/components/app/frames/assets/transfer/sft/amount.tsx b/packages/app/components/app/frames/assets/transfer/sft/amount.tsx new file mode 100644 index 000000000..e66f819e7 --- /dev/null +++ b/packages/app/components/app/frames/assets/transfer/sft/amount.tsx @@ -0,0 +1,156 @@ +import { FC, useCallback, useRef, useState } from "react" + +import { RouterOutputs } from "@/server/client" +import { useColumn, useColumnActions } from "@/state/columns" +import { CollectibleImage } from "@/components/app/sockets/collectibles/collectible-image" +import { Counter } from "@/components/shared/utils/counter" + +type TransferSFTAmountProps = { + id: string + collectible?: NonNullable + included?: NonNullable[number] + color: string +} +export const TransferSFTAmount: FC = ({ id, collectible, included, color }) => { + const containerRef = useRef(null) + const inputRef = useRef(null) + + const column = useColumn(id) + const frameKey = `collectible___${collectible?.id}___transfer-amount` + const { transfer } = useColumnActions(id, frameKey) + const [isPrecise, setIsPrecise] = useState(false) + + const balance = 1 + + const handleDragStart = useCallback( + (e: React.MouseEvent) => { + e.preventDefault() + + const activeElement = document.activeElement as HTMLElement + if (activeElement && activeElement.tagName === "INPUT") { + activeElement.blur() + } + + const handleDrag = (e: MouseEvent) => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect() + const x = e.clientX - rect.left + const rawPercentage = x / rect.width + const nearestWholeNumber = Math.round(rawPercentage * balance) + const snappedPercentage = (nearestWholeNumber / balance) * 100 + + transfer(prev => ({ + ...prev, + percentage: snappedPercentage, + precise: nearestWholeNumber.toString() + })) + } + } + + const handleDragEnd = () => { + document.removeEventListener("mousemove", handleDrag) + document.removeEventListener("mouseup", handleDragEnd) + if (inputRef.current) { + inputRef.current.focus() + } + } + + document.addEventListener("mousemove", handleDrag) + document.addEventListener("mouseup", handleDragEnd) + }, + [transfer, balance, containerRef] + ) + + const handleAmountChange = (value: string) => { + const numericValue = value.replace(/[^0-9]/g, "") + + if (numericValue === "") { + transfer(prev => ({ + ...prev, + precise: "0", + percentage: 0 + })) + } else { + const parsedValue = parseInt(numericValue) + // const maxAmount = parseInt(collectible.amount) + const maxAmount = 1 + const clampedValue = Math.min(Math.max(0, parsedValue), maxAmount) + const percentage = (clampedValue / maxAmount) * 100 + + transfer(prev => ({ + ...prev, + precise: clampedValue.toString(), + percentage + })) + } + } + + return
+
setIsPrecise(true)} + onMouseLeave={() => setIsPrecise(false)} + > +
+
+
+ +
+ +
+

{`${collectible?.attributes?.metadata?.name}`}

+

+ % +

+
+
+ +
+
+ {isPrecise && ( + handleAmountChange(e.target.value)} + className="sr-only pointer-events-none absolute inset-0" + autoFocus + /> + )} + +

+ + + {isPrecise && ( +

+ )} +

+
+
+
+ +
+
+
+
+
+
+
+} diff --git a/packages/app/components/app/frames/assets/transfer/success/frame.tsx b/packages/app/components/app/frames/assets/transfer/success/frame.tsx deleted file mode 100644 index 4c5177dd5..000000000 --- a/packages/app/components/app/frames/assets/transfer/success/frame.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { FC } from "react" - -import { Bell, Waypoints } from "lucide-react" - -import { useAtom, useAtomValue } from "jotai" - -import { Frame } from "@/components/app/frames/base" -import { ChainImage } from "@/components/app/sockets/chains/chain.image" -import { TokenImage } from "@/components/app/sockets/tokens/token-image" -import { Counter } from "@/components/shared/utils/counter" -import { cn, getChainId } from "@/lib" -import { RouterOutputs } from "@/server/client" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" - -type Token = - | NonNullable["tokens"][number] - | NonNullable[number] - -type TransferSuccessFrame = { - index: number - token: Token - color: string - textColor: string -} - -export const TransferSuccessFrame: FC = ({ index, token, color, textColor }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${token.symbol}-transfer-success` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame } = useColumnActions(index, frameKey) - - return ( - - -
- } - label="Transfer Confirmed" - visible={isFrame} - handleBack={() => frame(`${token.symbol}-token`)} - hasOverlay - > -
- - Your transfer of{" "} - - {" "} - -
- {token?.symbol} -
{" "} - - has been confirmed! Any updates to your token balances or positions will be reflected shortly. - -
- -
-

Transaction

-
-
- -
-

- - - Status - {" "} - Success -

- -

- - Chain - - - Base - -

-
- - -
- - ) -} diff --git a/packages/app/components/app/frames/auth/required/frame.tsx b/packages/app/components/app/frames/auth/required/frame.tsx index 5a00dd0de..0a67751ab 100644 --- a/packages/app/components/app/frames/auth/required/frame.tsx +++ b/packages/app/components/app/frames/auth/required/frame.tsx @@ -3,19 +3,17 @@ import { FC, useMemo } from "react" import { User } from "lucide-react" -import { useAtom } from "jotai" - import { Frame } from "@/components/app/frames/base" import { Button } from "@/components/shared/buttons/button" -import { columnByIndexAtom } from "@/state/columns" +import { useColumn } from "@/state/columns" import { useSidebar } from "@/state/sidebar" const FRAMES_REQUIRED_AUTH = ["schedule", "run"] -export const AuthRequiredFrame: FC<{ index: number }> = ({ index }) => { +export const AuthRequiredFrame: FC<{ id: string }> = ({ id }) => { const { data: session } = useSession() const { is, handleActivePane } = useSidebar() - const [column] = useAtom(columnByIndexAtom(index)) + const column = useColumn(id) const isFrame = useMemo( () => @@ -29,7 +27,7 @@ export const AuthRequiredFrame: FC<{ index: number }> = ({ index }) => { return ( } label="Authentication Required" visible={isFrame} diff --git a/packages/app/components/app/frames/base.tsx b/packages/app/components/app/frames/base.tsx index e007655b9..b3cf79ab0 100644 --- a/packages/app/components/app/frames/base.tsx +++ b/packages/app/components/app/frames/base.tsx @@ -5,12 +5,13 @@ import { ChevronLeft, X } from "lucide-react" import { Header } from "@/components/app/layout/header" import { Button } from "@/components/shared/buttons/button" -import { cn, useMediaQuery } from "@/lib" +import { useMediaQuery } from "@/lib/hooks/useMediaQuery" +import { cn } from "@/lib/utils/style" import { COLUMNS, useColumnActions } from "@/state/columns" type Props = React.HTMLAttributes & PropsWithChildren & { - index: number + id: string label: string | JSX.Element visible: boolean icon?: JSX.Element @@ -22,7 +23,7 @@ type Props = React.HTMLAttributes & } export const Frame: FC = ({ - index, + id, label, visible, icon, @@ -35,12 +36,12 @@ export const Frame: FC = ({ scrollBehavior = "content" }) => { const { md } = useMediaQuery() - const { frame } = useColumnActions(index) + const { frame } = useColumnActions(id) return ( - {visible ? ( - <> + {visible && ( +
= ({ className={cn( md ? "absolute" : "fixed", "bottom-0 left-0 right-0 z-[10] cursor-pointer", - index === COLUMNS.MOBILE_INDEX ? "top-0" : "top-[60px]", + id === COLUMNS.IDS.MOBILE ? "top-0" : "top-[60px]", (handleBack === undefined || hasOverlay === true) && - "rounded-b-lg bg-gradient-to-b from-plug-green/10 to-plug-green/20 filter" + "rounded-b-lg bg-gradient-to-b from-plug-green/10 to-plug-green/35 filter" )} onClick={() => frame()} /> @@ -67,24 +68,20 @@ export const Frame: FC = ({ transition={{ duration: 0.2, ease: "easeInOut" }} className={cn( md ? "absolute" : "fixed", - "inset-0 top-auto max-h-[80vh] w-full rounded-lg bg-white", + "inset-0 top-auto max-h-[80vh] w-full bg-white", scrollBehavior === "content" && "overflow-y-auto overflow-x-hidden", className, "z-[41]" )} > -
- {handleBack && ( - - )} +
frame()} nextLabel={next ?? } nextEmpty={next !== undefined} @@ -93,15 +90,15 @@ export const Frame: FC = ({
{children}
- - ) : null} +
+ )} ) } diff --git a/packages/app/components/app/frames/chart.tsx b/packages/app/components/app/frames/chart.tsx new file mode 100644 index 000000000..3628046cb --- /dev/null +++ b/packages/app/components/app/frames/chart.tsx @@ -0,0 +1,174 @@ +import { FC, useCallback, useEffect, useState } from "react" + +import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts" + +import { Button } from "@/components/shared/buttons/button" +import { cn } from "@/lib/utils/style" +import { api, RouterOutputs } from "@/server/client" + +type Token = NonNullable[number] | undefined + +const periods = [ + { + label: "1d", + period: "day", + title: "Today" + }, + { + label: "1w", + period: "week", + title: "Past Week" + }, + { + label: "1m", + period: "month", + title: "Past Month" + }, + { + label: "1y", + period: "year", + title: "Past Year" + } +] as const + +export const SocketTokenPriceChart: FC<{ + enabled: boolean + token: Token + color: string + handleHeader?: (data: { title: string; change?: number }) => void + handleTooltip?: (data?: { timestamp: string; price: number; start: number }) => void +}> = ({ enabled, token, color, handleHeader = () => {}, handleTooltip = () => {} }) => { + const [period, setPeriod] = useState<(typeof periods)[number]>(periods[0]) + + const { data } = api.service.zerion.fungibles.chart.useQuery( + { + path: { + fungibleId: token?.relationships.fungible.data.id ?? "", + period: period.period + }, + query: { + currency: "usd" + } + }, + { enabled: enabled && !!token, retry: false, placeholderData: prev => prev } + ) + + const price = data?.data + const domain = [(price?.attributes.stats.min ?? 0) * 0.95, (price?.attributes.stats.max ?? 0) * 1.05] + + const handleMouseMove = useCallback( + (e: any) => { + if (!price || !e.activePayload || !e.activePayload.length) return + + handleTooltip({ + ...e.activePayload[0].payload, + start: price.attributes.stats.first + }) + }, + [price, handleTooltip] + ) + + useEffect(() => { + if (!price) return + + const start = price.attributes.stats.first ?? 0 + const end = price.attributes.stats.last ?? 0 + const change = ((end - start) / start) * 100 + + handleHeader({ title: period.title, change }) + }, [period, price, handleHeader]) + + return ( +
+ + handleTooltip(undefined)} + margin={{ left: 0, right: 60, top: 0, bottom: 0 }} + > + ({ + timestamp, + price + }))} + type="monotone" + dataKey="price" + stroke={color} + strokeWidth={4} + strokeLinecap="round" + dot={} + activeDot={} + isAnimationActive={false} + /> + + + + } cursor={<>} /> + + + +
+ {periods.map(p => ( + + ))} +
+
+ ) +} + +export const Dot: FC<{ + length: number + color: string + cx?: number + cy?: number + index?: number +}> = ({ length, color, cx, cy, index }) => { + if (index === length - 1) { + return ( + + + + + + + + ) + } + + return null +} + +export const ActiveDot: FC<{ + color: string + cx?: number + cy?: number +}> = ({ color, cx, cy }) => diff --git a/packages/app/components/app/frames/link.tsx b/packages/app/components/app/frames/link.tsx new file mode 100644 index 000000000..3653478fd --- /dev/null +++ b/packages/app/components/app/frames/link.tsx @@ -0,0 +1,60 @@ +import { formatTitle } from "@/lib/functions/format" +import { ArrowRight, Camera, Code, ChartColumnStacked, ChartPie, Coins, Globe, ImageIcon, MessageCircleIcon, Sailboat, Send, Telescope, Twitter } from "lucide-react" + +const TokenFrameExternalLinkIcon = ({ name }: { name: string }) => { + switch (name.toLowerCase()) { + case "website": + case "project": + return + case "twitter": + return + case "telegram": + return + case "discord": + return + case "instagram": + return + case "coingecko": + case "coinmarketcap": + return + case "dex.guru": + return + case "opensea": + return + case "image": + return + case "etherscan": + case "explorer": + return + case "developer": + case "uri": + return + default: + return + } +} + +export const TokenFrameExternalLink = ({ link }: { link: { name: string; url: string } }) => { + return ( +

+ + + + + {link.name.length < 5 ? link.name.toUpperCase() : formatTitle(link.name)} + + + + + +

+ ) +} diff --git a/packages/app/components/app/frames/plugs/[id]/actions.tsx b/packages/app/components/app/frames/plugs/[id]/actions.tsx index 26a8809d4..b90d7c664 100644 --- a/packages/app/components/app/frames/plugs/[id]/actions.tsx +++ b/packages/app/components/app/frames/plugs/[id]/actions.tsx @@ -1,87 +1,113 @@ -import { FC, useMemo } from "react" +import { FC, useMemo, useState } from "react" import { Blocks, SearchIcon } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" import { Search } from "@/components/app/inputs/search" import { ActionItem } from "@/components/app/plugs/actions/action-item" -import { useDebounce } from "@/lib" -import { useActions } from "@/state/actions" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" -export const ActionsFrame: FC<{ index: number; item: string }> = ({ index, item }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${item}-actions` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) +import { isFrameAtom, useColumn } from "@/state/columns" - const [actions] = useActions() +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" - const [search, debouncedSearch, handleDebounce] = useDebounce("") +import { useContext } from "@terminallyonline/plug-sdk" - const allFilteredActions = useMemo( - () => - Object.keys(actions).flatMap(protocol => { - if (protocol.toLowerCase().includes(debouncedSearch.toLowerCase())) { - return Object.keys(actions[protocol].schema).map(action => ({ - protocol: protocol, - action: action - })) - } +export const ActionsFrame: FC<{ id: string; item: string }> = ({ id, item }) => { + const column = useColumn(id) + const frameKey = `${item}-actions` + const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - return Object.keys(actions[protocol].schema) - .filter(actionName => actionName.toLowerCase().includes(debouncedSearch.toLowerCase())) - .map(action => ({ - protocol, - action - })) - }), - [actions, debouncedSearch] - ) + const { address } = useAuthentication() + const { data: protocols } = useContext(address, { enabled: !!address, filter: { chain_id: 1 } }) - if (!column) return null + const [search, setSearch] = useState("") - return ( - } - label="Add Action" - visible={isFrame} - hasChildrenPadding={false} - > -
- } - placeholder="Search protocols and actions" - search={search} - handleSearch={handleDebounce} - clear={true} - /> + const filtered = useMemo(() => { + if (!protocols) return [] + if (!search.trim()) { + return Object.entries(protocols).flatMap(([protocolName, protocolData]) => + Object.keys(protocolData.actions || {}).map(actionName => ({ + protocol: protocolName, + action: actionName + })) + ) + } -
- {allFilteredActions.length > 0 ? ( - allFilteredActions.map(({ protocol, action }) => ( - - )) - ) : ( - <> -
-

No matching actions.

-

Search for another action or protocol.

-
- - )} -
-
- - ) + const normalized = search.toLowerCase() + + return Object.entries(protocols).flatMap(([protocolName, protocolData]) => { + return Object.entries(protocolData.actions || {}) + .filter(([actionName, actionData]) => { + const searchableContent = [ + protocolName, + protocolData.name, + protocolData.symbol, + ...protocolData.tags, + actionName, + actionData.name, + actionData.description, + actionData.type, + actionData.sentence?.raw, + actionData.sentence?.display, + ...(actionData.feeds || []) + ] + .filter(Boolean) + .join(' ') + .toLowerCase() + + return searchableContent.includes(normalized) + }) + .map(([actionName]) => ({ + protocol: protocolName, + action: actionName + })) + }) + }, [protocols, search]) + + if (!column) return null + + return ( + } + label="Add Action" + visible={isFrame} + hasChildrenPadding={false} + > +
+ } + placeholder="Search protocols and actions" + search={search} + handleSearch={setSearch} + clear={true} + /> + +
+ {filtered.length > 0 ? ( + filtered.map(({ protocol, action }) => ( + + )) + ) : ( + <> +
+

No matching actions.

+

Search for another action or protocol.

+
+ + )} +
+
+ + ) } diff --git a/packages/app/components/app/frames/plugs/[id]/execute.tsx b/packages/app/components/app/frames/plugs/[id]/execute.tsx index c241a5a4d..496581095 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute.tsx @@ -5,13 +5,13 @@ import { RecurringFrame } from "@/components/app/frames/plugs/[id]/execute/recur import { RunFrame } from "@/components/app/frames/plugs/[id]/execute/run" import { ScheduleFrame } from "@/components/app/frames/plugs/[id]/execute/schedule" -export const ExecuteFrame: FC<{ index: number; item: string }> = ({ index, item }) => { +export const ExecuteFrame: FC<{ id: string; item: string }> = ({ id, item }) => { return ( <> - - - - + + + + ) } diff --git a/packages/app/components/app/frames/plugs/[id]/execute/error.tsx b/packages/app/components/app/frames/plugs/[id]/execute/error.tsx index ed159fef0..91424571c 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/error.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/error.tsx @@ -2,13 +2,13 @@ import { FC } from "react" import { ShieldX } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" +import { isFrameAtom, useColumn } from "@/state/columns" -export const ErrorFrame: FC<{ index: number }> = ({ index }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const ErrorFrame: FC<{ id: string }> = ({ id }) => { + const column = useColumn(id) const frameKey = "error" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) @@ -16,7 +16,7 @@ export const ErrorFrame: FC<{ index: number }> = ({ index }) => { return ( } label="Error Encountered" diff --git a/packages/app/components/app/frames/plugs/[id]/execute/ran.tsx b/packages/app/components/app/frames/plugs/[id]/execute/ran.tsx index 86a9262c5..87ed8f09e 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/ran.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/ran.tsx @@ -8,14 +8,14 @@ import { Frame } from "@/components/app/frames/base" import { ChainImage } from "@/components/app/sockets/chains/chain.image" import { Button } from "@/components/shared/buttons/button" import { Counter } from "@/components/shared/utils/counter" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { plugByIdAtom } from "@/state/plugs" -export const RanFrame: FC<{ index: number; item: string }> = ({ index, item }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const RanFrame: FC<{ id: string; item: string }> = ({ id, item }) => { + const column = useColumn(id) const frameKey = "ran" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame } = useColumnActions(index, frameKey) + const { frame } = useColumnActions(id, frameKey) const [plug] = useAtom(plugByIdAtom(item)) @@ -23,7 +23,7 @@ export const RanFrame: FC<{ index: number; item: string }> = ({ index, item }) = return ( } label="Execution Queued" @@ -49,7 +49,7 @@ export const RanFrame: FC<{ index: number; item: string }> = ({ index, item }) =

Transaction

-
+

@@ -76,7 +76,7 @@ export const RanFrame: FC<{ index: number; item: string }> = ({ index, item }) = <>

Schedule

-
+

diff --git a/packages/app/components/app/frames/plugs/[id]/execute/recurring.tsx b/packages/app/components/app/frames/plugs/[id]/execute/recurring.tsx index 0e412f6e2..6fb67cff8 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/recurring.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/recurring.tsx @@ -2,22 +2,22 @@ import { FC } from "react" import { Clock } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" import { Button } from "@/components/shared/buttons/button" -import { frequencies } from "@/lib" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" +import { frequencies } from "@/lib/functions/format" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" -export const RecurringFrame: FC<{ index: number }> = ({ index }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const RecurringFrame: FC<{ id: string }> = ({ id }) => { + const column = useColumn(id) const frameKey = "recurring" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, schedule } = useColumnActions(index, frameKey) + const { frame, schedule } = useColumnActions(id, frameKey) return ( } label="Recurring Frequency" diff --git a/packages/app/components/app/frames/plugs/[id]/execute/run.tsx b/packages/app/components/app/frames/plugs/[id]/execute/run.tsx index 8a82f0fe7..8afc31869 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/run.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/run.tsx @@ -2,17 +2,7 @@ import { useSession } from "next-auth/react" import { FC, useCallback, useMemo, useState } from "react" import { motion } from "framer-motion" -import { - AlertTriangle, - Calendar, - CircleDollarSign, - Eye, - Library, - Pause, - Play, - Send, - Waypoints -} from "lucide-react" +import { AlertTriangle, Calendar, CircleDollarSign, Eye, Library, Pause, Play, Send, Waypoints } from "lucide-react" import { useAtom, useAtomValue } from "jotai" @@ -23,307 +13,312 @@ import { Image } from "@/components/app/utils/image" import { Button } from "@/components/shared/buttons/button" import { Counter } from "@/components/shared/utils/counter" import { connectedChains } from "@/contexts" -import { ChainId, cn, formatTitle, getChainName } from "@/lib" +import { ChainId } from "@/lib/constants/chains" +import { getChainName } from "@/lib/functions/blockchain" +import { formatTitle } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" import { useActions } from "@/state/actions" -import { columnByIndexAtom, COLUMNS, isFrameAtom, useColumnActions } from "@/state/columns" +/* import { useSocket } from "@/state/authentication" */ +import { COLUMNS, isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { plugByIdAtom, usePlugActions } from "@/state/plugs" import { areAllSentencesValidAtom } from "@/state/sentences" -import { useSocket } from "@/state/authentication" export const RunFrame: FC<{ - index: number - item: string -}> = ({ index, item }) => { - const { data: session } = useSession() - - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = "run" - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, navigate } = useColumnActions(index, frameKey) - - const [plug] = useAtom(plugByIdAtom(item)) - - const { socket } = useSocket() - const [solverActions] = useActions() - const { queue } = usePlugActions() - - // TODO: The functionality for this was not finished because right now our in our environment we - // only have one chain that is valid at any given time. - const [currentChainIndex] = useState(0) - - const supportedChains = useMemo(() => { - if (!plug || !plug.actions || !solverActions) return [] - - return Array.from( - plug.actions - .map(action => { - const protocol = action.protocol - const protocolSchema = solverActions[protocol] - const chains = new Set() - - if (protocolSchema?.metadata.chains) { - const protocolChainIds = protocolSchema.metadata.chains.flatMap(chain => chain.chainIds) - for (const chain of connectedChains) { - if (protocolChainIds.includes(chain.id)) chains.add(chain.id) - } - } - - return chains - }) - .reduce((acc, chains) => { - if (acc.size === 0) return chains - return new Set([...acc].filter(chainId => chains.has(chainId))) - }, new Set()) - ) as ChainId[] - }, [plug, solverActions]) - - const chain = useMemo(() => { - if (!supportedChains || supportedChains.length === 0) return null - if (supportedChains.length === 1) return supportedChains[0] - - return supportedChains[currentChainIndex] - }, [supportedChains, currentChainIndex]) - - const isActionful = useMemo(() => { - if (!plug || !solverActions) return false - - return plug.actions.some(action => solverActions[action.protocol]?.schema[action.action]?.type === "action") - }, [plug, solverActions]) - - // Using our atom to check if all sentences are valid - const checkAllSentencesValid = useAtomValue(areAllSentencesValidAtom) - - const isReady = useMemo(() => { - if (!plug || plug.actions.length === 0) return false - if (!isActionful) return false - - // Use our atom to check validation state - // This now properly accounts for coil references - return checkAllSentencesValid(item) - }, [isActionful, plug, item, checkAllSentencesValid]) - - const handleRun = useCallback(() => { - if (!column || !column.item || !chain) return - - const intent = { - plugId: column.item, - chainId: chain, - from: socket.socketAddress, - startAt: column.schedule?.date?.from ?? new Date(), - endAt: column.schedule?.date?.to, - frequency: parseInt(column.schedule?.repeats?.value ?? "0"), - socket: column.index !== COLUMNS.SIDEBAR_INDEX - } - - queue(intent, { - onError: data => console.error(data), - onSuccess: data => { - navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) - frame(`${data.id}-activity`) - } - }) - }, [index, socket.socketAddress, column, chain, queue, navigate, frame]) - - if (!column) return null - - return ( - } - label={ - <> - - Run: Preview - - - } - visible={(isFrame && session && session.user.anonymous === false) || false} - hasOverlay={true} - handleBack={column.schedule ? () => frame("schedule") : undefined} - > -

- {plug && plug.actions && plug.actions.length > 0 ? ( - - ) : ( -
-

- No actions added and configured on this Plug yet. Add some actions to run and schedule it. -

-
- )} - - {isReady && ( - <> -
-

Details

-
-
- - {solverActions && plug?.actions && (() => { - const uniqueProtocols = Array.from( - new Set(plug.actions?.map(action => action.protocol)) - ) - - const shouldScroll = uniqueProtocols.length >= 3 - - return ( - <> -
- - - Protocol{plug.actions.length > 1 && "s"} - {" "} -
- {shouldScroll && ( - <> -
-
- - )} - - - {[...Array(shouldScroll ? 2 : 1)].map((_, i) => ( -
- {uniqueProtocols.map(protocol => ( -
- {formatTitle(protocol)} - - {formatTitle(protocol)} - -
- ))} -
- ))} -
-
-
- - ) - })() - } - - {chain && ( -

- - - Blockchain - {" "} - - - {getChainName(chain)} - -

- )} - -

- - - Gas Fee - {" "} - - - ETH - - - Free - - -

- - )} - - {column.schedule && ( - <> -
-

Schedule

-
-
- -

- - - Frequency - {" "} - {column.schedule.repeats.label} -

- - {column.schedule.date && column.schedule.date.from instanceof Date && ( -

- - - Start At - {" "} - -

- )} - - {column.schedule.date && column.schedule.date.to instanceof Date && ( -

- - - Stop At - {" "} - -

- )} - - )} - - -
- - ) + id: string + item: string +}> = ({ id, item }) => { + return (<>) + /* const { data: session } = useSession() */ + /**/ + /* const [column] = useAtom(columnByIndexAtom(index)) */ + /* const frameKey = "run" */ + /* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ + /* const { frame, navigate } = useColumnActions(index, frameKey) */ + /**/ + /* const [plug] = useAtom(plugByIdAtom(item)) */ + /**/ + /* const { address } = useUser() */ + /* const [solverActions] = useActions() */ + /* const { queue } = usePlugActions() */ + /**/ + /* // TODO: The functionality for this was not finished because right now our in our environment we */ + /* // only have one chain that is valid at any given time. */ + /* const [currentChainIndex] = useState(0) */ + /**/ + /* const supportedChains = useMemo(() => { */ + /* if (!plug || !plug.actions || !solverActions) return [] */ + /**/ + /* return Array.from( */ + /* plug.actions */ + /* .map(action => { */ + /* const protocol = action.protocol */ + /* const protocolSchema = solverActions[protocol] */ + /* const chains = new Set() */ + /**/ + /* if (protocolSchema?.metadata.chains) { */ + /* const protocolChainIds = protocolSchema.metadata.chains.flatMap(chain => chain.chainIds) */ + /* for (const chain of connectedChains) { */ + /* if (protocolChainIds.includes(chain.id)) chains.add(chain.id) */ + /* } */ + /* } */ + /**/ + /* return chains */ + /* }) */ + /* .reduce((acc, chains) => { */ + /* if (acc.size === 0) return chains */ + /* return new Set([...acc].filter(chainId => chains.has(chainId))) */ + /* }, new Set()) */ + /* ) as ChainId[] */ + /* }, [plug, solverActions]) */ + /**/ + /* const chain = useMemo(() => { */ + /* if (!supportedChains || supportedChains.length === 0) return null */ + /* if (supportedChains.length === 1) return supportedChains[0] */ + /**/ + /* return supportedChains[currentChainIndex] */ + /* }, [supportedChains, currentChainIndex]) */ + /**/ + /* const isActionful = useMemo(() => { */ + /* if (!plug || !solverActions) return false */ + /**/ + /* return plug.actions.some(action => solverActions[action.protocol]?.schema[action.action]?.properties.type === "action") */ + /* }, [plug, solverActions]) */ + /**/ + /* // Using our atom to check if all sentences are valid */ + /* const checkAllSentencesValid = useAtomValue(areAllSentencesValidAtom) */ + /**/ + /* const isReady = useMemo(() => { */ + /* if (!plug || plug.actions.length === 0) return false */ + /* if (!isActionful) return false */ + /**/ + /* // Use our atom to check validation state */ + /* // This now properly accounts for coil references */ + /* return checkAllSentencesValid(item) */ + /* }, [isActionful, plug, item, checkAllSentencesValid]) */ + /**/ + /* const handleRun = useCallback(() => { */ + /* if (!column || !column.item || !chain) return */ + /**/ + /* const intent = { */ + /* plugId: column.item, */ + /* chainId: chain, */ + /* from: address, */ + /* startAt: column.schedule?.date?.from ?? new Date(), */ + /* endAt: column.schedule?.date?.to, */ + /* frequency: parseInt(column.schedule?.repeats?.value ?? "0"), */ + /* socket: column.index !== COLUMNS.SIDEBAR_INDEX */ + /* } */ + /**/ + /* queue(intent, { */ + /* onError: data => console.error(data), */ + /* onSuccess: data => { */ + /* navigate({ index, key: COLUMNS.KEYS.ACTIVITY }) */ + /* frame(`${data}-activity`) */ + /* } */ + /* }) */ + /* }, [index, address, column, chain, queue, navigate, frame]) */ + /**/ + /* if (!column) return null */ + /**/ + /* return ( */ + /* } */ + /* label={ */ + /* <> */ + /* */ + /* Run: Preview */ + /* */ + /* */ + /* } */ + /* visible={(isFrame && session && session.user.anonymous === false) || false} */ + /* hasOverlay={true} */ + /* handleBack={column.schedule ? () => frame("schedule") : undefined} */ + /* > */ + /*
*/ + /* {plug && plug.actions && plug.actions.length > 0 ? ( */ + /* */ + /* ) : ( */ + /*
*/ + /*

*/ + /* No actions added and configured on this Plug yet. Add some actions to run and schedule it. */ + /*

*/ + /*
*/ + /* )} */ + /**/ + /* {isReady && ( */ + /* <> */ + /*
*/ + /*

Details

*/ + /*
*/ + /*
*/ + /**/ + /* {solverActions && */ + /* plug?.actions && */ + /* (() => { */ + /* const uniqueProtocols = Array.from( */ + /* new Set(plug.actions?.map(action => action.protocol)) */ + /* ) */ + /**/ + /* const shouldScroll = uniqueProtocols.length >= 3 */ + /**/ + /* return ( */ + /* <> */ + /*
*/ + /* */ + /* */ + /* */ + /* Protocol{plug.actions.length > 1 && "s"} */ + /* */ + /* {" "} */ + /*
*/ + /* {shouldScroll && ( */ + /* <> */ + /*
*/ + /*
*/ + /* */ + /* )} */ + /**/ + /* */ + /* {[...Array(shouldScroll ? 2 : 1)].map((_, i) => ( */ + /*
*/ + /* {uniqueProtocols.map(protocol => ( */ + /*
*/ + /* {formatTitle(protocol)} */ + /* */ + /* {formatTitle(protocol)} */ + /* */ + /*
*/ + /* ))} */ + /*
*/ + /* ))} */ + /*
*/ + /*
*/ + /*
*/ + /* */ + /* ) */ + /* })()} */ + /**/ + /* {chain && ( */ + /*

*/ + /* */ + /* */ + /* Blockchain */ + /* {" "} */ + /* */ + /* */ + /* {getChainName(chain)} */ + /* */ + /*

*/ + /* )} */ + /**/ + /*

*/ + /* */ + /* */ + /* Gas Fee */ + /* {" "} */ + /* */ + /* */ + /* ETH */ + /* */ + /* Free */ + /* */ + /*

*/ + /* */ + /* )} */ + /**/ + /* {column.schedule && ( */ + /* <> */ + /*
*/ + /*

Schedule

*/ + /*
*/ + /*
*/ + /**/ + /*

*/ + /* */ + /* */ + /* Frequency */ + /* {" "} */ + /* {column.schedule.repeats.label} */ + /*

*/ + /**/ + /* {column.schedule.date && column.schedule.date.from instanceof Date && ( */ + /*

*/ + /* */ + /* */ + /* Start At */ + /* {" "} */ + /* */ + /*

*/ + /* )} */ + /**/ + /* {column.schedule.date && column.schedule.date.to instanceof Date && ( */ + /*

*/ + /* */ + /* */ + /* Stop At */ + /* {" "} */ + /* */ + /*

*/ + /* )} */ + /* */ + /* )} */ + /**/ + /* */ + /*
*/ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/plugs/[id]/execute/running.tsx b/packages/app/components/app/frames/plugs/[id]/execute/running.tsx index 5d5db75bb..e677ddde4 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/running.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/running.tsx @@ -5,14 +5,14 @@ import { LoaderCircle } from "lucide-react" import { useAtom, useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" import { plugByIdAtom } from "@/state/plugs" -export const RunningFrame: FC<{ index: number; item: string }> = ({ index, item }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const RunningFrame: FC<{ id: string; item: string }> = ({ id, item }) => { + const column = useColumn(id) const frameKey = "running" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame } = useColumnActions(index, frameKey) + const { frame } = useColumnActions(id, frameKey) const [plug] = useAtom(plugByIdAtom(item)) @@ -32,7 +32,7 @@ export const RunningFrame: FC<{ index: number; item: string }> = ({ index, item return ( } label={label} diff --git a/packages/app/components/app/frames/plugs/[id]/execute/schedule.tsx b/packages/app/components/app/frames/plugs/[id]/execute/schedule.tsx index 4c446ddf4..70868dbad 100644 --- a/packages/app/components/app/frames/plugs/[id]/execute/schedule.tsx +++ b/packages/app/components/app/frames/plugs/[id]/execute/schedule.tsx @@ -2,27 +2,28 @@ import { FC, lazy, Suspense, useMemo } from "react" import { CalendarPlus, ChevronLeft, ChevronRight, Clock } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" import { Dropdown } from "@/components/app/inputs/dropdown" import { Button } from "@/components/shared/buttons/button" -import { cn, frequencies } from "@/lib" -import { columnByIndexAtom, isFrameAtom, useColumnActions } from "@/state/columns" -import { useAccount } from "@/lib/hooks/account/useAccount" +import { frequencies } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" +import { isFrameAtom, useColumn, useColumnActions } from "@/state/columns" +import { useAuthentication } from "@/lib/hooks/account/useAuthentication" const DayPicker = lazy(() => import("react-day-picker").then(mod => ({ default: mod.DayPicker }))) export const ScheduleFrame: FC<{ - index: number + id: string item: string -}> = ({ index }) => { - const { isAuthenticated } = useAccount() +}> = ({ id }) => { + const { isAuthenticated } = useAuthentication() - const [column] = useAtom(columnByIndexAtom(index)) + const column = useColumn(id) const frameKey = "schedule" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - const { frame, schedule } = useColumnActions(index, frameKey) + const { frame, schedule } = useColumnActions(id, frameKey) const MemoizedDayPicker = useMemo(() => { if (!column) return null @@ -94,7 +95,7 @@ export const ScheduleFrame: FC<{ return ( } label={ diff --git a/packages/app/components/app/frames/plugs/[id]/share.tsx b/packages/app/components/app/frames/plugs/[id]/share.tsx index 01ef27d19..7eabd76bc 100644 --- a/packages/app/components/app/frames/plugs/[id]/share.tsx +++ b/packages/app/components/app/frames/plugs/[id]/share.tsx @@ -7,12 +7,13 @@ import { useAtom, useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" import { Image } from "@/components/app/utils/image" import { Button } from "@/components/shared/buttons/button" -import { routes, useClipboard } from "@/lib" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" +import { routes } from "@/lib/constants/routes" +import { useClipboard } from "@/lib/hooks/useClipboard" +import { isFrameAtom, useColumn } from "@/state/columns" import { plugByIdAtom } from "@/state/plugs" -export const ShareFrame: FC<{ index: number; item: string }> = ({ index, item }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const ShareFrame: FC<{ id: string; item: string }> = ({ id, item }) => { + const column = useColumn(id) const frameKey = "share" const isFrame = useAtomValue(isFrameAtom)(column, frameKey) @@ -23,7 +24,7 @@ export const ShareFrame: FC<{ index: number; item: string }> = ({ index, item }) if (!plug) return null return ( - } label="Share Plug" visible={isFrame}> + } label="Share Plug" visible={isFrame}>
@@ -38,7 +39,7 @@ export const ShareFrame: FC<{ index: number; item: string }> = ({ index, item })

Twitter

@@ -53,7 +54,7 @@ export const ShareFrame: FC<{ index: number; item: string }> = ({ index, item })

Telegram

@@ -68,7 +69,7 @@ export const ShareFrame: FC<{ index: number; item: string }> = ({ index, item })

Warpcast

diff --git a/packages/app/components/app/frames/plugs/deleted.tsx b/packages/app/components/app/frames/plugs/deleted.tsx index d0ec34308..e8f72fc86 100644 --- a/packages/app/components/app/frames/plugs/deleted.tsx +++ b/packages/app/components/app/frames/plugs/deleted.tsx @@ -2,21 +2,21 @@ import { FC } from "react" import { Trash2 } from "lucide-react" -import { useAtom, useAtomValue } from "jotai" +import { useAtomValue } from "jotai" import { Frame } from "@/components/app/frames/base" import { Button } from "@/components/shared/buttons/button" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" +import { isFrameAtom, useColumn } from "@/state/columns" import { usePlugActions } from "@/state/plugs" -export const DeletedFrame: FC<{ index: number }> = ({ index }) => { - const [column] = useAtom(columnByIndexAtom(index)) +export const DeletedFrame: FC<{ id: string }> = ({ id }) => { + const column = useColumn(id) const isFrame = useAtomValue(isFrameAtom)(column, "deleted") const { add } = usePlugActions() return ( - } label="Plug Deleted" visible={isFrame}> + } label="Plug Deleted" visible={isFrame}>

This content you were viewing is no longer available. It may have been deleted or made private by the creator. diff --git a/packages/app/components/app/frames/relationship.tsx b/packages/app/components/app/frames/relationship.tsx new file mode 100644 index 000000000..8d3d5d1d2 --- /dev/null +++ b/packages/app/components/app/frames/relationship.tsx @@ -0,0 +1,410 @@ +import { memo, useMemo } from "react" + +import ReactMarkdown from "react-markdown" + +import { + ArrowDownFromLine, + ArrowRightLeft, + BookText, + Check, + EyeClosed, + Hash, + MapPinHouse, + OctagonAlertIcon, + RefreshCcw, + Send, + Waypoints +} from "lucide-react" +import { motion } from "framer-motion" + +import { Frame } from "@/components/app/frames/base" +import { ChainImage } from "@/components/app/sockets/chains/chain.image" +import { CollectibleImage } from "@/components/app/sockets/collectibles/collectible-image" +import { TokenFrameExternalLink } from "@/components/app/frames/link" +import { TokenImage } from "@/components/app/sockets/tokens/token-image" + +import { ChainId } from "@/lib/constants/chains" +import { getBlockExplorerToken, getChainName } from "@/lib/functions/blockchain" +import { getTextColor } from "@/lib/functions/color" +import { formatTitle, formatTokenStandard } from "@/lib/functions/format" +import { useClipboard } from "@/lib/hooks/useClipboard" + +import { COLUMNS } from "@/state/columns" +import { usePositions, usePlug } from "@terminallyonline/plug-sdk" +import { Header } from "@/components/landing/header" +import { cn, routes } from "@/lib" +import { Button } from "@/components/shared/buttons/button" +import Link from "next/link" +import { useUser } from "@/state/authentication" + +type RelationshipFrameProps = { + id: string + token: ReturnType['data'][number] + tokenId: string + slug?: string + frameKey: string, + frame: (key: string) => void +} + +export const RelationshipFrame: React.FC = memo(({ id, token, tokenId, frameKey, frame }) => { + const { address } = useUser() + const { positions } = usePlug() + + const relationship = useMemo(() => { + return token.tokens?.find(rel => rel.token_id === tokenId) + }, [token, tokenId]) + + const { copied: addressCopied, handleCopied: handleAddressCopied } = useClipboard(token.address) + const { copied: tokenIdCopied, handleCopied: handleTokenIdCopied } = useClipboard(relationship?.token_id || "") + + if (!relationship) return null + + return ( + <> + } + label={<> + {relationship.name || `${token.symbol} #${relationship.token_id}`} + + } + visible={relationship !== undefined} + handleBack={() => frame(frameKey)} + hasChildrenPadding={false} + hasOverlay + > +

+ + +
+
+
+ +

{formatTitle(getChainName(token.chain_id as ChainId))}

+

+ + {(relationship?.balance?.float.length ?? 0) <= 10 ? ( + {relationship?.balance?.float} + ) : ( + <> + + {relationship?.balance?.float.slice(0, 6)} + + + {relationship?.balance?.float.slice(6, -4)} + + + {relationship?.balance?.float.slice(-4)} + + + )} + +

+ {/*

*/} + {/* % */} + {/*

*/} +
+
+
+ +
+
+
+ { + /* transfer(undefined) */ + /* frame( */ + /* id === COLUMNS.IDS.SIDEBAR */ + /* ? `${token.attributes.fungible_info.symbol}-transfer-deposit` */ + /* : `${token.attributes.fungible_info.symbol}-transfer-recipient` */ + /* ) */ + }} + > + {id === COLUMNS.IDS.SIDEBAR ? ( + <> + + Deposit + + ) : ( + <> + + Transfer + + )} + + + { + /* transfer(undefined) */ + /* frame(`${token.symbol}-swap-token`) */ + }} + > + + Sell + +
+
+ + {relationship.traits && <> +
+ +
+ {Object.entries(relationship.traits)?.map(([name, value], index) => ( +
+

+ {formatTitle(name)} +

+

+ {value} +

+
+ ))} +
+ } + + {relationship?.description && ( + <> +
+
+ +
+ + )} + + {relationship.status === "failed" && <> +
+
+
+

+ This relationship node has failed to fully propagate. This can happen due to data that is no longer accessible, dead or slow links, etc. If you believe this to be an error, + please reach out to support. + {" "}Otherwise, you can hide the token from your portfolio. +

+
+ { + /* transfer(undefined) */ + /* frame( */ + /* id === COLUMNS.IDS.SIDEBAR */ + /* ? `${token.attributes.fungible_info.symbol}-transfer-deposit` */ + /* : `${token.attributes.fungible_info.symbol}-transfer-recipient` */ + /* ) */ + }} + > + + Report + + + { + positions(address, { + filter: { + chain_id: [token.chain_id], + address: [token.address], + token_id: [relationship.token_id] + }, + action: { + hide: true + } + }) + frame(frameKey) + }} + > + + Hide + +
+
+
+ } + +
+
+
+ {relationship.status &&

+ + Status + + {formatTitle(relationship.status)} + +

} + + {relationship.token_id && } + {token.standard &&

+ + Token Standard + + {formatTokenStandard(token.standard)} + +

} +
+
+ +
+ +
+
+ + + {relationship.external && Object.entries(relationship.external).map(([name, url], linkIndex) => ( + + ))} +
+
+
+ + + {/* */} + + ) +}) + +RelationshipFrame.displayName = "RelationshipFrame" diff --git a/packages/app/components/app/frames/relationships.tsx b/packages/app/components/app/frames/relationships.tsx new file mode 100644 index 000000000..7acca20bf --- /dev/null +++ b/packages/app/components/app/frames/relationships.tsx @@ -0,0 +1,202 @@ +import ReactMarkdown from "react-markdown" + +import { BookOpenIcon, BookText, Check, Hash, MapPinHouse, Waypoints } from "lucide-react"; + +import { usePositions, usePlug } from "@terminallyonline/plug-sdk"; + +import { Frame } from "@/components/app/frames/base"; +import { TokenFrameExternalLink } from "@/components/app/frames/link"; +import { CollectibleImage } from "@/components/app/sockets/collectibles/collectible-image"; +import { TokenImage } from "@/components/app/sockets/tokens/token-image"; +import { ChainImage } from "@/components/app/sockets/chains/chain.image"; +import { Header } from "@/components/landing/header"; + +import { ChainId } from "@/lib/constants/chains"; +import { getBlockExplorerAddress, getChainName } from "@/lib/functions/blockchain"; +import { formatTitle, formatTokenStandard } from "@/lib/functions/format"; +import { useClipboard } from "@/lib/hooks/useClipboard"; +import { Counter } from "@/components/shared/utils/counter"; + +type RelationshipsFrameProps = { + id: string; + token: ReturnType['data'][number] + frameKey: string, + frame: (key: string) => void +} + +export const RelationshipsFrame: React.FC = ({ id, token, frameKey, frame }) => { + const { positions } = usePlug() + + const { copied, handleCopied } = useClipboard(token.address) + + if (!token.tokens) return null + + return ( + } + label={token?.name || ""} + visible={true} + hasChildrenPadding={false} + hasOverlay + > +
+ {token.tokens.map((relationship, relationshipIndex) => ( + + ))} +
+ +
+ +
+
+ {token.status &&

+ + Status + + {formatTitle(token.status)} + +

} + +

+ + + Chain + {" "} + + + {formatTitle(getChainName(token.chain_id as ChainId))} + +

+ {token.standard &&

+ + Token Standard + + {formatTokenStandard(token.standard)} + +

} + {token.decimals &&

+ + Decimals + + {token.decimals} + +

} + {token.protocol &&

+ + Protocol + + {token.protocol} + +

} +
+
+ + {token?.description && (<> +
+ +
+
+

{children}

, + a: ({ node, children, ...props }) => ( + { + e.preventDefault() + e.stopPropagation() + if (props.href) { + window.open(props.href, "_blank", "noopener,noreferrer") + } + }} + > + {children} + + ) + }} + > + {token?.description.trim()} +
+
+
+ )} + +
+ +
+
+ + + {token.external && Object.entries(token.external).map(([name, url], linkIndex) => ( + + ))} +
+
+ + ) +} + diff --git a/packages/app/components/app/frames/socket/deploy/frame.tsx b/packages/app/components/app/frames/socket/deploy/frame.tsx index 6e39887a7..cd83278bc 100644 --- a/packages/app/components/app/frames/socket/deploy/frame.tsx +++ b/packages/app/components/app/frames/socket/deploy/frame.tsx @@ -1,165 +1,151 @@ -import { FC, useCallback } from "react" +import { ChainId } from "@/lib/constants/chains" -import { formatEther } from "viem" -import { useBalance, useBytecode, useSendTransaction } from "wagmi" - -import { CircleDollarSign, Diameter, Factory, Router, Waypoints } from "lucide-react" - -import { useAtom, useAtomValue } from "jotai" - -import { ScrollingError } from "@/components/app/frames/assets/scrolling-error" -import { Frame } from "@/components/app/frames/base" -import { ChainImage } from "@/components/app/sockets/chains/chain.image" -import { ChainSpecificButton } from "@/components/shared/buttons/authenticate" -import { Button } from "@/components/shared/buttons/button" -import { ChainId, formatAddress, formatTitle, getBlockExplorerAddress, getChainName, routes } from "@/lib" -import { api } from "@/server/client/api" -import { useSocket } from "@/state/authentication" -import { columnByIndexAtom, isFrameAtom } from "@/state/columns" - -export const SocketDeployFrame: FC<{ index: number; chainId: ChainId }> = ({ index, chainId }) => { - const [column] = useAtom(columnByIndexAtom(index)) - const frameKey = `${chainId}-deploy` - const isFrame = useAtomValue(isFrameAtom)(column, frameKey) - - const { socket } = useSocket() - - const { data: bytecode, refetch } = useBytecode({ - chainId, - address: socket.socketAddress as `0x${string}`, - query: { enabled: isFrame } - }) - - const { data: balanceData } = useBalance({ - chainId, - address: socket.socketAddress as `0x${string}`, - query: { enabled: isFrame } - }) - - const { data: intent, error: intentError } = api.solver.actions.intent.useQuery( - { - chainId, - from: socket.id, - inputs: [ - { - protocol: "plug", - action: "deploy", - factory: socket.deploymentFactory ?? "", - nonce: socket.deploymentNonce ?? 1738, - admin: socket.id ?? "", - delegate: socket.deploymentDelegate ?? "", - implementation: socket.deploymentImplementation ?? "" - } - ], - options: { - isEOA: true, - simulate: true - } - }, - { - enabled: isFrame && !bytecode && socket && socket.id.startsWith("0x") - } - ) - const { error, sendTransaction, isPending } = useSendTransaction() - - const handleDeploy = useCallback(() => { - if (bytecode || !intent) return - - sendTransaction( - { - to: intent.transactions[0].to, - data: intent.transactions[0].data, - value: intent.transactions[0].value - }, - { - onSuccess: () => refetch(), - onError: error => console.error(error) - } - ) - }, [bytecode, intent]) - - return ( - } - label="Publish Socket" - visible={isFrame} - hasOverlay - > -
- {intentError || error ? ( - - ) : !bytecode ? ( -

- Only use this for emergencies as it consumes gas not needed. You can read more in{" "} - our documentation. -

- ) : ( -

- This Socket has already been published you have nothing left to do here. -

- )} - -
-
-

Details

-
-
-

- - Chain - - - {formatTitle(getChainName(chainId))} - -

- {intent && intent.transactions.length > 0 &&
-

- - Factory - navigator.clipboard.writeText(socket?.deploymentFactory ?? "")} - > - {formatAddress(socket?.deploymentFactory ?? "")} - -

-
} -
-

- - Code Length - {bytecode?.length ?? 0} -

-
-
-

- - Socket Balance - {balanceData ? Number(formatEther(balanceData.value)).toFixed(6) : "0.00"} ETH -

-
-
- -
- - - {isPending ? "Publishing..." : bytecode ? "Already Published" : "Publish"} - -
-
- - ) +export const SocketDeployFrame: React.FC<{ index: number; chainId: ChainId }> = () => { + return (<>) + /* const [column] = useAtom(columnByIndexAtom(index)) */ + /* const frameKey = `${chainId}-deploy` */ + /* const isFrame = useAtomValue(isFrameAtom)(column, frameKey) */ + /**/ + /* const { socket } = useSocket() */ + /**/ + /* const { data: bytecode, refetch } = useBytecode({ */ + /* chainId, */ + /* address: socket.socketAddress as `0x${string}`, */ + /* query: { enabled: isFrame } */ + /* }) */ + /**/ + /* const { data: balanceData } = useBalance({ */ + /* chainId, */ + /* address: socket.socketAddress as `0x${string}`, */ + /* query: { enabled: isFrame } */ + /* }) */ + /**/ + /* const { data: intent, error: intentError } = api.solver.actions.intent.useQuery( */ + /* { */ + /* chainId, */ + /* from: socket.id, */ + /* inputs: [ */ + /* { */ + /* protocol: "plug", */ + /* action: "deploy", */ + /* factory: socket.deploymentFactory ?? "", */ + /* nonce: socket.deploymentNonce ?? 1738, */ + /* admin: socket.id ?? "", */ + /* delegate: socket.deploymentDelegate ?? "", */ + /* implementation: socket.deploymentImplementation ?? "" */ + /* } */ + /* ], */ + /* options: { */ + /* isEOA: true, */ + /* simulate: true */ + /* } */ + /* }, */ + /* { */ + /* enabled: isFrame && !bytecode && socket && socket.id.startsWith("0x") */ + /* } */ + /* ) */ + /* const { error, sendTransaction, isPending } = useSendTransaction() */ + /**/ + /* const handleDeploy = useCallback(() => { */ + /* if (bytecode || !intent) return */ + /**/ + /* sendTransaction( */ + /* { */ + /* to: intent.transactions[0].to, */ + /* data: intent.transactions[0].data, */ + /* value: intent.transactions[0].value */ + /* }, */ + /* { */ + /* onSuccess: () => refetch(), */ + /* onError: error => console.error(error) */ + /* } */ + /* ) */ + /* }, [bytecode, intent, refetch, sendTransaction]) */ + /**/ + /* return ( */ + /* } */ + /* label="Publish Socket" */ + /* visible={isFrame} */ + /* hasOverlay */ + /* > */ + /*
*/ + /* {intentError || error ? ( */ + /* */ + /* ) : !bytecode ? ( */ + /*

*/ + /* Only use this for emergencies as it consumes gas not needed. You can read more in{" "} */ + /* our documentation. */ + /*

*/ + /* ) : ( */ + /*

*/ + /* This Socket has already been published you have nothing left to do here. */ + /*

*/ + /* )} */ + /**/ + /*
*/ + /*
*/ + /*

Details

*/ + /*
*/ + /*
*/ + /*

*/ + /* */ + /* Chain */ + /* */ + /* */ + /* {formatTitle(getChainName(chainId))} */ + /* */ + /*

*/ + /* {intent && intent.transactions.length > 0 && ( */ + /*
*/ + /*

*/ + /* */ + /* Factory */ + /* navigator.clipboard.writeText(socket?.deploymentFactory ?? "")} */ + /* > */ + /* {formatAddress(socket?.deploymentFactory ?? "")} */ + /* */ + /*

*/ + /*
*/ + /* )} */ + /*
*/ + /*

*/ + /* */ + /* Code Length */ + /* {bytecode?.length ?? 0} */ + /*

*/ + /*
*/ + /*
*/ + /*

*/ + /* */ + /* Socket Balance */ + /* {balanceData ? Number(formatEther(balanceData.value)).toFixed(6) : "0.00"} ETH */ + /*

*/ + /*
*/ + /*
*/ + /**/ + /*
*/ + /* */ + /* */ + /* {isPending ? "Publishing..." : bytecode ? "Already Published" : "Publish"} */ + /* */ + /*
*/ + /*
*/ + /* */ + /* ) */ } diff --git a/packages/app/components/app/frames/token.tsx b/packages/app/components/app/frames/token.tsx new file mode 100644 index 000000000..042f9cdbc --- /dev/null +++ b/packages/app/components/app/frames/token.tsx @@ -0,0 +1,457 @@ +import { memo, useMemo, useState } from "react" + +import { motion } from 'framer-motion' + +import { ArrowDown, ArrowDownFromLine, ArrowRightLeft, ArrowUp, BookOpenIcon, BookText, Check, Hash, MapPinHouse, Send, Waypoints } from "lucide-react" + + +import { Frame } from "@/components/app/frames/base" +import { TokenImage } from "@/components/app/sockets/tokens/token-image" +import { Counter } from "@/components/shared/utils/counter" +import { ChainId } from "@/lib/constants/chains" +import { getBlockExplorerAddress, getChainName } from "@/lib/functions/blockchain" +import { getTextColor } from "@/lib/functions/color" +import { formatMoney, formatTimestamp, formatTitle, formatTokenStandard } from "@/lib/functions/format" +import { cn } from "@/lib/utils/style" + +import { COLUMNS, useColumn } from "@/state/columns" +import { usePositions } from "@terminallyonline/plug-sdk" +import { TokenFrameExternalLink } from "./link" +import { Header } from "@/components/landing/header" +import { ChainImage } from "../sockets/chains/chain.image" +import { useUser } from "@/state/authentication" +import { SocketTokenPriceChart, type TooltipData } from "../sockets/tokens/token-chart" +import { useClipboard } from "@/lib" + +type TokenFrameProps = { + id: string + token: ReturnType['data'][number] + frameKey: string, + frame: (key: string) => void +} + +const periods = [ + { label: "1m", count: 60, step: 1 }, + { label: "5m", count: 60, step: 5 }, + { label: "15m", count: 60, step: 15 }, + { label: "30m", count: 60, step: 30 }, + { label: "1h", count: 60, step: 60 }, + { label: "4h", count: 60, step: 240 }, + { label: "1d", count: 24, step: 3600 }, + { label: "7d", count: 168, step: 3600 }, + { label: "1M", count: 30, step: 86400 }, + { label: "3M", count: 90, step: 86400 }, + { label: "1y", count: 365, step: 86400 }, + { label: "ALL", count: 500, step: 86400 }, +] as const + + +export const TokenFrame: React.FC = memo(({ id, token }) => { + const { address } = useUser() + + const [period, setPeriod] = useState(periods[6]) + + const { data } = usePositions(address, { + enabled: !!address && token.price && token.price.length > 0, + filter: { + chain_id: [token.chain_id], + address: [token.address], + token_id: token.token_id ? [token.token_id] : undefined, + }, + price: { + resolution: period.label, + limit: period.count, + } + }) + + const { copied: addressCopied, handleCopied: handleAddressCopied } = useClipboard(token.address) + + const column = useColumn(id) + const [tooltipData, setTooltipData] = useState() + + const priceData = data?.[0]?.price ?? token.price + + const change = useMemo(() => { + if (tooltipData) { + if (!tooltipData.open) return 0 + return ((tooltipData.close - tooltipData.open) / tooltipData.open) * 100 + } + + if (!priceData || priceData.length === 0) return 0 + const latest = priceData[priceData.length - 1] + const open = Number(latest.open) + const close = Number(latest.close) + + if (!open || !close) return 0 + return ((close - open) / open) * 100 + }, [priceData, tooltipData]) + + return ( + } + label={token?.name || ""} + visible={true} + hasChildrenPadding={false} + hasOverlay + > +
+
+
+ {tooltipData || priceData?.[priceData.length - 1]?.close ? ( +

+ +

+ ) : ( + <>- + )} +
+

+ {token.symbol} +

+
+ +
= 0 ? "text-chart-green" : "text-plug-red" + )} + > +
+ +

%

+
+

+ {formatTimestamp(tooltipData?.timestamp ?? priceData?.[priceData.length - 1]?.timestamp ?? 0)} +

+
+
+ + {token.price && token.price.length > 0 && <> + +
+
+ {periods.map((p) => ( + + ))} +
+
+ } + + +
+
+
+
+
+ + + + {token?.balance?.float.slice(0, 6)} + + + {token?.balance?.float.slice(6, -4)} + + + {token?.balance?.float.slice(-4)} + + +

{token.symbol}

+
+ +
+ +
+
+
+
+
+
+ { + /* transfer(undefined) */ + /* frame( */ + /* id === COLUMNS.IDS.SIDEBAR */ + /* ? `${token.attributes.fungible_info.symbol}-transfer-deposit` */ + /* : `${token.attributes.fungible_info.symbol}-transfer-recipient` */ + /* ) */ + }} + > + {id === COLUMNS.IDS.SIDEBAR ? ( + <> + + Deposit + + ) : ( + <> + + Send + + )} + + + { + /* transfer(undefined) */ + /* frame(`${token.symbol}-swap-token`) */ + }} + > + + Swap + +
+ +
+
+
+
+ +

{formatTitle(getChainName(token.chain_id as ChainId))}

+

+ + + {token?.balance?.float.slice(0, 6)} + + + {token?.balance?.float.slice(6, -4)} + + + {token?.balance?.float.slice(-4)} + + +

+ {/*

*/} + {/* % */} + {/*

*/} +
+
+
+ + {token.price && token.price.length > 0 && ( + <> +
+
+ {(() => { + const latest = priceData?.[priceData.length - 1] + const prevCandle = priceData?.[priceData.length - 2] + + const current = { + low: tooltipData?.low ?? Number(latest?.low) ?? 0, + high: tooltipData?.high ?? Number(latest?.high) ?? 0, + } + const prev = { + low: Number(prevCandle?.low) || current.low, + high: Number(prevCandle?.high) || current.high, + } + + const pct = (curr: number, prev: number) => prev ? ((curr - prev) / prev) * 100 : 0 + const sign = (val: number) => {val >= 0 ? "+" : "-"} + + return ( + <> +
+ +

Low

+ + = 0 ? "text-chart-green" : "text-plug-red")}> + {sign(pct(current.low, prev.low))}% + +
+
+ +

High

+ + = 0 ? "text-chart-green" : "text-plug-red")}> + {sign(pct(current.high, prev.high))}% + +
+ + ) + })()} +
+ + ) + } + + {/*
*/} + {/*
*/} + {/*

Market

*/} + {/*
*/} + {/*
*/} + + {/*

*/} + {/* */} + {/* Supply */} + {/*

*/} + {/* */} + {/*
*/} + {/**/} + {/*
*/} + {/* */} + {/* % */} + {/*
*/} + {/*

*/} + + {/*

*/} + {/* */} + {/* Market Cap */} + {/* */} + {/* $ */} + {/* */} + {/*

*/} + {/**/} + {/*

*/} + {/* */} + {/* Fully Diluted Valuation */} + {/* */} + {/* $ */} + {/* */} + {/* */} + {/*

*/} + {/*
*/} + +
+
+
+ {token.status &&

+ + Status + + {formatTitle(token.status)} + +

} + +

+ + + Chain + {" "} + + + {formatTitle(getChainName(token.chain_id as ChainId))} + +

+ {token.standard &&

+ + Token Standard + + {formatTokenStandard(token.standard)} + +

} + {token.decimals &&

+ + Decimals + + {token.decimals} + +

} + {token.protocol &&

+ + Protocol + + {token.protocol} + +

} +
+
+ +
+ +
+
+ + + {token.external && Object.entries(token.external).map(([name, url], linkIndex) => ( + + ))} +
+
+ + ) +}) + +TokenFrame.displayName = "TokenFrame" diff --git a/packages/app/components/app/inputs/checkbox.tsx b/packages/app/components/app/inputs/checkbox.tsx index d1e8eb048..ac488a080 100644 --- a/packages/app/components/app/inputs/checkbox.tsx +++ b/packages/app/components/app/inputs/checkbox.tsx @@ -3,7 +3,7 @@ import type { FC } from "react" import { motion } from "framer-motion" import { Check } from "lucide-react" -import { cn } from "@/lib" +import { cn } from "@/lib/utils/style" type Props = { checked: boolean diff --git a/packages/app/components/app/inputs/dropdown.tsx b/packages/app/components/app/inputs/dropdown.tsx index fa42f23f5..a59d5d5ae 100644 --- a/packages/app/components/app/inputs/dropdown.tsx +++ b/packages/app/components/app/inputs/dropdown.tsx @@ -2,7 +2,7 @@ import { FC, type JSX, PropsWithChildren } from "react" import { ChevronDown } from "lucide-react" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/style" type Props = { icon: JSX.Element diff --git a/packages/app/components/app/inputs/search.tsx b/packages/app/components/app/inputs/search.tsx index 6ebc0cb93..4a43f8abd 100644 --- a/packages/app/components/app/inputs/search.tsx +++ b/packages/app/components/app/inputs/search.tsx @@ -1,127 +1,141 @@ import { FC, type JSX, PropsWithChildren, RefObject, useEffect, useRef } from "react" import { AnimatePresence, motion } from "framer-motion" -import { X } from "lucide-react" +import { Loader, X } from "lucide-react" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/style" type Props = { - ref?: RefObject - icon: JSX.Element - placeholder: string - search?: string - clear?: boolean - focus?: boolean - textArea?: boolean - isNumber?: boolean - handleSearch?: (search: string) => void - handleOnClick?: () => void + ref?: RefObject + icon: JSX.Element + placeholder: string + search?: string + clear?: boolean + focus?: boolean + textArea?: boolean + isNumber?: boolean + isLoading?: boolean + handleSearch?: (search: string) => void + handleOnClick?: () => void } & PropsWithChildren & - React.HTMLAttributes + React.HTMLAttributes export const Search: FC = ({ - ref, - icon, - placeholder, - search, - clear = false, - focus = false, - textArea = false, - isNumber = false, - handleSearch, - handleOnClick, - className, - children + ref: externalRef, + icon, + placeholder, + search, + clear = false, + focus = false, + textArea = false, + isNumber = false, + isLoading = false, + handleSearch, + handleOnClick, + className, + children }) => { - const searchRef = useRef(null) - ref = ref ? ref : searchRef - - const handleChange = (event: React.ChangeEvent) => { - if (handleSearch) handleSearch(event.target.value) - } - - useEffect(() => { - const handleResize = () => { - if (!ref.current) return - - ref.current.style.height = "auto" - ref.current.style.height = `${ref.current.scrollHeight}px` - } - - if (textArea === false || !ref.current) return - - const currentRef = ref.current - - currentRef?.addEventListener("input", handleResize, false) - currentRef?.addEventListener("change", handleResize) - window.addEventListener("resize", handleResize) - - return () => { - currentRef?.removeEventListener("input", handleResize, false) - currentRef?.removeEventListener("change", handleResize) - window.removeEventListener("resize", handleResize) - } - }, [textArea, ref]) - - useEffect(() => { - if (!focus || !ref.current) return - - ref.current.focus({ preventScroll: true }) - }, [focus, ref]) - - return ( -
-
handleOnClick() : () => ref.current?.focus()} - > -
{icon}
- {textArea === false ? ( - } - type={isNumber ? "number" : "text"} - placeholder={placeholder} - className="w-full cursor-pointer bg-transparent font-bold outline-none" - value={search} - onChange={e => (handleSearch ? handleSearch(e.target.value) : null)} - autoCorrect="off" - /> - ) : ( -